CertbotでCloudflare登録ドメインの証明書を発行する
-
Certbot(Let's encrypt) を使って Cloudflare で管理しているドメインのワイルドカード証明書を DNS-01 方式で発行する手順を、毎度調べなくていいようにまとめておきます。
Cloudflare APIキー
My Profile | Cloudflare から Global API Key
を取得します。
環境変数
使用する環境変数は .env
にまとめておきます。
# Cloudflare
DNS_CLOUDFLARE_EMAIL=email@example.com
DNS_CLOUDFLARE_API_KEY=a1b2c3d4e5
CLOUDFLARE_CREDENTIALS=/root/.cloudflare/credentials
# Certbot
CERTBOT_DOMAIN=example.com
## 本番(テスト以外)は空文字にする
CERTBOT_TEST=--test-cert
DNS_CLOUDFLARE_EMAIL
: Cloudflare アカウントのメールアドレスDNS_CLOUDFLARE_API_KEY
: Cloudflare の `Global API KeyCLOUDFLARE_CREDENTIALS
: Certbot プラグインが使用する情報が書かれたファイルのパスCERTBOT_DOMAIN
: (ワイルドカード)証明書を発行するドメインCERTBOT_TEST
: テスト用の証明書を発行するかどうか- 本番: 空文字
- テスト: 何かしらの文字
Certbot 準備
Certbot の Dockerfile を作ります。docker-compose の args
から渡される DNS_CLOUDFLARE_EMAIL
, DNS_CLOUDFLARE_API_KEY
を credential ファイルに出力します。
FROM certbot/dns-cloudflare
ARG DNS_CLOUDFLARE_EMAIL
ARG DNS_CLOUDFLARE_API_KEY
ARG CLOUDFLARE_CREDENTIALS
ARG CERTBOT_TEST
ARG CERTBOT_DOMAIN
# certbot credential file
RUN \
mkdir -p /root/.cloudflare/ && \
echo "dns_cloudflare_email = ${DNS_CLOUDFLARE_EMAIL}" > ${CLOUDFLARE_CREDENTIALS} && \
echo "dns_cloudflare_api_key = ${DNS_CLOUDFLARE_API_KEY}" >> ${CLOUDFLARE_CREDENTIALS} && \
chmod 700 ${CLOUDFLARE_CREDENTIALS}
# entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod 500 /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
エントリーポイントには entrypoint.sh
を設定します。
Certbot コマンドによる証明書の発行・再発行ができるように、entrypoint.sh
の第 1 引数が certonly
のときは certbot certonly [opts]
が、renew
のときは certbot renew [opts]
が実行されるようにします。
#!/bin/sh
set -euC
error () {
err="ERROR"
message=": ${1}"
echo -e "\033[0;31m${err}\033[0;39m${message}" >&2
exit 1
}
# certbot command
command=""
add_command () {
command="$1 ${command}"
}
add_command_arg () {
command="${command} $1"
}
# debug mode
if [ -n "${CERTBOT_TEST}" ]; then
add_command_arg "--test-cert"
fi
# subcommand
sub_command=$1
if [ ${sub_command} = "certonly" ]; then
# certonly command
add_command ${sub_command}
# dns-cloudflare
if [ -n "${CLOUDFLARE_CREDENTIALS}" ]; then
add_command_arg "--dns-cloudflare"
add_command_arg "--dns-cloudflare-credentials ${CLOUDFLARE_CREDENTIALS}"
fi
# email
if [ -n "${DNS_CLOUDFLARE_EMAIL}" ]; then
add_command_arg "--email ${DNS_CLOUDFLARE_EMAIL}"
fi
# other opts
add_command_arg "--agree-tos"
add_command_arg "--non-interactive"
# domain
if [ -n "${CERTBOT_DOMAIN}" ]; then
add_command_arg "-d ${CERTBOT_DOMAIN}"
add_command_arg "-d *.${CERTBOT_DOMAIN}"
fi
elif [ ${sub_command} = "renew" ]; then
# renew command
add_command ${sub_command}
else
error "Invalid subcommand: ${sub_command}"
fi
# run certbot
eval "certbot ${command}"
Nginx 準備
Nginx の Dockerfile も作ります。
FROM nginx:1.17.8-alpine as base
RUN rm /etc/nginx/conf.d/*
##### production #####
FROM base as production
COPY ./default.conf /etc/nginx/conf.d/
CMD [ "nginx", "-g", "daemon off;" ]
# upstream appserver {
# server app:3000;
# }
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
# nginx version
server_tokens off;
# security header
add_header x-xss-orotection "1; mode=block";
add_header x-content-type-options nosniff;
add_header x-frame-options DENY;
add_header x-download-options noopen;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
root /usr/share/nginx/html;
index index.html;
# SSL Settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDH !aNULL !eNULL !SSLv2 !SSLv3';
# rename "example.com" to my domain
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Nginx version
server_tokens off;
# Security header
add_header x-xss-orotection "1; mode=block";
add_header x-content-type-options nosniff;
add_header x-frame-options DENY;
add_header x-download-options noopen;
# HTTP Strict Transport Security
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
# location / {
# proxy_pass http://appserver;
# }
}
docker-compose にまとめる
上記で用意した設定ファイルを docker-compose にまとめます。
version: '3.4'
services:
nginx:
build:
context: ./nginx
dockerfile: ./Dockerfile
target: production
ports:
- 80:80
- 443:443
volumes:
- letsencrypt:/etc/letsencrypt
restart: unless-stopped
certbot:
build:
context: ./certbot
dockerfile: ./Dockerfile
args:
- DNS_CLOUDFLARE_EMAIL=$DNS_CLOUDFLARE_EMAIL
- DNS_CLOUDFLARE_API_KEY=$DNS_CLOUDFLARE_API_KEY
- CLOUDFLARE_CREDENTIALS=$CLOUDFLARE_CREDENTIALS
- CERTBOT_TEST=$CERTBOT_TEST
env_file: .env
volumes:
- letsencrypt:/etc/letsencrypt
volumes:
letsencrypt:
証明書を発行する
証明書を発行するドメインなどの情報は .env
に記入されているものとします。
はじめに docker-compose build
でイメージの作成を行います。
$ docker-compose build
...
Successfully tagged certbot-cloudflare_nginx:latest
...
Successfully tagged certbot-cloudflare_certbot:latest
初回の証明書
証明書発行の初回は Certbot の certonly
コマンドを実行する必要があります。
entrypoint.sh
にて certbot certonly
を実行したいため、ここでは docker-compose run certbot certonly
コマンドを入力します。
$ docker-compose run certbot certonly
Creating network "certbot-cloudflare_default" with the default driver
Creating volume "certbot-cloudflare_letsencrypt" with default driver
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-cloudflare, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for example.com
dns-01 challenge for example.com
Waiting 10 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2020-10-13. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
これで証明書が発行されました。
証明書の更新
証明書の更新には certbot renew
コマンドを実行する必要があるため、ここでは docker-compose run certbot renew
を入力します。
$ docker-compose run certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator dns-cloudflare, Installer None
Renewing an existing certificate
Performing the following challenges:
dns-01 challenge for example.com
dns-01 challenge for example.com
Waiting 10 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
これで証明書の更新が完了しました。
ちなみに certbot renew
は証明書の有効期限が 30 日未満のときのみ証明書を再発行するため、前回の証明書発行から 2 ヶ月経過してなければ何も行われません。
$ docker-compose run certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not yet due for renewal
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certs are not due for renewal yet:
/etc/letsencrypt/live/example.com/fullchain.pem expires on 2020-10-13 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nginxの起動
発行した証明書は volumes で共通しているため、nginx コンテナでも証明書を読み込むことできます。
$ docker-compose up -d nginx
Creating certbot-cloudflare_nginx_1 ... done
おまけのログ確認
Certbot から発行された有効な証明書の履歴は crt.sh で手軽に確認できます。