zaki work log

作業ログやら生活ログやらなんやら

[Ansible] acme_certificateモジュールでACMEのDNS-01チャレンジを使った証明書作成

2021年に基本のHTTP-01チャレンジを使った実装を試してたけど、HTTP/HTTPSのwebサーバーの場合以外だとこの方式は使えないので、替わりにDNSのTXTレコードを使った方式であるDNS-01チャレンジを試してみた。

HTTP-01チャレンジの実装例は以下

zaki-hmkc.hatenablog.com

HTTP-01チャレンジとの差異

ACMEのやりとりとしてはHTTP-01の場合と流れは同じで、DNS-01の場合は以下のようになる。

  1. 認証局用のアカウントキーを用意
  2. サーバー証明書の秘密鍵を用意
  3. サーバー証明書の秘密鍵からCSRを作成
  4. アカウントキーとCSRを入力としてCAにACMEのチャレンジ要求
  5. チャレンジ要求のレスポンスの内容を使ってDNSサーバーにTXTレコードを作成
  6. チャレンジの検証を要求し、証明書を取得する

No.5のみHTTP-01チャレンジと異なる。
HTTP-01の場合は「webサーバーの指定のパスにレスポンスのトークンファイルを置くと」いう手順だったのが、DNSのTXTレコードに同様の情報を作成するという内容に変化。

No.4と6はACMEクライアント(今回はacme_certificateモジュール)の「DNS-01を使うよ」というパラメタを指定する。

他はHTTP-01チャレンジの場合と同じ内容。
またAnsibleタスク実装例は証明書を作成するまでで、証明書を使ったアプリケーションサーバーの起動等はHTTP-01チャレンジと異なり省略。(動作は確認済み)

環境

各製品1世代以上前のもの。

  • Ansible: ansible-core 2.20.4
  • community.crypt: 3.1.1
  • Python: 3.14.2
  • CA: HashiCorp Vault 1.21.2 (プライベート環境のK3sへセルフホスティング)
  • DNS: Dnsmasq 2.91 (on Proxmox VE)

CAにHashiCorp VaultのACMEの機能を使った証明書作成自動化については以下参照。

zaki-hmkc.hatenablog.com

community.cryptの3.2.0でチャレンジ方式のdns-account-01とdns-persist-01が追加されているっぽい。

playbook例

github.com

タスク実装のポイント

全体の流れはHTTP-01チャレンジの時のブログに書いてるので合わせて参照。

zaki-hmkc.hatenablog.com

community.crypto.acme_certificate

使用するパラメタはHTTP-01の実装があるならコピーして利用可能。
ただしDNS-01チャレンジのための challenge: dns-01 のみ追加する。(デフォルトhttp-01のため省略するとHTTP-01チャレンジとなる)

  - name: Create a challenge for {{ server_fqdn }} using a account key file.
    community.crypto.acme_certificate:
      account_key_src: "/etc/pki/tls/certs/{{ server_fqdn }}/account.key"
      account_email: "{{ email }}"
      csr: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.csr"
      dest: "/etc/pki/tls/certs/{{ server_fqdn }}/{{ server_fqdn }}.crt"
      validate_certs: "{{ validate_certs }}"
      challenge: dns-01
      acme_directory: "{{ acme_directory }}"
      acme_version: 2
      terms_agreed: true
      remaining_days: 60
    register: acme_challenge

これはチャレンジ要求だけでなく、検証と証明書の要求も同様。
(試してないけどチャレンジ要求の場合はレスポンスを見る限り全方式のチャレンジデータを返しているので、方式の指定は下記の検証&証明書要求の時だけで良いかも)

  - name: Let the challenge be validated and retrieve the cert and intermediate certificate
    community.crypto.acme_certificate:
      validate_certs: "{{ validate_certs }}"
      challenge: dns-01
      acme_directory: "{{ acme_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: "{{ acme_challenge }}"

TXTレコード作成

Amazon Route 53やAzure DNSなどのマネージドサービスを使ってるならそれ用のモジュールを使用したり、モジュールは無いがRESTで更新できるならansible.builtin.uriを使うなどする。
acme_certificateのExamplesにはRoute 53の実装例が掲載されているので参考にする。

docs.ansible.com

今回はローカル環境のDNS運用に使っているDnsmasqのため、「TXTレコード定義のファイルを作成しサービス再起動」という処理を行っている。

  - name: add TXT record to dns server
    copy:
      dest: "/etc/dnsmasq.d/{{ server_fqdn }}.conf"
      content: |
        txt-record={{ record }},"{{ value }}"
    vars:
        record: "{{ acme_challenge.challenge_data[server_fqdn]['dns-01'].record }}"
        value:  "{{ acme_challenge.challenge_data[server_fqdn]['dns-01'].resource_value }}"
    delegate_to: "{{ dns_delegate_host }}"
    become: "{{ dns_delegate_become }}"

  - name: restart DNS server
    systemd:
      name: dnsmasq.service
      state: restarted
    delegate_to: "{{ dns_delegate_host }}"
    become: "{{ dns_delegate_become }}"

内容は上記の通りで、delegate_toで証明書作成の対象からDNSサーバーへ接続先を切り替え、/etc/dnsmasq.d/{{ server_fqdn }}.confにTXTレコードを定義したファイルを作成している。
作成される定義ファイルの内容は以下の通り。

txt-record=<定義するレコード名>,"TXTレコードの値"

レコード定義の内容は以下の通りで、それぞれチャレンジレスポンスのchallenge_data.<FQDN>.dns-01.recordとchallenge_data.<FQDN>.dns-01.resource_valueに入っているので、registerで拾ってセットする。

delegate_toについては以下。

zaki-hmkc.hatenablog.com


ACMEを使った証明書作成(HTTP-01チャレンジ)はAnsibleクックブックにも載っているので(ちょっと古いけど)ご参考まで。

book.impress.co.jp