跳到内容

第 8 卷 / 第 9 章 / 第 1 课

9.1 DevSecOps 流水线:信号、门禁与制品晋级 ​

DevSecOps 不是把十个扫描器塞进 CI,而是让安全要求随每次变更执行,并把结果送到能做决定的人手里。流水线本身拥有源码、制品、发布权限和秘密,也是高价值攻击面。

按阶段回答不同问题 ​

text
提交前:秘密、格式和快速危险模式
PR:SAST、依赖/许可、IaC policy、单元安全测试、威胁差异
构建:固定依赖、隔离 runner、生成 SBOM/provenance、签名
测试环境:DAST、API 授权矩阵、fuzz/集成、配置验证
发布:验证 digest/attestation/policy、审批、渐进部署
运行时:攻击信号、配置漂移、漏洞可达性、反馈回 backlog

快速检查应在几分钟内给开发者反馈,长时间 fuzz 和全量 DAST 可异步运行;风险较高的发布必须等待相应证据,不能用“异步”永久跳过。

把检查绑定到交付对象 ​

源码仓库扫描、构建目录扫描和最终容器/包扫描回答不同问题。多阶段镜像可能不包含构建依赖,也可能在构建时意外加入未在 lockfile 中的二进制。

所有结果应绑定不可变身份:

text
source revision → artifact digest → SBOM/provenance → deployment revision

如果报告只写 image:latest,到修复时已无法证明扫描的是当前运行制品。

Gate 由风险与置信度决定 ​

建议立即阻断:新提交的有效秘密、禁止许可、可执行的高置信注入、未签名发布制品、生产 IaC 中公开管理端口等。需要人工 triage:低置信 SAST、只存在于开发依赖的 CVE、尚未确认可达路径的可选组件。开发依赖即使不进入最终镜像,仍可能在构建时执行代码、窃取凭据或篡改产物;不能仅凭 dev 标签降为无风险。

例外不是关闭规则:

text
finding + exact scope + evidence + compensating control
owner + approved_by + expiry + review trigger

例如例外只允许 digest D 上的 finding F 在周五 18:00 前晋级:D 换成 D2 时不能继承例外,到期后 D 也重新被阻断。时间、发现身份或证据缺失时,应返回待核实或失败,而非无条件放行。

到期自动恢复阻断。宽泛的 repo 永久忽略会让后来新增的真实问题一起消失。

PR 来自 fork 时视为不可信代码 ​

攻击者可修改 workflow、测试和构建脚本来窃取 CI secret。处理外部 PR 时:

  • 不向未受信代码注入 secret,生产与非生产凭据都可能授予重要权限;
  • 使用只读、短期 token;
  • 隔离 runner,任务后销毁;
  • 禁止挂载宿主 Docker socket 和共享 credential cache;
  • 需要特权的发布 job 只运行已经评审合并的 revision;
  • 审批后执行也要确认运行的是哪个 commit 和 workflow 版本。

Self-hosted runner 的网络、缓存和持久磁盘会造成跨构建污染。高权限后续 job 也不能直接执行低信任 PR 生成的脚本或恢复其可执行缓存。以 GitHub Actions 为例,pull_request_target 在基础仓库上下文运行;若再 checkout 并执行 PR head,就可能把不可信代码带进高权限环境。

制品只构建一次 ​

text
build digest D
→ 测试 D
→ staging 部署 D
→ production 晋级同一个 D

每个环境重新 build 会产生未测试差异。环境配置与制品分离,发布系统只更新被验证 digest。数据库迁移和 feature flag 仍需单独兼容策略。

用效果而非扫描数量衡量 ​

有意义的指标:新增风险阻断率、有效发现到修复时间、误报导致的开发等待、例外逾期、运行时逃逸问题、制品证据覆盖率。Scanner 发现数上升可能只是规则变多,不代表系统更危险或团队更成熟。

下一课处理流水线的秘密、策略执行权与运行时反馈。

参考资料 ​

Built with VitePress | Software Systems Atlas