musaumakau/supply-chain-security
GitHub: musaumakau/supply-chain-security
基于 GitHub Actions、Sigstore 和 Kubernetes 准入控制构建的生产级软件供应链安全管线,在准入层强制验证镜像签名、SBOM 和 SLSA 来源证明,阻止未签名镜像运行。
Stars: 0 | Forks: 0
# 软件供应链安全
一个构建在 GitHub Actions、Sigstore 和 Kubernetes 准入控制之上的生产级软件供应链安全 pipeline。每个到达集群的镜像都经过了扫描、签名、证明和验证——所有这些均在准入层强制执行。
本仓库实现并记录了两个独立的强制执行引擎:**Kyverno**(原生验证签名/证明)和 **Gatekeeper + Ratify**(Gatekeeper 通过外部数据提供者模式将验证委托给 Ratify)。在实际部署中,它们并不打算同时运行——请选择其一。本仓库记录这两个引擎是因为,比较同一个策略在两个引擎上的实现是一项有益的练习,而且各自的调试过程揭示了一些不同的、不明显的失败模式,值得记录下来。
## 本项目的作用
每次向 `main` 分支的推送都会触发一个 pipeline,该 pipeline 会:
1. 基于锁定的 Debian Bookworm 基础镜像构建一个多阶段 Docker 镜像
2. 使用 Trivy 扫描镜像中的严重漏洞
3. 使用 Cosign 无密钥签名对镜像 digest 进行签名(不存储任何私钥)
4. 使用 Syft 生成 SPDX SBOM,并将其作为 Cosign 证明附加
5. 生成 SLSA 来源断言,并将其作为 Cosign 证明附加
6. 在 pipeline 完成之前验证所有三个证明
7. 在 Kubernetes 中阻止任何未签名或未证明的镜像运行,在准入时强制执行
每个 PR 在进行任何构建之前,都必须通过 Semgrep SAST 和 Trivy 文件系统扫描的检查。
## 信任链
```
Developer opens PR
|
v
pr-check.yml
-- Semgrep SAST (workflows, Dockerfile, manifests, source)
-- Trivy filesystem scan (deps, secrets, misconfigs)
|
[PR blocked if findings]
|
v
PR merged to main
|
v
deploy.yml
-- build-push.yml
-- docker buildx build (python:3.12-slim-bookworm, multi-stage)
-- push to Docker Hub by SHA tag only (no :latest)
-- Trivy image scan post-push (CRITICAL, exit 1)
-- SARIF uploaded to GitHub Security tab
|
-- sign-attest.yml
-- Cosign keyless sign via Sigstore OIDC
-- GitHub Actions OIDC token -> Fulcio CA -> short-lived cert
-- Cert subject: sign-attest.yml@refs/heads/main
-- Signature + cert uploaded to Rekor transparency log
-- Syft generates SPDX JSON SBOM
-- Attached as Cosign OCI attestation (type: spdxjson)
-- Uploaded as GitHub Actions artifact (90-day retention)
-- SLSA provenance predicate generated
-- Embedded: repo URI, commit SHA, workflow, ref, builder ID
-- Attached as Cosign OCI attestation (type: slsaprovenance)
|
-- verify.yml
-- cosign verify (signature + Rekor log)
-- cosign verify-attestation (SBOM)
-- cosign verify-attestation (provenance)
-- Verifies: entryPoint, builder ID, source repo URI
|
v
Kubernetes admission (Kyverno ClusterPolicy, or Gatekeeper + Ratify -- see below)
-- valid Cosign keyless signature (verifyDigest / digest-pinned)
-- valid SBOM attestation
-- valid SLSA provenance attestation
-- Conditions: entryPoint, builder ID, source repo URI
[Pod blocked if any check fails]
```
不存储任何私钥。签名身份派生自 GitHub Actions OIDC token 并固定到 Sigstore 公共透明度日志(Rekor)。证书有效期约为 10 分钟——持久的证明存储在 Rekor 中。
## 仓库结构
```
.
├── app/
│ ├── main.py # FastAPI application (3 endpoints)
│ └── requirements.txt # Pinned Python dependencies
├── Dockerfile # Multi-stage, non-root, Bookworm-pinned
├── .trivyignore # Documented CVE suppressions with justification
├── .github/
│ ├── dependabot.yml # Weekly SHA-pin updates for Actions
│ ├── workflows/
│ │ ├── pr-check.yml # Triggered on pull_request -- scan only
│ │ ├── deploy.yml # Triggered on push to main -- build + sign + verify
│ │ ├── build-push.yml # Reusable: build, push, Trivy image scan
│ │ ├── sign-attest.yml # Reusable: Cosign sign, Syft SBOM, SLSA provenance
│ │ ├── sbom-vex.yml # Reusable: SBOM + VEX generation, non-blocking
│ │ ├── security-scan.yml # Reusable: Semgrep SAST, Trivy filesystem
│ │ └── verify.yml # Reusable: verify signature + attestations
│ └── actions/
│ ├── docker-login/ # Composite: Docker Hub login
│ ├── setup-cosign/ # Composite: install Cosign
│ └── setup-syft/ # Composite: install Syft
├── policy/
│ ├── kyverno/
│ │ └── block-unsigned-images.yaml # ClusterPolicy: 3 rules, Enforce mode
│ └── gatekeeper/
│ ├── constrainttemplate.yaml # OPA Rego, calls Ratify via external_data
│ ├── constraint.yaml # K8sRequireSignedImages, namespace scope
│ ├── store-oras.yaml # Ratify Store CRD (ORAS, cosign-enabled)
│ ├── verifier-cosign.yaml # Ratify Verifier CRD (keyless trust policy)
│ ├── test-mixed-containers.yaml # Test: one signed + one unsigned container
│ └── test-init-unsigned.yaml # Test: unsigned initContainer
└── docs/
└── evidence/
├── deny-stage-violations.yaml # Live Constraint status during deny-stage testing
├── unsigned-rejected.txt # Raw admission rejection, unsigned image
├── tampered-rejected.txt # Raw admission rejection, tampered signature
├── mixed-containers-rejected.txt # Raw admission rejection, mixed containers
├── init-container-rejected.txt # Raw admission rejection, unsigned init container
├── excluded-namespace-allowed.txt # Proof excluded namespaces bypass enforcement
├── init-container-gap-fix.md # Before/after: the initContainer bypass bug
└── troubleshooting-notes.md # Every real bug hit standing up Gatekeeper+Ratify
```
## Pipeline 设计
### PR 门禁 (pr-check.yml)
在每个针对 `main` 的 pull request 上运行。不构建,不推送。在合并任何内容之前,对代码质量和漏洞提供快速反馈。
- Semgrep 使用 `--config auto` 扫描源代码、GitHub Actions 工作流、Dockerfile 和 Kubernetes manifests
- Trivy 扫描文件系统以检查依赖项 CVE、密钥和配置错误
- 两者都将 SARIF 上传到 GitHub Security 标签页
- 任何一个发现都会阻止合并
- `policy/gatekeeper/` 下的测试 manifests 带有明确的 `securityContext`(`allowPrivilegeEscalation: false`,`runAsNonRoot: true`),专门用于保持此门禁干净——这些 pod 的存在只是为了测试准入控制,但它们仍然受制于与生产 manifests 相同的 Semgrep 规则
### 部署 pipeline (deploy.yml)
仅在推送到 `main` 时运行。假定 PR 门禁已经通过。
按顺序调用三个可重用工作流:`build-push` -> `sign-attest` -> `verify`。每个 job 通过 `needs:` 依赖于前一个 job。镜像 digest 从 `build-push` 的输出流向 `sign-attest` 和 `verify` 的输入——没有 tag 引用,仅使用 digest。
### 为什么这种分离很重要
签名仅在 `main` 上发生。不会从功能分支生成已签名的镜像。两个强制执行引擎的身份检查(`sign-attest.yml@refs/heads/main`)都非常有意义,因为它们直接映射到唯一产生签名构件的触发器。
### 触发器范围界定
`deploy.yml` 和 `pr-check.yml` 都进行了路径过滤,而不是在每次推送或 PR 时都运行:
- `deploy.yml` 仅在 `app/**`、`Dockerfile` 或 `.dockerignore` 发生更改时触发——仅仅修改 README 或调整策略 YAML 不会重新构建、重新签名或重新推送镜像。同时启用了 `workflow_dispatch` 以便手动重新运行(例如,在 Sigstore 发生故障后重新签名,而无需进行一次无意义的代码更改)。
- `pr-check.yml` 在相同的应用程序路径上触发,此外还包括 `.github/workflows/**` 和 `.github/actions/**`——对 CI 本身的更改在合并前也会进行扫描,因为受到破坏或配置错误的工作流文件与受损的应用程序代码一样,都是供应链风险。
这可以防止 pipeline 在每次不相关的提交(文档、策略 YAML、证据文件)时重新签名和重新推送镜像,同时仍然会对任何触及构建、应用程序或 CI 定义本身的内容进行门禁检查。
### SBOM + VEX 生成(非阻塞)
`sbom-vex.yml` 与 `sign-attest` 并行运行,而不是对其进行门禁检查。这是有意为之:SBOM/VEX 的生成和分类很有价值,但如果速度缓慢或出现短暂失败,则不应阻止部署,而签名和证明验证是硬性门禁。有关当前强制执行的内容与已记录的内容,请参阅下方的 [VEX](#vex-planned-enforcement-layer-currently-triage-only)。
## 漏洞管理
Trivy 在每次部署时运行两次:
- **构建前 (security-scan.yml):** 对源代码仓库进行文件系统扫描——在构建镜像之前捕获依赖项 CVE
- **推送后 (build-push.yml):** 对推送的 digest 进行镜像扫描——捕获仅在镜像组装后才会出现的基础镜像 CVE
没有可用修复方案的严重 CVE 会在 `.trivyignore` 中被抑制,并为每个条目提供了书面理由。每次抑制都解释了为什么无法访问易受攻击的代码路径,或者为什么上游不存在修复方案。这是故意的——在生产 pipeline 中,没有理由的静默抑制是不可接受的。
### VEX(计划中的强制执行层,目前仅用于分类)
SBOM + 漏洞扫描会产生大量的发现结果,其中许多是误报,或者在此部署的上下文中不适用。VEX(Vulnerability Exploitability eXchange)语句在原始扫描的基础之上添加了上下文:
- **Not Affected** —— 此代码库中未调用易受攻击的函数
- **Affected** —— 受到影响,已采取缓解措施
- **Fixed** —— 在当前版本中已解决
- **Under Investigation** —— 正在进行分类
VEX 目前用作记录 `.trivyignore` 抑制理由的文档,尚未接入准入强制执行。自然的下一步是将 VEX 文档(OpenVEX 或 CSAF 格式)作为第四种 Cosign 证明类型附加,并像今天验证 SBOM 和来源一样对其进行验证——这被列为未来的工作。
## 强制执行,选项 A:Kyverno
`block-unsigned-images` ClusterPolicy 在 `Enforce` 模式下运行,并应用于除 `kube-system`、`kyverno`、`argocd`、`crossplane-system` 和 `cert-manager` 之外的所有 namespace。
在 Pod 被准入之前,必须全部通过以下三个规则:
**规则 1 -- verify-image-signature**
验证 Rekor 中是否存在针对该镜像 digest 的有效 Cosign 无密钥签名。检查证书 subject 和 OIDC issuer。`verifyDigest: true` 可防止 tag 替换攻击。
**规则 2 -- verify-sbom-attestation**
验证是否存在 `spdxjson` 类型的 Cosign 证明,并且是否由同一身份签名。强制执行 `verifyDigest: true`。
**规则 3 -- verify-provenance-attestation**
验证是否存在 `slsaprovenance` 类型的 Cosign 证明,并验证以下三个条件:
- `invocation.configSource.entryPoint` 匹配 `.github/workflows/sign-attest.yml`
- `builder.id` 匹配 `https://github.com/actions/runner`
- `invocation.configSource.uri` 匹配 `git+https://github.com/musaumakau/supply-chain-security@refs/heads/main`
第三个条件是关键——它可以防止接受从 fork 或不同仓库生成的来源。
**关于条件键的重要说明:** Kyverno 会对证明进行解码,并将 JMESPath 评估直接限定在 predicate 主体上。条件键应引用 `{{ invocation.configSource.entryPoint }}`,而不是 `{{ predicate.invocation.configSource.entryPoint }}`——一旦进入证明条件块,就不存在可供下降的顶层 `predicate` 键。使用 `predicate.` 前缀编写会产生 `JMESPath query failed: Unknown key "predicate" in path` 错误,并静默阻止每个 pod,无论其是否已签名。
## 强制执行,选项 B:Gatekeeper + Ratify
Gatekeeper 本身不验证签名。它在准入时通过 `external_data` Rego 内置函数向 **Ratify** 发出调用;Ratify 执行实际的 registry 查找和 Cosign 验证,并内联返回结构化的通过/失败结果。
### 资源链(按依赖顺序)
1. **`Store`** (`store-oras.yaml`) —— 告诉 Ratify 如何从 registry 获取签名/证明构件(ORAS store,`cosignEnabled: true`)
2. **`Verifier`** (`verifier-cosign.yaml`) —— 定义信任策略:要检查哪些 registry scopes,以及预期的无密钥证书身份 + OIDC issuer
3. **`ConstraintTemplate`** (`constrainttemplate.yaml`) —— 调用 Ratify 外部数据提供者并将失败的验证转换为 Gatekeeper 违规的 Rego 代码
4. **`Constraint`** (`constraint.yaml`) —— 将模板绑定到某个范围(`Pod`,除系统/平台 namespace 之外的所有 namespace)并设置强制执行阶段
### 发布阶段
`enforcementAction` 支持分阶段发布,每个阶段都针对真实的签名、未签名和被篡改的镜像进行了独立测试:
- **`dryrun`** —— 仅观察,在 `.status.violations` 中记录违规,不阻止任何操作
- **`warn`** —— pod 仍会创建,但准入响应会带有可见的警告
- **`deny`** —— 在准入时直接拒绝创建 pod
### 安装 Ratify
```
helm repo add ratify https://notaryproject.github.io/ratify
helm repo update
helm install ratify ratify/ratify \
--namespace gatekeeper-system \
--set policy.useRego=true \
--set featureFlags.RATIFY_CERT_ROTATION=true
```
本地/测试集群需要设置 `RATIFY_CERT_ROTATION=true`——如果没有它,chart 会要求你通过 `--set-file provider.tls.crt=... provider.tls.key=...` 为 Ratify 的 webhook 服务器提供自己的 TLS 证书,这对于生产环境是正确的做法,但对于演示集群来说是不必要的开销。
### 应用 CRD
顺序很重要——`Store` 必须在 `Verifier` 之前,并且必须先建立 `ConstraintTemplate`,其生成的 CRD 才能接受 `Constraint`:
```
kubectl apply -f policy/gatekeeper/store-oras.yaml
kubectl apply -f policy/gatekeeper/verifier-cosign.yaml
kubectl apply -f policy/gatekeeper/constrainttemplate.yaml
kubectl wait --for=condition=established \
crd/k8srequiresignedimages.constraints.gatekeeper.sh --timeout=30s
kubectl apply -f policy/gatekeeper/constraint.yaml
```
在测试之前,确认两个 Ratify 资源都报告为健康状态:
```
kubectl get stores.config.ratify.deislabs.io
kubectl get verifiers.config.ratify.deislabs.io
# 两者都应显示 ISSUCCESS: true
```
## 测试证据
上面的每一项主张都由针对活动集群运行的真实命令提供支持,而不仅仅是假定可以工作的策略 YAML。原始输出位于 `docs/evidence/` 中。
| 测试用例 | 预期结果 | 已验证 |
|---|---|---|
| 已签名的镜像 (`latest`) | 准入 | 是——在所有三个阶段均通过 |
| 未签名的镜像 (`unsigned-test`) | 阻止 | 是——`docs/evidence/unsigned-rejected.txt` |
| 被篡改的签名 (`tampered`) | 阻止 | 是——`docs/evidence/tampered-rejected.txt`,证明 Ratify 检查的是加密有效性,而不仅仅是签名的存在 |
| 排除的 namespace + 未签名的镜像 | 准入(遵守排除规则) | 是——`docs/evidence/excluded-namespace-allowed.txt` |
| 包含一个已签名 + 一个未签名容器的 Pod | 阻止 | 是——`docs/evidence/mixed-containers-rejected.txt`,确认会检查 pod spec 中的每个容器,而不仅仅是第一个 |
| 包含未签名 `initContainer` 的 Pod | 阻止(修复后——见下文) | 是——`docs/evidence/init-container-rejected.txt` |
### 通过边缘情况测试发现的一个真实漏洞:未签名的 init container
第一个版本的 Gatekeeper Rego 只读取了 `input.review.object.spec.containers`。放置在 `initContainers` 中的未签名镜像从未发送给 Ratify 进行验证,并顺利通过了准入——这是一个真实的绕过漏洞,因为 init container 拥有对 pod volumes 的完全访问权限,并且可以在已验证的主容器启动之前执行任意代码。
这是通过系统性地测试正常路径之外的情况发现的,而不是假设 `containers[]` 的覆盖范围是完整的。包含确切被拒绝然后被阻止的命令的完整前后对比证明位于 `docs/evidence/init-container-gap-fix.md` 中。修复方法扩展了 Rego,在构建发送给 Ratify 的镜像列表之前,将 `containers`、`initContainers` 和 `ephemeralContainers` 拼接在一起:
```
remote_data := response {
containers := object.get(input.review.object.spec, "containers", [])
init_containers := object.get(input.review.object.spec, "initContainers", [])
ephemeral_containers := object.get(input.review.object.spec, "ephemeralContainers", [])
all_containers := array.concat(array.concat(containers, init_containers), ephemeral_containers)
images := [c.image | c = all_containers[_]]
response := external_data({"provider": "ratify-provider", "keys": images})
}
```
## 故障排除
在搭建此系统的过程中遇到了实际问题,将其保留在此处,以便下一次调试会话(我自己的或任何人的)不必从零开始。完整详细信息请参见 `docs/evidence/troubleshooting-notes.md`。
1. **Ratify Helm 安装失败:“must provide a TLS certificate”**
修复:对于本地/测试集群,使用 `--set featureFlags.RATIFY_CERT_ROTATION=true`。
2. **即使修复了磁盘上的 YAML,`Verifier` 仍停留在 `CONFIG_INVALID` 状态**
`kubectl apply` 执行的是双向合并——即使文件包含某些字段,资源早期损坏版本中的陈旧字段也会持续存在。修复:使用 `kubectl delete` 然后 `kubectl apply`,而不是重复使用 `apply`。
3. **Cosign 信任策略字段是 `scopes`,而不是 `registryScopes`**
`registryScopes` 是 Notation 验证器的字段名称。在 Cosign `Verifier` 上使用它会产生 `CONFIG_INVALID: scopes parameter is required`。
4. **即使 Ratify 正确地拒绝了镜像,Gatekeeper 也报告 0 个违规**
Ratify 在 `remote_data.responses[].isSuccess` 下显示每个镜像的验证失败,而不是 `remote_data.errors`(该字段保留用于系统级故障,例如无法访问的 registry)。仅检查 `errors` 的 Rego 将静默通过每个镜像,而不管实际的验证结果如何。
5. **正确签名的镜像仍被拒绝:“none of the expected identities matched”**
`Verifier` 的 `certificateIdentity` 必须与实际生成签名的 workflow 文件完全匹配。拼写错误或对已重命名 workflow 文件的陈旧引用,会导致真实的、正确签名的镜像无法通过身份匹配。
6. **Kyverno webhook 在 `kubectl run` 下超时**
通常是集群资源压力,而不是策略错误——在假设策略本身已损坏之前,请检查 `kubectl get events -n kyverno` 以查看 `NodeNotReady` / 存活探针超时。在使用 `docker` 驱动程序的 minikube 上,内存/CPU 分配受 Docker Desktop 自身资源设置的限制,而不仅仅是主机的限制。
## 应用程序端点
演示应用暴露了三个端点:
| 端点 | 响应 |
|---|---|
| `GET /` | `{"status": "ok", "service": "supply-chain-demo"}` |
| `GET /health` | `{"status": "healthy", "service": "supply-chain-demo"}` |
| `GET /info` | Git SHA,签名标志,服务名称 |
任何访问 `/info` 的正在运行的 pod 都已经通过了准入控制——它证明镜像在到达集群之前已进行了签名和证明。
## 所需的 GitHub Secrets
| Secret | 描述 |
|---|---|
| `DOCKERHUB_USERNAME` | Docker Hub 用户名 (`5936`) |
| `DOCKERHUB_TOKEN` | Docker Hub 访问令牌——在 hub.docker.com -> Account Settings -> Security 创建 |
## Dependabot 冷静期
`dependabot.yml` 为 `github-actions` 和 `pip` 生态系统都设置了 `cooldown.default-days: 7`。这将使 Dependabot 延迟 7 天为刚刚发布的新版本打开 PR,直到其发布了 7 天之后。全新的 GitHub Action 或 pip 软件包版本在短暂时间内不如经过一周实际使用的版本值得信赖——冷静期让生态系统有时间在本地仓库引入它之前,发现明显的回归或供应链问题(受损的版本、损坏的构建)。
## 在本地运行
```
# 构建
docker build \
--build-arg GIT_SHA=$(git rev-parse --short HEAD) \
-t supply-chain-demo:local .
# 运行
docker run -p 8000:8000 supply-chain-demo:local
# 测试
curl http://localhost:8000/
curl http://localhost:8000/health
curl http://localhost:8000/info
```
## 手动验证已签名的镜像
```
# 验证签名
cosign verify \
--certificate-identity-regexp="https://github.com/musaumakau/supply-chain-security/.github/workflows/sign-attest.yml@refs/heads/main" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
5936/supply-chain-demo@sha256:
# 验证 SBOM attestation
cosign verify-attestation \
--certificate-identity-regexp="https://github.com/musaumakau/supply-chain-security/.github/workflows/sign-attest.yml@refs/heads/main" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--type spdxjson \
5936/supply-chain-demo@sha256: \
| jq '.payload | @base64d | fromjson | {predicateType, packageCount: (.predicate.packages | length)}'
# 验证 provenance attestation
cosign verify-attestation \
--certificate-identity-regexp="https://github.com/musaumakau/supply-chain-security/.github/workflows/sign-attest.yml@refs/heads/main" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--type slsaprovenance \
5936/supply-chain-demo@sha256: \
| jq '.payload | @base64d | fromjson | {predicateType, builder: .predicate.builder.id, entryPoint: .predicate.invocation.configSource.entryPoint}'
```
## 部署到 EKS
首先以纯观察模式启动任何新的强制执行引擎,然后按环境逐步推出(先 dev,然后 staging,最后 production)。
**Kyverno:**
```
# 首先将 validationFailureAction 设置为 Audit
kubectl apply -f policy/kyverno/block-unsigned-images.yaml
kubectl get clusterpolicy block-unsigned-images
kubectl get policyreport -A
# 满意后:
kubectl patch clusterpolicy block-unsigned-images \
--type merge -p '{"spec":{"validationFailureAction":"Enforce"}}'
```
**Gatekeeper + Ratify:**
```
# constraint 以 dryrun 模式启动 -- 参见 constraint.yaml 中的 enforcementAction
kubectl apply -f policy/gatekeeper/
kubectl get k8srequiresignedimages require-signed-images -o yaml
# 在违规情况看起来正确后,依次提升至 warn,然后 deny
```
## 工具职责
| 工具 | 角色 |
|---|---|
| [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) | 提供用于无密钥签名的身份 token——无需 secrets |
| [Fulcio](https://docs.sigstore.dev/certificate_authority/overview/) | Sigstore CA——颁发绑定到 OIDC 身份的短期签名证书 |
| [Cosign](https://docs.sigstore.dev/cosign/overview/) | 对镜像 digest 进行签名,将 SBOM 和来源证明作为 OCI 构件附加 |
| [Rekor](https://docs.sigstore.dev/logging/overview/) | 公共仅追加透明度日志——持久存储签名和证书 |
| [Syft](https://github.com/anchore/syft) | 从容器镜像生成 SPDX JSON SBOM |
| [Trivy](https://trivy.dev/) | 扫描文件系统和容器镜像以查找 CVE、密钥和配置错误 |
| [Semgrep](https://semgrep.dev/docs/) | SAST——扫描源代码、工作流、Dockerfile 和 manifests 以发现安全问题 |
| [Kyverno](https://kyverno.io/docs/) | Kubernetes 准入控制器——原生验证签名/证明(选项 A) |
| [Gatekeeper](https://open-policy-agent.github.io/gatekeeper/website/docs/) | Kubernetes 准入控制器——将验证委托给 Ratify(选项 B) |
| [Ratify](https://ratify.dev/docs/quickstarts/quickstart-manifest-validation) | 通过外部数据提供者代表 Gatekeeper 执行实际的 Cosign 验证 |
| [Dependabot](https://docs.github.com/en/code-security/dependabot) | 每周保持 GitHub Actions SHA 锁定最新 |
## 已知局限性
- 验证工作流与签名在同一个 pipeline 中运行。完全分离的架构会在部署 pipeline 中触发验证,而不是在签名之后立即触发。这被列为未来的工作。
- 这两个强制执行引擎都不解析 SBOM 内容——两者都确认 SBOM 是由已批准的 workflow 附加的,而不是确认它包含特定的包。
- VEX 语句目前用于通知 `.trivyignore` 抑制,但尚未成为经过验证的准入时证明。将 VEX 作为第四种 Cosign 证明类型接入是自然的下一步。
- 在未启用该功能的集群上,Gatekeeper 的状态输出针对 `vap.k8s.io` 强制执行点报告 `K8sNativeValidation engine is missing`。这不会影响此处使用的基于 webhook 的强制执行路径,但这是一个值得注意的可见错误状态,而不是忽略它。
- 该 pipeline 针对 Docker Hub。对于 AWS 生产部署,建议使用基于 IRSA 身份验证迁移到 Amazon ECR。
- Kyverno 和 Gatekeeper+Ratify 在此处作为用于比较的并行选项进行记录。不建议在生产环境中针对相同的工作负载同时运行两者——它增加了操作复杂性(需要考虑两个 webhook,两个可能出现静默分歧的强制执行位置),而与选择一个配置良好的引擎相比,并没有带来额外的安全收益。
标签:GitHub Actions, SAST, Sigstore, 子域名突变, 盲注攻击, 自动笔记, 请求拦截, 软件供应链安全, 远程方法调用, 逆向工具, 镜像签名