Enabling https access on Nginx reverse proxy - シリーズ投稿4/7
Series Top: Dockerで作るおうちLAN遊び場
この投稿はシリーズ第四弾です。前回までの投稿で、Jupyter NotebookやGhostといったwebサービスをDockerで実行させ、更に手前にリバースプロキシを用意することで、DNS名で各サービスにアクセスできるようにしました。今回の投稿ではいくらか例として追加したものをクリーンアップし、httpsを有効にするなどリバースプロキシの設定を更に見ていきます。
以下の図は今回の投稿後のサービスアクセスのイメージ図です。リバースプロキシへのアクセスはhttpsとなり、その他すでにあるサービスや今後追加するサービスはhttpのまま配置できます。
また今回の投稿では更に作成するファイルが増えてくるため、投稿の最後にディレクトリやファイルのリストをまとめています。
Cleaning up examples used in the previous post
前回は2つ目のJupyter NotebookやGhostブログを例として追加しましたが、それらを止めて、ファイルおよびDNSレコードをクリーンアップしましょう。
まず$HOME/mylan/jupyter
でdocker compose down
でコンテナを消し、docker-compose.yml
ファイルからjupyter2
を消しましょう。docker compose up -d
でサービスを改めて走らせると、jupyter
一つだけが実行されている状態になります。
もともと1つ目のjupyter
を動かしていた時のままですが、以下がdocker-compose.yml
ファイルの中身です。
services:
jupyter:
container_name: jupyter
image: jupyter/base-notebook:notebook-6.5.1
user: root
ports:
- "8888:8888"
command: "start-notebook.sh --ServerApp.password='' --ServerApp.token='' --ip=0.0.0.0 --no-browser"
environment:
- "CHOWN_EXTRA=/home/jovyan"
- "CHOWN_EXTRA_OPTS=-R"
volumes:
- type: volume
source: jupyter_volume
target: /home/jovyan
volume:
nocopy: true
volumes:
jupyter_volume: {}
次にGhostに関してはdocker compose down
でコンテナを消します。
最後に、Nginx向けの各サービス用サーバコンフィグファイルを変更します。ここでは例としてjupyter2.conf
をjupyter2.conf.bak
と変更し、ブログ用も同様にblog.conf.bak
とします。そしてNginxのコンテナをリスタートするとファイルとしては残るものの、改名したサーバ設定は読み込まれないようになっています。docker exec rp nginx -T
を実行して確認できます。
クリーンアップの結果、ディレクトリやファイルは以下のようになっています。
$ cd $HOME/mylan
$ tree
.
|-jupyter
| |-docker-compose.yml
|-ghost
| |-docker-compose.yml
|-rp
| |-docker-compose.yml
| |-conf.d
| | |-jupyter.conf
| | |-blog.conf.bak
| | |-jupyter2.conf.bak
|-dns
| |-docker-compose.yml
| |-config
| | |-a-records.conf
## btw here I am actually using "find" and "sed" and making it alias
$ grep tree ~/.bash_aliases
alias tree='find . -not -path "*/\.git/*"| sed -e "s/[^-][^\/]*\// |/g" -e "s/|\([^ ]\)/|-\1/"'
Preparation for https access
Nginxサーバでhttps通信を扱えるようにするために必要なものはSSL/TLSのプライベートキーと証明書です。自身のドメインを持っているならば、そのドメイン用の証明書とキーを生成するのがベストです。シリーズの最初に触れましたが、私の実環境では購入したドメイン名とそれ用の証明書でサービスを走らせています。
Private CA and self-signed certificate
このシリーズではローカルのドメイン、mylan.local
で引き続き構築を進めていきます。この場合はインターネット上で通用する証明書をletsencryptなどで発行してもらうことができないので、以下いろいろと用意していきます:
- 自身のCA, certificate authorityとしてプライベートキーと証明書を生成
- リバースプロキシで利用する証明書用のCSRとプライベートキーを生成
- CAの証明書・キーでCSRを署名して証明書を生成
- DHパラメータファイルを生成
別ディレクトリ、$HOME/mylan/openssl
を用意してそこで一連の作業を進めていきます。
cd $HOME/mylan
mkdir openssl
cd openssl
まずはCAの証明書とキーの生成です。
# this will generate two files, crt and key
openssl req -x509 \
-sha256 -days 3650 \
-nodes \
-newkey rsa:2048 \
-subj "/CN=ca.mylan.local" \
-keyout rootCA.key -out rootCA.crt
# here is the result
$ ls
rootCA.crt rootCA.key
次に*.mylan.local
用のCSRとプライベートキーの生成です。
# generate a CSR for "*.mylan.local"
# along with the private key for this CSR/certificate
openssl req -new \
-newkey rsa:2048 -keyout tls.key -nodes \
-out tls.csr \
-subj "/CN=*.mylan.local"
# again, the result
$ ls
rootCA.crt rootCA.key tls.csr tls.key
見ての通りopenssl
を用いて準備を進めているのですが、次にCSRに署名して証明書を生成する時に使うopenssl
の設定ファイルを用意します。
cert.conf
というファイルで用意し、中身は以下です。
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = *.mylan.local
ついにCSRに署名して実際にリバースプロキシが使う証明書を生成します。出力は省略していますが、openssl x509 -in tls.crt -text -noout
で証明書の中身が確認できます。
openssl x509 -req \
-in tls.csr \
-CA rootCA.crt -CAkey rootCA.key \
-CAcreateserial -out tls.crt \
-days 365 \
-sha256 -extfile cert.conf
# to check the content of the signed certificate file
openssl x509 -in tls.crt -text -noout
ついでにDHパラメータファイルも生成します。
# generate DH parameter file as dhparam.pem
openssl dhparam -out dhparam.pem 2048
# the end result
$ ls
cert.conf dhparam.pem rootCA.crt rootCA.key rootCA.srl tls.crt tls.csr tls.key
Configuring https
https通信を扱うのに必要なファイルの生成が終わりました。$HOME/mylan/rp
ディレクトリに戻りリバースプロキシサーバの準備を進めていきます。
https通信用のディレクトリとしてtls
を用意してそこに必要なファイルをopenssl
ディレクトリよりコピーしてきます。
cd $HOME/mylan/rp
mkdir tls
cd tls
cp $HOME/mylan/openssl/tls.key .
cp $HOME/mylan/openssl/tls.crt .
cp $HOME/mylan/openssl/dhparam.pem .
このtls
ディレクトリにコンフィグファイルtls.conf
も作ります。このtls
ディレクトリはコンテナ内の/etc/nginx/tls
として配置する予定なので、コンフィグファイル内のssl_dhparam
, ssl_certificate
, ssl_certificate_key
の向き先はコンテナ内ファイル配置に応じた設定を書いておきます。
ホストマシンからコンテナ内に積み込んで読み込ませるファイルが増えていきます。Pathの指定で混乱してくるかもしれませんが、実際にサービスを走らせているコンテナ内でどのファイルがどこに置かれていることになるのかをイメージして、頑張って整理してみてください。
# this is tls.conf file
# dhparam
ssl_dhparam /etc/nginx/tls/dhparam.pem;
# options
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
# cert/key
ssl_certificate /etc/nginx/tls/tls.crt;
ssl_certificate_key /etc/nginx/tls/tls.key;
このrp
サービスコンテナに新たにtls
ディレクトリを載せるよう、docker-compose.yml
ファイルを更新します。また同時にポートも443に変えます。
services:
rp:
container_name: rp
image: nginx:1.23.2
ports:
- "443:443"
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./tls:/etc/nginx/tls
各サービス用サーバコンフィグファイルも更新します。クリーンアップしたのでjupyter
分のみですが、$HOME/mylan/rp/conf.d/jupyter.conf
ファイルを更新してtls.conf
を利用させるようにし、更に応答ポート、サービスも443にします。
server {
listen 443 ssl http2;
server_name jupyter.mylan.local;
# docker resolver
resolver 127.0.0.11 valid=30s;
# tls
include /etc/nginx/tls/tls.conf;
location / {
set $upstream 192.168.1.56:8888;
proxy_pass http://$upstream;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
rp
のDocker Composeをリスタートすると、ブラウザ上でhttps://jupyter.mylan.local
にアクセスすることでhttpsでJupyter Notebookサービスにアクセスできるようになっています。
Adding root CA certificate on my client machine
ブラウザ上ではhttps通信に関して警告メッセージが出ているはずです。メッセージがすでに示していると思いますが、ブラウザはアクセス先のサーバが提示している証明書を信用していません。ブラウザは一般的に信頼して良いとされている機関の情報を持っており、今回用いている証明書はあくまでプライベートに、自分で署名した証明書であり、信用に値しないからです。
Opensslで生成したrootCA.crt
ファイルをスマホなりラップトップなりにコピーし、そのシステムが信用するroot CAに追加してください。方法はOSによってまちまちですが検索すれば出てきます。
なおもちろん、パブリックに通用するドメインでパブリックに通用する証明書、キーを準備して構築しているのであれば、ブラウザは元から信用してくれます。
Files I have so far
今回の投稿は以上で、締めとしてディレクトリやファイルのまとめを以下に残しておきます。クリーンアップをどうしたかで残っているディレクトリ、ファイルは変わると思いますが、ここまでで実行しているサービス分を全て含めるとこのようになっています。
$ tree
.
|-jupyter # Jupyter用のディレクトリ
| |-docker-compose.yml # Jupyter Notebookを実行、ポート8888
|-openssl # 証明書関連のディレクトリ
| |-rootCA.crt # ルートCA証明書
| |-dhparam.pem # ウェブサーバで用いるDHパラメータファイル
| |-tls.key # *.mylan.localサーバ証明書用のプライベートキー
| |-rootCA.srl # ルートCAのシリアルファイル、CSR署名時に生成される
| |-cert.conf # CSR署名時に使ったopensslの設定ファイル
| |-tls.crt # *.mylan.localサーバ証明書
| |-rootCA.key # ルートCAのプライベートキー
| |-tls.csr # サーバ証明書生成依頼に用いたCSR
|-rp # リバースプロキシサーバのディレクトリ
| |-docker-compose.yml # Nginxを実行、ポート443
| |-conf.d
| | |-jupyter.conf # jupyter.mylan.local用のサーバコンフィグファイル
| |-tls
| | |-tls.conf # https関連の設定ファイル
| | |-dhparam.pem # DHパラメータファイル
| | |-tls.key # 証明書のプライベートキー
| | |-tls.crt # 証明書
|-dns # DNSのディレクトリ
| |-docker-compose.yml # Unboundを実行、ポート53、TCP/UDP両方
| |-config
| | |-a-records.conf # DNSレコード追加用のコンフィグファイル
Discussion