はじめに
この記事では、Ansible Automation Platform(AAP)から Web サーバー(ターゲットホスト)へ SSH 接続するための秘密鍵を、IBM Vault (HashiCorp Vault) の KV v2 に保管し、ジョブ実行時に Vault から取得して接続する手順を紹介します。
AAP の Machine 認証情報にある「SSH 秘密鍵」の欄を Vault 参照に切り替える構成で、実際に構築して動作確認した内容に基づいています。
メモ: HashiCorp が IBM の傘下に入ったことで、製品名は「IBM Vault」にも移行しています。ただし、AAP の画面や API、公式ドキュメントの多くは今も「HashiCorp Vault」と表記しています。この記事では、一般的な呼び方を「Vault」とし、AAP の画面に表示される名称は画面どおりに記載します。
- 対象読者: AAP の認証情報とジョブテンプレートの基本を理解していて、Vault の基本操作(
vault kv、vault policy)ができる方 - 前提: Vault Enterprise 1.21 系(namespace 利用)で動作を確認しました。Community Edition(旧 OSS 版)では namespace の指定を読み飛ばせば同じ手順です。2.0 以降の画面や挙動は確認していません
この記事のポイントは次の 5 つです。
- AAP の標準機能「HashiCorp Vault Secret Lookup」だけで実現でき、独自プラグインは不要です
- 参照に切り替えた「SSH 秘密鍵」の欄には鍵そのものを保存せず、ジョブ実行時に Vault から取得します
- 読み取り専用のポリシーと専用の AppRole を作り、権限を最小に絞ります
- AAP のルックアップは TLS 検証を必ず行うため、Vault の証明書に SAN が必要です
- 詰まりやすい点(TLS の SAN、
secret_idのコピーミス)と、切り分けに使った確認方法も紹介します
なぜ Vault で管理するのか
Web サーバーへ SSH 接続する秘密鍵は、AAP の Machine 認証情報に登録します。
既定ではデータベースに暗号化して保存されるため、鍵のローテーションや失効を AAP の画面で個別に行う必要があり、鍵の保管場所が Vault と AAP に分散します。
外部の秘密管理システムに値を持たせると、次の利点があります。
- 鍵の実体を 1 か所(Vault)に集約できる
- Vault の監査ログで「いつ・誰が・どのパスを読んだか」を追跡できる
- 鍵の差し替えは Vault 側のバージョン更新だけで済み、AAP の設定を変えなくてよい(Secret Version を空欄にした場合。Web サーバー側の
authorized_keysの更新は別途必要です)
全体像
AAP には 2 種類の認証情報が登場します。
- Vault に接続するための認証情報(HashiCorp Vault Secret Lookup タイプ)
- Web サーバー(ターゲットホスト)に SSH 接続するための Machine 認証情報(SSH 秘密鍵の欄だけ、1 を参照する)
ここで Vault に保管する秘密鍵は、AAP 自身のものではなく、接続先の Web サーバーの authorized_keys に公開鍵が登録されている鍵です。

図: ①Vault 参照用認証情報が AppRole でログインし、②Vault の KV v2 から秘密鍵を取得し、③Web サーバー(ターゲットホスト)へ SSH 接続する
処理の順序を時系列で示すと、次のようになります。
図: ジョブ実行時に、AAP が AppRole で Vault にログインして秘密鍵を取得し、Web サーバー(ターゲットホスト)に SSH 接続する流れ
手順の概要

図: 構築手順は Vault 側(①〜④)と AAP 側(⑤〜⑦)の 2 段階。④の TLS 証明書は SAN が必須
| # | 作業 | 場所 |
|---|---|---|
| 1 | 現状の Vault を確認する | Vault |
| 2 | 秘密鍵を KV v2 に登録する | Vault |
| 3 | 読み取り専用のポリシーと AppRole を作る | Vault |
| 4 | Vault の TLS 証明書を確認する | Vault |
| 5 | Vault Secret Lookup 認証情報を作る | AAP |
| 6 | Machine 認証情報の SSH 秘密鍵を Vault 参照に切り替える | AAP |
| 7 | 動作を確認する | AAP / Vault |
以降のコマンドは、次の変数を使う前提で書いています。実際の値に置き換えてください。
export VAULT_ADDR='https://<vault-host>:8200'
export VAULT_NAMESPACE='<namespace>' # Community Edition では不要
# 管理操作に使うトークンは、日常運用では root ではなく管理用ポリシーを持つものを使う
1. 現状の Vault を確認する
最初に、既存のリソースを調べます。追加するものが既存の設定と衝突しないかを確認するためです。
vault status # Sealed が false、HA Mode を確認
vault secrets list # KV のバージョン(v2 か)を確認
vault auth list # approle が有効か確認
vault policy list
確認する点は次のとおりです。
- KV が v2 であること(
vault secrets list -detailedの Options 欄がmap[version:2]) -
approle認証方式が有効であること - 既存のポリシー・AppRole を変更せずに済むこと
注意: 証明書の自動更新など、すでに別用途で AppRole を使っている場合は、そのロールに SSH 秘密鍵の読み取り権限を足さないでください。用途ごとにロールを分けるのが安全です。
2. 秘密鍵を KV v2 に登録する
Web サーバーに接続するための秘密鍵(対応する公開鍵が Web サーバーの authorized_keys に登録済みのもの)を登録します。コマンドライン引数に直接書かず、標準入力から渡します。シェルの履歴に残さないためです。
以下のコマンドで、ローカルの鍵ファイルを標準入力から Vault に登録します。ssh_key=- は「値を標準入力から読む」という指定です。VAULT_ADDR、VAULT_NAMESPACE、VAULT_TOKEN を設定したシェルで実行してください。
vault kv put <kv-mount>/aap-machine/<target-name> ssh_key=- < /path/to/private_key
Vault の CLI を使えない端末から登録する場合は、Vault ホストに SSH で接続し、環境変数を渡して実行する方法もあります。鍵のファイルをサーバー上に置かずに済みます。
ssh <vault-host> 'VAULT_ADDR=https://127.0.0.1:8200 VAULT_NAMESPACE=<namespace> VAULT_TOKEN=<token> \
vault kv put <kv-mount>/aap-machine/<target-name> ssh_key=-' < /path/to/private_key
<token> を直接書くと、手元のシェルの履歴に残ります。実際には、トークンを環境変数やファイルから渡してください。
登録できたかは、鍵の中身を表示せずに確認します。
vault kv metadata get <kv-mount>/aap-machine/<target-name> # current_version を確認
鍵が破損していないかは、フィンガープリントを比較して確認します。次のコマンドは、Linux の Vault ホスト上での実行例です(/dev/shm は Linux 専用で、macOS にはありません)。
vault kv get -field=ssh_key <kv-mount>/aap-machine/<target-name> > /dev/shm/chk
chmod 600 /dev/shm/chk
ssh-keygen -y -f /dev/shm/chk | ssh-keygen -lf - # ローカルの鍵と同じ SHA256 になるか
shred -u /dev/shm/chk
ローカルの鍵のフィンガープリントは、ssh-keygen -y -f /path/to/private_key | ssh-keygen -lf - で確認できます。
パスの設計は <kv-mount>/aap-machine/<ターゲット名> のように、接続先の Web サーバー(ターゲット)ごとにシークレットを分けることをおすすめします。ポリシーで 1 ターゲット 1 パスに絞れるため、他の Web サーバーの鍵が読めてしまう事故を防げます。
3. 読み取り専用のポリシーと AppRole を作る
AAP が使う Vault の権限は、必要最小限にします。ポリシーは、このシークレット 1 つの read だけを許可します。KV v2 の API パスは <mount>/data/<path> になる点に注意してください。
vault policy write policy-aap-machine-<target-name> - <<'EOF'
# AAP の Machine 認証情報(SSH 秘密鍵)の読み取り専用
path "<kv-mount>/data/aap-machine/<target-name>" {
capabilities = ["read"]
}
EOF
続けて AppRole を作ります。トークンの有効期間は短くしておきます。ジョブ 1 回の実行に必要な時間があれば足りるためです。
vault write auth/approle/role/approle-aap-machine-<target-name> \
token_policies=policy-aap-machine-<target-name> \
token_ttl=5m token_max_ttl=15m
# AAP に登録する role_id
vault read -field=role_id auth/approle/role/approle-aap-machine-<target-name>/role-id
secret_id は、AAP に入力する直前に発行します。ターミナルの表示をコピーせず、クリップボードに直接入れることをおすすめします(詳しくは後述の「つまずきポイント」を参照)。
# macOS の場合: 出力を pbcopy に渡す
vault write -f -field=secret_id \
auth/approle/role/approle-aap-machine-<target-name>/secret-id | pbcopy
ポリシーの範囲外が拒否されることを確認する
発行したロールで実際にログインし、権限の範囲を確かめます。同じシェルで実行してください(VAULT_ADDR と VAULT_NAMESPACE を引き継ぎます)。
SID=$(vault write -f -field=secret_id auth/approle/role/approle-aap-machine-<target-name>/secret-id)
T=$(VAULT_TOKEN= vault write -field=token auth/approle/login role_id=<role_id> secret_id=$SID)
VAULT_TOKEN=$T vault kv get -field=ssh_key <kv-mount>/aap-machine/<target-name> | wc -c # 読める
VAULT_TOKEN=$T vault kv get <kv-mount>/<別のパス> # permission denied
VAULT_TOKEN=$T vault kv put <kv-mount>/aap-machine/<target-name> x=y # permission denied
VAULT_TOKEN=$T vault token revoke -self # 後片付け
自分のパスは読めて、他のパスの読み取りと自分のパスへの書き込みは拒否されれば、期待どおりです。
検証用に発行した secret_id は、不要なので削除します。AAP に登録する secret_id を消さないよう、accessor を確認してから指定してください。
# 発行済みの secret_id の accessor を一覧表示する
vault list auth/approle/role/approle-aap-machine-<target-name>/secret-id
# 作成日時を確認し、検証用のものだけ削除する
vault write auth/approle/role/approle-aap-machine-<target-name>/secret-id-accessor/lookup \
secret_id_accessor=<accessor>
vault write auth/approle/role/approle-aap-machine-<target-name>/secret-id-accessor/destroy \
secret_id_accessor=<accessor>
4. Vault の TLS 証明書を確認する
ここは、この構成で詰まりやすい部分の一つです。
AAP の「HashiCorp Vault Secret Lookup」は、常に TLS 証明書を検証します。「検証をスキップする」設定はありません。Playbook 側にある validate_certs: false のような設定も、ルックアップには効きません。
そのため、Vault の証明書について次の条件が必要です。
- 接続に使う URL のホスト名または IP が、証明書の SAN(Subject Alternative Name)に含まれていること
- 自己署名証明書や内部 CA の場合は、AAP の「CA Certificate」欄にその証明書を入力すること
今回の環境では、Vault の証明書が SAN のない自己署名で、そのままでは AAP から接続できませんでした。AAP のコントローラーのコンテナから Vault に接続して確認したところ、CA 欄の有無で次のエラーになりました。
| CA 欄 | 実際のエラー |
|---|---|
| 空欄 |
self-signed certificate(自己署名で信頼できない) |
| 証明書を指定 |
IP address mismatch(SAN に接続先の IP がない) |
CA 欄に証明書を指定しても SAN がなければ通らないので、SAN の追加が必要になります。自己署名証明書を作り直す場合の例を示します。以下は Vault ホスト上の TLS ディレクトリ(例: /opt/vault/tls、vault.hcl の設定に合わせる)で実行します。
cd /opt/vault/tls
cat > vault-san.cnf <<'EOF'
[req]
distinguished_name=dn
x509_extensions=v3
prompt=no
[dn]
O=Example
CN=Vault
[v3]
basicConstraints=critical,CA:TRUE
keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign
extendedKeyUsage=serverAuth
subjectAltName=DNS:<vault-host>,IP:<vault-ip>,IP:127.0.0.1
EOF
openssl req -x509 -newkey rsa:4096 -sha256 -days 1095 -nodes \
-config vault-san.cnf -keyout new.key -out new.crt
入れ替える前に、必ず鍵と証明書が対応していることを確認します。
# 2 行のハッシュが一致すれば OK
openssl x509 -in new.crt -noout -pubkey | sha256sum
openssl pkey -in new.key -pubout | sha256sum
その後は、現行の証明書と鍵をバックアップしてから、Vault のリスナー(vault.hcl の tls_cert_file、tls_key_file)で使っているファイルに置き換えます。所有者は Vault を動かすユーザー、権限は 600 にします。反映は systemctl reload vault(SIGHUP)で行います。再起動やシールは不要です。
# 同じ TLS ディレクトリで実行する
mkdir -p ~/vault-tls-backup && cp -p tls.crt tls.key ~/vault-tls-backup/ # 現行の証明書と鍵をバックアップ
chown vault:vault new.crt new.key && chmod 600 new.crt new.key
mv new.crt tls.crt && mv new.key tls.key
restorecon -v tls.crt tls.key # SELinux が有効な場合のみ
systemctl reload vault # SIGHUP で証明書を再読み込みする
systemctl is-active vault # active であること
vault status | grep -E 'Sealed|HA Mode' # Sealed が false のままであること
反映できたかは、AAP の Server URL に指定する接続先で検証して確認します。
openssl s_client -connect <vault-host>:8200 -verify_hostname <vault-host> \
-CAfile tls.crt </dev/null 2>/dev/null | grep "Verify return"
# Verify return code: 0 (ok) になれば成功
# IP アドレスで接続する場合は、-verify_hostname の代わりに -verify_ip <vault-ip> を使う
注意: 本番環境では、自己署名ではなく社内 CA や公開 CA が発行した証明書を使うことをおすすめします。自己署名を使う場合は、有効期限が切れる前に、証明書と AAP の CA 欄を同時に更新する運用が必要です。
5. AAP に Vault Secret Lookup 認証情報を作る
AAP 2.6 の画面で、「Automation Execution → Infrastructure → Credentials」から「Create credential」を選びます。
| 項目 | 値 |
|---|---|
| Name |
vault-lookup-aap-machine など |
| Credential type | HashiCorp Vault Secret Lookup |
| Server URL |
https://<vault-host>:8200(証明書の SAN に含まれるホスト名または IP) |
| CA Certificate | 手順 4 の証明書(-----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- まで) |
| AppRole role_id | 手順 3 で取得した値 |
| AppRole secret_id | 手順 3 で発行した値 |
| Namespace name |
<namespace>(Community Edition では空欄) |
| Path to Auth | approle |
| API Version | v2 |
CA 証明書は、次のようにしてクリップボードに入れると貼り付けやすくなります。
ssh <vault-host> 'openssl x509 -in /path/to/tls.crt' | pbcopy
6. Machine 認証情報の SSH 秘密鍵を Vault 参照に切り替える
- Web サーバーへの接続に使っている Machine 認証情報を開き、「Edit」を選びます
- 「SSH Private Key」欄の右側にある鍵アイコンを押します
- 手順 5 の認証情報を選び、「Next」を押します
- メタデータを次のとおり入力します
| 項目 | 値 |
|---|---|
| Name of Secret Backend | <kv-mount> |
| Path to Secret | aap-machine/<target-name> |
| Key Name | ssh_key |
| Secret Version (v2 only) | 空欄(最新を使う) |
- 「Test」を押し、成功したら「OK」にして保存します
「Name of Secret Backend」にマウント名を入れる場合、「Path to Secret」には <kv-mount>/data/... ではなく、aap-machine/<target-name> と書く点に注意してください。KV v2 の data は、AAP が自動で補います。
ユーザー名、パスフレーズ、権限昇格の設定は変更しません。切り替える対象は SSH 秘密鍵の欄だけです。切り替え後は、この Machine 認証情報を使うジョブテンプレートを、設定を変えずにそのまま使えます。
7. 動作を確認する
AAP から Vault に接続できるか
手順 6 の「Test」が成功すれば、Vault までの接続と鍵の取得ができています。
ジョブで Web サーバーに SSH 接続できるか
対象の Machine 認証情報を使うジョブを実行します。確認する項目は次のとおりです。
- Web サーバー(ターゲットホスト)に接続できる(unreachable が 0 件)
- ジョブの認証情報に、対象の Machine 認証情報が表示される
SSH 秘密鍵の欄が Vault 参照になっていることは、ジョブ画面ではなく、Machine 認証情報の詳細画面で確認します。
Vault 側の監査ログで確認する
Vault の監査ログ(audit device)が有効なら、AAP からの取得を記録で確認できます。ログイン(auth/approle/login)と読み取り(<kv-mount>/data/aap-machine/<target-name>)が、ジョブの実行時刻に記録されていれば正常です。
# 実際の監査ログは JSON 形式です。以下は operation と path を抜き出した要約です
update auth/approle/login
read <kv-mount>/data/aap-machine/<target-name>
ジョブ 1 回につき、ログインと読み取りが 1 回ずつ記録されます(手順 6 の「Test」を押したときも同様に記録されます)。
つまずきポイント
Test が Bad request(HTTP 400)になる
AAP の画面には Bad request としか出ないので、原因が分かりにくくなります。次の順で切り分けました。
- AAP の Test API は、Vault から HTTP エラーが返ると 400 に丸めて返します
- Vault の監査ログで、
auth/approle/loginがinvalid credentialsになっているか確認します - 該当すれば、AAP に保存された
role_id/secret_idが正しくありません
今回の原因は、secret_id の末尾に余分な文字が入っていたことでした。secret_id は UUID の 36 文字ですが、AAP には末尾に % が付いた 37 文字が保存されていました。
vault write -f -field=secret_id の出力は、末尾に改行が付きません。zsh は、改行のない出力の末尾に % を表示するため、画面から範囲選択してコピーすると、この % が混入する場合があります。| pbcopy で出力をクリップボードに直接渡せば、混入しません。
Test の前に TLS で失敗する
手順 4 のとおり、SAN と CA 欄を確認してください。self-signed certificate が出れば CA 欄が空か不一致、IP address mismatch や Hostname mismatch が出れば SAN に接続先が入っていません。
namespace を指定し忘れる
Vault Enterprise で namespace を使う場合、認証情報の「Namespace name」に入力しないと、ルートの namespace に対してログインしようとして失敗します。
まとめ
- AAP の標準機能「HashiCorp Vault Secret Lookup」だけで、Web サーバーへの SSH 接続に使う秘密鍵(Machine 認証情報)を Vault で管理できました
- Vault 側には、Web サーバーごとの KV v2 シークレット、読み取り専用ポリシー、専用の AppRole を用意しました
- AAP のルックアップは TLS 検証を必ず行うため、Vault 証明書の SAN と CA 欄の設定が必須です
- 失敗時は AAP の画面だけで判断せず、Vault の監査ログで原因を切り分けると早く解決できます
- ジョブの実行と、Vault 監査ログのログイン・読み取りの記録の両方で、動作を確認できました
次のステップとして、パスワードや become のパスワード、クラウドの API キーも、同じ「HashiCorp Vault Secret Lookup」で参照できるはずですが、この記事では未検証です。また、静的な鍵を持たない方式として、Vault の SSH シークレットエンジンで署名済み証明書を発行する「HashiCorp Vault Signed SSH」もあります(こちらも未検証です)。
参考リンク
- Red Hat Ansible Automation Platform 2.6: Integrate with HashiCorp to secure sensitive data(Machine 認証情報の SSH 秘密鍵を Vault 参照にする手順。AppRole と v2 の指定を含む)
- HashiCorp Vault: Key/Value v2
- HashiCorp Vault: Use AppRole authentication
- HashiCorp ブログ: Managing Ansible Automation Platform (AAP) credentials at scale with Vault(Vault の SSH シークレットエンジンで署名済み証明書を使う方式。この記事の静的鍵の方式とは別のアプローチ)
今回の実装(Vault と AAP の設定、動作確認、この記事の執筆)は、AIアシスタントの IBM Bob に手伝ってもらいながら進めました。



