从源码到集群:使用 Cosign 实现容器镜像签名与 K8s 准入校验全流程
在云原生安全领域,软件供应链安全(Software Supply Chain Security)已成为重中之重。仅仅扫描镜像漏洞是不够的,我们需要确保在生产环境中运行的镜像确实是由我们的 CI/CD 流水线构建且未被篡改的。
本文将手把手教你如何使用 Cosign(Sigstore 项目成员)对镜像进行数字签名,并通过 Kyverno 在 Kubernetes 集群中实现准入校验,构建一套完整的“非签名镜像禁入”闭环体系。
1. 核心工具链简介
- Cosign: 由 Sigstore 提供的轻量级镜像签名工具,支持多种存储介质(如云端 KMS、本地密钥对、Keyless 模式)。
- Kyverno: Kubernetes 的策略管理引擎,能以声明式的方式对资源进行校验、修改和生成,原生支持镜像签名验证。
2. 环境准备
在开始之前,请确保你已安装以下工具:
- Cosign CLI
- Kyverno 已部署在 K8s 集群中
- 一个可用的容器镜像仓库(如 Docker Hub、GitHub Packages 或阿里云 ACR)
3. 第一步:生成签名密钥对
首先,在本地生成用于签名的公钥和私钥:
cosign generate-key-pair
输出说明:
cosign.key: 私钥(需妥善保存,切勿泄露)。cosign.pub: 公钥(后续用于 K8s 校验)。- 系统会提示输入密码,该密码用于加密私钥。
注意: 在生产环境中,建议将私钥存储在 HashiCorp Vault、AWS KMS 或 GitHub Secrets 中。
4. 第二步:在 CI/CD 流水线中集成签名
以 GitHub Actions 为例,我们在构建并推送镜像后立即进行签名。
首先,将 cosign.key 的内容和密码添加为 GitHub Repository Secrets:
COSIGN_PRIVATE_KEYCOSIGN_PASSWORD
编写 Workflow 文件 .github/workflows/build-sign.yml:
name: Build and Sign Image
on:
push:
branches: [ "main" ]
jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3.1.1
- name: Build and Push
id: build-push
uses: docker/build-push-action@v4
with:
push: true
tags: your-repo/demo-app:latest
- name: Sign the Image
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
# 获取推送后的 Digest(推荐使用 Digest 签名而非 Tag)
IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' your-repo/demo-app:latest)
echo "Signing image: $IMAGE_DIGEST"
cosign sign --key env://COSIGN_PRIVATE_KEY $IMAGE_DIGEST
5. 第三步:在 K8s 中配置准入策略
现在镜像已经带有签名,我们需要告诉 Kubernetes:“禁止运行任何未经我授权签名的镜像”。
我们将使用 Kyverno 创建一个 ClusterPolicy。首先,你需要获取公钥 cosign.pub 的内容。
创建 check-image-signature.yaml:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
validationFailureAction: Enforce # 关键:Enforce 表示直接拦截,Audit 表示仅记录日志
background: false
rules:
- name: check-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "index.docker.io/your-repo/*" # 匹配你的镜像范围
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...(此处替换为你的 cosign.pub 内容)
-----END PUBLIC KEY-----
执行部署:
kubectl apply -f check-image-signature.yaml
6. 第四步:验证效果
场景 A:部署已签名的镜像
当你部署流水线产出的镜像时,Pod 会被正常创建:
kubectl run test-signed --image=your-repo/demo-app:latest
# 结果: pod/test-signed created
场景 B:部署未签名的镜像
尝试部署一个随便找来的镜像(如官方 nginx):
kubectl run test-unsigned --image=nginx:latest
结果: 你会收到类似下方的拦截错误:
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request: policy ClusterPolicy/check-image-signature for resource Pod/default/test-unsigned failed: rule check-signature failed: image verification failed: no matching signatures found.
7. 进阶建议
- Keyless 签名:Cosign 支持通过 OIDC 身份(如 GitHub 身份)进行 Keyless 签名,无需管理私钥。这需要开启 Fulcio 和 Rekor 服务。
- 摘要(Digest)校验:在生产环境中,始终建议通过
image@sha256:xxx的方式部署镜像,防止 Tag 被恶意覆盖。 - 多策略并行:你可以为不同的团队或不同的命名空间配置不同的公钥校验策略。
总结
通过 Cosign + Kyverno,我们以极低的成本在 CI/CD 流水线中嵌入了“安全契约”。这不仅能有效抵御中间人攻击,还为容器化环境下的“零信任”架构迈出了坚实的一步。
工具链链接: