本記事は Ansible Advent Calendar 2025 の11日目の記事です。
昨今、セキュリティに対する関心が昔以上に高まっており、様々な領域でセキュリティを強化する流れになっています。その一つとして、「SSL証明書の有効期間を短くする」というものがあります。「証明書の47日問題」という単語を聞いた方もいると思います。
これは業界全体のセキュリティ強化という点では歓迎すべきですが、運用する観点から見ると単純に証明書の更新作業の回数が増えるということになり、運用の負荷が上がります。
証明書業界ではこの運用負荷を軽減するための仕組みを提供しており、それが Automated Certificate Management Environment(自動証明書管理環境) という自動手続きの仕組みです。これをサポートした Ansible のモジュール community.crypto.acme_certificate があり、これを使うと前後の処理を含めて SSL証明書の更新を完全に自動化することができます。
今回はその方法について見ていきましょう。
SSLの証明書の発行はざっくりと以下の流れで進みます。
ACEM ではこの流れを自動化するための仕組みを提供しています。ACME での手続きは以下のようになります。
この流れの 2-4 がポイントで、ACMEの中でドメイン所有権を証明する「チャレンジ」と呼ばれる工程です。
このチャレンジにはいくつかの方法があり、Let’s Encrypt のサイト で詳しく紹介されています(どのチャレンジ方法に対応しているかは証明書ベンダーによる)。
この中では HTTP-01 が最も一般的で簡単な方法になります。
次に、community.crypto.acme_certificate モジュール を使ってどのように証明書を自動化するのかを見ていきましょう。
この Playbook では Let’s Encrpyt の ACME を使って nginx 用の ssl 証明書を発行しています。実際の流れを見ていきましょう。ここではポイントのみ抜粋します。
まずCSRを作成したら、ACMEモジュールを使って証明書の発行リクエストを行います。
- name: Get challenge data for {{ server_fqdn }} from Let's Encrpyt
community.crypto.acme_certificate:
account_key_src: "/etc/pki/tls/certs/{{ server_fqdn }}/account.key"
account_email: "{{ admin_email }}"
csr: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.csr"
dest: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.crt"
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_version: 2
challenge: http-01
terms_agreed: true
remaining_days: 30
register: ret_acme_challenge
challenge: http-01: 行いたいチャレンジの種類を送信remaining_days: 30: 更新用オプション。有効期限の残り日数で実際にリクエストするかを決められる。このリクエストを行うと、公式ドキュメント にあるレスポンスがそのまま返ってきます。ここにチャレンジを行うための情報が含まれています。
http-01の場合はこんな感じです(抜粋)
ret_acme_challenge:
challenge_data:
challenges:
http-01:
resource: .well-known/acme-challenge/xxxyyyzzzzz
resource_value: aaaaabbbbccccddddeeeee
これがチャレンジ(ドメイン所有権の証明)を行うための材料になります。具体的には、上記のデータを自分のサイト(SSLを使いたいサイト)に配置し、そこに証明書ベンダーがアクセスしてデータを確認できれば「所有権あり(ドメイン所有者が証明書発行をリクエストしている)」と判断されます。
このデータを使って、自分のサイトにチャレンジデータを配置する例が以下になります(ファイルをおいているだけです)
- when:
- ret_acme_challenge is changed
- server_fqdn in ret_acme_challenge['challenge_data']
block:
- name: create challenge path
ansible.builtin.file:
state: directory
path: /var/www/letsencrypt/{{ ret_acme_challenge['challenge_data'][server_fqdn]['http-01']['resource'] | dirname }}
- name: put challenge data
ansible.builtin.copy:
dest: /var/www/letsencrypt/{{ ret_acme_challenge['challenge_data'][server_fqdn]['http-01']['resource'] }}
content: |
{{ ret_acme_challenge['challenge_data'][server_fqdn]['http-01']['resource_value'] }}
- name: nginx conf for challege data
ansible.builtin.copy:
dest: /etc/nginx/default.d/letsenc_challenge.conf
content: |
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt;
}
- name: reload nginx conf
ansible.builtin.systemd_service:
name: nginx
state: reloaded
これで http://<server_fqdn>/.well-known/acme-challenge/xxxyyyzzzzz にアクセスすると、resource_value の値が見られるようになります。
http-01 では http のアクセスが行われるので、FWやセキュリティグループでポート80へのアクセスが許可されている必要があります。これが難しい場合には DNS を使った dns-01 等の別の方法でチャレンジすることになります。
準備ができたら ACME にチャレンジ要求を行います。data 部分に更新要求したときの戻り値(ret_acme_challenge) をそのまま付けてリクエストを送信します。
- name: Validate challenge and save certs
community.crypto.acme_certificate:
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_version: 2
terms_agreed: true
account_key_src: "/etc/pki/tls/certs/{{ server_fqdn }}/account.key"
csr: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.csr"
dest: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.crt"
data: "{{ ret_acme_challenge }}"
チャレンジを要求すると、証明書ベンダーが配置したチャレンジデータを確認し、成功すると証明書が発行されて、dest に保存されます。
# ls -alF /etc/pki/tls/certs/aitac.emacs-lisp.net/
total 28
drwxr-xr-x. 2 root root 121 Dec 8 05:35 ./
drwxr-xr-x. 3 root root 8192 Dec 8 04:02 ../
-rw-------. 1 root root 3243 Dec 8 04:03 account.key
-rw-r--r--. 1 root root 2159 Dec 8 05:35 aitac.emacs-lisp.net.crt
-rw-r--r--. 1 root root 1667 Dec 8 04:04 aitac.emacs-lisp.net.csr
-rw-------. 1 root root 3243 Dec 8 04:03 aitac.emacs-lisp.net.key
community.crypto.acme_certificate では、パラメーター remaining_days: 30 で「証明書の期限が残り何日以上で更新する」というオプションが指定できます。
サンプルのように、ここに 30 と指定しておくと、最初のリクエスを送った際に、
という挙動になります。この変化を when や handler でキャッチして挙動を操作することができます。
既にACMEに対応している証明書ベンダーでは、サーバー用の更新スクリプト(有名どころは certbot) を提供しており、わざわざ Ansible で Playbook を書かなくてもスクリプト一発で更新できます。この手のスクリプトはリクエストからチャレンジまでを自動でやってくれます。
しかし、SSLの終端を専用のロードバランサやリバースプロキシで実施している場合にはこれらのスクリプトは利用出来ません。こういった場合に Ansible を用いることで自動化が可能となります。また、証明書の更新前後でメンテナンスページを表示するといった、何かしらの前後処理を実行したい場合などにも Ansible は活躍してくれます。
今回は SSL証明書の更新を Ansible を使って自動化する例について紹介しました。Ansible は自動化できる範囲が広いため、今回の証明書更新でも様々な応用が可能となります。例えば、 uri モジュールを使って Web サーバーにアクセスすることで、証明書の有効期限を一般ユーザーと同じ経路から確認することもできます。これを利用すると、
というような、監視の役割を兼ねた証明書更新の自動化を実現することも可能となります。
どんどん応用してAnsibleの可能性を追求していきましょう。