nginxコンテナを自己署名証明書でHTTPS化する
前回、Dockerでnginxを入れて基本的なことを試して以下の記事を書きました。
このときHTTPS化も試していたのですが話が長くなるので別の記事にすることにしました。
はじめに
今やサービスの入り口は全てHTTPS化するのが常識となりました。外部公開しないサービスも同様です。開発環境もHTTPS化しています。今後、HTTPSを設定をする機会は増えてくるのでしっかりと理解しておきたいところです。
今回はブラウザとnginxの間をHTTPS化します。
nginxとWebアプリケーションとの間は引き続きhttpにします。暗号化/復号化処理はnginxが実行してWebアプリケーションはアプリケーションの実行に専念する構図です。
最近ではコンテナ間通信もHTTPS化する流れがありますが、Webアプリケーション側まで手を入れると時間がかかるので今回はやめました。
証明書をどうするか?
HTTPS化するには証明書が必要です。
証明書ををどうするか考えてみました。選択肢は以下の3つです。
この記事では自己署名証明書を使ったケースを紹介します。
2の自己署名認証局を使ったケースは別の記事で紹介しています。
3も試す予定でしたが2でお腹いっぱいになったのでやめました。
自己署名証明書
通称、オレオレ証明書です。
本来、サーバ証明書の署名欄にはその証明書を発行したCAの署名が入ります。サーバ証明書とは第3者のCAが「私はこのサーバを調査し、その結果、このサーバが信頼に値することを保証します。」と宣言している保証書なのです。
CAの署名の代わりにサーバ自身の署名が入るのが自己署名証明書です。「オレぁはいいヤツだぜぇ!オレがそう云うんだからまちげぇねぇ!」と自らが喧伝しているわけです。そんなことする輩がまともなわけありません。出会ったら、すぐ逃げて下さい。
危険な自己署名証明書ですが、証明書が欲しいときには最も簡単に作成できます。opensslコマンド3発で完成です。お金はかかりません。CAによる実存性確認もありません。
お手軽な反面、ブラウザがセキュリティ警告をガンガン出すので保護されている気分に全くなれないのが大きな欠点です。また、フロントエンドのAPIが動かなくなることもあります。
結論を言うとこの方法はおすすめしません。簡易的なHTTPS環境を作りたい場合は2の自己署名認証局を使うことを勧めます。
nginxコンテナのTCP 443番を公開
それでは構築していきます。
これまで作業してきたnginxコンテナは下記の通りTCP 80番を公開してました。
docker run -it --name nginx -p 8080:80 nginx /bin/bash
HTTPSはTCP 443番を使うので、このままではHTTPS化できません。
新しいnginxコンテナを起動します。このとき下記の通りTCP 443番を公開します。
docker run -it --name nginx -p 443:443 nginx /bin/bash
秘密鍵と証明書を作成する
過去のPKIに関する記事を参考にnginxコンテナの中でopensslコマンドを3発、叩きます。
色々聞かれますが基本、空欄のままEnter押下で問題ありません。ただし秘密鍵作成のときに要求されるpass phraseの入力は必須です。pass phraseは後で使うので忘れないで下さい。
# openssl genrsa -aes192 -out mykey.key 4096 <略> Enter pass phrase for mykey.key:
また、Certificate Signing Requestを作成するときに下記の通りCommon Nameで「localhost」と入れるのも重要です。もしnginxにちゃんとしたホスト名を割りあてている場合は「localhost」ではなくそのホスト名を入れて下さい。
Common Name (e.g. server FQDN or YOUR name) []:localhost
最初、これをうっかり忘れてフロントエンドのfetch APIが動かなくなりました。fetch APIはSubjectのCommon Name(CN)がURLと一致するかチェックしているようです。
opensslコマンドが作成したファイルです。mycert.crtが証明書、mkey.keyが秘密鍵です。mycsr.csrはCSRですがここから先は出番はありません。
# ls /etc/nginx/conf.d/ssl/ mycert.crt mycsr.csr mykey.key
証明書をチェック
証明書の内容をチェックします。
SubjectのCNが「localhost」になっているのでOKです。
Issuer(発行者)もlocalhostなので自己署名証明書であることがわかります。
# openssl x509 -text -noout -in mycert.crt <略> Issuer: CN=localhost <略> Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = localhost
nginxの設定変更
/etc/nginx/conf.d/default.conf以下のように変更します。
vimが入っていない場合は前回の記事を参考にvimをインストールして下さい。
前回からの変更点は443をlistenしていることと、証明書と秘密鍵を指定していることの3点のみです。
server { listen 443 ssl; <<< 変更1 server_name localhost; ssl_certificate /etc/nginx/conf.d/ssl/mycert.crt; <<< 変更2 ssl_certificate_key /etc/nginx/conf.d/ssl/mykey.key; <<< 変更3 access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; } location /api { proxy_pass http://host.docker.internal:5000; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
設定に問題がないかconfigtestしてからnginxを再起動します。 このとき秘密鍵を生成するときに入力したpass phraseを要求されます。
/etc/init.d/nginx configtest /etc/init.d/nginx restart
これでnginxは準備完了です。
フロントエンド修正
前回に続きTCP 5000番で待っている適当なWebアプリケーションをローカルPCで立ち上げています。
HTTP環境で作成したのでフロントエンドのfetch APIのアクセス先がhttpです。
const request_url = "http://localhost:8080/api";
これではフロントエンドがデータを取得できません。httpsに変更します。
const request_url = "https://localhost/api";
これでnginxもフロントエンドもHTTPSに対応しました。最後に動作確認をしましょう。
動作確認
https://localhostへアクセスします。
chromeの場合、以下のようにERR_CERT_AUTHORITY_INVALIDエラーが表示されます。
「詳細設定」→「localhostにアクセスする(安全ではありません)」をクリックします。
無事、情報が表示されました。ChromeもフロントエンドのJavaScriptも問題なく動いてます。
ただしロケーションバーには「保護されていない通信」と表示されています。「https」には取り消し線が入ってます。
HTTPSを導入したはずですが保護されている感じがしません。
「保護されていない通信」をクリックすると証明書が「無効」となっています。
「無効」をクリックするとオレオレ証明書であることがわかります。
結論
短時間でHTTPS化することができました。その感想としては「これでいいの?」です。
外部公開するサービスでやるのは当然論外です。
しかし外部公開しない内輪向けのサービスだとしても信頼できない証明書を許容するようなことをユーザにさせていいのでしょうか?ユーザのセキュリティ感覚を麻痺させて危険にさらしていないでしょうか?
検証用だとしてもオレオレ証明書で動いていたフロントエンドが本番のサーバ証明書で動くでしょうか?安全なAPIに無理やりオレオレ証明書を許容させるようなコードや設定をしたときその検証に意味はあるのでしょうか?
次の記事では「自己署名認証局が署名した証明書」を紹介します。