镜像签名并非简单的哈希校验,而是基于数字签名机制对容器镜像内容(包括 manifest、config blob 和所有 layer digest)进行不可篡改的密码学绑定。其核心是将镜像的 OCI manifest JSON 序列化后,由私钥签名,生成可独立分发的签名对象(如 Cosign 的 `.sig` 或 Notary v2 的 envelope),并存储于镜像仓库的关联元数据路径中。 Kubernetes 1.30 起默认启用
组件驱动,它在 Pull 阶段主动向配置的签名验证 Webhook(如 Connaisseur 或 Cosign-based admission controller)发起 POST 请求,携带镜像 digest 与签名元数据 URI。
OCI镜像签名已从Notary v1的中心化信任模型,转向基于内容寻址与去中心化密钥管理的v2规范。Cosign作为CNCF孵化项目,原生支持OCI Artifact签名;Sigstore则通过Fulcio(CA)、Rekor(透明日志)和Cosign三组件构建零信任签名基础设施。
:v1.0.sig` artifact。Cosign自动验证证书链与Rekor日志条目,确保不可抵赖性。2.2 安装与配置Cosign:密钥生成、Fulcio OIDC身份绑定与证书轮换实践
快速安装与环境准备
推荐使用cosignv2.2.0+,支持 Fulcio 与 Keyless 签名:
# macOS brew install cosign-tap/cosign/cosign # Linux(静态二进制) curl -L https://github.com/sigstore/cosign/releases/download/v2.2.0/cosign-linux-amd64 \ -o cosign && chmod +x cosign && sudo mv cosign /usr/local/bin/
该命令下载经 Sigstore 自签名验证的官方二进制,确保供应链完整性;-L支持重定向,chmod +x赋予可执行权限。
Fulcio OIDC 绑定流程
- 首次运行
cosign sign --keyful=false <image>将自动打开浏览器完成 GitHub/GitLab OIDC 登录 - Fulcio 验证 ID Token 后签发短期证书(默认 10 小时),并存入 Rekor 公共透明日志
密钥轮换策略对比
| 方式 | 适用场景 | 证书有效期 |
|---|
| Keyful(本地私钥) | CI/CD 内网环境 | 长期(需人工管理) |
| Keyless(Fulcio) | 开发者本地签名 | ≤24 小时(自动轮换) |
2.3 使用Cosign对本地镜像签名:从docker build到cosign sign的完整流水线
构建与签名一体化流程
- 使用
docker build构建镜像并打标签 - 通过
cosign sign对本地镜像进行签名 - 验证签名完整性与签名者身份
关键命令示例
# 构建镜像 docker build -t localhost:5000/myapp:v1.0 . # 推送至本地 registry(可选,但推荐用于可复现验证) docker push localhost:5000/myapp:v1.0 # 使用 cosign 对本地镜像签名(无需推送) cosign sign --key cosign.key localhost:5000/myapp:v1.0
--key指定私钥路径;localhost:5000/myapp:v1.0是镜像的完整引用地址,Cosign 会自动解析其 digest 并生成对应签名载荷。签名元数据对照表
| 字段 | 说明 |
|---|
| digest | 镜像 manifest 的 SHA256 值,唯一标识 |
| timestamp | 签名生成时间(RFC3339 格式) |
| issuer | 密钥所属主体(如 email 或 OIDC 身份) |
2.4 验证签名有效性:离线校验、透明日志(Rekor)查询与篡改检测实操
离线签名验证流程
使用 Cosign 工具可完全离线验证签名哈希一致性,无需网络访问镜像仓库:cosign verify --key cosign.pub \ --certificate-oidc-issuer https://accounts.google.com \ --certificate-identity "user@example.com" \ ghcr.io/example/app:v1.2.0
该命令校验签名证书的 OIDC 发行方与身份声明是否匹配,并比对容器镜像摘要与签名中嵌入的 digest。`--key` 指定公钥用于验签,确保私钥未被泄露前提下签名不可伪造。Rekor 透明日志交叉验证
查询 Rekor 日志确认签名是否已公开记录,防止“签名存在但未上链”的隐蔽篡改:- 提取签名中的 signature 和 certificate 字段
- 调用 Rekor API 查询 entry ID:
curl -s "https://rekor.sigstore.dev/api/v1/log/entries?uuid=..." - 比对返回的
body.integrationTime时间戳是否早于可疑事件时间
篡改检测关键指标对比
| 检测维度 | 离线校验 | Rekor 查询 |
|---|
| 时效性 | 即时(本地 CPU) | 依赖网络延迟 |
| 防抵赖性 | 弱(无第三方见证) | 强(全局不可删日志) |
2.5 签名策略工程化:基于SBOM与SLSA Level 3的签名准入条件设计
准入校验核心逻辑
策略引擎需同时验证SBOM完整性与构建溯源链,确保所有构件满足 SLSA Level 3 的“可重复构建”与“受保护构建平台”要求。
| 校验维度 | 技术实现 | 失败阈值 |
|---|
| SBOM 签名有效性 | cosign verify-blob + dsse | 100% 匹配 |
| 构建事件溯源 | slsa-verifier --provenance | 必须含 buildDefinition |
策略配置示例
policy: sbom: required: true format: "spdx-json" provenance: slsaLevel: "3" builderID: "https://github.com/actions/runner"
该配置强制要求 SPDX 格式 SBOM 及 GitHub Actions 构建平台生成的 SLSA v1.0 Provenance,确保构建环境隔离与日志不可篡改。
自动化准入流程
- 接收制品上传请求
- 并行拉取 SBOM、Provenance、二进制哈希
- 调用 slsa-verifier + cosign 验证签名链
- 任一校验失败则拒绝入库
第三章:Kubernetes集群侧签名验证机制深度解析
3.1 ImagePolicyWebhook原理剖析:准入控制器如何拦截未签名/无效签名镜像
准入链路中的关键拦截点
ImagePolicyWebhook 作为 MutatingAdmissionWebhook 的对等组件,在ValidatingAdmissionWebhook阶段前执行策略校验。它通过解析 Pod 创建请求中的spec.containers[*].image字段,向外部 Webhook 服务发起同步 HTTP POST 请求。请求载荷结构
{ "apiVersion": "imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", "spec": { "containers": [{ "image": "nginx:1.25.3@sha256:abc123..." }] } }
该结构由 kube-apiserver 序列化后发送;image字段必须含 digest(如@sha256:...),否则 Webhook 可直接拒绝。典型校验逻辑
- 验证镜像 digest 是否存在于可信签名仓库(如 Notary v2 或 Cosign)
- 检查对应 signature bundle 是否由指定根密钥签发且未过期
- 拒绝无 digest、digest 不匹配或签名验证失败的镜像拉取请求
3.2 配置ClusterImagePolicy与NamespaceImagePolicy:策略粒度、匹配规则与拒绝动作实测
策略作用域对比
| 维度 | ClusterImagePolicy | NamespaceImagePolicy |
|---|
| 作用范围 | 集群全局生效 | 仅限指定命名空间 |
| 优先级 | 低(被Namespace级覆盖) | 高(覆盖Cluster级同名规则) |
匹配规则实测
# ClusterImagePolicy 示例 apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: block-unsigned spec: match: - imageRepository: "ghcr.io/*" actions: ["deny"] verify: - type: cosign key: https://public-keys.example.com/cosign.pub
该配置对所有ghcr.io/下镜像强制签名验证;若未签名或验签失败,则拒绝拉取。其中match.imageRepository支持通配符,actions定义策略触发后行为。拒绝动作效果验证
- 部署含未签名镜像的 Pod
- 观察事件:
kubectl get events --field-selector reason=ImagePullFailed - 确认日志中出现
cosign verification failed
3.3 与KMS集成实现密钥托管:AWS KMS/GCP KMS在签名验证中的安全落地
密钥生命周期解耦
将签名私钥完全托管于云KMS,应用层仅持有密钥ID(如arn:aws:kms:us-east-1:123456789012:key/abcd1234-),杜绝私钥导出与内存泄漏风险。服务端签名验证流程
// 使用 AWS SDK v2 进行 KMS 签名验证 result, err := client.Verify(ctx, &kms.VerifyInput{ KeyId: aws.String("alias/signing-key"), SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, Message: []byte(payload), Signature: sigBytes, }) // KeyId:可为别名、ARN 或KeyId;SigningAlgorithm 必须与密钥创建时一致 // Message 需与原始签名输入完全一致(含编码、哈希预处理)
KMS能力对比
| 特性 | AWS KMS | GCP KMS |
|---|
| ECDSA 支持 | ✅ (P-256/P-384) | ✅ (P-256) |
| 签名验证 API | Verify() | asymmetricSign() + verify() via IAM-permitted service account |
第四章:CI/CD流水线中镜像签名的无缝嵌入
4.1 GitHub Actions中自动化签名:构建→签名→推送→验证四阶段原子化Job编排
四阶段原子化设计原则
每个阶段封装为独立 job,通过needs显式声明依赖,确保签名仅在构建成功后触发,推送仅在签名通过后执行,验证作为最终守门员。核心 workflow 片段
jobs: build: runs-on: ubuntu-latest outputs: artifact_hash: ${{ steps.hash.outputs.sha256 }} steps: - uses: actions/checkout@v4 - name: Build binary run: make build - name: Compute SHA256 id: hash run: echo "sha256=$(sha256sum dist/app-linux-amd64 | cut -d' ' -f1)" >> $GITHUB_OUTPUT sign: needs: build runs-on: ubuntu-latest steps: - uses: sigstore/cosign-action@v3 with: cosign-release: 'v2.2.4' command: sign-blob file: dist/app-linux-amd64 key: ${{ secrets.COSIGN_PRIVATE_KEY }}
该片段将构建产物哈希导出为 job 输出,供后续签名与验证阶段复用;cosign-action使用 Sigstore 模式对二进制 blob 签名,密钥由 GitHub Secrets 安全注入。阶段间数据流转对比
| 阶段 | 输入依赖 | 输出产物 |
|---|
| 构建 | 源码 + Makefile | 二进制 + SHA256 哈希 |
| 签名 | 构建产物 + 私钥 | Cosign 签名(.sig) |
| 推送 | 二进制 + .sig + OCI registry 凭据 | 带签名的 OCI 镜像 |
| 验证 | 镜像 digest + 公钥 | Exit code 0 / 1 |
4.2 GitLab CI集成Cosign:使用CI_REGISTRY_IMAGE动态注入与多架构镜像签名策略
动态镜像路径注入
GitLab CI 提供的CI_REGISTRY_IMAGE变量自动拼接仓库地址、组名与项目名,无需硬编码:script: - cosign sign --key $COSIGN_KEY "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
该命令利用 GitLab 内置变量生成完整镜像引用(如registry.gitlab.com/group/proj:1.2.0),确保签名目标唯一且可追溯。多架构镜像签名策略
需对每个平台镜像分别签名,避免跨架构误签:- 构建阶段通过
buildx输出manifest-list及各子镜像 SHA - 在
signjob 中循环调用cosign sign,传入具体 digest - 签名后推送至同一 registry 路径,由 OCI 规范自动关联
| 变量 | 用途 | 示例值 |
|---|
CI_REGISTRY_IMAGE | 基础镜像路径前缀 | registry.gitlab.com/mygroup/app |
CI_COMMIT_SHORT_SHA | 精简提交哈希 | a1b2c3d |
4.3 Jenkins Pipeline增强:Groovy脚本调用cosign verify并触发质量门禁失败回滚
签名验证与门禁联动逻辑
Jenkins Pipeline 通过 Groovy 调用cosign verify验证容器镜像签名有效性,失败时自动触发回滚流程。sh "cosign verify --key ${COSIGN_PUBLIC_KEY} ${IMAGE_URI} || exit 1"
该命令使用指定公钥验证镜像签名;--key指定 PEM 格式公钥路径,${IMAGE_URI}为待验镜像地址;非零退出码将中断 Pipeline 并激活后续回滚分支。回滚策略执行条件
- 签名验证失败(exit code ≠ 0)
- 静态扫描(Trivy)发现 CRITICAL 漏洞 ≥ 1
- 单元测试覆盖率低于阈值(85%)
门禁状态映射表
| 检查项 | 失败响应 | 回滚目标 |
|---|
| cosign verify | 立即终止部署 | 上一稳定 Tag |
| Trivy 扫描 | 阻断发布流水线 | 镜像仓库删除当前 Tag |
4.4 Argo CD与签名验证协同:ImageUpdater + Policy-as-Code实现GitOps可信交付闭环
可信镜像更新流程
Argo CD ImageUpdater 自动检测镜像仓库新版本,并通过 Cosign 验证 OCI 镜像签名后,才触发 Git 仓库中image.tag字段更新。# argocd-image-updater-config.yaml registries: - name: ghcr.io cosign: true verify: "https://github.com/myorg/.sigstore"
该配置启用 Cosign 签名验证,verify指向可信的公钥托管路径,确保仅允许已签名且由指定密钥签署的镜像被采纳。策略执行层
Open Policy Agent(OPA)嵌入 Argo CD,依据policy.rego对待提交的镜像标签、签名者身份及 SBOM 合规性进行实时校验。| 策略维度 | 校验项 | 失败响应 |
|---|
| 签名有效性 | Cosign verify exit code == 0 | 阻断同步,标记为PolicyViolation |
| 来源白名单 | issuer ∈ [\"https://token.actions.githubusercontent.com\"] | 拒绝部署并告警 |
第五章:面向生产环境的签名治理与演进路线
在高并发、多租户的云原生生产环境中,签名机制不再仅是安全校验的“守门人”,更是服务治理的关键控制面。某头部电商中台在日均 2.4 亿次 API 调用下,因签名密钥硬编码与轮换策略缺失,曾导致灰度发布期间 17% 的支付回调被误拒。签名生命周期自动化管理
通过 Kubernetes Operator 实现密钥生成、分发、轮换与吊销全链路闭环。以下为签名配置 CRD 的关键字段片段:apiVersion: auth.example.com/v1 kind: SignaturePolicy metadata: name: payment-v2 spec: algorithm: "HMAC-SHA256" rotationInterval: "72h" gracePeriod: "48h" # 新旧密钥共存窗口 keySource: "vault://prod/payment/signing-key"
多级签名验证策略
- 边缘层(API 网关)执行基础签名校验与时间戳防重放
- 业务服务层依据请求路径动态加载租户专属密钥环
- 审计服务异步解析签名头并写入不可篡改的区块链存证链
签名性能与可观测性协同优化
| 指标 | 基线值(P95) | 优化后(P95) |
|---|
| 签名验证耗时 | 12.8ms | 3.2ms |
| 密钥获取延迟 | 8.4ms(HTTP Vault) | 0.3ms(本地 eBPF 缓存) |
渐进式演进路径
v1.0 → 静态密钥 + Nginx 模块校验
v2.0 → Vault 动态密钥 + Envoy WASM 插件
v3.0 → eBPF 内核态签名卸载 + 签名策略 DSL 引擎