Ngrokを安全に使いたかった

# 概要
ngrokという開発者向けクラウドサービスがある。
これを使うことによって(インターネットに繋がっていれば)ローカルリソースにどこからでもアクセスできるようにすることができる。
主にテスト用途で使われるサービスのようだが、このようなことが実現できることにかなり驚きを覚えた。
今回このngrokをopensslによって、より安全に使える構成を目指した。
また構成の可搬性を上げるためにコンテナを使うことにした。

# 目標
* 通信開始時に、クライアント証明書による認証をかける
* サーバー類はコンテナ利用して全てローカルのWindows PCにてまかなう

# ツール類
* Ngrok: この構成の主題となるサービス。Proプラン以上が必要。
* Docker: ローカルWindowsにてまかなう為と、サーバ内構成を管理する用途。
* OpenSSL: 証明書作成用
* AWS Route53: DNS設定用
* Nginx: アクセス対象webサーバ用


# 手順

## ドメイン取得
お名前ドットコムにて取得。

## DNS設定
1) route53にてホストゾーン作成

route53ダッシュボード＞ホストゾーン＞ホストゾーンの作成＞
    ドメイン名：取得したドメイン名
    タイプ：パブリックホストゾーン

2) route53のネームサーバー（NSレコードの値）をお名前ドットコム側に設定

ドメイン＞ドメイン詳細＞ネームサーバー設定＞その他

3) 動作確認

route53ダッシュボード＞ホストゾーン＞レコードを作成
    レコード名：入力なし
    レコードタイプ：A
    値：AWSリソースのパブリックIP（どれでも良い）
    TTL：300（デフォルト）
    ルーティングポリシー：シンプルルーティング（デフォルト）

コマンドプロンプトにて
    `nslookup hogehoge.com`

設定したAWSリソースのパブリックIPが返されることを確認

## Ngrokセットアップ

https://ngrok.com/

signupのところから登録を進める。
ソフトウェアのインストール、トークン認証後、
powershellからngrok.exe ～ 系コマンドが実行可能。
今回Proプラン以上が必要になるので、ライセンス購入した。

## カスタムドメイン設定
## Ngrok側での設定
Domains>Create a domain>
    Region：Japan
    Domain：ngrok.（ドメイン）
DNS TARGETS
    設定なし
Domain Record
    Valueを控える

## Route53に移動
Route53にCNAMEレコード追加
route53ダッシュボード＞ホストゾーン＞
    レコード名：ngrok
    レコードタイプ：CNAME
    値：ngrok側で控えたValue
    TTL：300（デフォルト）
    ルーティングポリシー：シンプルルーティング（デフォルト）

## CAサーバ構築
ローカルPCでDockerを使用して構築したかったのと、一般的な(可搬性のある)環境という意味でAmazon-linux 2のイメージを使用して構築。
こちらのQiitaの記事を参考にして土台となる部分を作成しました。

https://qiita.com/aucfan-engineer/items/6881d0026b5937b2558d

記事の内容からの変更箇所としては、opensslのインストールとopensslの設定ファイル配置と変更が必要だった。

```
# opensslインストール
RUN yum -y install openssl

# 設定ファイルの配置
COPY openssl.cnf /etc/pki/tls/openssl.cnf
COPY openssl.cnf /etc/pki/tls/openssl-ca.cnf
COPY openssl.cnf /etc/pki/tls/openssl-server.cnf
COPY openssl.cnf /etc/pki/tls/openssl-client.cnf
 
# 設定ファイルの編集
RUN sed -ie 173s/'CA:FALSE'/'CA:TRUE'/g /etc/pki/tls/openssl-ca.cnf && \
    sed -ie 251s/'# nsCertType'/'nsCertType'/g /etc/pki/tls/openssl-ca.cnf && \
    sed -ie 251s/'# nsCertType = sslCA, emailCA'/'nsCertType = server'/g /etc/pki/tls/openssl-server.cnf && \
    sed -ie 251s/'# nsCertType = sslCA, emailCA'/'nsCertType = client, email, objsign'/g /etc/pki/tls/openssl-client.cnf
```

OpenSSLバージョンはOpenSSL 1.0.2k-fipsが入った。
こちらのプライベートCA構築記事を参考に構築しました。

https://server-setting.info/centos/private-ca-cert.html

## 証明書発行
証明書発行に関して、SAN(SubjectAltName)に対応させるために旧来のCA.shを用いる方法と、opensslから始まる新しいコマンドを両方用いる必要があった。
これは、IEや旧Edge、旧Firefoxなどの古いブラウザでは証明書のCNとドメインとの一致を見て認証しているのに対し、現在のChrome、Edge、FirefoxではCNとドメインの一致ではなくSAN(SubjectAltName)に含まれるDNSフィールドとドメインの一致を見て認証している為、対応が必要であった。

## CA証明書
```
CADAYS="-days 9999"
SSLEAY_CONFIG="/etc/pki/tls/openssl-ca.cnf"
cd /etc/pki/tls/
CADAYS="-days 9999" SSLEAY_CONFIG="-config /etc/pki/tls/openssl-ca.cnf" /etc/pki/tls/misc/CA -newca
（対話入力）
cp /etc/pki/CA/cacert.pem .
```

実施後、WIN SCPを使用して証明書をクライアント端末に移動

```
rm cacert.pem
```

実施後、WindowsにCA証明書をインストール

## サーバー証明書
```
openssl req -extensions v3_req -new -newkey rsa:4096 -keyout server.key -nodes -days 365 -out server.csr
cat ‘DNS Name=<DNS名>’ > san.txt
openssl x509 -req -in server.csr -CA /etc/pki/CA/cacert.pem -CAkey /etc/pki/CA/private/cakey.pem -CAcreateserial -days 10000 -out server.crt -extfile san.txt

mv /etc/pki/tls/server.crt /etc/pki/CA/certs/<カスタムドメイン名>.crt
openssl x509 -in /etc/pki/CA/certs/web.nginx.com.crt -outform PEM -out /etc/pki/CA/certs/<カスタムドメイン名>.pem
openssl rsa -in /etc/pki/tls/server.key -out /etc/pki/CA/private/<カスタムドメイン名>.key
cp /etc/pki/CA/certs/<カスタムドメイン名>.crt .
cp /etc/pki/CA/private/<カスタムドメイン名> .
```

実施後、WIN SCPを使用してクライアント端末に移動

## クライアント証明書
```
cd /etc/pki/tls/
DAYS="-days 9999" SSLEAY_CONFIG="-config /etc/pki/tls/openssl-client.cnf" /etc/pki/tls/misc/CA -newreq
（対話入力）
openssl x509 -in /etc/pki/CA/cacert.pem -outform PEM -out /etc/pki/CA/capem.pem
SSLEAY_CONFIG="-config /etc/pki/tls/openssl-client.cnf" /etc/pki/tls/misc/CA -sign
（対話入力）
openssl pkcs12 -export -in newcert.pem -inkey newkey.pem -out private.pfx
```

## WEBサーバ構築
## Dockerfile

```Dockerfile:Dockerfile
FROM nginx:latest
COPY index.html /usr/share/nginx/html
RUN mkdir /etc/nginx/ssl_crt
COPY cacert.pem /etc/nginx/ssl_crt
COPY ngrok.hogehoge.com.crt /etc/nginx/ssl_crt
COPY ngrok.hogehoge.com.key /etc/nginx/ssl_crt
COPY nginx.conf /etc/nginx
COPY ngrok.hogehoge.com.conf /etc/nginx/conf.d
```

nginx.conf等設定ファイルに関して、こちらの記事を参考に作成しました。

https://zoo200.net/nginx-client-verify/

## 証明書インポート、信頼設定
発行されたクライアント証明書は拡張子がcrt(もしくはpem)になっているので、テキストファイル化してから、--BIGIN CERTIGICATE–~--END CERTIFICATE–までの部分のみを残して、拡張子をcer形式に変換して保存。

CA証明書はcacert.pemという名前のファイルで、これを「信頼された証明機関」のストアにインポート行う。

クライアント証明書は、「現在のユーザー」の「個人」ストアにインポートする。

## テスト実施
## ngrokのコマンド
`.\ngrok.exe tls -region='jp' -hostname='ngrok.hogehoge.com' 443`

ローカルのWindows 10にngrok経由でアクセスして、chromeブラウザ、Edgeブラウザ利用時に証明書を聞かれることができた。

# トピック
＜ngrok関連＞
・ngrokのFreeプランでhttp通信ができるが、同時にhttps通信も可能（当初はhttps通信はFreeプランでできないと認識していた）
・上記Freeプランでできる方のhttps通信は、*ngrok.ioのワイルドカードドメインを使用したngrok側で用意している証明書になる。
・このとき通信はngrokのクラウドサーバーにてSSL Terminationされるので、ngrokのクラウドサーバーからローカルサーバーに行く通信に関して、https化されているわけではない。なのでEnd to EndでSSL化するためにはやはりProプラン以上が必要。
・認証に関して、FreeプランでBasic認証が利用可能。
・以上のことから、今回実施した構成をやらなくとも、テスト用途であれば十分に安全に通信できる。しかし、要件・用途次第では今回実施したようなセキュリティを高める構成も有用となる可能性はある。

＜認証関連＞
・CAサーバーの運用に課題感がある。証明書発行の際に対話入力が挟まるので自動化の難易度が上がる。そこをクリアできれば別構成でも十分活用できるだけの機能はある。
・証明書の有効期限を9999日に設定したことで、iOSからの認証が信頼できない通信となる事象が起こっているので、現在対応中。
・SubjectAltNameや証明書のバージョンに関して、現行のものに常に発行プロファイル仕様をキャッチアップして合わせていく必要があることの重要性がわかった。
・Safariブラウザにおいて2020年9月1日以降、有効期限13ヶ月を超える証明書は信頼されない設定になっている。Chrome、Edge等もこういった影響を少なからず受け全体的な有効期限の見直し(短縮)の傾向がある。
・プライベートCAを利用している利点を今後さらに活かす形に発展させたい。具体的にはLDAPやADなどと連携して登録ユーザーに対して証明書を発行し企業内で使える形にする、アクセス先サーバーの登録システムを作成しサーバ利用者のグループとマッピングして証明書の配布や認証制御を行うなど。