checkov 和 tfsec 安裝後直接跑,通常會產出幾十到幾百條命中。全部修完不切實際、全部忽略又失去價值。這篇處理的是怎麼從「裝了工具」走到「工具的產出可信且可操作」——規則選擇、嚴重度過濾、豁免管理、自訂規則、CI 整合,以及 false positive 的處理流程。

規則選擇策略

兩個工具的內建規則集都超過數百條,涵蓋從加密設定到命名慣例。全開跑會讓命中清單長到沒人看。規則選擇的判準是「這條規則命中後,團隊會不會真的去修」——答案是不會的規則,開著只是製造噪音。

分層啟用

把規則分成三層逐步啟用,而非一次全開:

層次規則類型範例啟用時機
地基層資料外洩與權限失控S3 public access、SG 0.0.0.0/0、IAM wildcardday 1
營運層加密與備份RDS encryption、EBS encryption、backup retentionIaC 覆蓋率 >50%
規範層命名、tagging、logging缺 tag、缺 log group、resource naming治理成熟後

地基層是即使其他規則都關掉也要開的——S3 bucket 對外公開(CKV_AWS_19CKV_AWS_53)和 security group 全開(CKV_AWS_24CKV_AWS_25)這類規則命中就是真問題。營運層在 IaC 覆蓋率夠高時啟用,否則會掃到大量不在 IaC 管理內的資源。規範層等團隊有能力消化命中量再開。

checkov 的規則過濾

1# 只跑地基層規則
2checkov -d . --check CKV_AWS_19,CKV_AWS_53,CKV_AWS_24,CKV_AWS_25,CKV_AWS_40,CKV_AWS_145
3
4# 或者用 framework 過濾(只掃 Terraform)
5checkov -d . --framework terraform --compact --quiet

checkov 支援 --check(白名單,只跑這些)和 --skip-check(黑名單,跳過這些)。初期用 --check 白名單比較可控——明確列出要跑的規則,而非從全集去扣。隨著團隊消化能力提升再擴大白名單。

tfsec 的嚴重度過濾

1# 只報 CRITICAL 和 HIGH
2tfsec . --minimum-severity HIGH
3
4# 排除特定規則
5tfsec . --exclude aws-s3-specify-public-access-block

tfsec 的嚴重度分 CRITICAL / HIGH / MEDIUM / LOW。初期設 --minimum-severity HIGH 把低嚴重度的過濾掉,減少噪音量。降低閾值的時機是 HIGH 以上的命中清零後。

豁免管理

不是每個命中都是錯——對外的 ALB 在 port 443 開 0.0.0.0/0 是設計意圖、不是漏洞。豁免的重點是讓例外顯式化、有理由、可被 review。

行內豁免

1resource "aws_security_group_rule" "alb_https" {
2  type        = "ingress"
3  from_port   = 443
4  to_port     = 443
5  protocol    = "tcp"
6  cidr_blocks = ["0.0.0.0/0"]
7  #checkov:skip=CKV_AWS_24:ALB 的 HTTPS 入站需要對外開放
8}

tfsec 的行內豁免:

1resource "aws_security_group_rule" "alb_https" {
2  #tfsec:ignore:aws-ec2-no-public-ingress-sgr -- ALB HTTPS listener requires public access
3  cidr_blocks = ["0.0.0.0/0"]
4}

行內豁免的好處是理由跟程式碼在一起,review 時一眼可見。壞處是散落在各檔案裡,盤點所有豁免要 grep。

集中式豁免

checkov 支援 .checkov.yaml 集中管理豁免:

1# .checkov.yaml
2skip-check:
3  - CKV_AWS_24  # ALB public-facing SG rules
4  - CKV_AWS_19  # Legacy S3 buckets pending migration

集中式的好處是一個地方看到所有豁免,適合全域性的例外(如「這批 legacy S3 bucket 還沒遷完、暫時跳過 public access 檢查」)。壞處是理由離程式碼太遠,三個月後沒人記得為什麼跳過。

豁免紀律

每個豁免都要寫理由(-- 之後的文字)。沒有理由的豁免等於靜默跳過——review 時看不出是故意的還是為了讓 CI 過而隨手加的。定期(每季度)跑一次豁免盤點:

1# 盤點所有 checkov 豁免
2grep -rn "checkov:skip" --include="*.tf" .
3
4# 盤點所有 tfsec 豁免
5grep -rn "tfsec:ignore" --include="*.tf" .

每個命中問一句:當初跳過的原因還成立嗎?legacy 遷移完了嗎?臨時的例外變成永久的了嗎?

自訂規則

內建規則覆蓋通用安全實踐,但專案特有的規範(如「所有 RDS 必須有 cost-center tag」「所有 S3 bucket 名稱必須以公司前綴開頭」)需要自訂。

checkov 自訂規則(Python)

 1# custom_checks/require_cost_center_tag.py
 2from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
 3from checkov.common.models.enums import CheckResult, CheckCategories
 4
 5class CostCenterTagRequired(BaseResourceCheck):
 6    def __init__(self):
 7        name = "Ensure cost-center tag is present"
 8        id = "CUSTOM_001"
 9        supported_resources = ["aws_instance", "aws_db_instance", "aws_s3_bucket"]
10        categories = [CheckCategories.GENERAL_SECURITY]
11        super().__init__(name=name, id=id, categories=categories,
12                         supported_resources=supported_resources)
13
14    def scan_resource_conf(self, conf):
15        tags = conf.get("tags", [{}])[0]
16        if isinstance(tags, dict) and "cost-center" in tags:
17            return CheckResult.PASSED
18        return CheckResult.FAILED
19
20check = CostCenterTagRequired()
1# 跑自訂規則
2checkov -d . --external-checks-dir ./custom_checks

tfsec 自訂規則(YAML)

 1# .tfsec/custom_rules.yaml
 2- id: CUSTOM_001
 3  description: S3 bucket name must start with company prefix
 4  impact: Non-standard naming breaks cross-account policies
 5  resolution: Add company prefix to bucket name
 6  requiredTypes:
 7    - resource
 8  requiredLabels:
 9    - aws_s3_bucket
10  severity: MEDIUM
11  matchSpec:
12    name: bucket
13    action: startsWith
14    value: acme-

自訂規則的數量保持精簡——每條規則都是維護成本。只有「違反後會在後續流程造成問題」的規範值得寫成自動化規則,純粹的風格偏好留給 review 時口頭提醒。

CI 整合

把掃描接進 CI 的目標是「PR 合併前就攔下問題」,而非 apply 之後才發現。

GitHub Actions 範例

 1jobs:
 2  security-scan:
 3    runs-on: ubuntu-latest
 4    steps:
 5      - uses: actions/checkout@v4
 6
 7      - name: Run checkov
 8        uses: bridgecrewio/checkov-action@v12
 9        with:
10          directory: .
11          check: CKV_AWS_19,CKV_AWS_53,CKV_AWS_24,CKV_AWS_25
12          quiet: true
13          compact: true
14          soft_fail: false
15
16      - name: Run tfsec
17        uses: aquasecurity/tfsec-action@v1
18        with:
19          minimum_severity: HIGH
20          soft_fail: false

soft_fail: false 讓掃描命中時 CI 失敗、阻擋合併。初期可以先設 soft_fail: true(掃描報告但不阻擋),讓團隊觀察命中量,確認規則集合理後再切成強制。

掃描結果回貼 PR

checkov 和 tfsec 的 GitHub Actions 都支援把結果以 PR comment 回貼。讓 reviewer 在 PR 頁面直接看到掃描結果,不用去翻 CI log。checkov-action 預設會回貼;tfsec-action 需要額外的 github_token 設定。

漸進式導入

1Week 1-2:soft_fail=true,觀察命中量和 false positive 率
2Week 3:修完所有真問題,豁免所有合理的 false positive
3Week 4:切 soft_fail=false,掃描變成強制 gate

這個節奏讓團隊在掃描變成強制之前就清理完存量,避免「一開 hard fail 所有 PR 都過不了」的窘境。

False positive 處理

false positive 的處理有三條路,依復發頻率選:

路徑適用情境做法
行內豁免單一資源的合理例外在該資源加 checkov:skip + 理由
全域跳過整個規則不適用於此專案加進 .checkov.yaml skip-check
自訂規則覆蓋內建規則的判準不適合寫自訂規則取代內建規則

最常見的 false positive 是 ALB 的 public-facing security group(設計就是要開 443)和開發環境的寬鬆設定(dev 允許、prod 不允許)。後者可以用 checkov 的 --var-file 搭配環境變數區分——dev 跑寬鬆規則集、prod 跑嚴格規則集。

處理 false positive 時要抵抗「加 skip 讓 CI 過」的捷徑衝動。每個 skip 都要問:這是設計意圖(ALB 要開放)還是技術債(dev 環境暫時放寬)?前者寫永久豁免加理由,後者寫臨時豁免加 TODO 和預計修復時間。

跨分類引用