<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tfsec on Tarragon</title><link>https://tarrragon.github.io/blog/tags/tfsec/</link><description>Recent content in Tfsec on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/tfsec/index.xml" rel="self" type="application/rss+xml"/><item><title>checkov 與 tfsec 規則配置</title><link>https://tarrragon.github.io/blog/infra/07-infra-as-pr/checkov-tfsec-rule-customization/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/07-infra-as-pr/checkov-tfsec-rule-customization/</guid><description>&lt;p>checkov 和 tfsec 安裝後直接跑，通常會產出幾十到幾百條命中。全部修完不切實際、全部忽略又失去價值。這篇處理的是怎麼從「裝了工具」走到「工具的產出可信且可操作」——規則選擇、嚴重度過濾、豁免管理、自訂規則、CI 整合，以及 false positive 的處理流程。&lt;/p>
&lt;h2 id="規則選擇策略">規則選擇策略&lt;/h2>
&lt;p>兩個工具的內建規則集都超過數百條，涵蓋從加密設定到命名慣例。全開跑會讓命中清單長到沒人看。規則選擇的判準是「這條規則命中後，團隊會不會真的去修」——答案是不會的規則，開著只是製造噪音。&lt;/p>
&lt;h3 id="分層啟用">分層啟用&lt;/h3>
&lt;p>把規則分成三層逐步啟用，而非一次全開：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層次&lt;/th>
 &lt;th>規則類型&lt;/th>
 &lt;th>範例&lt;/th>
 &lt;th>啟用時機&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>地基層&lt;/td>
 &lt;td>資料外洩與權限失控&lt;/td>
 &lt;td>S3 public access、SG 0.0.0.0/0、IAM wildcard&lt;/td>
 &lt;td>day 1&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>營運層&lt;/td>
 &lt;td>加密與備份&lt;/td>
 &lt;td>RDS encryption、EBS encryption、backup retention&lt;/td>
 &lt;td>IaC 覆蓋率 &amp;gt;50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規範層&lt;/td>
 &lt;td>命名、tagging、logging&lt;/td>
 &lt;td>缺 tag、缺 log group、resource naming&lt;/td>
 &lt;td>治理成熟後&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>地基層是即使其他規則都關掉也要開的——S3 bucket 對外公開（&lt;code>CKV_AWS_19&lt;/code>、&lt;code>CKV_AWS_53&lt;/code>）和 security group 全開（&lt;code>CKV_AWS_24&lt;/code>、&lt;code>CKV_AWS_25&lt;/code>）這類規則命中就是真問題。營運層在 IaC 覆蓋率夠高時啟用，否則會掃到大量不在 IaC 管理內的資源。規範層等團隊有能力消化命中量再開。&lt;/p>
&lt;h3 id="checkov-的規則過濾">checkov 的規則過濾&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只跑地基層規則&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">checkov -d . --check CKV_AWS_19,CKV_AWS_53,CKV_AWS_24,CKV_AWS_25,CKV_AWS_40,CKV_AWS_145
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或者用 framework 過濾（只掃 Terraform）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">checkov -d . --framework terraform --compact --quiet&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>checkov 支援 &lt;code>--check&lt;/code>（白名單，只跑這些）和 &lt;code>--skip-check&lt;/code>（黑名單，跳過這些）。初期用 &lt;code>--check&lt;/code> 白名單比較可控——明確列出要跑的規則，而非從全集去扣。隨著團隊消化能力提升再擴大白名單。&lt;/p>
&lt;h3 id="tfsec-的嚴重度過濾">tfsec 的嚴重度過濾&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只報 CRITICAL 和 HIGH&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">tfsec . --minimum-severity HIGH
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 排除特定規則&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">tfsec . --exclude aws-s3-specify-public-access-block&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>tfsec 的嚴重度分 CRITICAL / HIGH / MEDIUM / LOW。初期設 &lt;code>--minimum-severity HIGH&lt;/code> 把低嚴重度的過濾掉，減少噪音量。降低閾值的時機是 HIGH 以上的命中清零後。&lt;/p>
&lt;h2 id="豁免管理">豁免管理&lt;/h2>
&lt;p>不是每個命中都是錯——對外的 ALB 在 port 443 開 &lt;code>0.0.0.0/0&lt;/code> 是設計意圖、不是漏洞。豁免的重點是讓例外顯式化、有理由、可被 review。&lt;/p>
&lt;h3 id="行內豁免">行內豁免&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;alb_https&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ingress&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n"> from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n"> to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n"> protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n"> cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0/0&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"> #checkov:skip=CKV_AWS_24:ALB 的 HTTPS 入站需要對外開放
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>tfsec 的行內豁免：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;alb_https&amp;#34;&lt;/span> {&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"> #tfsec:ignore:aws-ec2-no-public-ingress-sgr -- ALB HTTPS listener requires public access
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n"> cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0/0&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>行內豁免的好處是理由跟程式碼在一起，review 時一眼可見。壞處是散落在各檔案裡，盤點所有豁免要 grep。&lt;/p>
&lt;h3 id="集中式豁免">集中式豁免&lt;/h3>
&lt;p>checkov 支援 &lt;code>.checkov.yaml&lt;/code> 集中管理豁免：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># .checkov.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">skip-check&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CKV_AWS_24 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ALB public-facing SG rules&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CKV_AWS_19 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Legacy S3 buckets pending migration&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>集中式的好處是一個地方看到所有豁免，適合全域性的例外（如「這批 legacy S3 bucket 還沒遷完、暫時跳過 public access 檢查」）。壞處是理由離程式碼太遠，三個月後沒人記得為什麼跳過。&lt;/p>
&lt;h3 id="豁免紀律">豁免紀律&lt;/h3>
&lt;p>每個豁免都要寫理由（&lt;code>--&lt;/code> 之後的文字）。沒有理由的豁免等於靜默跳過——review 時看不出是故意的還是為了讓 CI 過而隨手加的。定期（每季度）跑一次豁免盤點：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 盤點所有 checkov 豁免&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">grep -rn &lt;span class="s2">&amp;#34;checkov:skip&amp;#34;&lt;/span> --include&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;*.tf&amp;#34;&lt;/span> .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 盤點所有 tfsec 豁免&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">grep -rn &lt;span class="s2">&amp;#34;tfsec:ignore&amp;#34;&lt;/span> --include&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;*.tf&amp;#34;&lt;/span> .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個命中問一句：當初跳過的原因還成立嗎？legacy 遷移完了嗎？臨時的例外變成永久的了嗎？&lt;/p></description><content:encoded><![CDATA[<p>checkov 和 tfsec 安裝後直接跑，通常會產出幾十到幾百條命中。全部修完不切實際、全部忽略又失去價值。這篇處理的是怎麼從「裝了工具」走到「工具的產出可信且可操作」——規則選擇、嚴重度過濾、豁免管理、自訂規則、CI 整合，以及 false positive 的處理流程。</p>
<h2 id="規則選擇策略">規則選擇策略</h2>
<p>兩個工具的內建規則集都超過數百條，涵蓋從加密設定到命名慣例。全開跑會讓命中清單長到沒人看。規則選擇的判準是「這條規則命中後，團隊會不會真的去修」——答案是不會的規則，開著只是製造噪音。</p>
<h3 id="分層啟用">分層啟用</h3>
<p>把規則分成三層逐步啟用，而非一次全開：</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>規則類型</th>
          <th>範例</th>
          <th>啟用時機</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>地基層</td>
          <td>資料外洩與權限失控</td>
          <td>S3 public access、SG 0.0.0.0/0、IAM wildcard</td>
          <td>day 1</td>
      </tr>
      <tr>
          <td>營運層</td>
          <td>加密與備份</td>
          <td>RDS encryption、EBS encryption、backup retention</td>
          <td>IaC 覆蓋率 &gt;50%</td>
      </tr>
      <tr>
          <td>規範層</td>
          <td>命名、tagging、logging</td>
          <td>缺 tag、缺 log group、resource naming</td>
          <td>治理成熟後</td>
      </tr>
  </tbody>
</table>
<p>地基層是即使其他規則都關掉也要開的——S3 bucket 對外公開（<code>CKV_AWS_19</code>、<code>CKV_AWS_53</code>）和 security group 全開（<code>CKV_AWS_24</code>、<code>CKV_AWS_25</code>）這類規則命中就是真問題。營運層在 IaC 覆蓋率夠高時啟用，否則會掃到大量不在 IaC 管理內的資源。規範層等團隊有能力消化命中量再開。</p>
<h3 id="checkov-的規則過濾">checkov 的規則過濾</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 只跑地基層規則</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">checkov -d . --check CKV_AWS_19,CKV_AWS_53,CKV_AWS_24,CKV_AWS_25,CKV_AWS_40,CKV_AWS_145
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 或者用 framework 過濾（只掃 Terraform）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">checkov -d . --framework terraform --compact --quiet</span></span></code></pre></div><p>checkov 支援 <code>--check</code>（白名單，只跑這些）和 <code>--skip-check</code>（黑名單，跳過這些）。初期用 <code>--check</code> 白名單比較可控——明確列出要跑的規則，而非從全集去扣。隨著團隊消化能力提升再擴大白名單。</p>
<h3 id="tfsec-的嚴重度過濾">tfsec 的嚴重度過濾</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 只報 CRITICAL 和 HIGH</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">tfsec . --minimum-severity HIGH
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 排除特定規則</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">tfsec . --exclude aws-s3-specify-public-access-block</span></span></code></pre></div><p>tfsec 的嚴重度分 CRITICAL / HIGH / MEDIUM / LOW。初期設 <code>--minimum-severity HIGH</code> 把低嚴重度的過濾掉，減少噪音量。降低閾值的時機是 HIGH 以上的命中清零後。</p>
<h2 id="豁免管理">豁免管理</h2>
<p>不是每個命中都是錯——對外的 ALB 在 port 443 開 <code>0.0.0.0/0</code> 是設計意圖、不是漏洞。豁免的重點是讓例外顯式化、有理由、可被 review。</p>
<h3 id="行內豁免">行內豁免</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_security_group_rule&#34; &#34;alb_https&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  type</span>        <span class="o">=</span> <span class="s2">&#34;ingress&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  from_port</span>   <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  to_port</span>     <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">  protocol</span>    <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;0.0.0.0/0&#34;</span><span class="p">]</span><span class="c1">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">  #checkov:skip=CKV_AWS_24:ALB 的 HTTPS 入站需要對外開放
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span>}</span></span></code></pre></div><p>tfsec 的行內豁免：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_security_group_rule&#34; &#34;alb_https&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">  #tfsec:ignore:aws-ec2-no-public-ingress-sgr -- ALB HTTPS listener requires public access
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="n">  cidr_blocks</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;0.0.0.0/0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">}</span></span></code></pre></div><p>行內豁免的好處是理由跟程式碼在一起，review 時一眼可見。壞處是散落在各檔案裡，盤點所有豁免要 grep。</p>
<h3 id="集中式豁免">集中式豁免</h3>
<p>checkov 支援 <code>.checkov.yaml</code> 集中管理豁免：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># .checkov.yaml</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">skip-check</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="l">CKV_AWS_24 </span><span class="w"> </span><span class="c"># ALB public-facing SG rules</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="l">CKV_AWS_19 </span><span class="w"> </span><span class="c"># Legacy S3 buckets pending migration</span></span></span></code></pre></div><p>集中式的好處是一個地方看到所有豁免，適合全域性的例外（如「這批 legacy S3 bucket 還沒遷完、暫時跳過 public access 檢查」）。壞處是理由離程式碼太遠，三個月後沒人記得為什麼跳過。</p>
<h3 id="豁免紀律">豁免紀律</h3>
<p>每個豁免都要寫理由（<code>--</code> 之後的文字）。沒有理由的豁免等於靜默跳過——review 時看不出是故意的還是為了讓 CI 過而隨手加的。定期（每季度）跑一次豁免盤點：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 盤點所有 checkov 豁免</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">grep -rn <span class="s2">&#34;checkov:skip&#34;</span> --include<span class="o">=</span><span class="s2">&#34;*.tf&#34;</span> .
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 盤點所有 tfsec 豁免</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">grep -rn <span class="s2">&#34;tfsec:ignore&#34;</span> --include<span class="o">=</span><span class="s2">&#34;*.tf&#34;</span> .</span></span></code></pre></div><p>每個命中問一句：當初跳過的原因還成立嗎？legacy 遷移完了嗎？臨時的例外變成永久的了嗎？</p>
<h2 id="自訂規則">自訂規則</h2>
<p>內建規則覆蓋通用安全實踐，但專案特有的規範（如「所有 RDS 必須有 <code>cost-center</code> tag」「所有 S3 bucket 名稱必須以公司前綴開頭」）需要自訂。</p>
<h3 id="checkov-自訂規則python">checkov 自訂規則（Python）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># custom_checks/require_cost_center_tag.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">checkov.terraform.checks.resource.base_resource_check</span> <span class="kn">import</span> <span class="n">BaseResourceCheck</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">checkov.common.models.enums</span> <span class="kn">import</span> <span class="n">CheckResult</span><span class="p">,</span> <span class="n">CheckCategories</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">CostCenterTagRequired</span><span class="p">(</span><span class="n">BaseResourceCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Ensure cost-center tag is present&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="nb">id</span> <span class="o">=</span> <span class="s2">&#34;CUSTOM_001&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">supported_resources</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;aws_instance&#34;</span><span class="p">,</span> <span class="s2">&#34;aws_db_instance&#34;</span><span class="p">,</span> <span class="s2">&#34;aws_s3_bucket&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">categories</span> <span class="o">=</span> <span class="p">[</span><span class="n">CheckCategories</span><span class="o">.</span><span class="n">GENERAL_SECURITY</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">,</span> <span class="n">categories</span><span class="o">=</span><span class="n">categories</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                         <span class="n">supported_resources</span><span class="o">=</span><span class="n">supported_resources</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">scan_resource_conf</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">conf</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">tags</span> <span class="o">=</span> <span class="n">conf</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tags&#34;</span><span class="p">,</span> <span class="p">[{}])[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">tags</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span> <span class="ow">and</span> <span class="s2">&#34;cost-center&#34;</span> <span class="ow">in</span> <span class="n">tags</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">return</span> <span class="n">CheckResult</span><span class="o">.</span><span class="n">PASSED</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">CheckResult</span><span class="o">.</span><span class="n">FAILED</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">check</span> <span class="o">=</span> <span class="n">CostCenterTagRequired</span><span class="p">()</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 跑自訂規則</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">checkov -d . --external-checks-dir ./custom_checks</span></span></code></pre></div><h3 id="tfsec-自訂規則yaml">tfsec 自訂規則（YAML）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .tfsec/custom_rules.yaml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">CUSTOM_001</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">S3 bucket name must start with company prefix</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">impact</span><span class="p">:</span><span class="w"> </span><span class="l">Non-standard naming breaks cross-account policies</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">resolution</span><span class="p">:</span><span class="w"> </span><span class="l">Add company prefix to bucket name</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">  </span><span class="nt">requiredTypes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span>- <span class="l">resource</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">requiredLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span>- <span class="l">aws_s3_bucket</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">severity</span><span class="p">:</span><span class="w"> </span><span class="l">MEDIUM</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">matchSpec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">bucket</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">startsWith</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">acme-</span></span></span></code></pre></div><p>自訂規則的數量保持精簡——每條規則都是維護成本。只有「違反後會在後續流程造成問題」的規範值得寫成自動化規則，純粹的風格偏好留給 review 時口頭提醒。</p>
<h2 id="ci-整合">CI 整合</h2>
<p>把掃描接進 CI 的目標是「PR 合併前就攔下問題」，而非 apply 之後才發現。</p>
<h3 id="github-actions-範例">GitHub Actions 範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">security-scan</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run checkov</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">bridgecrewio/checkov-action@v12</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">          </span><span class="nt">directory</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">          </span><span class="nt">check</span><span class="p">:</span><span class="w"> </span><span class="l">CKV_AWS_19,CKV_AWS_53,CKV_AWS_24,CKV_AWS_25</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">          </span><span class="nt">quiet</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">          </span><span class="nt">compact</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">          </span><span class="nt">soft_fail</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run tfsec</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">aquasecurity/tfsec-action@v1</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">          </span><span class="nt">minimum_severity</span><span class="p">:</span><span class="w"> </span><span class="l">HIGH</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">          </span><span class="nt">soft_fail</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre></div><p><code>soft_fail: false</code> 讓掃描命中時 CI 失敗、阻擋合併。初期可以先設 <code>soft_fail: true</code>（掃描報告但不阻擋），讓團隊觀察命中量，確認規則集合理後再切成強制。</p>
<h3 id="掃描結果回貼-pr">掃描結果回貼 PR</h3>
<p>checkov 和 tfsec 的 GitHub Actions 都支援把結果以 PR comment 回貼。讓 reviewer 在 PR 頁面直接看到掃描結果，不用去翻 CI log。checkov-action 預設會回貼；tfsec-action 需要額外的 <code>github_token</code> 設定。</p>
<h3 id="漸進式導入">漸進式導入</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Week 1-2：soft_fail=true，觀察命中量和 false positive 率
</span></span><span class="line"><span class="ln">2</span><span class="cl">Week 3：修完所有真問題，豁免所有合理的 false positive
</span></span><span class="line"><span class="ln">3</span><span class="cl">Week 4：切 soft_fail=false，掃描變成強制 gate</span></span></code></pre></div><p>這個節奏讓團隊在掃描變成強制之前就清理完存量，避免「一開 hard fail 所有 PR 都過不了」的窘境。</p>
<h2 id="false-positive-處理">False positive 處理</h2>
<p>false positive 的處理有三條路，依復發頻率選：</p>
<table>
  <thead>
      <tr>
          <th>路徑</th>
          <th>適用情境</th>
          <th>做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行內豁免</td>
          <td>單一資源的合理例外</td>
          <td>在該資源加 <code>checkov:skip</code> + 理由</td>
      </tr>
      <tr>
          <td>全域跳過</td>
          <td>整個規則不適用於此專案</td>
          <td>加進 <code>.checkov.yaml</code> skip-check</td>
      </tr>
      <tr>
          <td>自訂規則覆蓋</td>
          <td>內建規則的判準不適合</td>
          <td>寫自訂規則取代內建規則</td>
      </tr>
  </tbody>
</table>
<p>最常見的 false positive 是 ALB 的 public-facing security group（設計就是要開 443）和開發環境的寬鬆設定（dev 允許、prod 不允許）。後者可以用 checkov 的 <code>--var-file</code> 搭配環境變數區分——dev 跑寬鬆規則集、prod 跑嚴格規則集。</p>
<p>處理 false positive 時要抵抗「加 skip 讓 CI 過」的捷徑衝動。每個 skip 都要問：這是設計意圖（ALB 要開放）還是技術債（dev 環境暫時放寬）？前者寫永久豁免加理由，後者寫臨時豁免加 TODO 和預計修復時間。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/07-infra-as-pr/plan-review-apply-guardrails/" data-link-title="infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">infra 走 PR 流程與自動化護欄</a>：掃描在 PR 流程裡的定位與 plan/apply 的關係</li>
<li>→ <a href="/blog/infra/07-infra-as-pr/terraform-ci-pipeline-setup/" data-link-title="Terraform CI Pipeline 設定指南" data-link-desc="用 GitHub Actions 建立完整的 Terraform CI pipeline：fmt → validate → tflint → plan → PR comment → apply，含 OIDC credential 與環境保護規則">Terraform CI Pipeline 設定</a>：掃描步驟怎麼嵌入完整的 CI workflow</li>
<li>→ <a href="/blog/infra/03-network-foundation/security-group-audit-cleanup/" data-link-title="Security Group 稽核與清理" data-link-desc="盤點所有 security group 規則、找出 0.0.0.0/0 全開與未使用的 SG、依賴檢查後安全刪除、自動化治理">模組三：Security Group 稽核與清理</a>：掃描命中 0.0.0.0/0 後的處理流程</li>
</ul>
]]></content:encoded></item></channel></rss>