要するに次のことを行います。
- Nginx で TLS->TLS なプロキシを作る
- TLS は Nginx でいったん切る
- 証明書は Let's Encrypt で取得する
- Nginx は http はリッスンさせないので DNS 認証を使う
- ドメインは Cloudflare で管理している
- ゾーンの委任やレコードの登録は既に終わっている前提
次のような docker-compose.yml
を用意します。
version: "3.7" services: nginx: image: nginx:alpine ports: - 9999:9999 pid: service:letsencrypt volumes: - letsencrypt:/etc/letsencrypt/ - run:/run/ environment: NGINX_CONFIG: | daemon off; worker_processes auto; error_log /dev/stderr notice; pid /run/nginx.pid; events { worker_connections 32; } stream { error_log /dev/stderr info; upstream backend { server 192.0.2.123:9999; } server { listen 9999 ssl; proxy_pass backend; proxy_ssl on; ssl_certificate /etc/letsencrypt/live/ore.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ore.example.com/privkey.pem; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; ssl_session_tickets off; ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; } } command: - sh - -euc - | echo "$$NGINX_CONFIG" > /root/nginx.conf exec nginx -c /root/nginx.conf letsencrypt: image: certbot/dns-cloudflare volumes: - letsencrypt:/etc/letsencrypt/ - run:/run/ environment: DNS_CLOUDFLARE_EMAIL: ~ DNS_CLOUDFLARE_API_KEY: ~ entrypoint: - sh - -euc - | mkdir -p /root/.cloudflare/ > /root/.cloudflare/credentials chmod 700 /root/.cloudflare/ chmod 600 /root/.cloudflare/credentials echo dns_cloudflare_email = "$$DNS_CLOUDFLARE_EMAIL" >> /root/.cloudflare/credentials echo dns_cloudflare_api_key = "$$DNS_CLOUDFLARE_API_KEY" >> /root/.cloudflare/credentials exec "$$@" - -- command: - sh - -euc - | while :; do certbot renew -n --agree-tos --keep --post-hook='kill -HUP "$$(cat /run/nginx.pid)"' sleep 86400 done volumes: letsencrypt: run:
要点を記載します。
- nginx で TLS->TLS なプロキシをします
- stream の listen の ssl と proxy_ssl
- サーバ証明書のドメイン認証には DNS 認証を使います
- DNS は CloudFlare に委任しており certbot の dns-cloudflare で DNS 認証できます
- 証明書更新時に nginx に HUP を撃つ必要があるので・・・
pid: service:letsencrypt
で PID 名前空間を共用します- nginx の pid ファイルのディレクトリをボリュームで共有します
- nginx.conf は環境変数で渡して実行時にファイルに書き出し
- Docker Remote API でデプロイする想定なのでホストのファイルのマウントはやりたくない
- CloudFlare のAPIキーも環境変数で渡します
- ENTRYPOINT でファイルに書き込みます
docker-compose run
したときでも使えるように
まずは次のように実行して証明書を取得します。
docker-compose run --rm letsencrypt \ certbot certonly -n --agree-tos \ --test-cert \ --dns-cloudflare \ --dns-cloudflare-credentials /root/.cloudflare/credentials \ --dns-cloudflare-propagation-seconds 60 \ -m 'ore@example.com' \ -d 'ore.example.com'
サービスを開始します。
docker-compose up -d docker-compose ps docker-compose logs -f
動作確認します。
openssl s_client -connect ore.example.com:9999 </dev/null | openssl x509 -noout -text
さいごに
とあるニッチな理由で TLS->TLS なプロキシが欲しくてやったのだけど、よく考えると使わなさそうでお蔵入りしそうなので備忘的にポスト。