WEBKT

从源码到集群:使用 Cosign 实现容器镜像签名与 K8s 准入校验全流程

3 0 0 0

在云原生安全领域,软件供应链安全(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_KEY
  • COSIGN_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. 进阶建议

  1. Keyless 签名:Cosign 支持通过 OIDC 身份(如 GitHub 身份)进行 Keyless 签名,无需管理私钥。这需要开启 Fulcio 和 Rekor 服务。
  2. 摘要(Digest)校验:在生产环境中,始终建议通过 image@sha256:xxx 的方式部署镜像,防止 Tag 被恶意覆盖。
  3. 多策略并行:你可以为不同的团队或不同的命名空间配置不同的公钥校验策略。

总结

通过 Cosign + Kyverno,我们以极低的成本在 CI/CD 流水线中嵌入了“安全契约”。这不仅能有效抵御中间人攻击,还为容器化环境下的“零信任”架构迈出了坚实的一步。

工具链链接:

云原生安全架构师 容器安全KubernetesCICD

评论点评