本記事で構築する AAP × Vault による証明書運用自動化の全体像
はじめに
Web サイトを HTTPS で配信するには、TLS 証明書(いわゆる SSL 証明書)が必要です。TLS 証明書は、通信の暗号化と、接続先が本物のサーバーであることの証明という2つの役割を担っています。証明書には有効期限があり、期限が切れるとブラウザが警告を表示し、利用者がサイトにアクセスできなくなります。
Let's Encrypt は、この TLS 証明書を 無料 で発行してくれる認証局(CA)です。ただし、発行される証明書の有効期間は 90 日間 と短く、定期的な更新が欠かせません。
更新は certbot renew コマンドを実行して完了する方法もありますが、実際の運用では
- 秘密鍵をどこに・どう保管するか(サーバーに平置きしたくない)
- 更新に失敗したときにどう安全に切り戻すか
- 複数台の Nginx サーバーに同じ証明書を配る場合の手順統一
- 誰が・いつ更新したかを、後から追跡できる形にしておくか
といった、「更新そのもの」より「運用を安全に維持する」部分に考慮が必要です。サーバーの台数が増えるほど、運用負荷は台数に比例する以上に増大します。
ご参考動画
証明書の短命化 — なぜ今、自動化が必須なのか
この更新頻度自体が、今後大幅に厳しくなります。
CA/Browser Forum の決定(Ballot SC-081v3)
CA/Browser Forum は Ballot SC-081v3 により、公的に信頼されたすべての TLS 証明書の最大有効期間を段階的に短縮することを決定しました。
| 施行日 | 最大有効期間 | ドメイン検証(DCV)再利用期間 |
|---|---|---|
| 2026年3月15日 | 200日 | 200日 |
| 2027年3月15日 | 100日 | 100日 |
| 2029年3月15日 | 47日 | 10日 |
このルールは公的に信頼されたすべての認証局に適用されます。90日サイクルでは何とか手動運用が成立していた現場でも、47日サイクルになると更新の間隔が短くなり、手動運用を続けることは現実的ではなくなります。
Let's Encrypt 自身の短縮ロードマップ
Let's Encrypt も独自に有効期間の短縮を進めています。
| 時期 | 内容 |
|---|---|
| 2026年5月〜 |
tlsserver ACME プロファイルで 45日 証明書をオプトイン可能 |
| 2027年2月 | デフォルトを 64日 に変更予定 |
| 2028年2月 | デフォルトを 45日 に変更予定 |
さらに、160時間(約6日間)の短命証明書プロファイルもすでに提供されています。証明書の自動更新は、選択肢の一つというより、前提として備えておくべき段階に入っていると考えています。
今回、Ansible Automation Platform(AAP) — Red Hat が提供する Ansible の商用プラットフォーム — を実行基盤に、HashiCorp Vault を秘密鍵の保管庫として使う証明書運用の自動化を実装しました。
この記事では、
の2軸で紹介します。
全体アーキテクチャ
構成要素は次の4つです(IP・ドメインは一般化した値で表記しています)。
- AAP(実行基盤): Playbook の実行と証明書発行ワークフローのオーケストレーション。AWS 上の EC2(VM)で稼働し、バックエンドの DB には RDS を使用
-
Let's Encrypt + AWS Route53: ワイルドカード証明書(
*.example.com)を DNS-01 チャレンジで発行 - HashiCorp Vault: 機密情報を一元管理するミドルウェア(詳細は後述)。ここでは発行した証明書・秘密鍵の保管と、秘密鍵の暗号化に使用
- Nginx サーバー: 実際に証明書を配置してサービスを提供するサーバー群
DNS-01 チャレンジで、AWS Route53 の TXT レコードを Ansible から自動操作する構成にしています。Route53 へのアクセスは、AAP が稼働する EC2 インスタンスに付与された IAM ロールを通じて行っており、アクセスキーを別途発行・管理する必要はありません。これにより、証明書の発行から配置までを人手を介さず一本のワークフローとして実行できます。
なぜ AAP を実行基盤に選んだのか
AAP を選んだのは、単発の cron + ansible-playbook の組み合わせではなく、
- 実行履歴・実行ログが GUI で残り、後から誰でも確認できる
- ジョブテンプレート単位で認証情報(Credential)を分離できる
- 複数の Playbook をワークフローとして連結し、成功・失敗による分岐を組める
- スケジュール実行と手動実行のどちらも同じ仕組みの上で管理できる
という、運用チームで長く使い続けることを前提にした基盤で、47日サイクルのような短命化にも耐えられる「人手を介さずに回せる」仕組みを目指しています。
カスタム Execution Environment(EE)
AAP のジョブは Execution Environment(EE) というコンテナ内で実行されます。今回の Playbook では certbot、openssl、boto3(Route53 操作用)など、標準 EE に含まれないツールを使用するため、ansible-builder でカスタム EE をビルドしています。
# execution-environment.yml(抜粋)
version: 3
dependencies:
python_requirements:
- certbot
- certbot-dns-route53
- boto3
system:
- openssl
カスタム EE のビルド手順や設定の詳細は、別記事「Ansible Automation Platform : カスタム Execution Environment(EE)の作成」にまとめています。
証明書発行〜配置の主要フロー
処理は「発行」と「配置」で Playbook を分けています。実行場所が異なるためです。以降の説明に出てくる「Vault」は、いずれも HashiCorp Vault のことを指します。
| フェーズ | Playbook | 実行場所 |
|---|---|---|
| 発行 |
cert-issue.yml(wrapper)→ cert-issue-nginx_per_cert.yml(1証明書ずつ処理) |
localhost(AAP 実行環境) |
| 配置 | cert-replace.yml |
各 Nginx サーバー |
| 検証 | cert_verify.yml |
各 Nginx サーバー |
| 有効期限チェック | check_certs.yml |
各 Nginx サーバー |
発行フェーズと配置フェーズを通して見ると、次のような時系列になります。
発行フェーズ
証明書の発行には certbot コマンドを使っています。certbot は、Let's Encrypt が策定した ACME(Automatic Certificate Management Environment)プロトコル(RFC 8555)に対応したクライアントです。ACME は、認証局(CA)とのやり取り(ドメイン所有権の証明、証明書の発行・更新・失効)を自動化するための標準プロトコルで、従来のように CSR を手動で発行局に提出して手動で受け取る、という作業を必要としません。
ACME にはドメイン所有権を証明するための「チャレンジ」がいくつか用意されており、代表的なものが HTTP-01(Web サーバーに特定ファイルを置く)と DNS-01(DNS に特定の TXT レコードを追加する)です。ワイルドカード証明書は DNS-01 チャレンジでのみ発行できるため、今回は certbot の --dns-route53 プラグインを使い、DNS-01 チャレンジに必要な TXT レコードの追加・削除を AWS Route53 に対して自動で行っています。
- Vault に AppRole でログインし、一時トークンを取得
-
certbot certonly --dns-route53でワイルドカード証明書を発行(内部で ACME サーバーとの通信・DNS-01 チャレンジ・証明書の受け取りまでを一括して行う) - 証明書・秘密鍵を読み込み、シリアル番号や有効期限などのメタデータを openssl で抽出
- 秘密鍵を Vault の暗号化機能で暗号化
- 証明書(平文)と暗号化済み秘密鍵を、それぞれ Vault KV の別パスに格納
- ローカルの certbot 作業ディレクトリを削除(クリーンアップ)
複数証明書をまとめて発行したい場合は certs 変数にリストを渡すことで、この一連の処理をループで回せるようにしています。
配置フェーズ
- Vault から証明書と暗号化済み秘密鍵を取得
- Vault の暗号化機能で秘密鍵を復号
- 現在配置されている証明書・秘密鍵を一時ディレクトリにバックアップ(直近 3世代 を保持し、古い世代は
alwaysブロックでクリーンアップ) - 新しい証明書・秘密鍵を配置し、ペアが一致するか(公開鍵の比較)を検証
-
nginx -tで構文チェック → 問題なければsystemctl reload
cert-replace.yml 単体は nginx -t が通れば systemctl reload で反映する作りですが、実際の AAP ワークフローでは前後に nginx_stop.yml / nginx_start.yml を挟み、配置の前に明示的に Nginx を停止し、配置後に起動し直す構成で運用しています。reload だけに任せず一度完全に停止させることで、設定の反映漏れや、稼働中プロセスとファイルの入れ替えが絡む不整合を避ける狙いです。
配置フェーズは block/rescue/always で構成しており、途中で失敗した場合は自動でバックアップから復旧します。証明書配置は本番サービスに直結するため、失敗時に中途半端な状態を残さないことを重視しました。
複数台の Nginx サーバーへの配置は、AAP のインベントリに対してジョブを実行する形になります。配置に失敗したホストがあった場合は、そのホスト上で rescue ブロックによるロールバックが走り、失敗ホストの情報が AAP のジョブ結果に残ります。全台一斉にロールバックするのではなく、失敗したホストのみがロールバック対象です。
検証・監視フェーズ
配置が完了した証明書は、cert_verify.yml で実際に HTTPS 接続を行い、正しいドメインの証明書が返ってきているかを確認します。証明書ファイルが存在すること・有効期限が切れていないことに加えて、実際に openssl の -checkhost で CN / SAN / ワイルドカードの一致まで見ているため、「ファイルは置き換わっているが、実は違う証明書が配信されている」といった見落としを防げます。
一方、check_certs.yml は配置後の証明書を継続的に監視するための Playbook です。証明書の残り有効日数が警告しきい値(cert_renewal_threshold_days)を下回った場合や、すでに期限切れの場合に fail するようになっており、これを定期実行することで「そろそろ更新が必要な証明書」を検知します。実運用では、この check_certs.yml の実行結果を起点に、発行〜配置のワークフローを起動する形を想定しています。
つまり、cert_verify.yml は「配置した証明書が正しく機能しているか」を確認する一時的な検証、check_certs.yml は「証明書の状態を継続的に監視する」ための定期チェックという、役割の異なる2つの Playbook です。
実際に AAP 上でこのワークフローをジョブテンプレート化すると、以下のように「証明書更新確認(check_certs.yml) → 発行(cert-issue.yml) → Web サーバー停止(nginx_stop.yml) → 配置(cert-replace.yml) → 起動(nginx_start.yml) → 動作確認(cert_verify.yml)」という一連の流れが、成功/失敗の分岐込みで可視化されます。
証明書更新ワークフロー(Web)の実行画面。実際のフローは 証明書更新確認(check_certs.yml)→ 証明書発行(cert-issue.yml)→ Web サーバー停止(nginx_stop.yml)→ 証明書置き換え(cert-replace.yml)→ 起動(nginx_start.yml)→ 動作確認(cert_verify.yml)の順で進む。この画面キャプチャでは先頭の確認ステップが画面外に切れているため、証明書発行 以降のみが写っている。失敗時は別ルート(Run on fail)に分岐する
HashiCorp Vault による秘密鍵保護の仕組み
HashiCorp Vault は、パスワードや API キー、証明書といった機密情報(シークレット)を一元的に安全に管理するためのミドルウェアです。単なる保管庫ではなく、データを暗号化・復号する機能や、AppRole のような認証の仕組みも備えています。今回はこの機能を使って、証明書と秘密鍵を Vault に保管する際、あえて次の2段構成にしています。
-
KV シークレットエンジン: 証明書(平文)と暗号化済み秘密鍵を、それぞれ別パスに保存(
certs/<fqdn>/keys/<fqdn>) - Vault の暗号化機能: 秘密鍵そのものを暗号化・復号する専用のキーを管理
秘密鍵を Vault の KV にそのまま置かず、Vault の暗号化機能で暗号化してから格納しているのは、KV の閲覧権限と「秘密鍵を復号できる権限」を分離するためです。証明書のメタデータを見る権限を持つ人が、そのまま秘密鍵の平文を取得できてしまう状態を避けています。
また、保存レイアウトは「証明書と鍵を別パスに保存する distributed モード」と「1パスにまとめる unified モード」の2種類を用意しました。将来的に証明書の数が増えたときに、運用しやすい方を選べるようにするためです。
実際に Vault の KV に格納されたシークレットを見ると、certificate / chain / fqdn / fullchain といったキーで保存されていることが確認できます。
Vault の Secrets Engine(KV)画面。cert-issue-nginx_per_cert.yml が発行直後に格納したデータで、秘密鍵は別パスに暗号化済みで保存されるため、この画面には登場していません
IBM Bob(AIコーディング・アシスタント)での実装
この Playbook 群の実装は、AI コーディング・アシスタントの IBM Bob に指示して作成しました。
IBM Bob は、IBM が提供するエージェント型の AI コーディング・パートナーで、リポジトリ全体のコンテキストを理解した上で、複数ファイルにまたがるコード生成を行えるのが特徴です。
VS Code 上の IBM Bob。エディタでリポジトリ内の Playbook 群を開きながら、右側のチャットパネルでコーディングを依頼できる
どのように指示したか
最初の指示では、以下のような要件を伝えて、徐々にまとめきれてなかった要件を加えながら修正を繰り返しました。
AAP から実行する、Nginx 向けの Let's Encrypt 証明書の発行・Vault 格納・配置の Playbook 群を作ってほしい。
- Vault は AppRole 認証、Vault の暗号化機能で秘密鍵を暗号化、KV に証明書と暗号化済み秘密鍵を格納
- DNS-01 チャレンジは Route53 を使用(IAM ロールでアクセス)
- 配置失敗時はバックアップから自動ロールバック
- 複数証明書のループ処理に対応
Playbook の作り込み方には、要件を細かく分解して段階的に指示する、設計方針を詳細に指定するなど、様々な進め方があります。ただ、AI コーディング・アシスタント自体の性能が上がってきていることもあり、今回のように一定範囲の要件をシンプルに伝えるだけでも、ドラフトとしては十分な品質の実装が得られることが多い、という印象を持ちました。
この時点で得られたのは、変数名やファイル間の参照、ワークフローの流れが一通り整合した「机上で完成された」Playbook 群です。コード生成の段階で IBM Bob に Ansible の文法チェック(ansible-lint / syntax-check 等)を含む静的レビューを実施させており、構文エラーのない状態からスタートできます。そこから実際に AAP 上で稼働をテストしつつ仕上げていく、という段階を踏んでいます。
AI 生成コードで手直しが必要だった箇所
実際に AAP 上で稼働させてからは、以下のような点を運用しながら手直ししています。
| カテゴリ | 具体例 | 対処 |
|---|---|---|
| 正規表現 | 証明書メタデータ抽出の正規表現が過剰に複雑で一部で一致しなかった | シンプルな抽出ロジックに変更 |
| shell コマンド | 対象サーバー上で実行する shell モジュール実行のコマンドオプション指定に誤りがあった | 実機での動作確認後に修正 |
| ロールバック | rescue ブロック内でバックアップファイルのパスがハードコードされていた | 変数化して動的に参照するよう修正 |
| AAP 固有 | ジョブテンプレートの Credential 注入方法(extra_vars vs カスタム Credential Type)の不整合 |
AAP のカスタム Credential Type に合わせて変数名を調整 |
つまり、**「最初の骨格は Bob にまとめて作ってもらい、稼働させながら実運用に合わせて整えていく」**という方法で実装を進めています。
ゼロから手で書くよりも早く土台に到達できたことで、実環境の細部調整に集中できたのが大きな利点でした。
なお、certbot による証明書発行から Nginx への配置までの手動実行手順は、別記事「Certbot による証明書更新 : 手動実行手順の確認」にまとめています。手動で一通りの流れを整理しておくと、Playbook 化の際にどこを自動化し、どこにエラーハンドリングを入れるべきかの判断の目安になると考えています。
AI が生成したコードをレビューする際のチェックポイントとして、以下が参考になるかもしれません。
- 正規表現は「本当にそのパターンで十分か」を実際の出力データで検証する
- shell / command モジュールで実行するコマンドオプションは、実行対象で使えるか確認する
- Ansible 固有の変数スコープ、ワークフロージョブ間での変数の引き継ぎなどに注意する
- AAP のクレデンシャル注入は、ローカル実行とは異なる挙動になるため、AAP 上でのテストが必須
このワークフローが正しく回るかを、どう判断するか
自動化の品質を考えるとき、最初は Ansible Playbook の書き方に目が向きがちです。実際に必要であったのは、発行から配置・検証・監視までのワークフロー全体が意図したとおりに回るかという点でした。Playbook はその構成要素の一つで、個々の Playbook が正しく書けていても、フローとして噛み合っているかは別に見る必要がありました。今回の実装を振り返りながら、何を見て判断していたのかを整理してみます。
手直しが発生した箇所を読み替えてみる
前掲の手直し一覧を、「本質的に何が足りなかったか」という観点で読み替えると、次のように整理できそうです。
| 手直し箇所 | 足りなかったと思われるもの |
|---|---|
| 正規表現 | その環境でコマンド実行が実際にどんな文字列を返すか、その正規表現で対応できるかを確かめていたか |
| shell モジュール実行 | 対象サーバーの OS・シェル・コマンドのバージョンで、そのコマンドが通るかを確かめていたか |
| rescue 内のパスがハードコード | コード設計の質(変数化・再利用のしやすさ) |
| AAP の Credential 注入方法 | AAP が Credential をどの変数名・どの形式で渡してくるかを把握していたか |
このうち3件は、コードの書き方というより、動かす先の環境が実際にどう振る舞うかを知っていたかどうかに寄った内容だったように思います。本記事ではこれを「対象環境の解像度」と呼んでいます。ドキュメントを読んで得られる一般的な知識ではなく、「この環境の、このコマンドの、この出力」という具体のレベルまで把握できているか、という意味合いです。
「今回対象にしているこのサーバーでどうなっているか」となると、実際に叩いて確認するまでは不明な部分も残ります。生成されたコード自体は文法的に正しく、ファイル間の参照も整合していました。それでも実機で差分が出てきたのは、この具体のレベルの情報が渡っていなかったためではないかと考えています。
裏を返せば、一度実機で動かして出力を確認すれば埋められる差分も多いように思います。手動で一通り実行した手順が整理されていれば、こうした差分に事前に気づける場面も増えると考えています。
手動手順を目安にする
今回の実装では検証用のため手動手順との体系的な突き合わせは行っていませんが、もし手動で実行した手順が整理されていれば、それは Playbook の受け入れ基準として活用できます。
たとえば、次のような観点で手動手順と Playbook の挙動を突き合わせると、生成されたコードの妥当性を判断しやすくなります。
- 実行するコマンドとそのオプションが、手動で確認したものと同じか
- Nginx の停止・配置・起動の順序が、手動時の流れと一致しているか
- 配置先のパス、ファイルの所有者・パーミッションが、手動時と同じ状態になるか
- 手動では暗黙に行っていた確認(配置後の HTTPS 接続確認など)が、Playbook 側でも実施されているか
生成されたコードは、それ単体で読むと妥当に見えることもあり、「何と比べて正しいのか」という基準を持っておくと判断がしやすくなります。手動手順は、その基準の一つになり得ます。
フローが回るかを決める要素の順番
証明書運用の自動化では、「正しく回るか」に効く要素にいくつかの層があるように思います。
- 信頼境界の設計 — Vault の2段構成、IAM ロール、AppRole の一時トークン。ここは後から変更しにくく、問題が起きたときの影響範囲にも関わってきます
-
実行ワークフローの設計 — どの Playbook を、どの順序で、どの対象に対して実行するか。今回であれば
check_certs.yml→cert-issue.yml→nginx_stop.yml→cert-replace.yml→nginx_start.yml→cert_verify.ymlという並びです。ここは、手動手順と同じ順序・同じ粒度になっているかが判断の目安になります。個々の Playbook が正しく書けていても、順序や実行対象がずれると、想定した手順と噛み合わなくなることがあります。またこの分割の仕方は、次の失敗時の挙動をどこに置けるかにも影響してきます -
失敗時の挙動設計 —
block/rescue/always、3世代バックアップ、失敗ホストのみのロールバック - Playbook の実装品質 — 変数化、冪等性、可読性
Playbook の書き方そのもの(4)に目が行きやすいのですが、フローが回るかどうかに効いてくるのは 2 と 3 の設計で、それが Playbook にどこまで反映されているか、という見方のほうが近いのかもしれません。また、実装が素朴であっても、鍵が Vault で保護され静的な認証情報を持たない構成であれば、問題が起きたときの影響はある程度抑えられるように思います。1 から 4 はどれか一つで代替できるものではなく、順に土台になっている関係だと捉えています。
呼ぶとすれば「運用設計力」
こう整理してみると、ここで問われているのは、コードを書くスキルそのものというより、運用設計力 — 失敗したときの挙動まで含めてフロー全体を設計する力に近いのかもしれません。
AI コーディング・アシスタントが骨格を書けるようになったことで、生成物を評価する側の比重が以前より増しているようにも感じます。前節に挙げたレビューのチェックポイントも、見方を変えれば評価する側の観点のリストとも言えそうです。
確認できる形にしておく
次のように問える形にしておくと扱いやすいと思います。
- 失敗したときに何が起きるかを、実行する前に説明できるか
- 途中で落ちた後、再実行しても安全か(冪等になっているか)
- ロールバックを、実際に発火させて確認したか
- 手動手順があれば、Playbook の挙動との齟齬がないか(コマンドのオプション、停止・起動の順序、配置先や権限)
- 人手が介在する箇所が残っていないか
これらを確認できていれば、Playbook の行数や見た目の整い方は、優先度としては後ろでもよいと考えています。
まとめ
- AAP(AWS EC2 + RDS で稼働)を実行基盤に、Let's Encrypt(ACME / certbot)・AWS Route53・HashiCorp Vault を組み合わせ、証明書の発行から配置・検証・監視までを一本のワークフローとして自動化しました
- 秘密鍵は HashiCorp Vault の KV + 暗号化機能の2段構成で保護し、AWS へのアクセスは IAM ロール、Vault へのアクセスは AppRole の一時トークンと、いずれも長期間有効な静的認証情報を持たない形にしています
- 配置失敗時は自動でロールバックし、
check_certs.ymlによる定期的な有効期限監視とcert_verify.ymlによる配置後の動作検証を組み合わせることで、証明書の状態を継続的に把握できる構成にしています - 実装は IBM Bob への指示から始め、稼働させながら実運用に合わせて調整していくスタイルで進めました
- 手直しが必要になった箇所は、コードを書く力よりも「対象環境の解像度」に関わるものが多くありました。手動手順が整理されていれば、それを目安にして齟齬を見つけやすくなると考えています
今回は Let's Encrypt や Nginx Web サーバーを例に紹介しましたが、商用認証局(有償 CA)を使うケースや、配置対象についても Web サーバーだけでなく BIG-IP、AWS ACM、Postfix などへ拡大した対応を、同様の構成で進めています。
シンプルな要件伝達だけで AI コーディング・アシスタントに実用的な骨格を作ってもらい、そこから人が仕上げていくという進め方は、今後ますます広がっていくと思います。AI コーディング・アシスタントのなかった頃の開発には、もう戻れないというのが最近の実感です。
証明書運用は、一度自動化してしまえば目立たない存在になりますが、失敗したときの影響(サービス停止)は小さくありません。CA/Browser Forum の Ballot SC-081v3 により、2029年には47日サイクルが現実のものとなります。手動運用からの移行を検討されている方にとって、本記事の構成が参考になれば幸いです。
参考リンク




