第一章:Docker镜像签名的现状与信任危机
在容器化生产环境中,Docker镜像已成为软件分发的事实标准。然而,镜像来源不可信、中间人篡改、供应链投毒等事件频发,暴露出签名机制在实际落地中的严重断层。尽管Docker Content Trust(DCT)和Notary v2(现为CNCF项目Cosign核心基础)提供了签名能力,但默认禁用、密钥管理复杂、验证流程未深度集成CI/CD流水线,导致绝大多数镜像未经签名即被拉取运行。
签名启用率极低的现实困境
多项行业调研表明,公有镜像仓库中仅有不足7%的热门镜像(如nginx、redis官方tag)启用了强签名验证;私有仓库中该比例更低,常低于1%。根本原因在于:
- 开发者需手动配置DOCKER_CONTENT_TRUST=1环境变量,且首次签名需生成并分发根密钥,操作门槛高
- CI流水线中缺乏标准化的签名注入步骤,易因权限或密钥泄露引入风险
- Kubernetes集群默认不校验镜像签名,kubelet拉取时完全信任registry响应
典型签名验证失败场景
# 启用DCT后拉取未签名镜像将失败 $ export DOCKER_CONTENT_TRUST=1 $ docker pull ubuntu:22.04 # 输出错误:No valid trust data for 22.04 —— 说明该tag未被发布者签名
该行为虽体现安全原则,却因破坏向后兼容性而被多数团队禁用,形成“安全即中断”的实践悖论。
主流签名方案能力对比
| 方案 | 签名标准 | 密钥托管支持 | K8s原生集成 | OCI Artifact兼容 |
|---|
| Docker Notary v1 | The Update Framework (TUF) | 本地文件/自建服务器 | 无 | 否 |
| Cosign + Sigstore | OCI Image Signatures | Fulcio(免费证书颁发)、Rekor(透明日志) | 通过KMS+Policy Controller支持 | 是 |
第二章:Cosign核心原理与本地环境搭建
2.1 理解Sigstore生态与Fulcio/Rekor信任链
Sigstore 是面向软件供应链安全的开源签名基础设施,其核心由三支柱构成:Fulcio(证书颁发机构)、Rekor(透明日志)与Cosign(签名工具),共同构建零信任验证闭环。
Fulcio 的身份绑定机制
Fulcio 不依赖传统 PKI 证书,而是通过 OIDC 身份(如 GitHub 登录)动态签发短期代码签名证书:
// Cosign 调用 Fulcio 获取证书 resp, _ := client.IssueCertificate(ctx, &fulcio.IssueRequest{ PublicKey: pubKeyBytes, Email: "user@example.com", OIDCToken: idToken, // 来自 GitHub OIDC ID Token })
该请求经 Fulcio 验证 OIDC 声明后,签发含 `subject`、`notBefore` 和 `codeSigning` 扩展的 X.509 证书,有效期默认仅 10 小时,杜绝长期密钥泄露风险。
Rekor 的不可篡改日志
所有签名元数据(含证书哈希、签名值、artifact digest)被写入 Rekor 的 Merkle Tree 日志:
| 字段 | 作用 |
|---|
body | Base64 编码的签名条目(含证书+签名+payload) |
integratedTime | Unix 时间戳,证明该条目在该时刻已存在 |
2.2 安装Cosign CLI并配置OIDC身份认证(GitHub/GitLab/企业IdP)
安装 Cosign CLI
推荐使用官方脚本一键安装(Linux/macOS):
# 下载并验证签名后安装 curl -sL https://raw.githubusercontent.com/sigstore/cosign/main/install.sh | sh -s -- -b /usr/local/bin
该脚本自动校验二进制哈希与 GitHub Release 签名,确保供应链完整性。-b 指定安装路径,需确保其在 $PATH 中。
OIDC 身份提供方支持对比
| IdP 类型 | 自动发现端点 | 需手动配置 |
|---|
| GitHub | ✅https://token.actions.githubusercontent.com | — |
| GitLab | ❌ | issuer URL、client ID |
| 企业 IdP(如 Keycloak) | ❌ | full issuer URL、client ID、redirect URI |
登录并获取 OIDC Token
- 执行
cosign login --oidc-issuer https://github.com/login/oauth/authorize启动浏览器授权流; - 成功后,Cosign 将 OIDC ID Token 缓存至
~/.sigstore/cosign-token; - 后续签名操作自动复用该 token,无需重复交互。
2.3 生成和管理密钥对:ECDSA vs. Ed25519 vs. Keyless模式对比实践
密钥生成性能对比
| 算法 | 生成耗时(平均) | 公钥长度 | 签名长度 |
|---|
| ECDSA (P-256) | ~1.2 ms | 65 字节 | 72 字节 |
| Ed25519 | ~0.3 ms | 32 字节 | 64 字节 |
| Keyless(HSM代理) | ~8.5 ms(网络RTT主导) | 32 字节(曲线同Ed25519) | 64 字节 |
Ed25519 密钥生成示例(Go)
// 使用crypto/ed25519生成密钥对 priv, pub, err := ed25519.GenerateKey(rand.Reader) if err != nil { log.Fatal(err) // 随机源不可用时失败 } // pub是32字节,priv是64字节(含私钥+公钥副本)
该调用基于RFC 8032,使用SHA-512哈希与扭曲Edwards曲线;无需指定曲线参数,抗侧信道攻击设计内建。
Keyless 模式核心流程
客户端 → TLS终止器(带Keyless插件) → 远程密钥服务(HSM/Cloud KMS)
签名操作在服务端完成,私钥永不离开HSM边界
2.4 镜像签名基础操作:sign/verify/attest命令全参数解析与调试技巧
核心命令概览
cosign sign:对容器镜像生成并上传签名cosign verify:校验镜像签名有效性与签名者身份cosign attest:附加SBOM、SLSA等不可变声明
签名与验证典型流程
# 使用OIDC身份签名(自动获取短时效token) cosign sign --oidc-issuer https://token.actions.githubusercontent.com \ --oidc-client-id github.com/myorg/mypipeline \ ghcr.io/myorg/app:v1.2.0 # 验证时强制检查签名链与证书信任锚 cosign verify --certificate-identity-regexp ".*@myorg\.com" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/myorg/app:v1.2.0
该流程依赖可信OIDC颁发方,
--certificate-identity-regexp确保签名者邮箱归属组织域,
--oidc-client-id防止跨租户伪造。
常见调试参数对照表
| 参数 | 用途 | 调试场景 |
|---|
--debug | 输出签名/验证全过程HTTP请求与证书链 | 签名上传失败或证书链不完整 |
--insecure-ignore-tls | 跳过registry TLS校验(仅测试环境) | 私有registry自签名证书报错 |
2.5 签名策略落地:基于OCI Artifact的多签名协同与签名吊销模拟
多签名协同流程
OCI Artifact 支持通过独立 `subject` 关联多个签名,实现多方联合验证。以下为签名绑定示例:
{ "subject": { "digest": "sha256:abc123...", "mediaType": "application/vnd.oci.image.manifest.v1+json" }, "signatures": [ { "keyId": "kms-aws-prod", "algo": "ecdsa-p256" }, { "keyId": "cosign-ci", "algo": "rsa-pkcs1v15" } ] }
该结构声明同一制品由不同信任域(生产密钥管理服务与CI流水线)分别签名,验证时需满足预设策略(如“至少2/3签名有效”)。
签名吊销模拟机制
吊销通过独立 OCI artifact(`application/vnd.cncf.notary.signature.revoke.v1+json`)发布,并关联原制品 digest:
| 字段 | 说明 |
|---|
| revokedDigest | 被吊销签名对应 content digest |
| reason | "compromised-key" 或 "policy-violation" |
第三章:构建5层可信验证架构的基石能力
3.1 第一层:镜像完整性验证——SHA256摘要绑定与SBOM交叉校验
双重校验机制设计
镜像构建时同步生成 SHA256 摘要与 SPDX 格式 SBOM,并通过签名锚定二者关联关系,防止独立篡改。
校验流程示例
# 构建后立即提取并绑定 docker image inspect myapp:1.2 | jq -r '.[0].GraphDriver.Data.MergedDir' | xargs sha256sum > image.sha256 syft myapp:1.2 -o spdx-json > sbom.spdx.json cosign attach sbom --sbom sbom.spdx.json myapp:1.2
该流程确保镜像层哈希、文件级指纹与组件清单原子绑定;
syft输出符合 SPDX 2.3 规范,
cosign attach sbom将 SBOM 作为独立签名载荷存入 OCI 注册表。
绑定关系验证表
| 字段 | 来源 | 校验方式 |
|---|
| 镜像 digest | registry manifest | OCI spec v1.1 digest |
| SBOM digest | cosign signature payload | SHA256(SBOM bytes) |
| 绑定签名 | cosign public key | ECDSA-P256 验证 |
3.2 第二层:签名者身份强认证——OIDC颁发证书链提取与X.509策略约束
证书链提取关键逻辑
OIDC Provider(如Keycloak或Auth0)在颁发ID Token时,需同步提供可验证的X.509证书链。客户端通过JWKS端点获取公钥后,需进一步校验其证书路径是否满足策略约束:
// 从ID Token中解析并验证证书链 certChain, err := x509.ParseCertificates(idToken.CertBytes) if err != nil { return errors.New("invalid cert chain in OIDC token") } // 验证策略:必须包含至少1个CA签发的中间证书,且末级证书Subject需匹配issuer
该代码确保证书链完整且符合最小信任深度要求;
idToken.CertBytes为嵌入在JWT头部的PEM格式证书链字节流。
X.509策略约束检查项
- 证书链长度 ≥ 2(根CA + 中间CA 或 中间CA + 签名者)
- 末级证书的
ExtKeyUsage必须包含ExtKeyUsageCodeSigning PolicyConstraints扩展中禁止inhibitPolicyMapping
策略合规性对照表
| 约束项 | 允许值 | 校验方式 |
|---|
| 证书链深度 | ≥2 | len(certChain) |
| 密钥用法 | digitalSignature, keyEncipherment | cert.KeyUsage |
3.3 第三层:时间锚定与不可抵赖性——Rekor透明日志写入与存在性证明验证
日志写入核心流程
Rekor 将签名、哈希与元数据以 Merkle Tree 叶节点形式持久化,由共识节点批量提交至链上锚点(如 Ethereum 或 Fulcio)。
entry := &models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(bodyBytes), IntegratedTime: time.Now().Unix(), Kind: "hashedrekord", } logEntry, _ := client.CreateLogEntry(context.Background(), entry)
Body为 Base64 编码的 JSON 结构体(含 artifact hash 与 signature);
IntegratedTime是服务端写入时间戳,由 Rekor 签名并上链锚定,构成时间不可篡改依据。
存在性证明验证路径
客户端通过
GetLogEntryByUUID获取条目后,调用
VerifyInclusionProof验证其在 Merkle Tree 中的位置合法性:
- 比对返回的
RootHash与链上最新锚点哈希 - 逐层计算 Merkle 路径,确认叶节点哈希可推导出该根哈希
| 字段 | 作用 |
|---|
inclusionProof.Hashes | 从叶到根的兄弟节点哈希列表 |
inclusionProof.LogIndex | 全局唯一位置索引,提供顺序不可抵赖性 |
第四章:企业级可信流水线集成实战
4.1 CI阶段嵌入签名:GitHub Actions中自动化Cosign签名与策略门禁(OPA/Gatekeeper)
签名流程集成
在CI流水线中,Cosign签名需在镜像构建完成后立即执行,确保不可篡改性。以下为关键步骤:
- 构建容器镜像并推送至注册中心
- 使用OIDC身份向Sigstore Fulcio申请短期证书
- 调用
cosign sign对镜像摘要签名 - 将签名上传至同一注册中心的
signature命名空间
策略验证门禁
Gatekeeper通过
ValidatingAdmissionPolicy拦截未签名或签名无效的部署请求:
apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: name: require-cosign-signature spec: paramKind: apiVersion: policies.sigstore.dev/v1alpha1 kind: ClusterImagePolicy matchConstraints: resourceRules: - resources: ["pods"] apiGroups: [""]
该策略强制所有Pod镜像必须通过Cosign验证,并匹配预定义的公钥或OIDC发行者。
信任链协同表
| 组件 | 职责 | 验证依据 |
|---|
| Cosign | 本地签名/验证 | ECDSA密钥或Fulcio OIDC证书 |
| OPA/Gatekeeper | 集群准入控制 | ClusterImagePolicy规则集 |
4.2 Registry侧验证拦截:配置Notary v2兼容的Cosign验证Webhook与Harbor插件
Webhook注册与签名验证流程
Harbor 2.8+ 原生支持 Notary v2(即 Sigstore 兼容)验证 Webhook。需在 `harbor.yml` 中启用:
notification: webhook: enabled: true # 启用 OCI artifact 签名验证回调 notary_v2: enabled: true endpoint: "https://cosign-verifier.example.com/validate"
该配置使 Harbor 在推送镜像时,向指定 endpoint 发送包含 digest、artifact reference 和 signature manifest URL 的 JSON 请求,触发 Cosign 验证器执行 keyless 或 OIDC 签名链校验。
验证插件部署依赖
- Cosign v2.2+(支持
--signature-ref与--certificate-ref) - Harbor 插件目录中已加载
notaryv2-verifier插件(位于/usr/local/harbor/plugins/notaryv2-verifier)
验证策略匹配表
| 策略类型 | 适用场景 | 是否强制阻断 |
|---|
| Keyless(Fulcio) | CI/CD 流水线签发 | 是 |
| OIDC + Rekor | 审计追踪需求强 | 是 |
4.3 运行时强制执行:Kubernetes准入控制器(kyverno/opa-gatekeeper)校验镜像签名策略
策略即代码的运行时拦截
Kubernetes 准入控制器在对象持久化前拦截请求,Kyverno 与 OPA/Gatekeeper 均通过 ValidatingWebhookConfiguration 注入校验逻辑,但抽象层级不同:Kyverno 以 Kubernetes 原生资源(ClusterPolicy)定义策略;Gatekeeper 依赖 Rego 语言编写约束逻辑。
镜像签名验证示例(Kyverno)
apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-signed-images spec: validationFailureAction: enforce rules: - name: check-image-signature match: resources: kinds: [Pod] verifyImages: - image: "ghcr.io/example/*" subject: "https://github.com/example/*" issuer: "https://token.actions.githubusercontent.com"
该策略强制所有匹配 ghcr.io/example/ 的镜像必须携带由 GitHub Actions OIDC 签发的有效 cosign 签名。verifyImages 字段触发 Kyverno 内置的 cosign 验证器,自动拉取签名和证书并完成链式信任校验。
策略能力对比
| 能力维度 | Kyverno | OPA/Gatekeeper |
|---|
| 策略语法 | Kubernetes YAML + 模板函数 | Rego(图灵完备逻辑语言) |
| 签名验证原生支持 | ✅ 内置 cosign 集成 | ❌ 需自定义 Rego 调用外部服务 |
4.4 审计与可观测性:聚合Rekor日志、Sigstore审计日志与集群事件构建可信溯源图谱
多源日志统一采集架构
采用 Fluent Bit 作为边缘日志收集器,通过自定义插件同步三类关键事件流:
# fluent-bit.conf 片段:关联 Sigstore 与 Kubernetes 事件 [INPUT] Name tail Path /var/log/sigstore/audit.log Parser sigstore-json Tag sigstore.audit [INPUT] Name kubernetes Kube_URL https://kubernetes.default.svc:443 Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token Tag kube.events
该配置实现 Sigstore 审计日志与集群原生事件的时序对齐,Parser 模块将非结构化审计日志解析为标准 JSON Schema,便于后续图谱关联。
溯源图谱核心字段映射
| 数据源 | 关键字段 | 图谱语义角色 |
|---|
| Rekor | entryID,body.integrationTimestamp | 软件物料锚点与签名时间戳 |
| Sigstore | subject,issuer,signatureTime | 身份断言与签名上下文 |
| Kubernetes | involvedObject.name,reason,firstTimestamp | 部署行为与执行轨迹 |
第五章:未来演进与可信软件供应链终局思考
SBOM 驱动的自动化合规审计
大型金融客户已将 SPDX 2.3 SBOM 嵌入 CI/CD 流水线,结合 Syft + Grype 实现每次 PR 合并前自动扫描依赖树。以下为 GitLab CI 中关键作业片段:
audit-sbom: image: docker:latest script: - apk add --no-cache syft grype - syft $CI_PROJECT_DIR -o spdx-json > sbom.spdx.json # 生成标准 SPDX - grype sbom.spdx.json --fail-on high, critical # 阻断高危漏洞镜像发布
签名验证的生产级落地路径
| 阶段 | 工具链 | 验证点 |
|---|
| 构建时 | Cosign + Tekton | 镜像签名绑定 OCI 注册表元数据 |
| 部署时 | Notary v2 + Kubernetes Admission Controller | 拒绝未签名或签名失效的容器启动 |
零信任构建环境的实践约束
- 所有构建节点运行于隔离的 eBPF 安全沙箱,禁止网络外连(仅允许访问内部 artifact 仓库)
- 源码哈希、构建环境指纹、签名密钥 ID 全部写入硬件安全模块(HSM)并上链存证
- Google 的 BuildKit+Buildkitd with SLSA Level 3 配置已开源,支持复现性构建验证
跨组织协作的信任锚点演进
某国家级开源基金会采用三重锚定机制:
- 根证书由国家密码管理局 SM2 签发
- 项目维护者密钥经社区多签门限(3/5)授权更新
- 每次发布包附带时间戳服务器(RFC 3161)与区块链哈希存证(以太坊 Polygon 主网)