インフラ

GitHub Actionsの「Skip output since it may contain secret」とは

numa

こんにちは、ぬまです。
先日GitHub Actionsでパイプラインを構築していた時に、とある問題に遭遇しました。

この記事では、何が起きるのか、どう困るのか、どう対処するかを整理します。

何が起きるか

リポジトリに登録した Secret と同一の文字列が含まれていると、GitHub は「secret を含む可能性がある」と判断し、その output の送信をスキップします。

ワークフロー実行画面の Annotations には、次のような警告が出ます。

Skip output 'aws_account_id' since it may contain secret.

aws_account_id は output 名の例です。実際のジョブでは任意の名前が入ります。)

どう困るか

後続ジョブに値が届かない

後続ジョブで参照した時に値が空文字になります。Secretが含む文字列がセットされてもエラーにならずに後続ジョブが実行されるので、原因が output のスキップだと気づくまで時間がかかります。

ログや URL が読みにくくなる

Secret にした値はログ上で マスキング* などに置換)されます。アカウント ID が URL のパスに含まれていると、リンクとして壊れたり、コピーしても欠けたり**して、障害時のデバッグがしづらくなります。

たとえばコンソール上の S3 バケット URLは、ざっくり次のような形です(123456789012 はダミーのアカウント ID)。

https://us-east-1.console.aws.amazon.com/s3/buckets/example-bucket-123456789012?region=us-east-1&tab=objects

この 123456789012 が Secret と同一だと、ログではマスクされ、クリック可能な「そのまま正しい URL」として残らないことがあります。

まずはシンプルな例

最初に、何が起きているのかを見てみます。

ジョブ A で Secrets 由来のアカウント ID を output に載せ、ジョブ B で受け取る、というシンプルなパターンです。

1jobs:
2  jobA:
3    runs-on: ubuntu-latest
4    outputs:
5      aws_account_id: ${{ steps.set.outputs.aws_account_id }}
6    steps:
7      - id: set
8        run: echo "aws_account_id=${{ secrets.AWS_ACCOUNT_ID }}" >> "$GITHUB_OUTPUT"
9
10  jobB:
11    needs: jobA
12    runs-on: ubuntu-latest
13    steps:
14      - run: echo "from A:${{ needs.jobA.outputs.aws_account_id }}"

これをGitHub Actionsで実行すると、JobAのログは画像のようになります。

aws_account_idの出力がスキップされていることが警告されています。

jobBへの値参照を見てみると、みなさんお気づきだとは思いますが、空で渡されることになります。

警告として出力がスキップされているだけで、このパイプラインとしては正常終了で完了します。

正常終了するからこそ起こった際に発見しにくい点かと思います。

実際に起きた例

筆者が実際に遭遇したのは、Secret そのものではなく、Secret と同じ値を含む別の文字列を渡していたケースでした。

  • S3 バケット名が **example-bucket-{AWS アカウント ID}** のような命名
  • ジョブ A でアカウント ID を output に載せ、ジョブ AA で 完全なバケット名を組み立てて、ジョブ BB に渡したい

バケット名そのものを Secret にしているわけではありません。しかし バケット名の中に Secret と同じ値(アカウント ID)が含まれるため、s3_bucket_name の output だけがスキップされます。

1jobs:
2  jobAA:
3    runs-on: ubuntu-latest
4    outputs:
5      s3_bucket_name: ${{ steps.set.outputs.s3_bucket_name }}
6    steps:
7      - id: set
8        run: |
9          ACCOUNT="${{ secrets.AWS_ACCOUNT_ID }}"
10          echo "s3_bucket_name=gha-test-app-${ACCOUNT}" >> "$GITHUB_OUTPUT"
11
12  jobBB:
13    needs: jobAA
14    runs-on: ubuntu-latest
15    steps:
16      - run: echo "from AA (S3 bucket):${{ needs.jobAA.outputs.s3_bucket_name }}"

jobBBの出力はこのようになります。

aws_account_idのところだけがスキップされてしまっているのがわかります。

対策

1. SecretsとVariables を適切に使い分ける(推奨)

12桁のAWSアカウントIDは、アクセスキーやパスワードのような認証情報そのものではありません。
一方で、組織のセキュリティポリシーによっては管理対象情報として扱われる場合があります。
公開しても問題ないと判断できる値については、SecretsではなくVariablesで管理することも選択肢になります。

VariablesはSecretsと異なりマスキングされないため、認証情報、APIキー、パスワード、秘密鍵、トークンなどの機密情報は格納しないでください。
Variablesに置くのは、組織のポリシー上公開しても問題ない非機密の設定値に限定します。

# Secrets ではなく Variables を参照する
run: echo "aws_account_id=${{ vars.AWS_ACCOUNT_ID }}" >> "$GITHUB_OUTPUT"

公式の整理は Using secrets in GitHub ActionsVariables を参照するとよいです。

2. 暗号化してから output に載せる

どうしても job output 経由で値を受け渡す必要がある場合の、例外的なワークアラウンドとして、暗号化した値を output に載せ、受け側で復号する方法があります。平文では secret スキャンに引っかかるため、AES-256-CBC(OpenSSL)で暗号化したものを Base64 にした文字列を output に載せます。

ただし、復号鍵の管理復号後のログ出力抑止権限境界の設計が必要になります。原則としては、Secrets を job output で受け渡さない設計を優先してください。

暗号化側(Secretを渡すJob)

# Secrets 由来の AWS アカウント ID を平文のまま GITHUB_OUTPUT に書かない
# AES-256-CBC → base64(1 行にしたいので -w 0)。末尾改行を付けないよう echo -n
AWS_ACCOUNT_ENC=$(echo -n "${{ secrets.AWS_ACCOUNT_ID }}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:"${{ inputs.encryption-key }}" 2>/dev/null | base64 -w 0)
echo "aws_account_id_enc=${AWS_ACCOUNT_ENC}" >> "$GITHUB_OUTPUT"

復号側(Secretを受け取るJob)

AWS_ACCOUNT_ID=$(echo "${{ needs.jobA.outputs.aws_account_id_enc }}" | base64 -d \
  | openssl enc -aes-256-cbc -pbkdf2 -d -pass pass:"${{ inputs.encryption-key }}" 2>/dev/null)
echo "復号したアカウント ID: ${AWS_ACCOUNT_ID}"
# 例: バケット名を組み立てる → example-bucket-${AWS_ACCOUNT_ID}

3. Jobの境界を見直して、output に載せない

API キーやパスワードなど、本当に秘匿すべき値は Secrets に限定し、ジョブ間では output に載せない設計にします。artifact、OIDC、同一ジョブ内で完結させる、など別の信頼境界を検討します。

まとめ

#内容
起きることGITHUB_OUTPUT に書いた値に、リポジトリの Secret と同じ文字列が含まれると、GitHub がその output の送信を抑止する
影響後続ジョブの needs.*.outputs.* がになる。ログ上の Secret マスキングで URL なども読みづらくなる
対策1. SecretsVariables(vars を適切に使い分ける。
2.どうしても output 経由なら暗号化→復号化。
3.Jobの境界を見直して、outputに載せない。

とりあえず「Secretsに入れればOK」だけではなく、Secretとして管理することの副作用も含めて設計する必要があることを今回学びました。

AUTHOR
numa
numa
記事URLをコピーしました