GitHub ActionsでAnsibleを実行した時に秘密情報が記録されるのをsedでマスクする
Ansibleのpostgresql_userモジュールを使ってRDS PostgreSQLのユーザ管理を行なっています。 それをGitHub ActionsでCDしたいとなりましたが、Ansibleの標準出力にパスワードが含まれてしまうためマスクした話です。
別の方法もあるよ(2021-11-12追記)
アウトプットするとインプットが加速すると言いますが、 @naa0yama さんから素晴らしい情報をいただきました。 sed は万能ですがシンプルではないので、Ansibleの機能でコントロールする方が良いですね。
情報ありがとうございます!!!
これでやってることできる気がする。https://t.co/Pk1CaugTFu
— Naoki Aoyama (@naa0yama) November 11, 2021
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#loop-control
マスク前後の例
具体的な例を以下に挙げます。
'password': 'SuperSecrets' のようにパスワードが生で出力されてしまっていたのが 'password': '<MASKED>' となります。
マスク前の出力
TASK [postgres : Add user to morihaya.local] ****************
changed: [localhost] => (item={'name': 'morihaya_dev', 'password': 'SuperSecrets', 'db': 'morihaya', 'groups': 'readwrite', 'state': 'present'}) => {"ansible_loop_var": "item", "changed": true, "item": {"db": "morihaya", "groups": "readwrite", "name": "morihaya_dev", "password": "SugoiHimitsu", "state": "present"}, "queries": [], "user": "morihaya_dev", "warnings": ["Module did not set no_log for no_password_changes"]}
マスク後の出力
TASK [postgres : Add user to morihaya.local] ****************
changed: [localhost] => (item={'name': 'morihaya_dev', 'password': '<MASKED>', 'db': 'morihaya', 'groups': 'readwrite', 'state': 'present'}) => {"ansible_loop_var": "item", "changed": true, "item": {"db": "morihaya", "groups": "readwrite", "name": "morihaya_dev", "password": "<MASKED>", "state": "present"}, "queries": [], "user": "morihaya_dev", "warnings": ["Module did not set no_log for no_password_changes"]}
どうしたのか
sed を使いました。GitHub Actionsのコードを以下に示します。
たくさん書いてありますが、本記事のテーマとしては最終行の ansible-playbook ${{ matrix.ANSIBLE_PLAYBOOK }} --check --diff -v | sed -E "s/password': '[^']+/password': '<MASKED>/g" | sed -E 's/password": "[^"]+/password": "<MASKED>/g' がポイントです。
name: Deploy-Check
on:
workflow_dispatch:
push:
paths:
- ".github/workflows/deploy-check.yml"
- "*.yml"
- "group_vars/*.yml"
- "host_vars/*.yml"
- "secret_vars/*.yml"
- "roles/*/*/*.yml"
branches-ignore:
- master
pull_request:
paths:
- "*.yml"
- "group_vars/*.yml"
- "host_vars/*.yml"
- "secret_vars/*.yml"
- "roles/*/*/*.yml"
jobs:
deploy:
runs-on: [self-hosted, morihaya]
strategy:
fail-fast: true # どれかか失敗しても完走させる
matrix:
ANSIBLE_PLAYBOOK:
- morihaya-db-prod.yml
- morihaya-deb-dev.yml
steps:
- uses: actions/checkout@v2
- name: Ansible setting
run: |
# ansbile のインストール&設定
apt-get install -y ansible
ansible-galaxy collection install community.general
# postgresql モジュールのための準備
apt-get install -y python3 python3-psycopg2
echo "[defaults]" > ~/.ansible.cfg
echo "interpreter_python = /usr/bin/python3" >> ~/.ansible.cfg
# Ansible vault用のパスワードを作成
echo "${{secrets.ANSIBLE_VAULT_PASSWORD}}" > ~/.vault_password
ansible --version
- name: Deploy
run: |
type python3
# "module_stderr": "/bin/sh: 1: /usr/bin/python: not found\n" 対応
ln -sf /usr/bin/python3 /usr/bin/python
# Ansible playbook を実行
ansible-playbook ${{ matrix.ANSIBLE_PLAYBOOK }} --check --diff -v | sed -E "s/password': '[^']+/password': '<MASKED>/g" | sed -E 's/password": "[^"]+/password": "<MASKED>/g'
マスクするまでの雑な経緯
実装直後は ansible-playbook ${{ matrix.ANSIBLE_PLAYBOOK }} --check --diff -v だけでしたが、ログにパスワードが記録されてしまうことに気づいて慌ててマスクすることにしました。
GitHub Actionsのドキュメントや、Ansibleのドキュメントを軽くみましたが、それらの機能で特定の文字列を正規表現でマッチさせてマスクすることは現状できないと判断しました。
難しく考えかけたところを単純に sed コマンドでマスクし、期待した結果を得ることができました。この結果は自分にとっては大きくて、今後どのような秘密情報を出力する処理であってもマスクすることができる自信がつきました。
トラップとして、Ansibleの出力がシングルクォートの場合とダブルクォートの場合がありどちらもマスクする必要があったので sed を2回行う必要がありました。
'password': 'SuperSecrets'->sed -E "s/password': '[^']+/password': '<MASKED>/g"で処理"password": "SugoiHimitsu"->sed -E 's/password": "[^"]+/password": "<MASKED>/g'で処理
以下のツイートが率直な感想です。