Skip to content

Kyverno CVE Scanning Gates

Requires vulnerability scan attestations before deployment. Blocks images with critical or high severity CVEs from reaching production environments.

Scan Attestations Require Image Signatures

CVE scanning gates depend on cosign attestations. Combine with image signature verification policies to ensure attestation authenticity.


Template 5: CVE Scanning Gates

Requires vulnerability scan attestations before deployment. Blocks images with critical or high severity CVEs from reaching production.

Complete Policy

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-vulnerability-scan
  namespace: kyverno
spec:
  validationFailureAction: enforce
  background: false  # Attestation verification only works on admission
  webhookTimeoutSeconds: 30
  failurePolicy: Fail
  rules:
    - name: verify-vulnerability-scan-attestation
      match:
        resources:
          kinds:
            - Pod
            - Deployment
            - StatefulSet
            - DaemonSet
            - Job
            - CronJob
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          attestors:
            - count: 1
              entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8xVwU5R3...
                      -----END PUBLIC KEY-----
          attestations:
            - predicateType: https://cosign.sigstore.dev/attestation/vuln/v1
              conditions:
                - all:
                    # Require trivy scanner
                    - key: "{{ attestation.predicate.scanner.name }}"
                      operator: Equals
                      value: "trivy"
                    # Block critical vulnerabilities
                    - key: "{{ attestation.predicate.metadata.scanResult.vulnerabilities[?severity=='CRITICAL'] | length(@) }}"
                      operator: LessThanOrEquals
                      value: 0
                    # Block high vulnerabilities in production
                    - key: "{{ attestation.predicate.metadata.scanResult.vulnerabilities[?severity=='HIGH'] | length(@) }}"
                      operator: LessThanOrEquals
                      value: 0
                    # Scan must be recent (within 24 hours)
                    - key: "{{ attestation.predicate.metadata.scanFinishedOn }}"
                      operator: GreaterThan
                      value: "{{ time_now_utc() - '24h' }}"
    - name: allow-medium-cves-in-dev
      match:
        resources:
          kinds:
            - Pod
            - Deployment
            - StatefulSet
            - DaemonSet
            - Job
            - CronJob
          namespaces:
            - development
            - staging
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          attestors:
            - count: 1
              entries:
                - keys:
                    publicKeys: "{{ configmap.kyverno.data.cosign-pub }}"
          attestations:
            - predicateType: https://cosign.sigstore.dev/attestation/vuln/v1
              conditions:
                - all:
                    # Block only critical in dev/staging
                    - key: "{{ attestation.predicate.metadata.scanResult.vulnerabilities[?severity=='CRITICAL'] | length(@) }}"
                      operator: LessThanOrEquals
                      value: 0

Customization Variables

Variable Default Purpose
validationFailureAction enforce Block images with CVEs
webhookTimeoutSeconds 30 Timeout for attestation verification
max-critical-cves 0 Max allowed CRITICAL vulnerabilities
max-high-cves 0 Max allowed HIGH vulnerabilities
scan-freshness 24h Maximum age of vulnerability scan
scanner-name trivy Required vulnerability scanner
dev-namespaces development, staging Allow more CVEs in non-prod

Validation Commands

# Apply policy
kubectl apply -f cve-scanning-policy.yaml

# Scan image and generate attestation
trivy image --format cosign-vuln --output vuln-report.json registry.example.com/app:v1.2.3
cosign attest --predicate vuln-report.json --key cosign.key --type vuln registry.example.com/app@sha256:abc123

# Verify attestation locally
cosign verify-attestation --key cosign.pub --type vuln registry.example.com/app@sha256:abc123

# Test image without scan (should fail)
kubectl run test --image=registry.example.com/unscanned@sha256:abc123

# Test image with passing scan (should pass)
kubectl run test --image=registry.example.com/scanned@sha256:def456

# Check CVE gate violations
kubectl logs -n kyverno deployment/kyverno | grep "vulnerability scan"

# List images in cluster without scan attestations
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.spec.containers[*].image}{"\n"}{end}'

Use Cases

  1. Zero-Day Protection: Block images with newly discovered critical CVEs
  2. Compliance Requirements: Meet PCI-DSS, SOC2 requirements for vulnerability scanning
  3. Shift-Left Security: Catch vulnerabilities before production deployment
  4. Risk Management: Different CVE thresholds for dev, staging, production
  5. Supply Chain Transparency: Require SBOM and vulnerability scan attestations

CI/CD Integration

Generate vulnerability scan attestations in GitHub Actions:

name: Build, Scan, and Attest

on:
  push:
    branches: [main]

jobs:
  build-scan-attest:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: |
          docker build -t registry.example.com/app:${{ github.sha }} .
          docker push registry.example.com/app:${{ github.sha }}

      - name: Install trivy and cosign
        run: |
          curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
          curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
          chmod +x cosign-linux-amd64
          sudo mv cosign-linux-amd64 /usr/local/bin/cosign

      - name: Scan for vulnerabilities
        run: |
          trivy image --format cosign-vuln --output vuln-report.json registry.example.com/app:${{ github.sha }}

          # Fail if critical or high CVEs found
          trivy image --severity CRITICAL,HIGH --exit-code 1 registry.example.com/app:${{ github.sha }}

      - name: Attest vulnerability scan
        run: |
          IMAGE_DIGEST=$(docker inspect registry.example.com/app:${{ github.sha }} --format='{{index .RepoDigests 0}}' | cut -d'@' -f2)
          cosign attest --yes --predicate vuln-report.json --type vuln registry.example.com/app@${IMAGE_DIGEST}

      - name: Generate SBOM
        run: |
          trivy image --format spdx-json --output sbom.spdx.json registry.example.com/app:${{ github.sha }}
          IMAGE_DIGEST=$(docker inspect registry.example.com/app:${{ github.sha }} --format='{{index .RepoDigests 0}}' | cut -d'@' -f2)
          cosign attest --yes --predicate sbom.spdx.json --type spdx registry.example.com/app@${IMAGE_DIGEST}

CVE Severity Mapping

Different environments can have different CVE tolerances:

# Production: Zero tolerance
- namespaces: [production, prod-*]
  max-critical: 0
  max-high: 0
  max-medium: 5

# Staging: Allow medium, block critical/high
- namespaces: [staging, stage-*]
  max-critical: 0
  max-high: 0
  max-medium: 999

# Development: Block only critical
- namespaces: [development, dev-*]
  max-critical: 0
  max-high: 10
  max-medium: 999

Vulnerability Scan Report Format

Example Trivy cosign-vuln output:

{
  "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1",
  "predicate": {
    "invocation": {
      "uri": "https://github.com/aquasecurity/trivy",
      "parameters": ["--severity", "CRITICAL,HIGH"]
    },
    "scanner": {
      "name": "trivy",
      "version": "0.48.0",
      "db": {
        "version": "2024-01-15"
      }
    },
    "metadata": {
      "scanStartedOn": "2024-01-15T10:00:00Z",
      "scanFinishedOn": "2024-01-15T10:05:00Z",
      "scanResult": {
        "vulnerabilities": [
          {
            "id": "CVE-2024-1234",
            "severity": "CRITICAL",
            "package": "openssl",
            "version": "1.1.1",
            "fixedVersion": "1.1.1w"
          }
        ]
      }
    }
  }
}

Combining with Base Image Policies

For maximum security, combine CVE scanning with base image enforcement:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: comprehensive-image-security
spec:
  validationFailureAction: enforce
  background: false
  rules:
    - name: approved-base-and-scanned
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Images must use approved base image"
        pattern:
          spec:
            containers:
              - image: "registry.example.com/base/*"
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          attestors:
            - count: 1
              entries:
                - keyless:
                    subject: "https://github.com/myorg/*"
                    issuer: "https://token.actions.githubusercontent.com"
          attestations:
            # Require vulnerability scan
            - predicateType: https://cosign.sigstore.dev/attestation/vuln/v1
              conditions:
                - all:
                    - key: "{{ attestation.predicate.metadata.scanResult.vulnerabilities[?severity=='CRITICAL'] | length(@) }}"
                      operator: Equals
                      value: 0
            # Require SBOM
            - predicateType: https://spdx.dev/Document

Troubleshooting

Common Issues

Error: no matching attestations found

# Verify image has attestation
cosign verify-attestation --key cosign.pub --type vuln registry.example.com/app@sha256:abc123

# Check attestation format matches policy
cosign verify-attestation --key cosign.pub --type vuln registry.example.com/app@sha256:abc123 | jq '.payload | @base64d | fromjson'

Error: scan too old

# Check scan timestamp
cosign verify-attestation --key cosign.pub --type vuln registry.example.com/app@sha256:abc123 | jq '.payload | @base64d | fromjson | .predicate.metadata.scanFinishedOn'

# Re-scan and re-attest
trivy image --format cosign-vuln --output vuln-report.json registry.example.com/app@sha256:abc123
cosign attest --yes --predicate vuln-report.json --type vuln registry.example.com/app@sha256:abc123

Error: critical vulnerabilities found

# View full vulnerability report
trivy image registry.example.com/app@sha256:abc123

# Fix vulnerabilities and rebuild
# Update base image or dependencies
docker build --no-cache -t registry.example.com/app:fixed .


External Documentation

Comments