跳过正文
SLSA 软件供应链等级实施:从 L1 到 L3 的工程化路径

SLSA 软件供应链等级实施:从 L1 到 L3 的工程化路径

·1394 字·7 分钟·
目录

为什么要谈 SLSA
#

SolarWinds 之后,安全团队关心的问题从"代码安不安全"变成了"交付物到底是不是我写的代码"。构建系统本身被污染的话,你签出来的二进制照样是"合法"的——SolarWinds 就是这么被搞的。

SLSA(读作 salsa)是 Google 2021 年发起、后来转给 OpenSSF 的一套供应链安全等级框架。不是工具,是评估标准。v1.0 2023 年发布,到 2025 年已经被云厂商、Linux 发行版和企业安全团队广泛采用。

这篇是我们落地 SLSA 的经验,也是整个零信任系列的收尾。我会把 Build Track L1→L3 的实际工程路径一条条讲清楚,并把前面九篇里的 Sigstore、Cosign、Kyverno、SBOM 串起来。

一、SLSA v1.0 框架速览
#

1.1 Tracks 的概念
#

SLSA v1.0 最大的变化是引入了 “Tracks":把原来一套笼统的 Level 拆成多个独立的纬度。目前定义了:

  • Build Track:构建过程的完整性(最核心,优先落地)
  • Source Track:源码管理的完整性(v1.1 草案中)
  • Dependencies Track:依赖审核(规划中)

目前绝大部分生产实现只关注 Build Track,这篇文章也主要讲 Build Track。

1.2 Build Track 的等级
#

Level简述核心要求
L0无保障没有任何供应链信号
L1有 provenance构建过程输出 provenance,说明"我是怎么构建的”
L2托管构建服务构建在受信任的托管服务上,provenance 由构建服务签名
L3隔离与可验证构建作业之间相互隔离,provenance 不可伪造
L4规划中(v1.0 未定义,v1.1 草案)

核心概念是 provenance——一份描述"这个制品是怎么来的"的结构化声明,格式是 in-toto 的 SLSA Provenance Predicate。典型字段:

  • buildType: 用什么构建工具和流程
  • builder.id: 谁在构建(比如 GitHub Actions 的 workflow ref)
  • invocation.configSource: 源码 commit hash 和仓库 URL
  • invocation.parameters: 构建参数
  • materials: 所有输入(依赖包、base image 等)
  • buildStartedOn / buildFinishedOn

有了 provenance,一个可信的消费者可以验证:“这个镜像确实是从我们的 main 分支 commit abc123 通过 build.yml workflow 构建的,构建时间是 X,输入依赖是 Y。” 任何一步被篡改都会被发现。

1.3 Provenance 不等于签名
#

这是初学者最容易混淆的点。签名(Cosign)证明"这个制品被某人签名过",provenance 证明"这个制品是怎么来的"。两者是互补关系:

  • 签名只说明"来源可信"
  • Provenance 说明"来源可信 + 过程透明"

SLSA 要求 provenance 本身被签名(通常用 Sigstore/DSSE 格式),形成"可验证的构建声明"。所以 SLSA 实施通常是 Provenance + Sigstore 组合,不是二选一。

二、SLSA L1:最基础的 provenance 生成
#

L1 的要求最简单:构建过程输出 provenance,provenance 至少描述基本信息。允许 provenance 由构建脚本自己生成、不强制签名、允许 provenance 伪造。

这个级别的价值主要是"让团队习惯 provenance 的存在",为 L2/L3 打基础。

2.1 手写 L1 provenance
#

一个最简单的 L1 provenance 示例(JSON):

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "ghcr.io/myorg/myapp",
      "digest": {
        "sha256": "abc123...."
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://github.com/actions/workflow/v1",
      "externalParameters": {
        "workflow": {
          "ref": "refs/heads/main",
          "repository": "https://github.com/myorg/myrepo",
          "path": ".github/workflows/build.yml"
        }
      },
      "resolvedDependencies": [
        {
          "uri": "git+https://github.com/myorg/myrepo",
          "digest": { "gitCommit": "abcdef1234" }
        }
      ]
    },
    "runDetails": {
      "builder": {
        "id": "https://github.com/actions/runner"
      },
      "metadata": {
        "invocationId": "1234567",
        "startedOn": "2025-10-15T08:00:00Z",
        "finishedOn": "2025-10-15T08:05:00Z"
      }
    }
  }
}

这样的 JSON 可以用 cosign attest 挂到镜像上:

cosign attest --predicate provenance.json \
              --type slsaprovenance1 \
              ghcr.io/myorg/myapp@sha256:abc123...

L1 的局限:因为构建脚本自己写 provenance,攻击者能伪造任何内容。例如攻击者构建一个恶意镜像,然后写一份假 provenance 声称自己来自 main 分支。

要防这种伪造必须升级到 L2。

三、SLSA L2:可信的构建服务
#

L2 要求:

  1. 使用托管构建服务(GitHub Actions、GitLab CI、Cloud Build、Tekton 等)
  2. Provenance 由构建服务自身生成(不是用户的构建脚本)
  3. Provenance 被构建服务签名
  4. Source 和 build 服务提供"来自何处"的验证

GitHub 在 Actions 里原生支持生成 SLSA L2 provenance(通过 slsa-github-generator)。关键是生成器运行在 GitHub 的 reusable workflow 里,构建脚本本身不能污染它。

3.1 GitHub Actions L2 实现
#

name: ci

on:
  push:
    tags: [ 'v*' ]

permissions: {}

jobs:
  build:
    permissions:
      id-token: write
      contents: read
      packages: write
    runs-on: ubuntu-22.04
    outputs:
      image: ${{ steps.meta.outputs.image }}
      digest: ${{ steps.push.outputs.digest }}
    steps:
      - uses: actions/checkout@v4

      - id: meta
        run: |
          echo "image=ghcr.io/${{ github.repository }}" >> "$GITHUB_OUTPUT"

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.image }}:${{ github.sha }}
          provenance: false   # 用 SLSA generator 生成,而不是 buildx 自带

  provenance:
    needs: build
    permissions:
      id-token: write
      packages: write
      actions: read
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
    with:
      image: ${{ needs.build.outputs.image }}
      digest: ${{ needs.build.outputs.digest }}
      registry-username: ${{ github.actor }}
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

注意这里用的是 generator_container_slsa3.yml——GitHub Actions 官方的生成器其实能直接产出L3 级别的 provenance。它被实现成 reusable workflow,生成过程运行在一个独立的 ephemeral runner 上,和主 build job 隔离,这个隔离就是 L3 的关键。

流程:

  1. build job 负责构建和推镜像
  2. provenance job 调用官方 generator workflow,不执行用户的任何脚本
  3. generator 读取 build job 的 outputs(不可篡改)
  4. generator 生成 SLSA v1.0 provenance,用 Sigstore keyless 签名
  5. generator 把 provenance attestation 推到 registry

用户构建脚本无法影响 provenance 内容,这是 L3 级别的关键特性。

3.2 Tekton Chains 实现
#

如果你不用 GitHub Actions,Tekton Chains 是同等级别的 Tekton Pipeline 选项。

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: build-and-attest
spec:
  tasks:
    - name: build
      taskRef:
        name: kaniko
      params:
        - name: IMAGE
          value: ghcr.io/myorg/myapp

然后启用 Chains controller:

apiVersion: v1
kind: ConfigMap
metadata:
  name: chains-config
  namespace: tekton-chains
data:
  artifacts.oci.format: "slsa/v2"
  artifacts.oci.storage: "oci"
  artifacts.oci.signer: "x509"
  transparency.enabled: "true"
  transparency.url: "https://rekor.sigstore.dev"

Tekton Chains 会 watch 所有 PipelineRun,PipelineRun 结束后自动生成 provenance,签名后推到 OCI registry。和 GHA 相比,Tekton Chains 的优势是可以在私有集群跑,不依赖 GitHub 的托管 runner。

3.3 GitLab CI 实现
#

GitLab 17.0+ 原生支持 SLSA provenance 生成:

build:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - cosign attest --predicate provenance.json --type slsaprovenance1 \
                    $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  artifacts:
    reports:
      cyclonedx: sbom.json

GitLab 目前的 provenance 只是 L2 级别,没做到完整 L3 隔离。但对大多数场景够用。

四、SLSA L3:隔离和不可伪造
#

L3 在 L2 基础上增加两个硬要求:

  1. Build 过程隔离:不同构建任务之间不能互相影响(内存、磁盘、网络)
  2. Provenance 不可伪造:用户的构建脚本无法改变 provenance 内容

GitHub Actions 的官方 generator (slsa-github-generator) 达到 L3 的方式:

  • Provenance 生成在一个独立的 reusable workflow
  • 这个 workflow 使用 id-token: write + Sigstore keyless,拿到的 OIDC token 的 audiencesubject 字段由 GitHub 平台控制,用户脚本无法篡改
  • Provenance 的关键字段(commit hash、repo、workflow ref)从 GitHub 平台元数据读取,而不是 build step 的 output
  • 整个生成过程在一个独立 runner 上,构建 job 的磁盘/环境变量不会泄露进来

这些机制加起来使得"攻击者即便能污染 build step(比如安装一个恶意 npm 包),也无法伪造 provenance**"。

4.1 L3 验证流程
#

消费者拿到镜像后的验证流程:

slsa-verifier verify-image \
  ghcr.io/myorg/myapp@sha256:abc... \
  --source-uri github.com/myorg/myrepo \
  --source-tag v1.2.3

这条命令会:

  1. 从 registry 拉 provenance attestation
  2. 验证 Sigstore 签名(证书来自 Fulcio + 在 Rekor 里有记录)
  3. 验证 provenance 里的 builder.id 是官方 SLSA generator
  4. 验证 source-uri 符合传入的 repo
  5. 验证 source-tag 符合传入的 tag/commit
  6. 全部通过才退出 0

任何一步失败,slsa-verifier 返回非零退出码。可以直接嵌进 CI/CD 里作为"部署前门禁"。

4.2 Kyverno 集成 SLSA 验证
#

Kyverno 1.11+ 的 VerifyImages 直接支持 SLSA provenance 验证:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-slsa-provenance
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-slsa
      match:
        any: [{ resources: { kinds: [Pod], namespaces: ["prod-*"] }}]
      verifyImages:
        - imageReferences: ["ghcr.io/myorg/**"]
          attestors:
            - count: 1
              entries:
                - keyless:
                    subject: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.0.0"
                    issuer: https://token.actions.githubusercontent.com
          attestations:
            - type: https://slsa.dev/provenance/v1
              conditions:
                - all:
                    - key: "{{ buildDefinition.externalParameters.source.uri }}"
                      operator: Equals
                      value: "git+https://github.com/myorg/myrepo"
                    - key: "{{ buildDefinition.externalParameters.source.ref }}"
                      operator: AnyIn
                      value: ["refs/heads/main", "refs/heads/release/*"]

这条策略强制 prod-* namespace 里的所有镜像必须有来自 SLSA generator workflow 签名的 provenance,且 source 必须是我们自己的 repo 的 main 或 release 分支。任何 PR 分支、fork repo 构建的镜像都过不了。

注意 subject 里的 refs/tags/v2.0.0:这锁定了用的是官方 generator 的哪个版本。升级 generator 版本时要同步更新策略。

五、生产落地路线
#

SLSA 落地不是一次性工程,是循序渐进的过程。

5.1 评估当前等级
#

很可能你现在处于 L0 或 L1。评估 checklist:

  • 有没有受信任的托管构建系统?(GH Actions / GitLab / Tekton)
  • 有没有在构建时生成某种 provenance?
  • Provenance 是不是被签名过?
  • Provenance 能不能被 build step 伪造?
  • 消费者是不是真的在部署前验证?

能全部打勾是 L3。其他情况请对照前面章节补上缺失的部分。

5.2 按项目优先级推进
#

不是所有项目都需要 L3。我们的实践:

类别目标等级
第三方依赖 / base imageL3 (选有 SLSA 的 upstream)
生产核心服务(支付/登录/数据)L3
生产一般服务L2
内部工具 / stagingL1
临时实验L0 (不要求)

核心原则:优先保护"攻破代价最大"的制品

5.3 与前九篇的整合
#

这是整个零信任系列的总结图:

                 ┌─────────────────────┐
                 │      开发者          │
                 └──────────┬──────────┘
                            │ commit
                            ▼
                 ┌─────────────────────┐
                 │   源码仓库           │
                 │   (SLSA Source v1.1) │
                 └──────────┬──────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────┐
│              CI/CD 流水线 (SLSA Build L3)             │
│                                                     │
│  构建 ─▶ 扫描 (Trivy) ─▶ SBOM (Syft)                 │
│    │                         │                     │
│    ▼                         ▼                     │
│  签名 (Cosign keyless)     Dependency-Track        │
│    │                                                │
│    ▼                                                │
│  Provenance (SLSA generator, Sigstore 签名)        │
│    │                                                │
└────┼────────────────────────────────────────────────┘
     │
     ▼ push
┌─────────────────────┐
│    OCI Registry     │
│  image + .sig + .att│
└──────────┬──────────┘
           │ pull
           ▼
┌─────────────────────────────────────────────────────┐
│            Kubernetes 集群                          │
│                                                     │
│  Admission:                                         │
│    ├── PSA (Pod Security)                           │
│    ├── Kyverno (policy + verify signatures/SLSA)    │
│    └── Kyverno (verify SBOM/vuln attestation)       │
│                                                     │
│  Runtime:                                           │
│    ├── SPIRE (workload identity)                    │
│    ├── Cilium (network policy + L7)                 │
│    ├── Falco (runtime detection)                    │
│    └── Secret Rotation (Vault / SM / SOPS)          │
│                                                     │
│  Edge:                                              │
│    └── WireGuard Mesh (跨云互联)                     │
└─────────────────────────────────────────────────────┘

每一层有专门的工具:

  1. 构建层:SLSA provenance + Cosign 签名 + Syft SBOM + Trivy 漏洞
  2. 准入层:PSA + Kyverno + VerifyImages(签名 + provenance + SBOM 条件)
  3. 运行时层:Falco 检测 + Cilium 网络 + SPIRE 身份
  4. 密钥层:Vault/SM/SOPS 动态和轮换管理
  5. 网络层:WireGuard mesh VPN 跨域连接

缺一不可:这些工具没有单一能覆盖整个供应链,只有组合起来才能形成完整防御。SLSA 提供的是整体框架,告诉你"怎么衡量自己到哪一层"。

六、踩坑记录
#

6.1 GHA generator 版本升级破坏策略
#

我们把 slsa-github-generator 从 v1.10 升级到 v2.0,Kyverno policy 里的 subject 字段是 refs/tags/v1.10.0,升级后的 provenance subject 变成了 v2.0.0,policy 直接拒绝所有新镜像。

修复:

  1. 升级 generator 时先更新 Kyverno policy,再触发新构建
  2. 或者用 subjectRegExp 匹配多个版本:
    subjectRegExp: "^https://github.com/slsa-framework/slsa-github-generator/.*@refs/tags/v[12]\\..*$"
    

6.2 Provenance 里 source commit 不是预期的
#

有次我们发现 provenance 里的 resolvedDependencies 里 commit hash 和 UI 上看到的不一致。根因是 actions/checkout 的 fetch-depth 默认是 1,checkout 后的 HEAD 是一个合并的临时 commit,不是真实 main 分支的 commit。

修复:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0
    ref: ${{ github.sha }}

显式 checkout 真实 commit。

6.3 Rekor 不可用导致验证失败
#

前面 Sigstore 那篇也提过这个坑。SLSA 验证依赖 Rekor 的 inclusion proof,Rekor 公共实例偶发抽风,会让所有部署卡住。

修复:

  • 生产用私有 Sigstore 实例(Fulcio + Rekor)
  • 或者用 Sigstore 2.2+ 的 offline bundle 机制:
    cosign sign --new-bundle-format ...
    cosign verify --offline ...
    

6.4 L3 generator 性能问题
#

slsa-github-generator 本身是一个独立 job,会多花 1~2 分钟。对于高频构建的单仓库,每天多加半小时的 CI 时间。

优化:

  • 只在 tag push 时生成 L3 provenance,push main 只生成 L1/L2
  • 用 reusable workflow 的 concurrency group 避免重复触发
  • 接受这个开销——L3 的安全收益远超 1~2 分钟的成本

6.5 多 arch 镜像 provenance 复杂性
#

docker buildx 构建 multi-arch 镜像时会有一个 manifest list + N 个 arch-specific manifest。provenance 应该挂在哪个上?

  • 方案 A:挂在每个 arch manifest 上,消费者按 arch 验证
  • 方案 B:挂在 manifest list 上,但有些工具不支持

slsa-github-generator v2.0 的做法是挂在 manifest list(OCI Index)上,然后在 provenance 的 subject 里列出所有 arch digest。Kyverno 和 slsa-verifier 都支持这种模式,其他工具要确认一下。

七、衡量进度:SLSA 指标
#

SLSA 落地后怎么衡量?几个关键指标:

# provenance 覆盖率
total_images / images_with_valid_provenance

# L3 覆盖率
total_images / images_with_l3_provenance

# 验证失败率
kyverno_verifyimages_failures_total

# 部署前验证成功率
slsa_verifier_success_total / slsa_verifier_total

做成 Grafana 仪表盘,每周团队 review。指标的意义是"让这件事可见"——不能被度量的东西,就不会被改进。

八、未来方向:SLSA v1.1 和 Source Track
#

SLSA v1.1 的草案里有两个重要方向:

  1. Source Track:衡量源码管理的完整性,包括"commit 是不是经过 review"、“强制 2FA”、“commit 历史不可篡改"等
  2. Verification Summary Attestation (VSA):让消费者信任上游的验证结果。比如你信任 Google distroless 的团队,就可以信任他们发布的 VSA 而不自己验证每个 provenance

2025 年这些都还是草案阶段,但值得关注。一旦成熟,供应链安全的标准会变得更完整。

九、实战建议
#

最后几条总结性建议:

1. 不要追求完美。先从 L1 开始,让团队习惯 provenance 的存在。L3 是远期目标,先有东西比完美重要。

2. 选择官方 generator。不要自己实现 SLSA generator,你写的肯定做不到 L3 的隔离保证。GitHub Actions 用 slsa-github-generator,Tekton 用 Chains,GitLab 用原生。

3. 把验证做在多个位置。部署前验证、运行时验证、审计时验证。单点验证容易被绕过。

4. 建立"白名单” generator 策略。你的 Kyverno 策略里只接受官方 generator 签名的 provenance。私有的构建工具,写明显的豁免机制并严格 review。

5. 关注上游供应链。Base image (distroless/chainguard)、语言包管理器 (npm/pypi/maven)、基础依赖 (openssl/libcurl) 都有各自的 SLSA 进展。选有 SLSA 的上游比自己搞更有效。

6. 不要只盯技术。SLSA 涉及工程、安全、开发、运维多角色协作。技术实施只是 30%,剩下 70% 是流程和文化。

十、整个零信任系列的收尾
#

这是系列最后一篇。十篇里用到的工具:Falco(运行时)、SPIRE(身份)、Sigstore(制品)、Dependency-Track(依赖)、Cilium(网络)、WireGuard(加密通道)、Vault/SM/SOPS(密钥)、PSA+Kyverno(准入)、SLSA(供应链框架)。没有一个工具能单独搞定零信任,它就是个拼图。

我们走完这条路最大的体感是:一旦这套东西跑起来,你再也不相信"内网就是安全的"、“管理员手改一下就行”、“明文密码存 YAML 临时用一下"这种话。每次调用都要验身份、每个镜像都有 provenance、每条网络流量都有策略——习惯之后再看老环境,真的像远古。

零信任不是 slogan,也不是某个产品,是一堆把"默认不信任、持续验证、最小权限"固化成工程实践的苦活。走完这条路值。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

Sigstore/Cosign 镜像签名实战:从 keyless 签名到准入策略验证

·1658 字·8 分钟
一份 Sigstore 生产化落地笔记:讲清楚 Fulcio/Rekor/Cosign 三件套的工作原理,演示 GitHub Actions 和 GitLab CI 下的 keyless 签名流水线,对接 Kyverno/Policy Controller 做准入验证,并分享签名验证性能、Rekor 不可用降级、多签策略等真实运维经验。

SBOM 生成与 Dependency-Track 漏洞管理实战

·1422 字·7 分钟
一份基于生产环境的 SBOM 实战指南:讲清楚 CycloneDX 与 SPDX 的格式差异、Syft/cdxgen/Trivy 三款主流生成器的对比,部署 Dependency-Track 4.12 做持续漏洞监测,通过策略违规自动化处置 CVE,并分享 SBOM 消费链路上的真实踩坑。

DevSecOps 安全左移实践:从代码到生产的全链路安全

·1864 字·9 分钟
安全不是最后一道关卡,而是嵌入每个研发环节的连续过程。本文从代码静态分析、依赖漏洞扫描、镜像安全、K8s 运行时防护到供应链签名,逐层拆解 DevSecOps 的完整实施路径,并给出一个可落地的流水线设计。