Image Signature Verification Policy

本指南演示如何配置 Kyverno,以验证容器镜像在 Kubernetes 集群中运行前是否已正确签名。可以将其类比为检查身份证——只有带有有效“签名”的镜像才被允许使用。

目录

什么是镜像签名验证?

镜像签名验证就像门口的保安检查身份证。它确保:

  • 镜像真实可信:来源确实是其声称的发布者
  • 镜像未被篡改:签名后没有人修改过镜像
  • 仅允许可信镜像运行:阻止未签名或签名不正确的镜像
  • 审计追踪:记录哪些镜像何时被验证过

快速开始

1. 生成密钥

# 创建签名密钥对(类似创建身份证系统)
cosign generate-key-pair
# 这会生成:cosign.key(私钥,需保密)和 cosign.pub(公钥,可自由分享)

2. 签名镜像

# 签名镜像(类似盖上官方印章)
cosign sign --key cosign.key registry.company.com/app:v1.0.0

3. 创建基础验证策略

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce  # 阻止未签名镜像
  background: false
  rules:
    - name: check-signatures
      match:
        any:
        - resources:
            kinds:
            - Pod
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"  # 检查公司镜像仓库的镜像
        attestors:
        - count: 1
          entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                # 在此粘贴 cosign.pub 内容
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
                5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
                -----END PUBLIC KEY-----
        mutateDigest: true  # 将标签转换为安全的摘要格式

4. 测试

# 应用策略
kubectl apply -f signature-policy.yaml

# 尝试运行未签名镜像(应失败)
kubectl run test --image=nginx:latest

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

常见用例

场景 1:多个团队需要签署关键镜像

对于关键应用,开发团队和安全团队都可能需要对镜像进行签名:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-dual-signatures
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: critical-app-signatures
      match:
        any:
        - resources:
            kinds:
            - Pod
      verifyImages:
      - imageReferences:
        - "registry.company.com/critical/*"
        attestors:
        # 两个团队都必须签名
        - count: 1  # 安全团队签名
          entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                # 安全团队公钥
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
                5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
                -----END PUBLIC KEY-----
        - count: 1  # 开发团队签名
          entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                # 开发团队公钥
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyctVd7iEcnessRQjU917hmKO6JWV
                GHpDguIyakZA8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5==
                -----END PUBLIC KEY-----
        mutateDigest: true

场景 2:不同环境使用不同规则

生产环境需要严格验证,开发环境可以更宽松:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: environment-specific-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    # 生产环境严格规则
    - name: production-must-be-signed
      match:
        any:
        - resources:
            kinds:
            - Pod
            namespaces:
            - production
      verifyImages:
      - imageReferences:
        - "*"  # 所有镜像必须签名
        failureAction: Enforce  # 未签名时阻止
        attestors:
        - count: 1
          entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                # 生产签名密钥
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
                5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
                -----END PUBLIC KEY-----
        mutateDigest: true
    
    # 开发环境宽松规则
    - name: development-warn-unsigned
      match:
        any:
        - resources:
            kinds:
            - Pod
            namespaces:
            - development
            - staging
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"  # 仅检查公司镜像
        failureAction: Audit  # 审计但允许未签名镜像
        attestors:
        - count: 1
          entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                # 开发签名密钥
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyctVd7iEcnessRQjU917hmKO6JWV
                GHpDguIyakZA8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5==
                -----END PUBLIC KEY-----
        mutateDigest: true

场景 3:使用证书代替密钥

在企业环境中,可能使用 X.509 证书:

# 使用证书签名
cosign sign --cert company-cert.pem --cert-chain ca-chain.pem \
  registry.company.com/myapp:v1.0.0
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: certificate-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-with-certificates
      match:
        any:
        - resources:
            kinds:
            - Pod
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"
        attestors:
        - count: 1
          entries:
          - certificates:
              cert: |-
                -----BEGIN CERTIFICATE-----
                # 公司的签名证书(请替换为真实证书)
                MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
                BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
                aWRnaXRzIFB0eSBMdGQwHhcNMTcwODI4MTExNzQwWhcNMTgwODI4MTExNzQwWjBF
                MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
                ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
                CgKCAQEAuuExVilGcXIZ3ulNuL7wLrA7VkqJoGpB1YPmYnlS7sobTggOGSqMUvqU
                BdLXcAo3ZCOXuKrBHBlltvcNdFHynfxOtkAOCZjirD6uQBrNPiQDlgMYMy14QIDAQAB
                o1AwTjAdBgNVHQ4EFgQUhKs8VQFhVLp5J4W1sFVLOVgnQxwwHwYDVR0jBBgwFoAU
                hKs8VQFhVLp5J4W1sFVLOVgnQxwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF
                AAOCAQEAuuExVilGcXIZ3ulNuL7wLrA7VkqJoGpB1YPmYnlS7sobTggOGSqMUvqU
                -----END CERTIFICATE-----
              rekor:
                url: https://rekor.sigstore.dev
        mutateDigest: true