会社でRaspberryPiにカメラをつけてストリーミングするということをやっているのですが、外部から接続して見れるようにしたいと思い、クライアント→EC2→RaspberryPiというフローを作成しました。こちらのようにルーターのポートを開ける方法もあるのですが、今回はそれをやらずに行う方法をご紹介します。
前提条件
・mjpeg-streamerでカメラのストリーミングができていること
※1 こちらなんかが参考になります。
※2 最終的にはプロキシの向け先だけの話なので、別にできていなくても理解できればよいです。
今回やりたいこと
・外部からEC2経由でRaspberryPiに接続する
環境
・RaspberryPi3 Raspbian GNU/Linux 8.0 (jessie)
・EC2 Red Hat Enterprise Linux 7.4 (HVM), SSD Volume Type
イメージ
手順
- OpenVPNの設定
- プロキシの設定
OpenVPNの設定(EC2)
次にOpenVPNの設定をします。私はこちらを参考にしました。
まずはopenvpnとeasy-rsaをインストールします。
$ yum install openvpn easy-rsa
認証方式は静的鍵(Static Key)を使用します。つまりEC2とラズパイは両方同じ鍵を使用します。
$ cd /etc/openvpn/
$ openvpn --genkey --secret static.key
$ ls
client server static.key <- static.keyが作成されていることを確認
次にサーバー用のOpenVPN設定ファイルを作成します。以下の内容のテキストファイルを作成し、ファイル名を statickey_server.conf として保存してください。
$ vi statickey_server.conf
$ cat statickey_server.conf <- statickey_server.confの内容は以下の通り
proto udp
dev tun
port 1194
ifconfig 10.8.0.1 10.8.0.2
secret static.key
comp-lzo
keepalive 10 60
ping-timer-rem
persist-tun
persist-key
$ /etc/init.d/openvpn restart <-再起動
OpenVPNの設定(RaspberryPi側)
まずはopenvpnをインストールします。
$ sudo apt-get install openvpn
次にクライアント用のOpenVPN設定ファイルを作成します。以下の内容のテキストファイルを作成し、ファイル名を statickey_client.conf として保存してください。
$ vi static.key <- ec2で作成したもの内容コピーしておき、同じ値を入力
$ vi statickey_client.conf
$ cat statickey_client.conf <- statickey_client.confの内容は以下の通り
remote ec2-12-345-6-789.ap-northeast-1.compute.amazonaws.com
proto udp
dev tun
port 1194
ifconfig 10.8.0.2 10.8.0.1
secret static.key
comp-lzo
keepalive 10 60
ping-timer-rem
persist-tun
persist-key
$ /etc/init.d/openvpn restart <-再起動
remoteのところは自分のec2のドメインを入力してください。
確認(EC2側)
traceroute(あるいはping)が通るか確認します。
$ traceroute 10.8.0.2
traceroute to 10.8.0.2 (10.8.0.2), 30 hops max, 60 byte packets
1 ip-10-8-0-2.ap-northeast-1.compute.internal (10.8.0.2) 21.155 ms 21.142 ms 21.134 ms
確認(RaspberryPi側)
traceroute(あるいはping)が通るか確認します。
$ traceroute 10.8.0.1
traceroute to 10.8.0.1 (10.8.0.1), 30 hops max, 60 byte packets
1 10.8.0.1 (10.8.0.1) 13.763 ms 13.948 ms 14.141 ms
プロキシの設定
クライアントからEC2に接続してきた際にRaspberryPiへ中継させるようにします。
プロキシサーバーにはNginxを使用します。
$ sudo yum install nginx
$ cd /etc/nginx/
$ vi nginx.conf
$ cat nginx.conf
(省略)
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
proxy_pass http://10.8.0.2:8000/;
}
(省略)
locationにraspberrypiで接続したいパスを追加してください。
確認
EC2に接続します。
ブラウザを開いて、自分のEC2のドメインを叩きます。以下例です。
http://ec2-12-345-6-789.ap-northeast-1.compute.amazonaws.com
これで開きたいページが開けたらOKです。
私が開くと以下のように502 Bad Gatewayというようになりました。
なるほど、わからん。
原因
早く解決策を教えろよって方はこちらは飛ばしていただいても構いません。
こちらを参考にしました。502の原因は
・名前解決できて
・プロキシサーバ発見できてコネクションも成立して
・だけどプロキシサーバより先のサーバ(上位サーバっていうの?)が
・「正しいレスポンスを」「何かしらの理由で返してくれない」場合
502 Bad Gateway
らしいです。
もう少し調べます。こちらを参考にしました。
nginxのログを見ます。
$ sudo su -
$ cd /var/log/nginx
$ cat error.log
2017/10/13 03:50:22 [crit] 29403#0: *1 connect() to 10.8.0.2:3000 failed (13: Permission denied) while connecting to upstream, client: XX.XXX.XXX.XXX, server: _, request: "GET / HTTP/1.1", upstream: "http://10.8.0.2:8000/stream.html", host: "ec2-12-345-6-789.ap-northeast-1.compute.amazonaws.com"
クライアントからこのサイトにアクセスした際に、HTTP/1.1をGETしようとしてhttp://10.8.0.2:8000/stream.htmlに接続したが失敗したようです。
監査ログを調べます。
$ cat /var/log/audit/audit.log | grep nginx | grep denied
type=AVC msg=audit(1507644731.286:286778): avc: denied { name_connect } for pid=5877 comm="nginx" dest=3000 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:ntop_port_t:s0 tclass=tcp_socket
このログにより、「httpd_t」のコンテキストが設定された(scontext)プロセス「nginx」(comm)から、「http_cache_port_t」のコンテキスト(tcontext)への、クラス「tcp_socket」(tclass)の操作「{ name_connect }」が拒否(denied)されたことが分かります。
私はよくわかりませんでしたが、SELinuxが原因でアクセス制御がされていることが原因のようでした。SELinuxとは細かいアクセス制御が可能になるものでroot権限ですら制限をかけることも可能となっており、redhat系のディストリビューションであるCentOSやFedoraの場合はデフォルトで導入されているシステムらしいです。
SELinuxが有効になっているかは以下のコマンドで確認できます。
$ getenforce
Enforcing <-Enforcingの場合、有効 disabledの場合、無効
$ getsebool -a | grep httpd_can_network_relay
httpd_can_network_relay --> off <- offの場合、502になる
解決方法
SELinuxのブール値というものを変えるらしいです。SELinuxを有効にしていてもブール値を変えたらそのサービスを有効にできるらしいです。
httpd をフォワードプロキシまたはリバースプロキシとして使用する場合、httpd_can_network_relayというブール値を有効にします。
$ setsebool httpd_can_network_connect on -P
ちなみに-Pをつけるとnginx再起動後でも有効になります。
これをやった後で再度接続すると、無事接続できました!
まとめ
とりあえず外部から接続する方法を確立することができましたが、これではec2のドメインを知っている人がすべて確認できるので、何らかの制限をする必要があります。
接続したい人が一人(端末)であればその端末だけ接続できるように制限する、不特定多数の場合は認証をかけるなど。