<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Terraform on AIquoteBA</title>
        <link>https://blog.mayaton.net/tags/terraform/</link>
        <description>Recent content in Terraform on AIquoteBA</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>ja-jp</language>
        <lastBuildDate>Wed, 24 Jun 2026 05:00:00 +0900</lastBuildDate><atom:link href="https://blog.mayaton.net/tags/terraform/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>GitHub ActionsのGITHUB_TOKENをRead既定に寄せた話</title>
            <link>https://blog.mayaton.net/posts/gha-permissions-hardening/</link>
            <pubDate>Wed, 24 Jun 2026 05:00:00 +0900</pubDate>
            <guid>https://blog.mayaton.net/posts/gha-permissions-hardening/</guid>
            <description>&lt;h2 id=&#34;概要&#34;&gt;概要&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;ある組織で管理している複数リポジトリの &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; 既定権限を「Read and write」から「Read」に切り替えた。&lt;/li&gt;&#xA;&lt;li&gt;そのうえで、各workflowのjobに、必要最小限の &lt;code&gt;permissions:&lt;/code&gt; を書き足した。&lt;/li&gt;&#xA;&lt;li&gt;あわせて &lt;code&gt;actions/checkout&lt;/code&gt; に &lt;code&gt;persist-credentials: false&lt;/code&gt; を指定し、後段のstepからtokenを持ち出しにくくした。&lt;/li&gt;&#xA;&lt;li&gt;ADRで方針を決め、3リポに展開した。&lt;/li&gt;&#xA;&lt;li&gt;切替の順序を間違えるとrelease系workflowがまとめて落ちるので、そこだけは注意して回した。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;前提-permissions-の3階層&#34;&gt;前提: &lt;code&gt;permissions&lt;/code&gt; の3階層&#xA;&lt;/h2&gt;&lt;p&gt;GitHub Actionsのjobが使う &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; の権限は、次の3階層で決まる。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;repository default&lt;/strong&gt;: リポジトリ設定の &lt;code&gt;Settings &amp;gt; Actions &amp;gt; General &amp;gt; Workflow permissions&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;workflow level&lt;/strong&gt;: workflowファイルの最上位に書く &lt;code&gt;permissions:&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;job level&lt;/strong&gt;: 各job内に書く &lt;code&gt;permissions:&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;下の階層で &lt;code&gt;permissions:&lt;/code&gt; を書くと、上の階層の指定を丸ごと上書きする。&lt;br&gt;&#xA;job levelにscopeを1つでも書いた瞬間、そのjobは他階層の指定を一切継承しない&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;&#xA;&lt;p&gt;repository defaultがWriteのままだと、workflow / jobで &lt;code&gt;permissions:&lt;/code&gt; を書き忘れた瞬間、そのjobは &lt;code&gt;contents: write&lt;/code&gt; を含む広い権限で走る。&lt;br&gt;&#xA;サードパーティactionがそのjobの実行中に &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; を触れば、リポジトリへの書き込みまで手が届く。&lt;/p&gt;&#xA;&lt;h2 id=&#34;出発点&#34;&gt;出発点&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;全リポジトリでrepository default = &lt;strong&gt;Read and write permissions&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;workflowの8割以上で &lt;code&gt;permissions:&lt;/code&gt; が未指定。&lt;/li&gt;&#xA;&lt;li&gt;release用のworkflowはtag pushやrelease assetのuploadを実際に必要としており、書き込み権限が要る。&lt;/li&gt;&#xA;&lt;li&gt;サードパーティactionはSHA pin済みだが、pin先のコミット自体を検証しているわけではない。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;workflow側は「動けばいい」で書かれており、権限付与は既定値まかせだった。&lt;br&gt;&#xA;攻撃者側から見ると、read-onlyな用途のjobにまで書き込みトークンが降ってきており、supply chain攻撃の刺さる面積は広い状態だった。&lt;/p&gt;&#xA;&lt;h2 id=&#34;変更1-repository-defaultをreadに寄せる&#34;&gt;変更1: repository defaultをReadに寄せる&#xA;&lt;/h2&gt;&lt;p&gt;最初にやるのはrepository defaultの切替だ。&lt;br&gt;&#xA;GitHub UIから &lt;code&gt;Settings &amp;gt; Actions &amp;gt; General &amp;gt; Workflow permissions&lt;/code&gt; で変えられるが、複数リポで一貫して当てたいのでTerraformで書いた。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;github_actions_repository_permissions&amp;#34; &amp;#34;this&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  repository                       &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;repository&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  default_workflow_permissions     &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;read&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  can_approve_pull_request_reviews &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この設定だけを先にmergeすると、&lt;code&gt;permissions:&lt;/code&gt; を明示していないjobはほぼ全滅する。&lt;br&gt;&#xA;書き込みを伴うstepが一斉に落ちる形で表面化する。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;git push&lt;/code&gt; を伴う自動コミット&lt;/li&gt;&#xA;&lt;li&gt;release assetのupload&lt;/li&gt;&#xA;&lt;li&gt;PR / Issueへのコメントやlabel操作&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;なので後述の順序で回す必要がある。&lt;/p&gt;&#xA;&lt;h2 id=&#34;変更2-job-levelに最小権限を書く&#34;&gt;変更2: job levelに最小権限を書く&#xA;&lt;/h2&gt;&lt;p&gt;repository defaultをReadに落とすので、書き込みが必要なjobだけ、必要なscopeに絞って &lt;code&gt;permissions:&lt;/code&gt; を書き足す。&lt;/p&gt;&#xA;&lt;p&gt;tag pushを伴うrelease jobなら、こうなる。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;release&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;runs-on&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-24.04&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;permissions&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;contents&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;write  &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# tag と release 作成のため&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# 他の scope は指定しない = Read すら降りてこない&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/checkout@&amp;lt;sha&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;softprops/action-gh-release@&amp;lt;sha&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;permissions:&lt;/code&gt; をworkflow levelではなく &lt;strong&gt;job level&lt;/strong&gt; に書く理由は2つある。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;job levelで書けば、そのjob以外はrepository defaultのまま影響を受けない。&lt;/li&gt;&#xA;&lt;li&gt;同じworkflowに複数jobがあるとき、書き込みが要るjobだけに権限を閉じ込められる。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;workflow levelに &lt;code&gt;permissions:&lt;/code&gt; を書くと、そのworkflowの全jobに一律で継承される。&lt;br&gt;&#xA;「lint jobにまでwriteが降ってくる」構図が復活してしまうので、書き先はjob levelに統一した。&lt;/p&gt;&#xA;&lt;h2 id=&#34;変更3-actionscheckout-に-persist-credentials-false&#34;&gt;変更3: &lt;code&gt;actions/checkout&lt;/code&gt; に &lt;code&gt;persist-credentials: false&lt;/code&gt;&#xA;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;actions/checkout&lt;/code&gt; は既定で、&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; をローカルの &lt;code&gt;.git/config&lt;/code&gt; にextraheaderとして書き込む&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;br&gt;&#xA;これは後段のstepから &lt;code&gt;git push&lt;/code&gt; するために必要な挙動だが、書き込みが不要なjobでは単にtoken漏洩の経路になる。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/checkout@&amp;lt;sha&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;persist-credentials&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;書き込みが必要なjob（release / bot系）だけ &lt;code&gt;persist-credentials: true&lt;/code&gt; を明示し、それ以外はfalseに倒した。&lt;br&gt;&#xA;job levelの &lt;code&gt;permissions: contents: read&lt;/code&gt; と組み合わせると、&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; はcheckoutのHTTPS fetchにしか使われなくなる。&lt;/p&gt;&#xA;&lt;h2 id=&#34;展開の順序&#34;&gt;展開の順序&#xA;&lt;/h2&gt;&lt;p&gt;3リポで同じ変更が要ったので、次の順で回した。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;ADRで方針を書き、レビューを回す。&lt;/li&gt;&#xA;&lt;li&gt;各リポのworkflow側PRで、job levelの &lt;code&gt;permissions:&lt;/code&gt; と &lt;code&gt;persist-credentials: false&lt;/code&gt; を全jobに入れる。&lt;/li&gt;&#xA;&lt;li&gt;workflow側PRをすべてmergeした状態を確認する。&lt;/li&gt;&#xA;&lt;li&gt;Terraform側のPR（repository default = Read）をmergeする。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;順序を逆にすると、Terraform mergeの直後に既存jobが全滅する。&lt;br&gt;&#xA;「workflow側の穴埋めが終わった状態」を先に作ってから、defaultをReadに落とすのが安全側。&lt;/p&gt;&#xA;&lt;h2 id=&#34;ハマったポイント&#34;&gt;ハマったポイント&#xA;&lt;/h2&gt;&lt;h3 id=&#34;create-github-app-token-は-id-token-write-を要求する&#34;&gt;&lt;code&gt;create-github-app-token&lt;/code&gt; は &lt;code&gt;id-token: write&lt;/code&gt; を要求する&#xA;&lt;/h3&gt;&lt;p&gt;App tokenを発行するstepは &lt;code&gt;contents: read&lt;/code&gt; だけでは動かず、&lt;code&gt;id-token: write&lt;/code&gt; が要る。&lt;br&gt;&#xA;これは &lt;code&gt;permissions:&lt;/code&gt; のscope一覧を眺めても直感的に選びにくいので、失敗ログを読んで補うしかなかった。&lt;br&gt;&#xA;似た例として、&lt;code&gt;aws-actions/configure-aws-credentials&lt;/code&gt; のOIDC経路も &lt;code&gt;id-token: write&lt;/code&gt; が要る。&lt;/p&gt;&#xA;&lt;h3 id=&#34;workflow_dispatch-のre-runが突然落ちる&#34;&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; のRe-runが突然落ちる&#xA;&lt;/h3&gt;&lt;p&gt;repository defaultをReadにしたあと、過去に走らせたrunの &lt;code&gt;Re-run all jobs&lt;/code&gt; を押すと、run当時のworkflow定義に対して &lt;strong&gt;現在の&lt;/strong&gt; repository defaultが適用される。&lt;br&gt;&#xA;「昔動いていた再実行」が理由不明で落ちるので、リリース担当は気付きにくい。&lt;/p&gt;&#xA;&lt;blockquote class=&#34;alert alert-important&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;📌&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;Important&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;repository defaultをReadに切替えた直後は、過去runのRe-runはほぼ確実に壊れる。&lt;br&gt;&#xA;切替直後の再実行はRe-runに頼らず、&lt;code&gt;workflow_dispatch&lt;/code&gt; を新規に発火し直す運用に寄せた方が事故が少ない。&lt;/p&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;pull_request_target-を使うworkflowの見直し&#34;&gt;&lt;code&gt;pull_request_target&lt;/code&gt; を使うworkflowの見直し&#xA;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; の権限を絞ると、&lt;code&gt;pull_request_target&lt;/code&gt; トリガのworkflowで、外部コントリビュータPRに対して発火する処理の権限も同時に狭まる。&lt;br&gt;&#xA;既存のlabel付与botやauto-assign系が壊れたので、job levelに &lt;code&gt;pull-requests: write&lt;/code&gt; を明示的に足して回した。&lt;br&gt;&#xA;このタイミングで「そもそも &lt;code&gt;pull_request_target&lt;/code&gt; を使う必要があるのか」も一緒に見直せると、後の作業が減る。&lt;/p&gt;&#xA;&lt;h2 id=&#34;まとめ&#34;&gt;まとめ&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;repository default = Readに落とす。&lt;/li&gt;&#xA;&lt;li&gt;書き込みが要るjobだけに、&lt;code&gt;permissions:&lt;/code&gt; を &lt;strong&gt;job level&lt;/strong&gt; で書く。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;actions/checkout&lt;/code&gt; は書き込みが要らないなら &lt;code&gt;persist-credentials: false&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;展開は「workflow側の穴埋め → repository default切替」の順で。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;repository defaultがWriteのままだと、workflow側の書き忘れがそのまま「余計な書き込み権限を持つjob」になる。&lt;br&gt;&#xA;defaultをReadに閉じてから、書き込みが要る場所だけ穴を開けていく方が、状態としては安全側に倒せる。&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;公式ドキュメント &lt;a class=&#34;link&#34; href=&#34;https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Controlling permissions for GITHUB_TOKEN&lt;/a&gt;。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/actions/checkout#usage&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;actions/checkoutのREADME&lt;/a&gt;を参照。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item></channel>
</rss>
