Ansible Vault:PlaybookのSecretsを暗号化してGitに安全に保存する

Security tutorial - IT technology blog
Security tutorial - IT technology blog

深夜3時。新しいサーバーと同期するため、Ansible playbookのファイルをGitHubにプッシュし終えたところだった。5分後、スマホが振動した――GitHub Security Alertからのメール:「We found a potential secret exposed in your repository.」データベースのパスワード、WordPressの認証キー、監視ツールのAPIキー――すべてがvars/main.ymlにプレーンテキストのまま、パブリックリポジトリに上がっていた。これが、Ansible Vaultの教訓を最も高い代償で学んだ最初の経験だった。

Ansibleでインフラを管理しplaybookをGitで管理しているなら――たとえプライベートリポジトリであっても――これは本当の障害になる前に解決すべき問題だ。

問題:Ansible PlaybookとGitにおけるSecrets

Ansible playbookは多くの機密情報を必要とする:データベースのパスワード、SSHの秘密鍵、クラウドプロバイダーのAPIトークン、Telegram botのトークン、SMTPパスワード……。これらはAnsibleが利用できるようコードベースに存在しなければならない。Gitにコミットすれば秘密情報が漏洩する。コミットしなければチームで共有できず、新しいサーバーには変数がなくて動かない。

これが典型的なジレンマだ:Infrastructure-as-codeはすべてをGitに入れたいが、secretsはGitに入れてはいけない。

Ansibleにおけるシークレット管理4つの方法の比較

方法1:varsファイルに直接ハードコード

# vars/main.yml — これは絶対にやってはいけない
db_password: "MyP@ssw0rd123"
api_key: "sk-ant-api03-xxxxxxxxxxxxx"
wp_secret_key: "abcdefghijklmnop"

その場は便利だが、一度Gitにコミットしてしまったら――後で削除しても――gitの履歴には残り続ける。git log -pで見つかる。GitHub Secret Scannerが見つかる。パブリックリポジトリをスキャンするボットがプッシュ後数分以内に見つける。

方法2:環境変数

# vars/main.yml
db_password: "{{ lookup('env', 'DB_PASSWORD') }}"
api_key: "{{ lookup('env', 'API_KEY') }}"

セキュリティ面では改善されている――gitに機密情報は入らない。しかし10台のサーバーにデプロイする場合、各サーバーで環境変数を設定しなければならない。新しいメンバーをオンボードする際には、Slack DMやメールで共有しなければならず――それも安全ではない。パスワードをローテーションする際には、各箇所を手動で更新しなければならない。

方法3:外部シークレットマネージャー(HashiCorp Vault、AWS Secrets Manager)

# vars/main.yml — HashiCorp Vault lookupを使う場合
db_password: "{{ lookup('hashi_vault', 'secret=secret/db/password:value') }}"

エンタープライズソリューション――完全な監査ログ、詳細なアクセス制御、自動ローテーション。しかしHashiCorp Vaultを動かすには専用のサーバークラスターが必要で、認証の設定やメンテナンスも必要だ。小規模チームやサイドプロジェクトにはオーバーキルである。AWS Secrets Managerは優れているが、AWSへのロックインが発生し、毎月追加コストがかかる。

方法4:Ansible Vault(built-in、無料)

Ansible VaultはAnsibleに組み込まれた機能で、追加インストールは不要だ。varsファイルをAES-256で暗号化し、暗号化済みファイルをGitにコミットしても完全に安全。vaultパスワードを持つ人だけが復号できる。

メリット・デメリットの比較

方法 セキュリティ 使いやすさ チーム共有 コスト
Hardcode 非常に危険 非常に簡単 簡単 無料
環境変数 良い 複雑 難しい 無料
外部Vault 最高 非常に複雑 良い コスト高/インフラ必要
Ansible Vault 良い 適度 良い 無料

Ansible Vaultはスイートスポットに位置する:十分に安全で、十分に使いやすく、gitを通じてチームで共有でき、コストもかからない。ユースケースの80%――個人VPS、小〜中規模チーム、スタートアップ――においては、最も合理的な選択だ。

Ansible Vaultを選ぶべき場面

  • Ansibleを使ってサーバーのデプロイや設定を行っている
  • チーム規模が1〜20人で、エンタープライズレベルのシークレット管理がまだ不要
  • secretsを含むすべてのインフラ設定をGitで管理したい
  • シークレット管理のためだけに追加のインフラを構築したくない
  • コンプライアンス要件として、シークレットへのアクセスごとの詳細な監査ログが不要

ロールによるきめ細かいアクセス制御、完全な監査証跡、またはエンタープライズ規模が必要になってはじめて、HashiCorp Vaultを検討すればいい。

Ansible Vault 実装ガイド

ステップ1:強力なvaultパスワードを作成する

vaultパスワードはすべてのシークレットを復号するための鍵だ――十分に強力で、安全に保管する必要がある(チームのパスワードマネージャーに保存し、付箋やSlackメッセージには書かない)。

vaultパスワードの生成にはtoolcraft.appのパスワードジェネレーターを使っている――ブラウザ上で100%動作するため、パスワードがどのサーバーにも送信される心配がない。生成したらすぐにチームのBitwardenか1Passwordに保存する。

# プロジェクト内にvaultパスワードファイルを作成する(.gitignoreへの追加を忘れずに)
echo "your-very-strong-vault-password-here" > .vault_pass
chmod 600 .vault_pass

ステップ2:secretsファイルを作成して暗号化する

secretsは別ファイルに分離する――一般的な慣習はvars/vault.ymlgroup_vars/all/vault.ymlだ。変数名にvault_プレフィックスを付けると区別しやすい:

# vars/vault.yml — 暗号化前の内容
vault_db_password: "MySecretDbPass123!"
vault_api_key: "sk-ant-api03-xxxxxxxxxxxxxxxxx"
vault_wp_secret_key: "random-64-char-string-here"
vault_telegram_bot_token: "1234567890:ABCDEFxxxxx"
vault_smtp_password: "smtp-password-here"
# ファイルを暗号化する(ファイルが存在しない場合は'encrypt'の代わりに'create'を使う)
ansible-vault encrypt vars/vault.yml --vault-password-file .vault_pass

# 新しいファイルを作成してエディタを開き、内容を直接入力する
ansible-vault create vars/vault.yml --vault-password-file .vault_pass

暗号化後のファイルはこのようになる――gitにコミットしても完全に安全だ:

$ANSIBLE_VAULT;1.1;AES256
38663266623431313530356635663438656665383066346433383836326438633165303832666562
3566356334363931313630353962373061386564326266310a623539353531313135356235363033
...

ステップ3:playbookからvault変数を参照する

vars/main.yml(暗号化しない、通常通りコミット)からvault変数を参照する:

# vars/main.yml — secretsは含まない、参照のみ
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
wp_secret_key: "{{ vault_wp_secret_key }}"
# deploy.yml
---
- hosts: webservers
  vars_files:
    - vars/main.yml
    - vars/vault.yml
  tasks:
    - name: Configure database
      template:
        src: templates/db.conf.j2
        dest: /etc/myapp/db.conf
      # テンプレートでは{{ db_password }}を通常通り使用する

ステップ4:vaultを使ってplaybookを実行する

# vaultパスワードファイルを指定して実行する
ansible-playbook deploy.yml --vault-password-file .vault_pass

# 環境変数を設定する(CI/CDに便利)
export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass
ansible-playbook deploy.yml

# 手動でパスワードを入力する(開発環境向け)
ansible-playbook deploy.yml --ask-vault-pass

ステップ5:.gitignoreを設定する

# .gitignore
.vault_pass
*.vault_pass
.ansible_vault_password

# vars/vault.yml は追加しないこと――暗号化済みファイルなので、コミットしても安全

ステップ6:vaultファイルの表示と編集

# 復号した内容を表示する(プレーンテキストファイルには保存しない)
ansible-vault view vars/vault.yml --vault-password-file .vault_pass

# 直接編集する(エディタを開き、保存時に自動で再暗号化する)
ansible-vault edit vars/vault.yml --vault-password-file .vault_pass

ステップ7:必要に応じてvaultパスワードをローテーションする

チームメンバーが退職したり、vaultパスワードの漏洩が疑われる場合はすぐにローテーションする:

# 新しいパスワードで再キー設定する
ansible-vault rekey vars/vault.yml \
  --vault-password-file .vault_pass \
  --new-vault-password-file .vault_pass_new

# 古いパスワードファイルを新しいものに置き換える
mv .vault_pass_new .vault_pass

CI/CD(GitHub Actions)との統合

vaultパスワードをGitHubリポジトリのシークレットに保存し、ワークフローで使用する:

# .github/workflows/deploy.yml
- name: Deploy with Ansible
  env:
    ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
  run: |
    echo "$ANSIBLE_VAULT_PASSWORD" > /tmp/.vault_pass
    chmod 600 /tmp/.vault_pass
    ansible-playbook deploy.yml --vault-password-file /tmp/.vault_pass
    rm /tmp/.vault_pass

よくあるエラー

エラー:「Decryption failed (no vault secrets would decrypt)」――vaultパスワードが間違っているか、vaultファイルが破損している。.vault_passファイルを確認し、特に末尾のスペースやWindowsの改行コードに注意:

cat -A .vault_pass
# 行末が $ (Unixの改行) でなければならない
# ^M$ が表示される場合はWindowsの改行コードが含まれている — 修正が必要:
sed -i 's/\r//' .vault_pass

暗号化前のファイルを誤ってコミットした場合:BFG Repo Cleanerを使ってgitの履歴から削除し、漏洩したすべてのシークレットをすぐにローテーションする――先延ばしにしてはいけない。

単一の値だけを暗号化したい場合:Ansible Vaultはインライン暗号化をサポートしている。結果をvarsファイルに直接貼り付けることができる:

ansible-vault encrypt_string 'my-secret-value' \
  --name 'vault_db_password' \
  --vault-password-file .vault_pass

あの出来事の後

あの深夜3時の出来事の後、約2時間ですべてのAnsible playbookをVaultを使う構成に移行した。パターンはシンプルだ:secretsをvars/vault.ymlに分離し、暗号化し、gitにコミットし、チームはパスワードマネージャー経由でvaultパスワードを共有する。vaultパスワードはtoolcraft.appで生成した――完全にクライアントサイドで動作するため、第三者のサーバーには何も送信されない。

これは完璧なソリューションではない――vaultパスワードが漏洩すれば、すべてのシークレットが漏洩する。しかしgitにプレーンテキストでハードコードしたり、Slackでシークレットを共有したりするのと比べれば、Ansible Vaultは最小限の導入コストで大きな改善をもたらす。Ansibleを使っている小〜中規模のチームの大半にとって、これで十分だ――そして何もしないよりはるかに良い。

Share: