使用 Secrets 的镜像签名验证策略

本指南演示如何使用 Kubernetes Secrets 存储 Kyverno 镜像签名验证的公钥,相较于将密钥直接嵌入策略中,这种方式提供了更好的安全性和密钥管理。

为什么使用 Secrets 存储公钥?

使用 Kubernetes Secrets 存储公钥具有以下优势:

  • 增强安全性:密钥安全地存储在 Kubernetes Secret 存储中
  • 便捷的密钥轮换:无需修改策略即可更新密钥
  • 访问控制:使用 RBAC 控制谁可以访问 Secrets

快速开始

1. 生成并存储密钥到 Secret

# 生成 cosign 密钥对
cosign generate-key-pair

# 从公钥文件创建 Secret
kubectl create secret generic cosign-public-key \
  --from-file=cosign.pub=./cosign.pub \
  --namespace=kyverno

# 验证 Secret 是否创建成功
kubectl get secret cosign-public-key -n kyverno

2. 为 Kyverno 配置 RBAC

创建 Kyverno 的 Service Account

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kyverno-secret-reader
  namespace: kyverno

创建访问 Secret 的 Role

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: kyverno
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
  resourceNames: ["cosign-public-key", "team-keys"]  # 仅限特定 Secret

将 Role 绑定到 Service Account

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: kyverno
subjects:
- kind: ServiceAccount
  name: kyverno-secret-reader
  namespace: kyverno
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

3. 使用 Secret 引用创建策略

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-with-secret
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: check-signatures
      match:
        any:
        - resources:
            kinds: [Pod]
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: cosign-public-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        mutateDigest: true

4. 测试配置

# 签署镜像
cosign sign --key cosign.key registry.company.com/app:v1.0.0

# 应用策略
kubectl apply -f verify-with-secret.yaml

# 使用已签名镜像测试(应成功)
kubectl run test --image=registry.company.com/app:v1.0.0

# 使用未签名镜像测试(应失败)
kubectl run test-fail --image=nginx:latest

Secret 创建方式

方式一:从文件创建

# 从已有 cosign 公钥文件创建 Secret
kubectl create secret generic cosign-public-key \
  --from-file=cosign.pub=./cosign.pub \
  --namespace=kyverno

方式二:从字符串字面量创建

# 使用内联公钥内容创建 Secret
kubectl create secret generic cosign-public-key \
  --from-literal=cosign.pub="-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----" \
  --namespace=kyverno

方式三:从 YAML 清单创建

apiVersion: v1
kind: Secret
metadata:
  name: cosign-public-key
  namespace: kyverno
  labels:
    app: kyverno
    component: image-verification
type: Opaque
stringData:
  cosign.pub: |
    -----BEGIN PUBLIC KEY-----
    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
    5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
    -----END PUBLIC KEY-----
kubectl apply -f cosign-secret.yaml

常见使用场景

场景 1:单团队使用单个 Secret

简单场景,一个团队管理所有镜像签名:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: single-team-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-team-signatures
      match:
        any:
        - resources:
            kinds: [Pod, Deployment, StatefulSet, DaemonSet]
      exclude:
        any:
        - resources:
            namespaces: [kube-system, kyverno]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"
        - "gcr.io/myproject/*"
        
        failureAction: Enforce
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: team-cosign-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        verifyDigest: true
        required: true

场景 2:多团队使用不同 Secret

不同团队拥有各自的签名密钥和 Secret:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: multi-team-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    # 前端团队镜像
    - name: verify-frontend-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [frontend-*]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/frontend/*"
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: frontend-team-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true
    
    # 后端团队镜像
    - name: verify-backend-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [backend-*]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/backend/*"
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: backend-team-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true

场景 3:关键镜像需要多重签名

高安全环境,多个团队必须对关键镜像签名:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: critical-multi-signature
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-critical-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [production]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/critical/*"
        
        failureAction: Enforce
        
        attestors:
        # 安全部门签名(必需)
        - count: 1
          entries:
          - keys:
              secret:
                name: security-team-key
                namespace: kyverno
                key: security.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        # 开发团队签名(必需)
        - count: 1
          entries:
          - keys:
              secret:
                name: dev-team-key
                namespace: kyverno
                key: development.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        # 发布团队签名(必需)
        - count: 1
          entries:
          - keys:
              secret:
                name: release-team-key
                namespace: kyverno
                key: release.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true

场景 4:离线环境使用 Secrets

在隔离环境中使用 Secrets:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: offline-verification-with-secret
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-offline-images
      match:
        any:
        - resources:
            kinds: [Pod, Deployment, StatefulSet, DaemonSet]
      
      verifyImages:
      - imageReferences:
        - "registry.internal.com/*"
        - "airgap.company.com/*"
        
        failureAction: Enforce
        emitWarning: false
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: offline-cosign-key
                namespace: kyverno
                key: cosign.pub
              
              # 离线模式配置
              rekor:
                url: ""                    # 离线模式下为空 URL
                ignoreTlog: true           # 忽略透明日志
                ignoreSCT: true            # 忽略 SCT
              
              ctlog:
                ignoreTlog: true           # 忽略证书透明日志
                ignoreSCT: true            # 忽略 SCT
        
        mutateDigest: true
        verifyDigest: true
        required: true