自分用ngrokを構築する(self hosted ngrok)

#きっかけ
Raspberry piに構築した、Node-redとMQTTへ、外部からアクセスさせたい。
よくあるhttp/httpsだけではなくMQTTプロトコルが使用するTCPポート指定してのトンネリングが行えること。
証明書を使ったサーバー認証を行うため、固定IPで独自ドメイン取得前提。

自宅がマンションタイプの2重ルーターなのでそれを超えられること。

#方針検討：
ngrok：有料：TCPポートの開放に対応しているサービス。
試しにsshをTCPトンネルさせてみると、下記の通り。


```
$ ngrok tcp 22
ngrok by @inconshreveable                                       (Ctrl+C to quit)

Tunnel Status                 online
Version                       2.0.19/2.0.19
Web Interface                 http://127.0.0.1:4040
Forwarding                    tcp://0.tcp.ngrok.io:36198 -> localhost:22

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

```


[ドキュメント](https://ngrok.com/docs)によると、無料のアカウントでは、

```
<ランダムな数字1桁>.tcp.ngrok.io:<ランダムなポート番号>

```

これでは証明書が保証できるURLとしては接続できない。
有料だと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/56ffbe3e-d651-e957-b0f3-e466e46e4fba.png)

年間99ドルで"2 reserved TCP sddress"とあるが、これも、ポート番号が固定できるだけ。


serveo：無料：まったくアクセスできず使えなかった。

独自ドメインが必要なので自分でサーバーを構築できるものを探してみた。

[Softether](https://ja.softether.org/)
VPSなどのパブリック固定IPのサーバーへvpnserverをインストール、それに向かってRaspberry pi側からvpnbridge、もしくはvpnclientで接続するのが基本構成だが、どうやってもうまくいかなかった。Raspberry pi側をマルチNIC（Ether+Wifi）にすればうまくいったかもしれない。

[localtunnel](https://localtunnel.github.io/www/)
ドキュメントをざっと読んで、インストールしてみたが、httpsしか設定できないのでボツ。

[ngrok](https://github.com/inconshreveable/ngrok)
旧バージョン(1.7)のngrokがサーバー・クライアント両方ともソースが公開されている。
"DO NOT RUN THIS VERSION OF NGROK (1.X) IN PRODUCTION."と記載されており注意が必要だが、商用版の"ngrok.io"部分を独自ドメインにして使えそう。
これを試してみる。

#サーバーの検討
固定IPを使えるVPSサービスを検討。スペックは最低レベル。
■[WEBARENA VPSクラウド](https://web.arena.ne.jp/vps-cloud/)
月額360円で1コア、SSD20GB、メモリ1GBと申し分なく愛用できるサービスだが、
2019年11月現在、残念ながら在庫なし。

■[Conoha VPS](https://www.conoha.jp/vps/)
月額630円で1コア、SSD20GB、メモリ512MB,やむなくこちらを選択する。

#ドメインの取得
すでにアカウントを持っている[バリュードメイン](https://www.value-domain.com/)で
年間99円の（初年度だけ）ドメインを購入し、下記1~5を参考にし設定。
[VALUE-DOMAIN で取得したドメインをConohaで使ってみる](https://qiita.com/hexaforce/items/9a3e465ef9f60755ea30)

#構成
サーバー：Conoha VPS　CentOS7
クライアント：Raspberry pi Zero WH Raspbian buster

#セットアップ
基本的にサーバー、クライアント側両方ともにGoのセットアップを行う。
下記を参考にGoのセットアップ。
[Linux(CentOS, Ubuntu)にGo言語の開発環境を構築する](https://mem-archive.com/2018/12/02/post-1220/)
あまりGoが新しいとエラーになるので、Go1.7をインストールする。

下記を実行する。

```
git clone https://github.com/inconshreveable/ngrok.git
cd ngrok
#raspbianは下記
GOOS=linux GOARCH=arm make release-client
#CentOS7は下記
GOOS=linux GOARCH=amd64 make release-server

```
上記でngrok/bin/内にバイナリができる。

#自己認証局、サーバー証明書の作成

[OpenSSL ECDSAでの自己認証局作成](https://qiita.com/kmitsu76/items/ac2db516ba46c50f43d6)を参考にルートCA,中間CAを作成。

証明書を作成：
サーバー証明書の秘密鍵とCSRを作成

```
openssl req -new -config openssl.cnf -newkey ec:<(openssl ecparam -name prime256v1) -keyout server.key -out server.csr
```

サーバー証明書を中間CAで署名

```
openssl ca -config openssl.cnf -extensions svr_cert -keyfile icacert.key -cert icacert.pem -in server.csr -out server.crt -days 3650
```

openssl.cnfの主な変更点

```
[ svr_cert ]
subjectAltName= @alt_names

[ alt_names ]
#SAN対策
DNS.1 = <取得したドメイン>
DNS.2 = *.<所得したドメイン>

```

#証明書チェーンの作成
サーバー証明書として組み込むため、
サーバー証明書＋中間CA証明書＋自己認証局CA証明書をくっつける。

[SSLCertificateChainFile Deprecation Warning on Apache 2.4.8+
](https://serverfault.com/questions/588986/sslcertificatechainfile-deprecation-warning-on-apache-2-4-8)

```

./bundle.sh server.crt > server.chain
./bundle.sh icacert.pem >> server.chain
./bundle.sh cacert.pem >> server.chain

```

#サーバー、クライアントへの自己認証局CA証明書の組み込み
下記を参考に信頼されたルート証明書として作成した自己認証局CA証明書を組み込む。
[独自のルートCA証明書を追加する方法（Ubuntu, CentOS 7）](https://cloudpack.media/14148)

#ngrokクライアントのCA証明書の入れ替え

クライアント側の[ngrokroot.crt](https://github.com/inconshreveable/ngrok/blob/a8e7fa486348f391f4bdf241344d798297f953bc/assets/client/tls/ngrokroot.crt)を、自己認証局CA証明書に入れ替えて
再度GOOS= linux GOARCH=arm make release-clientを実施する。

#サーバー、クライアントの実行。

サーバー側

```
bin/ngrokd -tlsKey=server.key -tlsCrt=server.chain -domain=<取得したドメイン>
```


クライアント側
設定ファイルを書いて実行する。

```
vi .ngrok


server_addr: <所得したドメイン>:4443
trust_host_root_certs: false
tunnels:
  mqtt:
    proto:
      tcp: 8883
    remote_port: 8883
  node-red:
    proto:
      https: 1880
    subdomain: nodered


#実行
bin/ngrok -config=.ngrok start-all
```

#実行確認

クライアント側の状態

```
ngrok                                                           (Ctrl+C to quit)

Tunnel Status                 online
Version                       1.7/1.7
Forwarding                    https://nodered.<取得したドメイン> -> 127.0.0.1:1880
Forwarding                    tcp://<取得したドメイン>:8883 -> 127.0.0.1:8883
Web Interface                 127.0.0.1:4040
# Conn                        106
Avg Conn Time                 436138.60ms



HTTP Requests
-------------

GET /icons/node-red/rpi.svg   304 Not Modified
GET /icons/node-red/file.svg  304 Not Modified
GET /icons/node-red/file-in.s 304 Not Modified
GET /icons/node-red/watch.svg 304 Not Modified
GET /icons/node-red/parser-ya 304 Not Modified
GET /icons/node-red/file-out. 304 Not Modified
GET /icons/node-red/parser-xm 304 Not Modified
GET /icons/node-red/parser-js 304 Not Modified
GET /icons/node-red/parser-ht 304 Not Modified
```


問題なくTCP接続が確認できた。