0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AAP から Web サーバーへ SSH 接続する秘密鍵を IBM Vault (HashiCorp Vault) で管理する(KV v2 + AppRole)

0
Posted at

はじめに

この記事では、Ansible Automation Platform(AAP)から Web サーバー(ターゲットホスト)へ SSH 接続するための秘密鍵を、IBM Vault (HashiCorp Vault) の KV v2 に保管し、ジョブ実行時に Vault から取得して接続する手順を紹介します。

1.png

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 種類の認証情報が登場します。

  1. Vault に接続するための認証情報(HashiCorp Vault Secret Lookup タイプ)
  2. Web サーバー(ターゲットホスト)に SSH 接続するための Machine 認証情報(SSH 秘密鍵の欄だけ、1 を参照する)

ここで Vault に保管する秘密鍵は、AAP 自身のものではなく、接続先の Web サーバーの authorized_keys に公開鍵が登録されている鍵です。

2.png
図: ①Vault 参照用認証情報が AppRole でログインし、②Vault の KV v2 から秘密鍵を取得し、③Web サーバー(ターゲットホスト)へ SSH 接続する


処理の順序を時系列で示すと、次のようになります。

図: ジョブ実行時に、AAP が AppRole で Vault にログインして秘密鍵を取得し、Web サーバー(ターゲットホスト)に SSH 接続する流れ


手順の概要

3.png
図: 構築手順は 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 参照に切り替える

  1. Web サーバーへの接続に使っている Machine 認証情報を開き、「Edit」を選びます
  2. 「SSH Private Key」欄の右側にある鍵アイコンを押します

test01-1.png

  1. 手順 5 の認証情報を選び、「Next」を押します
  2. メタデータを次のとおり入力します

test01-2.png

項目 値
Name of Secret Backend <kv-mount>
Path to Secret aap-machine/<target-name>
Key Name ssh_key
Secret Version (v2 only) 空欄(最新を使う)
  1. 「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 としか出ないので、原因が分かりにくくなります。次の順で切り分けました。

  1. AAP の Test API は、Vault から HTTP エラーが返ると 400 に丸めて返します
  2. Vault の監査ログで、auth/approle/login が invalid credentials になっているか確認します
  3. 該当すれば、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」もあります(こちらも未検証です)。

参考リンク


今回の実装(Vault と AAP の設定、動作確認、この記事の執筆)は、AIアシスタントの IBM Bob に手伝ってもらいながら進めました。

Bob6.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?