基础设施即代码的安全扫描,真正难的不是把工具跑起来,而是让扫描结果在流水线里“可信”。同一个 Terraform 目录,用不同工具、不同版本、不同规则集扫出来的告警数量可能相差数倍,其中既有真问题,也有大量因为缺少运行时上下文而产生的误报。所以在把任何扫描器接入 CI 之前,比较稳妥的顺序是:先在一台隔离环境或一次性容器里把它跑通,看清楚它读什么、报什么、需要什么权限,再决定它在流水线中的位置和阻断策略。下面以 Checkov、Trivy、KICS 和 Kubescape 为例,说明这四类工具各自擅长什么、最小可运行流程长什么样,以及结果复核和供应链边界该怎么处理。
四类工具的分工
这四个工具经常被放在一起比较,但它们的覆盖面其实并不重合,混着用之前先分清定位,能省掉很多重复告警。
| 工具 | 主要能力 | 典型输入 | 适合的阶段 |
|---|---|---|---|
| Checkov | 内置策略数量多,含跨资源关系的图检查,可映射到 CIS、PCI DSS、SOC 2 等合规框架 | Terraform、CloudFormation、Kubernetes、Helm、Dockerfile、Ansible 等 | 提交前检查、合规基线核对 |
| Trivy | 单二进制多面手,IaC 配置、容器镜像、依赖、SBOM、密钥一并覆盖,已合并 tfsec 的检测能力 | Terraform、Kubernetes、Helm、镜像、文件系统 | 一体化流水线,希望少装几个工具时 |
| KICS | 基于 Rego 查询驱动的开源扫描器,规则可读可改 | Terraform、Kubernetes、CloudFormation、Dockerfile、Ansible 等 | 已有 OPA/Rego 体系、需要自写规则的团队 |
| Kubescape | 面向 Kubernetes 的集群与清单姿态检查,贴合 NSA、CIS 等框架 | 运行中的集群、Kubernetes 清单、Helm 渲染结果 | 集群侧持续检查、K8s 合规取证 |
一个实用的判断方式是:如果主要资产是 Terraform 和云资源模板,Checkov 的策略深度和合规映射更直接;如果希望用最少的组件同时覆盖 IaC、镜像和依赖,Trivy 的性价比更高;KICS 适合愿意维护自定义 Rego 规则的组织;Kubescape 则更贴近“集群已经跑起来之后”的检查场景。Checkov 由 Palo Alto Networks 维护,Trivy 由 Aqua Security 维护,两者都是 Apache 2.0 许可,这一点在评估长期维护风险时值得记一笔;社区对这几款工具的横向对比可参考 IaC 扫描工具评测 和 Checkov 与 Trivy 的对比。

准备条件与隔离边界
动手之前,把这几件事固定下来,后面复现结果会轻松很多:
- 一份只读的仓库副本:不要在开发者的工作目录里直接扫,也不要让扫描过程有机会写文件。
- 可记录的版本号:工具版本、规则库版本、扫描时间,三者缺一不可。否则两次结果不一致时无从排查。
- 受控网络:多数扫描器首次运行需要拉取策略库或镜像。隔离环境里要么放行这部分出网,要么预先做好内网镜像,不要临时开全局代理。
- 最小权限身份:本地验证阶段不需要云厂商的写权限,更不需要 Kubernetes 的集群管理员凭据。需要访问集群时,用只读的 kubeconfig。
如果一开始就用容器运行扫描器,尽量指定明确的版本标签或镜像摘要,而不是 latest。这不只是为了结果可复现,也是后面要讲的供应链问题的第一道闸。
最小运行示例
以下命令都只做只读扫描,输出到本地文件,方便后续对比。不同大版本的子命令可能有调整,执行前用 --help 确认一次即可。
Checkov
python3 -m pip install --user "checkov==<固定版本>"
checkov -d ./infra --framework terraform --output json --compact > checkov.json
--framework 用来把扫描范围收敛到当前仓库实际使用的格式,避免为无关文件生成噪声。需要同时看 Kubernetes 清单时,去掉该参数或改用对应的框架值。
Trivy
trivy config ./infra --format json --output trivy.json
trivy fs --scanners misconfig,secret ./infra
config 面向 IaC 配置,fs 可以对目录同时做配置错误和明文密钥检查。想顺带看镜像,把目标换成镜像名即可,但注意那会触发镜像拉取,隔离环境里要提前准备好网络或本地镜像。
KICS
docker run --rm -v "$PWD":/path checkmarx/kics:<固定标签>
scan -p /path --output-path /path/results --report-formats json
上面的写法特意保留了版本占位符——实际使用时不要写 latest。KICS 的优势在于规则本身就是可读的查询语句,误报时可以打开对应规则看判断逻辑,而不是只对着一个规则 ID 猜。
Kubescape
kubescape scan framework nsa ./manifests --format json --output kubescape.json
针对清单文件的扫描可以在没有集群的情况下跑;如果要扫运行中的集群,先确认所用身份是只读的,并且清楚它会读取哪些资源对象。
把告警变成可复核的证据
扫描器输出的原始结果只是线索。要让它在团队里站得住,需要给每条告警补齐四要素:规则标识、命中的文件与行号、触发原因、复核结论。
先做分级。 实践中可以粗分为三档:可以直接确认的高风险项(对象存储公开可读、IAM 策略含通配动作、数据库未开启加密或备份)、需要结合上下文判断的项(安全组放通了内网网段、日志保留期偏短)、以及明显与当前架构不匹配的噪声(策略假设了某种没用到的部署形态)。第一档直接进修复队列,第二档进人工确认,第三档要么调整策略配置,要么写进例外清单并注明原因和到期日。
误报的复核套路。 三个问题通常能解决大部分争议:这个资源在真实环境里是否可达?是否有其他控制措施做了补偿(网络策略、WAF、组织级策略、云平台的默认加密)?把命中片段单独剪切到一个最小目录里重扫,告警是否仍然复现?最后一步尤其有用,它能区分“规则真的命中了这段配置”和“规则被其他文件的关联关系带偏了”。
建立基线。 第一次全量扫描的结果存档,作为基线。之后每次只看新增和消失的条目,而不是盯着总数波动。Helm 和 Kustomize 项目尤其要注意:扫描渲染后的最终清单,比扫描带大量变量的原始模板误报少得多。
接入流水线的边界
本地验证通过不等于可以直接进主构建链。扫描器本身也是需要被审视的组件:它会读取全部源码、会拉取远程规则库、有时还持有云侧或集群侧凭据。
固定版本与来源。 工具版本、镜像摘要、规则库版本都要固定,并通过内部镜像仓库或代理分发,避免流水线每次运行都从公网拉最新版。这不是过度谨慎——以 Trivy 这类同时以二进制和容器镜像分发的工具为例,一旦上游发布产物出问题,所有自动拉取最新版的流水线都会同步受到影响。围绕扫描工具自身发布渠道的供应链风险已有公开讨论,处理方式和对待其他构建依赖没有区别:锁版本、校验产物、控制出网、隔离验证。
权限最小化。 扫描步骤用只读的代码检出凭据,不注入云厂商的长期访问密钥。确实需要云侧增强检查时,拆成独立 job,配一个只读角色和短时凭据,与构建、部署步骤分开。容器方式运行时只挂载代码目录,不挂载宿主机的凭据目录。
失败策略要渐进。 直接让高危告警阻断流水线,通常会在第一周就引发抵触。更可行的路径是:先跑两到四周的观察期,只报告不阻断,同时积累例外清单;然后只对确认无误的高危类别开启阻断;中危项保持提示。每个例外条目都要有过期时间,否则清单会变成永久豁免。
回滚与卸载。 把扫描做成独立的 job 或独立阶段,而不是塞进构建脚本中间。这样工具本身出问题、规则库拉取失败或报告格式变化时,可以单独禁用或降级为仅报告,而不影响正常的构建发布。本地验证环境用容器运行的话,退出即清理,不会在机器上留下规则缓存和凭据。
几个常见故障
- 扫出零结果:多数是路径写错,或工作目录挂载错了。先确认扫描器能看到目标文件,再怀疑规则。
- 规则库拉取失败:隔离网络没有放行对应域名,或内网镜像未同步。日志里通常能看到具体的拉取地址。
- JSON 报告解析失败:工具升级后输出结构变化。固定版本能规避大部分问题;解析脚本里对缺失字段做容错。
- 告警数量异常偏高:检查是否扫了未渲染的模板、是否把测试目录和示例目录一起扫了、是否同时启用了多个重叠框架。
- 扫描耗时过长:先收窄目录范围和框架,再考虑并行拆分,不要一上来就调大资源。
落地顺序
对大多数团队来说,一条务实的路径是:选一个与主要 IaC 格式最匹配的工具,在隔离环境里跑通并留存基线;用两到三周时间复核告警质量,把误报沉淀成配置和例外清单;然后以只报告的方式接入流水线,观察一段时间后再开启分级阻断。第二个工具只在第一个工具确实覆盖不到的场景里引入——比如已经有 Terraform 扫描,但集群侧的姿态和合规证据仍然空白,这时才轮到 Kubescape 这类偏 Kubernetes 的工具。工具数量不是目标,能持续产出可复核证据的那一条链路才是。
- it2021
- it2021.com
- Kubernetes
- test

1F
正想给流水线加IaC扫描,这篇顺序讲得挺清楚