CI/CD 流水线安全:供应链攻击防御与硬编码凭证治理

加固 CI/CD 流水线:攻击面与威胁模型、短时令牌与动态 Secret(OIDC/Vault)、Runner 隔离与最小权限、签名与 SBOM 门禁、Gitleaks 硬编码凭证治理、审计检测与事件响应、供应链防御纵深。

CI/CD 流水线拥有通往生产的所有钥匙,也成了攻击者的高价值目标。一次 PR 里的恶意脚本、一个泄露的部署密钥、一个失控的 Runner,都能让"代码即信任"崩塌。本文按防御纵深展开:认识攻击面(RCE/提权/投毒)→ 凭证治理(短时令牌/动态 Secret)→ Runner 隔离与最小权限 → 签名与 SBOM 门禁 → Gitleaks 硬编码凭证扫描 → 审计检测 → 事件响应。


目录


1. 流水线攻击面:RCE 提权与投毒

1.1 流水线为什么是目标

流水线有生产凭证、能改镜像、能发版本 = 通往一切的钥匙
攻击者只要控制一个环节,就能"合法地"把恶意产物送进生产

1.2 三类核心攻击向量

攻击向量场景典型后果
RCE恶意 PR 触发构建脚本执行命令在 Runner 上执行任意代码
提权窃取流水线里挂的宽权限凭证横向到云/制品库/生产
投毒篡改依赖/镜像/构建产物把恶意组件送进生产

1.3 威胁模型

入口:未审 PR、恶意依赖、失陷的第三方 Action/工具镜像;载体:Runner 执行/凭证/产物
信任链原则:任何"未经验证就信任"的环节都是弱点

2. 凭证管理:短时令牌与动态 Secret

2.1 静态 Secret 的致命伤

把云密钥/数据库密码直接写进流水线变量 = 泄露一次全完蛋:
  泄露无感知、无法单独吊销、任何人可复用
目标:生产凭证永不静态存在于 CI 配置里

2.2 OIDC 联邦:CI 短时令牌

# GitHub Actions OIDC:工作负载直接从 IdP 换短时云凭证
permissions:
  id-token: write
  contents: read
# 换取 AWS 短时凭证(无需存储任何长期密钥)
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::xxx:role/ci-deploy \
  --web-identity-token "$GITHUB_OIDC_TOKEN" \
  --duration-seconds 900
关键收益:凭证有效期只有 15 分钟、绑定本次运行、按角色最小授权

2.3 动态 Secret:Vault

# 流水线运行期从 Vault 动态拉取,用完即失效
vault read database/creds/app   # 动态生成一次性数据库账号
vault kv get secret/ci/app      # 动态拉取应用密钥
# 动态 secret:短生命周期、自动轮转、按角色权限分发
分级:能 OIDC 用 OIDC;需"任意时刻可用"的走 Vault 动态 Secret;静态 secret(第三方密钥)加密托管 + 定期轮转

3. 隔离与最小权限:Runner 隔离

3.1 Runner 隔离

隔离等级:SaaS Runner 天然隔离适合普通构建;自托管 Runner 当生产资源保护
不可信代码跑容器/沙箱;不同信任级别用不同 Runner 池

3.2 最小权限

# GitHub Actions:声明最小权限,别用默认全量 token
permissions:
  contents: read
  pull-requests: write     # 只给需要的
  issues: none
原则:凭证按"本次任务最小需要"授予:构建只需读代码就不给写制品库;
  不同环境用不同角色(staging 角色碰不到生产资源)

3.3 网络与行为隔离

Runner 出网最小化:按需放行(拉依赖、推制品、调 API),其余拦截
禁止 Runner 有"通用网络通路":出网要审计、入网要拒绝
高风险构建(跑第三方代码)与生产部署构建分开池

4. 签名与 SBOM 门禁

4.1 产物签名门禁

流水线产出必须签名(cosign):构建后签名 → 部署前验签 → 准入控制强制
没有有效签名的产物 = 来源不明,禁止进入下一阶段
cosign sign --key cosign.key registry:5000/shop/api:v1.2.3
cosign verify --key cosign.pub registry:5000/shop/api:v1.2.3

4.2 SBOM 门禁

每个产物必须生成 SBOM 并扫描通过才能进发布:
  syft 生成 → grype 扫描 → 新引入高危漏洞拦截
SBOM 缺失或扫描失败 → 流水线红灯

4.3 依赖来源校验

# 只允许从可信源拉依赖/镜像(示例策略)
allowed_sources:
  registries: [registry:5000, registry.npmjs.org]
  forbid_scripts: true      # 禁止第三方安装脚本自动执行
  lockfile_required: true
对第三方 Action/工具:固定到 commit SHA 而非 tag(防 tag 被改写)

5. 硬编码凭证治理:Gitleaks

5.1 为什么用 Gitleaks

# 快速、可配置、能当 CI 门禁的 secret 扫描器
gitleaks detect --source . --exit-code 1 --report-format sarif --report-path gitleaks.sarif
# 扫描历史(不留存量尾巴)
gitleaks detect --source . --log-opts="--all"
能识别:云密钥、API key、私钥、token 等 100+ 种模式;
  自定义正则覆盖内部系统专用密钥格式

5.2 扫进 CI 的三种位置

PR 时扫:新提交引入 secret → 阻止合并(最快反馈)
合并前扫:全量 diff 检查,防止漏网
定期全仓扫:扫历史与分支,清除存量泄露
# GitHub Actions PR 门禁
steps:
  - uses: actions/checkout@v4
  - run: gitleaks detect --exit-code 1

5.3 发现后的处置流程

发现即"假设已泄露":立刻轮转/吊销,不只"删掉代码"
流程:定位 → 轮转 → 清理历史 → 分析泄露面(是否进入制品/镜像/日志)→ 通知相关方

6. 审计与检测

6.1 审计日志

流水线全量审计:谁触发/哪个 Runner/什么凭证/产物 hash;三处日志对齐(平台+系统+凭证访问)
审计日志不可篡改,接入 SIEM 留存 >180 天

6.2 行为检测

对"异常行为"告警而非只对"已知攻击":陌生 Runner/触发者/仓库、凭证被异常 IP 使用、
  产物 hash 与声明不符、深夜非计划触发;用过去 N 周基线做异常检测

6.3 合规基线

基线落地检查:无静态凭证 / OIDC+动态 secret / Runner 分级隔离 / 权限最小化 / 签名+SBOM 门禁 / secret 扫描全覆盖
定期自查 + 外部审计,把流水线当"生产系统"对待

7. 事件响应

7.1 疑似攻破的处理

发现异常(可疑 PR/凭证外泄/产物被改):立即隔离(暂停流水线/Runner、冻结凭证)
  → 取证(保留日志/产物/镜像勿清理)→ 评估攻击者触达范围

7.2 应急止血清单

□ 轮转所有受影响凭证(不确定就全轮转);下线可疑 Runner 重建环境
□ 回滚/下线被投毒产物,验签线上镜像;按 SBOM 定位受影响版本
□ 通知利益相关方,按预案沟通

7.3 复盘与加固

复盘三问:怎么进来的/为什么没拦住/下次怎么拦住 → 落成新门禁
供应链是持续对抗,避免只修一个洞就不管了

8. 供应链防御纵深

8.1 纵深分层

入口层:依赖白名单 + 锁文件 + 固定 SHA;构建层:Runner 隔离 + 最小权限 + 短时凭证
产物层:签名 + SBOM + 扫描门禁;部署层:准入验签 + 部署后监控
每一层独立失效也要能兜住——攻击者要穿透全部才有机会

8.2 工具链组合

能力工具
secret 扫描Gitleaks
依赖/SBOMSyft + Grype + osv-scanner
签名cosign
凭证动态化Vault / OIDC 联邦
准入控制Kyverno/Gatekeeper

8.3 持续改进节奏

季度重扫全量 secret + 权限评审 + 基线自查;事件后必复盘沉淀门禁
发布前门禁清单过一遍(签名/SBOM/扫描/最小权限)

9. 案例与最佳实践

9.1 真实案例

某 SaaS 公司(概念案例):PR 中第三方依赖被投毒,构建时窃取挂着的云密钥,攻击者拿到生产部署权限并篡改镜像
  修复:凭证改 OIDC 短时令牌 + Vault 动态 Secret;Runner 分级隔离;Gitleaks 扫 PR + 全仓历史;
      cosign 签名 + 部署验签;SBOM 门禁拦截新引入高危依赖
  结果:泄露影响面从"全部生产"缩小到"单个 15 分钟令牌",半年零复发

9.2 最佳实践 Checklist

□ 生产凭证无静态:OIDC 短时令牌 + Vault 动态 Secret
□ Runner 分级隔离,不可信代码跑沙箱,出网最小化
□ 流水线权限最小化声明,按环境分角色
□ 产物全部 cosign 签名,部署准入验签
□ SBOM 生成 + 扫描进发布门禁,新漏洞拦截
□ 第三方工具固定 commit SHA,依赖来源白名单;Gitleaks 扫 PR + 全仓历史,发现即轮转
□ 审计日志不可篡改,异常行为告警
□ 事件响应有预案,攻破后按止血清单处置并复盘

9.3 常见坑

坑现象对策
静态密钥挂流水线一次泄露全盘皆输OIDC + Vault 动态化
Runner 全权共用一个任务波及全局分级隔离 + 最小权限
只扫新代码存量 secret 漏网全仓历史扫描 + 轮转
tag 引用第三方工具tag 被改写即投毒固定 commit SHA
签名只签不验部署环节被绕过准入控制部署时验签
发现 secret 只删代码泄露持续可利用立即轮转,假设已泄露
攻破后不复盘同一条船翻两次复盘沉淀新门禁

小结

CI/CD 流水线安全 = 攻击面认知(RCE/提权/投毒)→ 凭证治理(OIDC 短时令牌 + Vault 动态 Secret)→ Runner 隔离与最小权限 → 产物签名 + SBOM 门禁 → Gitleaks 硬编码凭证扫描 → 审计与异常检测 → 事件响应与复盘。核心原则:“流水线里的信任必须被验证,凭证必须短命且最小,任何一次泄露都按’已泄露’处置”。先把静态凭证换掉、把 secret 扫描进 PR 门禁、给产物加上签名与验签,这三件事做扎实,就能挡住绝大多数供应链攻击;再逐步补全 Runner 隔离、审计与事件响应,把流水线当成生产系统持续加固。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「DevOps」更多文章

  1. 多云与混合云工程:成本、身份与统一编排
  2. AI 辅助运维:GenAI 在事件响应与排障中的实践
  3. 开发者体验与内部开发者门户:平台工程落地