GitHub Actions で文字列を一時的にマスクする
ワークフローの中でクレデンシャルを更新する場合、「取得したトークなどを不用意に表示するとマスクされない」ことは少し面倒だと感じていました。
これについて、別の記事を作成中に tibdex/github-app-token Action のドキュメントを読んでいたところ、取得した Token をマスクしていたので方法を調べてみました。
追記:「試してみる」節に「他テキストとの表示」を追加しました。
API での設定
Action のソースを読むと @actions/core
の setSecret()
を利用していました。
ドキュメントでは以下のように説明されています。runner への登録なので恒久的な設定ではなくジョブ実行時の一時的なものと思われます。
Setting a secret registers the secret with the runner to ensure it is masked in logs.
core.setSecret('myPassword');
ワークフローコマンドでの設定
@actions/core
の機能はワークフローコマンドでも使える場合が多いです。
actions/toolkitには、ワークフローコマンドとして実行できる多くの関数があります。
::
構文を使って、YAMLファイル内でワークフローコマンドを実行してください。それらのコマンドはstdout
を通じてランナーに送信されます。 たとえば、コードを使用して出力を設定する代わりに、以下のようにします。
今回の setSecret()
も対象に含まれていたので bash などからも利用できます。
ログに "Mona The Octocat" を出力すると、"***" が表示されます。
Shell
echo "::add-mask::Mona The Octocat"
試してみる
ワークフローコマンドでマスクを試すリポジトリを作成し、確認してみました。
通常の設定
まずは普通に secret_text.txt
に保存されている文字列を設定してみます。
▼ リスト 3-1
simple-add-mask:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check not masked
run: cat secret_text.txt
- name: Add mask
run: |
echo -n '::add-mask::' > cmd.txt
cat secret_text.txt >> cmd.txt
cat cmd.txt
rm cmd.txt
- name: Check masked
run: cat secret_text.txt
ログを確認すると Check masked
ステップではマスクされていることが確認できます。
▼ 図 3-1
有効範囲
上記ジョブの後で実行されるジョブでも確認してみます。
▼ リスト 3-2
check-in-other-job:
needs: simple-add-mask
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check
run: echo 'ultra super secret'
時系列的にはコマンド実行後ですがマスクされていないことが確認できます。
▼ 図 3-2
これは runner のインスタンスが分かれているためだと思われます。よって、ジョブ間で値を受け渡す場合は再設定する必要があるようです。
アンチパターン?
「マスクしたい文字列が確定したステップ」と「マスクするコマンドを実行するステップ」が分かれていたとします。このときに :env
フィールドを使うと少し問題があります。
▼ リスト 3-3
anti-add-mask:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set secet text to outputs
id: get_secret
run: echo '::set-output name=SECRET_TEXT::ultra super secret'
- name: Add mask
run: echo "::add-mask::$SECRET_TEXT"
env:
SECRET_TEXT: ${{ steps.get_secret.outputs.SECRET_TEXT}}
- name: Check masked
run: echo 'ultra super secret'
この場合はマスクされる前にフィールドへ設定した内容が表示されてしまいます。また、「通常の設定」のところでも書きましたが、コマンドラインの引数でマスクする文字列を扱っているのもあまり良いことではありません。
▼ 図 3-3
上記の例は少し無理やり状況を発生させていますが、ステップ間の受け渡しは値が表示されやすいので注意が必要です(Action の入力である with:
でも同様のことが発生します)。
マスクしたい文字列がある場合は「値が確定したステップ内で実施する」か、他ステップで実施する必要がある場合は「ファイルなどで渡す」のがよさそうです。
他コマンドで利用
以下が少し気になったのでマスクした文字列を ::set-output
で利用してみました。
When you mask a value, it is treated as a secret and will be redacted on the runner. For example, after you mask a value, you won't be able to set that value as an output.
マスク前と後で ::set-output
した値の比較はハッシュ値を表示することで対応ています。
▼ リスト 3-4
use-in-other-command:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check not masked
run: echo -n 'ultra super secret' | sha256sum
- name: Set secet text to outputs
id: out_not_masked
run: echo '::set-output name=SECRET_TEXT::ultra super secret'
- name: Check outputs
run: echo -n "${SECRET_TEXT}" | sha256sum
env:
SECRET_TEXT: ${{ steps.out_not_masked.outputs.SECRET_TEXT}}
- name: Add mask
run: |
echo -n '::add-mask::' > cmd.txt
cat secret_text.txt >> cmd.txt
cat cmd.txt
rm cmd.txt
- name: Set secet text to outputs
id: out_masked
run: echo '::set-output name=SECRET_TEXT::ultra super secret'
- name: Check outputs
run: echo -n "${SECRET_TEXT}" | sha256sum
env:
SECRET_TEXT: ${{ steps.out_masked.outputs.SECRET_TEXT}}
結果としてもどちらも同じハッシュ値になりました。
▼ 図 3-4
少しスッキリしないのですが、$ gh run view --log
などで取得したログから値を取り出せないという話なのか、他の落とし穴的なものがあるのかは不明です。
他テキストとの表示
以下の「空白で分離された」も気になったので試してみました。
値をマスクすることにより、文字列または値がログに出力されることを防ぎます。 空白で分離された、マスクされた各語は "*" という文字で置き換えられます。
▼ リスト 3-5
split-by-blank:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check not masked
run: echo 'ultra super secret'
- name: Add mask
run: |
echo -n '::add-mask::' > cmd.txt
cat secret_text.txt >> cmd.txt
cat cmd.txt
rm cmd.txt
- name: Check masked
run: echo 'ultra super secret'
- name: Check with blank
run: echo 'start ultra super secret end'
- name: Check without blank
run: echo 'startultra super secretend'
- name: Check with quote
run: echo '"ultra super secret"'
- name: Check each words
run: echo 'ultra A super B secret'
- name: Check each words(multi lines)
run: |
echo 'ultra'
echo '-- separator --'
echo 'super'
echo '-- separator --'
echo 'secret'
結果としては空白なしで文字列が連続していても対象となる文字列はマスクされました。また、空白で区切られた単語を個別に表示した場合はマスクされませんでした。
▼ 図 3-5
感覚的に予想する挙動との違いはなかったのですが、これも少しスッキリしないところはあります。
おわりに
ワークフロー実行中に動的に文字列をマスクする方法を試してみました。
少し扱いに注意するところもありますが、bash からも実行できることから利用できる局面は多いと思われます。
クレデンシャル以外にも「ワークフロー実行中に確定した値」をマスクしたい場合もあるので[1]、この辺の機能は活用していきたいところです。
-
今回の API 知ったとき、Google Drive へ Upload したファイルの id を他ステップに渡したいと考えていたところでした。 ↩︎
Discussion