GitHub Actions 講座 #3 シークレット・変数・環境 — secrets と vars、environment の承認ゲート

読了 5分

#2までのワークフローには秘密がありませんでした。実戦のワークフローは違います。デプロイ先の認証情報、API キー、Webhook の URL といった値が必要になり、これを YAML に書いた瞬間、リポジトリを読めるすべての人に公開されます。今回は秘密と設定をワークフローへ安全に供給する仕組み、そしてその上に人間の承認ゲートまで設ける environment を扱います。

secrets と vars — 隠すものと見せるもの #

値をリポジトリの設定に置き、ワークフローから参照する通路が 2 つあります。Settings の Secrets and variables メニューで登録します。

  • secrets: 秘密の値です。登録後は誰も値を見直せず(上書きのみ可能)、ログではマスキングされます。${{ secrets.API_KEY }} で参照します。
  • vars: 秘密ではない設定値です。値は見え、ログにもそのまま出ます。${{ vars.AWS_REGION }} で参照します。

使い分けの基準は「ログに出てもよいか」です。リージョン名、バケット名、機能フラグのように公開されても無害な値を secrets に入れるとログのデバッグが難しくなるだけなので vars に置き、漏えいすると被害が出る値だけを secrets に置きます。Terraform 運用講座 #1のワークフローでロール ARN を vars.TF_PLAN_ROLE_ARN に置いたのが、この基準の適用例です。ARN は秘密ではないからです。

マスキングは補助装置です #

secrets の値がログに現れると、GitHub が *** で隠してくれます。便利ですが、これを安全の根拠にしてはいけません。マスキングは既知の値と正確に一致する文字列を隠すものなので、base64 でエンコードして出力したり、1 文字ずつ分けて出力したりすると、そのまま露出します。つまりマスキングはミスを減らしてくれる補助装置であり、本質的な安全は、そもそもシークレットを echo しないこと、そして #9 で扱う「信頼できないコードにシークレットを渡さない構造」から来ます。

3 つの階層 — 組織、リポジトリ、environment #

secrets と vars は 3 か所に置くことができ、狭いほうが勝ちます。

シークレットの階層構造
組織(organization)   ← 複数リポジトリで共用 (例: 共用の通知 Webhook)
  └ リポジトリ(repository) ← このリポジトリ内で共用
      └ environment      ← 特定の環境専用 (例: prod のデプロイロール)

同じ名前が重なると environment > リポジトリ > 組織の順で適用されます。この構造のおかげで、「dev と prod が同じワークフローを使いながら別々の認証情報を受け取る」構成が名前 1 つで表現できます。ワークフローのコードは ${{ secrets.DEPLOY_ROLE }} の 1 か所だけなのに、どの environment で実行されるかによって別の値が入ってくるわけです。

environment — デプロイの前のゲート #

environment はシークレットのスコープであると同時に、保護ルールの単位でもあります。Settings の Environments で作成し、ジョブに指定します。

.github/workflows/deploy-prod.yml
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - run: ./deploy.sh   # production スコープの secrets/vars が適用される

environment に設定できる保護ルールが実戦の核心です。

  • Required reviewers: 指定した人・チームが承認しないとジョブが始まりません。承認待ちのジョブは Actions 画面で Review ボタンとともに止まっていて、承認・却下は記録に残ります。「デプロイのボタンは自動化しつつ、最後の判断は人間に」という要件への標準的な答えです。
  • Wait timer: ジョブ開始前の待機時間を強制します。デプロイを予告したあとに取り消す時間を稼ぐ用途です。
  • Deployment branches: この environment へデプロイできるブランチを制限します。production を main 専用にロックすれば、別のブランチのワークフローが誤って prod のシークレットを受け取ること自体が遮断されます。

Terraform 運用講座 #1で「environment の承認で prod の前に人間のゲート」と予告だけしていた仕組みの全体像がこれです。

GITHUB_TOKEN — 登録した覚えのないシークレット #

secrets コンテキストには、登録しなくても常に存在する値が 1 つあります。secrets.GITHUB_TOKEN です。ワークフローの実行ごとに GitHub が自動発行する短期トークンで、このリポジトリに対する API 権限を持ち、実行が終わると失効します。PR へのコメント、リリースの作成、#6で見る GHCR へのプッシュまで、このトークンで処理できるので、リポジトリの中で完結する作業のために別途トークンを作る必要はありません。このトークンがどんな権限を持つのか、なぜデフォルトの権限を絞るべきなのかは、#9 セキュリティ回の最初のテーマです。

まとめ #

今回扱った内容です。

  • 使い分けの基準は「ログに出てもよいか」です。秘密だけを secrets に、残りの設定は vars に置いたほうがデバッグしやすくなります
  • マスキングは正確に一致する文字列だけを隠す補助装置です。安全の本質はシークレットを出力しないことと構造的な隔離です
  • secrets・vars は組織、リポジトリ、environment の 3 階層で、狭いほうが勝ちます。同じコードが環境ごとに別の値を受け取る基盤です
  • environment はシークレットのスコープであり保護ルールの単位です。承認者、待機時間、ブランチ制限でデプロイのゲートを設けます
  • GITHUB_TOKEN は実行ごとに自動発行される短期トークンで、リポジトリ内の作業はほとんどこれで足ります

次回(#4 マトリックス・キャッシュ・アーティファクト)では、実行を複数のバリエーションに増やすマトリックスと、逆に時間とコストを削るキャッシュ、そしてジョブ間でファイルを渡すアーティファクトを扱います。

X