RaspberryPiをEC2経由で接続する方法

会社でRaspberryPiにカメラをつけてストリーミングするということをやっているのですが、外部から接続して見れるようにしたいと思い、クライアント→EC2→RaspberryPiというフローを作成しました。[こちら](https://qiita.com/kinpira/items/c9e6dc910e8d96e8c19b)のようにルーターのポートを開ける方法もあるのですが、今回はそれをやらずに行う方法をご紹介します。

##前提条件
・mjpeg-streamerでカメラのストリーミングができていること
※1 [こちら](https://qiita.com/PonDad/items/9a6dbd6957df06a4532e)なんかが参考になります。
※2 最終的にはプロキシの向け先だけの話なので、別にできていなくても理解できればよいです。

##今回やりたいこと
・外部からEC2経由でRaspberryPiに接続する

##環境
・RaspberryPi3 Raspbian GNU/Linux 8.0 (jessie)
・EC2 Red Hat Enterprise Linux 7.4 (HVM), SSD Volume Type

##イメージ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202761/31019368-ac31-9ba3-1c40-2d4957685d7c.png)


##手順
1. OpenVPNの設定
2. プロキシの設定

##OpenVPNの設定(EC2)
まずはEC2側でUDPの接続を許可します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202761/ee2fd61c-0b92-8bb0-8db2-b1ba6610b6c4.png)

次にOpenVPNの設定をします。私は[こちら](https://www.openvpn.jp/document/quickstart-statickey/)を参考にしました。
まずは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を使用します。


```EC2
$ 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というようになりました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202761/685047b1-5d09-3623-dee6-aec05672298a.png)
なるほど、わからん。

##原因
早く解決策を教えろよって方はこちらは飛ばしていただいても構いません。
[こちら](http://otiai10.hatenablog.com/entry/2013/10/10/095213)を参考にしました。502の原因は
>・名前解決できて
>・プロキシサーバ発見できてコネクションも成立して
>・だけどプロキシサーバより先のサーバ（上位サーバっていうの？）が
>・「正しいレスポンスを」「何かしらの理由で返してくれない」場合
>502 Bad Gateway

らしいです。
もう少し調べます。[こちら](http://www.crystalsnowman.com/?p=723)を参考にしました。
nginxのログを見ます。

```EC2
$ 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に接続したが失敗したようです。
監査ログを調べます。

```EC2
$ 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が有効になっているかは以下のコマンドで確認できます。

```EC2
$ 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というブール値を有効にします。

```EC2
$ setsebool httpd_can_network_connect on -P
```

ちなみに-Pをつけるとnginx再起動後でも有効になります。
これをやった後で再度接続すると、無事接続できました！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202761/f00022b3-0e21-8d7a-1649-60784fcf7712.png)

##まとめ
とりあえず外部から接続する方法を確立することができましたが、これではec2のドメインを知っている人がすべて確認できるので、何らかの制限をする必要があります。
接続したい人が一人(端末)であればその端末だけ接続できるように制限する、不特定多数の場合は認証をかけるなど。