2. 安全流水线的架构与组件
安全流水线不是把若干安全工具“挂”在 CI/CD 上,而是把五类核心组件按明确的职责边界和数据契约组织成一条可追溯、可阻断、可复现的链路。这五类组件是:CI/CD 引擎(调度与执行)、制品库(产物与元数据的唯一可信源)、密钥管理(凭据与签名的供给)、策略引擎(准入判断的决策中心)、扫描工具链(证据生产者)。理解它们之间的集成模式与数据流方向,是后续所有章节(策略即代码、供应链安全、密钥治理)的基础。
一、组件职责与边界
先明确每个组件“拥有什么数据、对什么负责”,否则集成时极易出现职责重叠。CI/CD 引擎负责触发、编排、隔离执行环境和收集退出码,它不负责判断“这个漏洞是否可放行”;制品库负责存储镜像、包、SBOM、签名与扫描报告,并保证不可变(immutable)与可追溯;密钥管理负责签发短期凭据、托管签名密钥、执行密钥轮换,流水线中不应出现长期静态密钥;策略引擎接收扫描证据与上下文,输出 allow/deny/warn 决策;扫描工具链负责产生结构化证据(SARIF、CycloneDX、JSON),而不是自己决定是否阻断。
二、集成模式:三种典型拓扑
模式 A:引擎内嵌式。扫描与策略判断都写在 CI 的 job 里,工具以容器步骤运行,策略以 CLI 形式调用。优点是上手快、延迟低;缺点是策略散落在各仓库 YAML 中,难以统一治理,适合团队规模小、合规要求低的场景。
模式 B:策略中心式(推荐)。CI 只负责“收集证据并上报”,策略引擎作为独立服务接收证据包,返回决策。CI 根据决策结果决定是否继续。这种模式下策略集中版本化,扫描工具可替换,是 DevSecOps 的主流形态。
模式 C:制品库门禁式。把准入判断下沉到制品库的推送/拉取环节,例如镜像推送到 registry 时触发签名校验与漏洞阈值检查,未通过则拒绝入库。它适合作为模式 B 的补充,形成“构建时 + 入库时 + 部署时”的多点卡口。
三、数据流设计:从代码提交到准入决策
下面这张图给出模式 B 的完整数据流。注意箭头方向代表数据与证据的流动方向,而非简单的执行顺序:扫描工具把证据写入制品库并上报策略引擎,策略引擎读取制品元数据与密钥管理提供的签名状态,最终把决策回传给 CI/CD 引擎。
四、关键数据契约
组件之间能否解耦,取决于数据契约是否稳定。实践中至少要约定三类契约:
- 证据契约:扫描结果统一为 SARIF(静态/代码类)与 CycloneDX 或 SPDX(依赖与 SBOM)。字段至少包含
tool、ruleId、severity、location、artifactDigest。缺少artifactDigest的证据无法与制品绑定,等于无效证据。 - 决策契约:策略引擎返回结构化结果,例如
{"decision":"deny","policyId":"no-critical-cve","evidenceRef":"sha256:..."}。CI 只解析decision字段,不解析规则细节,这样规则演进不会破坏流水线。 - 凭据契约:CI 通过 OIDC 向密钥管理换取短期令牌(如 15 分钟有效期),令牌绑定到具体 job 与仓库,避免把长期密钥写入 CI 变量。
五、一个可落地的集成示例
假设某服务构建镜像并推送。CI 在构建阶段执行:
# 1. 构建并生成 SBOM
syft packages dir:. -o cyclonedx-json > sbom.json
# 2. 扫描依赖与镜像,输出 SARIF
grype sbom:sbom.json -o sarif > deps.sarif
trivy image --format sarif -o image.sarif $IMAGE_REF
# 3. 用 OIDC 换取短期令牌并签名
cosign sign --key env://COSIGN_KEY $IMAGE_REF
# 4. 上报证据包给策略引擎
curl -sS -X POST $POLICY_URL/v1/evaluate \
-H "Authorization: Bearer $OIDC_TOKEN" \
-d @evidence-bundle.json
策略引擎读取证据包与制品元数据,若发现 severity=critical 且该 CVE 不在豁免清单中,返回 deny;CI 收到 deny 后终止后续部署 job,并把 policyId 与证据引用写入构建记录。若返回 warn,则允许继续但生成待办工单。整个过程中,扫描工具没有权限决定放行,密钥没有出现在日志里,制品库保存了签名与证据的对应关系。
if grep -q CRITICAL。这会让策略无法集中治理,也无法审计“当时为什么放行”。