GitHub Actions 講座 #3 シークレット・変数・環境 — secrets と vars、environment の承認ゲート
#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 で作成し、ジョブに指定します。
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 マトリックス・キャッシュ・アーティファクト)では、実行を複数のバリエーションに増やすマトリックスと、逆に時間とコストを削るキャッシュ、そしてジョブ間でファイルを渡すアーティファクトを扱います。