Ansible のシークレットマスキング

Ansible の Playbook では、サーバーへの接続やアプリケーションのデプロイにパスワードや API トークンを使うことがよくあります。こうした値はタスクの出力に含まれることもあります。たとえば、コマンドがパスワードを含むエラーを返す場合です。

この問題に対処するため、Ansible にシークレットマスキングが追加されます。出力に含まれる既知のシークレット値を $REDACTED$ に置き換え、メッセージの残りは読める状態に保ちます。タスクは引き続き元の値を使います。

この機能は、2026 年 9 月 10 日にマージされた Secret Masking API のプルリクエストで追加されました。この記事の執筆時点では、ansible-core 2.22 の開発ビルドで利用できます。安定版にはまだ含まれていません。

まず簡単な Playbook を見てから、Semaphore UI でこの機能をどのように使う予定なのかを紹介します。

まずシークレットを登録する

Ansible は、どの値をマスキングするかを知る必要があります。新しい API は、Vault で暗号化された変数や no_log が指定されたモジュールパラメーターなど、既知のシークレットソースから値を登録します。

通常の変数には register_secret フィルターを使えます。--extra-vars で渡した変数も登録が必要です。名前を db_password にするだけでは不十分です。

この例では架空のデータベースパスワードを使います。masking.yml という Playbook を作成してください。

- name: Try secret masking
  hosts: localhost
  gather_facts: false
  vars:
    db_password: "example-db-password-123456"

  tasks:
    - name: Register the password before using it
      ansible.builtin.set_fact:
        db_password: "{{ db_password | ansible.builtin.register_secret }}"

    - name: Show a message containing the password
      ansible.builtin.debug:
        msg: "Connecting to db.internal with password={{ db_password }}"

最初のタスクがパスワードを登録します。フィルターは元の値を返すため、Playbook は引き続きその値をデータベースへの接続に使えます。どのタスクもその値を出力する前に、登録を済ませる必要があります。

ansible-core 2.22 の開発ビルドで Playbook を実行します。

ansible-playbook -i localhost, -c local masking.yml

2 番目のタスクには、次のメッセージが表示されます。

Connecting to db.internal with password=$REDACTED$

ここではマスキングを確認するためだけにパスワードを出力しています。実際の Playbook ではシークレットの管理元から取得し、認証情報を意図的に出力しないようにしてください。

Semaphore UI との連携

Semaphore UI は、どの変数がシークレットとして設定されているかをすでに把握しています。今後のバージョンでは、この情報を使って値を Ansible に自動登録する予定です。

そのために、Ansible の _SECRETS_INPUT_FILES オプションを使います。このオプションは環境変数 _ANSIBLE_SECRETS_INPUT_FILES で設定し、Playbook の開始前に Ansible がシークレットの一覧を読み込めるようにします。

入力には YAML または JSON を使えます。先ほどのパスワードを含むドキュメントは、次のようになります。

version: 1
secrets:
  - example-db-password-123456

この一覧は、マスキングする値を Ansible に伝えます。変数自体は、これまでどおり別途 Playbook に渡します。

予定している連携では、次のシークレット値を対象にします。

  • 変数グループ(Variable Groups) — タスクで使うために保存されたシークレット。
  • サーベイ(Surveys) — タスクの開始時に入力されるシークレット回答。

これらの値については、各 Playbook に register_secret を使うタスクを追加する必要がなくなります。

予定している Semaphore UI 連携:シークレットを Playbook に渡すと同時に、_SECRETS_INPUT_FILES で別途登録します。タスクは実際の値を使い、Ansible はタスクログ内の値を $REDACTED$ に置き換えます。

この方法を ansible-core 2.22 の開発ビルドで検証しました。登録した値は、debug メッセージ、コマンドの結果、エラーメッセージ、詳細出力、Ansible のログファイルでマスキングされました。パイプ経由での一覧の受け渡しも機能し、シークレット専用のファイルをディスクに書き込まずに済みました。

このオプションは現在内部用であり、変更される可能性があります。Semaphore UI での対応は今後のリリースを予定しており、互換性のある Ansible のバージョンが必要になります。

例:アプリケーションのデプロイ

アプリケーションをデプロイするタスクテンプレート(Task Template)があるとします。デプロイスクリプトは、変数グループにシークレットとして保存された API トークンを使います。

予定している連携では、タスクは次のように動作します。

  1. Semaphore UI がトークンを Playbook に渡します。
  2. 実行前に、_SECRETS_INPUT_FILES を使って Ansible のマスキング一覧にもトークンを渡します。
  3. Playbook は実際のトークンを使ってデプロイスクリプトを実行します。
  4. スクリプトが出力にトークンを含めた場合、Ansible は表示する結果でその値をマスキングします。

たとえば、トークンの有効期限が切れていると、タスクログに次のようなメッセージが表示される場合があります。

Deployment rejected: service=payments environment=staging token=$REDACTED$ reason=token expired

ログを読む人全員にトークンを見せずに、どのデプロイがなぜ失敗したのかを確認できます。サーベイのシークレットフィールドから入力した一時的なトークンにも、同じ流れが適用されます。

制限事項

マスキングの対象になるのは、登録済みの値です。開発版の実装では 4 文字未満のシークレットは対象外となり、エンコードなどの変換を行った値は別途登録が必要になる場合があります。

また、プロセス引数、Playbook が書き込んだファイル、Ansible の外部で生成されたログ内の認証情報は保護しません。出力全体を非公開にする必要があるタスクには、引き続き no_log: true を使ってください。

この連携により、Semaphore UI で指定したシークレットを Ansible のマスキングシステムも認識するようになります。タスクログに認証情報が残るのを抑えつつ、トラブルシューティングに役立つ情報を保持できます。