阶段二 · 密钥与供应链安全

供应链安全

一句话总结

供应链安全确保"部署的东西确实是构建出来的东西", 没有被篡改, 没有被注入恶意代码, 来源可追溯.
通过 SBOM 记录成分, Cosign 签名防篡改, SLSA 框架量化供应链安全等级.

供应链攻击案例

2020-2024 年发生的真实供应链攻击:

事件 攻击方式 影响
SolarWinds(2020) 攻击者入侵 CI 系统, 在构建过程中注入后门 18,000+ 企业和政府机构
Codecov(2021) 篡改 CI 脚本, 窃取环境变量中的密钥 数百企业的 CI/CD 密钥泄露
ua-parser-js(2021) npm 包被注入加密货币挖矿代码 每周 800 万下载量的包被投毒
Log4Shell(2021) Log4j 库本身的漏洞 数十亿 Java 应用受影响

共同模式: 攻击者不直接攻击目标, 而是攻击目标的依赖, 工具链或构建系统.

传统安全 = 检查送到门口的包裹.
供应链安全 = 追溯包裹从工厂到快递到家门口的每一个环节, 是否被拆过, 是否被替换, 是否来自合法工厂.

SBOM(软件物料清单)

SBOM(Software Bill of Materials)列出软件中包含的所有组件及其版本, 类似食品的配料表.

为什么需要 SBOM

  • 漏洞响应: Log4Shell 公布后, 有 SBOM 的团队 10 分钟内就知道哪些服务受影响; 没有的要花几天排查
  • 合规要求: 美国行政令 14028 要求向联邦政府供应的软件必须提供 SBOM
  • 许可证管理: 确保没有使用与商业许可冲突的 GPL 组件

生成 SBOM

# 用 Syft 生成 SBOM(支持 SPDX 和 CycloneDX 格式)
$ syft myapp:v1.0 -o spdx-json > sbom.spdx.json
$ syft myapp:v1.0 -o cyclonedx-json > sbom.cdx.json

# 用 Trivy 同时生成 SBOM + 漏洞报告
$ trivy image --format spdx-json myapp:v1.0 > sbom.json

# CI 中: 构建后自动生成并附加到镜像
$ syft packages myapp:${GIT_SHA} -o spdx-json | cosign attest --predicate - myapp:${GIT_SHA}

镜像签名(Cosign / Sigstore)

签名证明"这个镜像确实是我(或我的 CI)构建的", 防止中间人篡改, 防止部署来源不明的镜像.

Sigstore 生态

组件 职责
Cosign 签名和验证容器镜像/SBOM
Fulcio 短期证书颁发(无需管理长期密钥)
Rekor 透明日志(签名不可否认, 可公开审计)

Cosign 使用

# 生成密钥对(传统方式)
$ cosign generate-key-pair

# 签名镜像
$ cosign sign --key cosign.key myregistry.io/myapp:v1.0

# 验证签名
$ cosign verify --key cosign.pub myregistry.io/myapp:v1.0

# Keyless 签名(推荐: 使用 OIDC 身份, 无需管理密钥)
$ cosign sign myregistry.io/myapp:v1.0
# 自动通过 Fulcio 获取短期证书, 签名记录到 Rekor 透明日志

# 在 CI 中自动签名
$ cosign sign --yes myregistry.io/myapp:${GIT_SHA}

Keyless 签名是最佳实践

传统签名需要管理私钥, 私钥本身又成了需要保护的 Secret. Keyless 模式通过 OIDC(GitHub Actions / Google Workload Identity)认证身份, Fulcio 签发短期证书(10 分钟有效), 签名记录到 Rekor 公开透明日志.

不需要管理任何长期密钥.

集群内验证镜像签名

配合准入控制器(Kyverno/Connaisseur)拒绝未签名或签名无效的镜像:

# Kyverno 策略: 只允许已签名的镜像
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["myregistry.io/*"]
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/myorg/*"

SLSA 框架

SLSA(Supply-chain Levels for Software Artifacts, 读作"salsa")是 Google 提出的供应链安全成熟度框架:

等级 要求 防御的攻击
SLSA 1 构建过程有文档记录(provenance) 知道构建来源
SLSA 2 构建使用版本控制 + 可信构建平台 防止开发者本机被入侵后注入恶意代码
SLSA 3 构建平台不可被单人篡改 + 来源证明可验证 防止 CI 被入侵(SolarWinds 级别)
SLSA 4 双人审查 + 密封构建 + 完整依赖声明 防止内部恶意行为

来源证明(Provenance)

SLSA 的核心产出物, 记录"谁, 在哪里, 用什么源码, 什么时间构建了这个制品":

{
"buildType": "https://github.com/slsa-framework/slsa-github-generator",
"builder": {
"id": "https://github.com/actions/runner"
},
"invocation": {
"configSource": {
"uri": "git+https://github.com/myorg/myapp@refs/heads/main",
"digest": {"sha1": "abc123..."},
"entryPoint": ".github/workflows/build.yml"
}
},
"materials": [
{"uri": "pkg:golang/github.com/gin-gonic/gin@v1.9.1"}
]
}
# GitHub Actions 中生成 SLSA provenance
# 使用 slsa-framework/slsa-github-generator
- uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1
with:
image: myregistry.io/myapp
digest: ${{ steps.build.outputs.digest }}

供应链安全最佳实践

  • 固定依赖版本: go.sum / package-lock.json / poetry.lock, 确保构建可复现
  • 定期更新依赖: Dependabot / Renovate 自动创建升级 PR
  • 私有镜像仓库: 不从公共 Docker Hub 直接拉取生产镜像
  • 签名所有生产镜像: CI 构建后 Cosign 签名 + 集群内验证
  • 生成 SBOM: 与镜像一起存储, 漏洞响应时能快速定位
  • 最小化构建环境: CI runner 最小权限, 构建完成后销毁环境

快速回顾

  • 供应链攻击目标是依赖, 工具链和构建系统, 而非直接攻击目标
  • SBOM: 软件配料表, 漏洞响应时快速定位受影响组件
  • Cosign: 镜像签名防篡改, Keyless 模式无需管理密钥
  • SLSA: 供应链安全成熟度框架(1-4 级), 来源证明(Provenance)是核心产出
  • 准入验证: Kyverno 拒绝未签名镜像, 确保只有可信来源的镜像能部署

动手练习

  1. 生成 SBOM: 用 Syft 为一个 Docker 镜像生成 SBOM(SPDX 格式), 检查其中包含的依赖列表
  2. Cosign 签名验证: 用 Cosign 为一个测试镜像签名(先用密钥对模式), 然后验证签名
  3. 准入策略拦截: 配置 Kyverno 策略要求镜像必须有 Cosign 签名才能部署, 尝试部署未签名镜像验证被拒绝
  4. SLSA provenance: 查看 GitHub Actions 的 SLSA generator, 理解 provenance 文件的结构