Files
git.stella-ops.org/docs/modules/provcache/oci-attestation-verification.md
2025-12-25 23:10:09 +02:00

11 KiB

Provcache OCI Attestation Verification Guide

This document describes how to verify Provcache decision attestations attached to OCI container images.

Overview

StellaOps can attach provenance cache decisions as OCI-attached attestations to container images. These attestations enable:

  • Supply chain verification — Verify security decisions were made by trusted evaluators
  • Audit trails — Retrieve the exact decision state at image push time
  • Policy gates — Admission controllers can verify attestations before deployment
  • Offline verification — Decisions verifiable without calling StellaOps services

Attestation Format

Predicate Type

stella.ops/provcache@v1

Predicate Schema

{
  "_type": "stella.ops/provcache@v1",
  "veriKey": "sha256:abc123...",
  "decision": {
    "digestVersion": "v1",
    "verdictHash": "sha256:def456...",
    "proofRoot": "sha256:789abc...",
    "trustScore": 85,
    "createdAt": "2025-12-24T12:00:00Z",
    "expiresAt": "2025-12-25T12:00:00Z"
  },
  "inputs": {
    "sourceDigest": "sha256:image...",
    "sbomDigest": "sha256:sbom...",
    "policyDigest": "sha256:policy...",
    "feedEpoch": "2024-W52"
  },
  "verdicts": {
    "CVE-2024-1234": "mitigated",
    "CVE-2024-5678": "affected"
  }
}

Field Descriptions

Field Type Description
_type string Predicate type URI
veriKey string VeriKey hash identifying this decision context
decision.digestVersion string Decision digest schema version
decision.verdictHash string Hash of all verdicts
decision.proofRoot string Merkle proof root hash
decision.trustScore number Overall trust score (0-100)
decision.createdAt string ISO-8601 creation timestamp
decision.expiresAt string ISO-8601 expiry timestamp
inputs.sourceDigest string Container image digest
inputs.sbomDigest string SBOM document digest
inputs.policyDigest string Policy bundle digest
inputs.feedEpoch string Feed epoch identifier
verdicts object Map of CVE IDs to verdict status

Verification with Cosign

Prerequisites

# Install cosign
brew install cosign  # macOS
# or
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

Basic Verification

# Verify attestation exists and is signed
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  registry.example.com/app:v1.2.3

Verify with Identity Constraints

# Verify with signer identity (Fulcio)
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  --certificate-identity-regexp '.*@stellaops\.example\.com' \
  --certificate-oidc-issuer https://auth.stellaops.example.com \
  registry.example.com/app:v1.2.3

Verify with Custom Trust Root

# Using enterprise CA
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  --certificate /path/to/enterprise-ca.crt \
  --certificate-chain /path/to/ca-chain.crt \
  registry.example.com/app:v1.2.3

Extract Attestation Payload

# Get raw attestation JSON
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  --certificate-identity-regexp '.*@stellaops\.example\.com' \
  --certificate-oidc-issuer https://auth.stellaops.example.com \
  registry.example.com/app:v1.2.3 | jq '.payload' | base64 -d | jq .

Verification with StellaOps CLI

Verify Attestation

# Verify using StellaOps CLI
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache

# Output:
# ✓ Attestation found: stella.ops/provcache@v1
# ✓ Signature valid (Fulcio)
# ✓ Trust score: 85
# ✓ Decision created: 2025-12-24T12:00:00Z
# ✓ Decision expires: 2025-12-25T12:00:00Z

Verify with Policy Requirements

# Verify with minimum trust score
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache \
  --min-trust-score 80

# Verify with freshness requirement
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache \
  --max-age 24h

Extract Decision Details

# Get full decision details
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache \
  --output json | jq .

# Get specific fields
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache \
  --output json | jq '.predicate.verdicts'

Kubernetes Admission Control

Gatekeeper Policy

# constraint-template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: provcacheattestation
spec:
  crd:
    spec:
      names:
        kind: ProvcacheAttestation
      validation:
        openAPIV3Schema:
          type: object
          properties:
            minTrustScore:
              type: integer
              minimum: 0
              maximum: 100
            maxAgeHours:
              type: integer
              minimum: 1
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package provcacheattestation

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          image := container.image
          not has_valid_attestation(image)
          msg := sprintf("Image %v missing valid provcache attestation", [image])
        }

        has_valid_attestation(image) {
          attestation := get_attestation(image, "stella.ops/provcache@v1")
          attestation.predicate.decision.trustScore >= input.parameters.minTrustScore
          not is_expired(attestation.predicate.decision.expiresAt)
        }

        is_expired(expiry) {
          time.parse_rfc3339_ns(expiry) < time.now_ns()
        }
# constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ProvcacheAttestation
metadata:
  name: require-provcache-attestation
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - production
  parameters:
    minTrustScore: 80
    maxAgeHours: 48

Kyverno Policy

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-provcache-attestation
spec:
  validationFailureAction: enforce
  background: true
  rules:
    - name: check-provcache-attestation
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "*"
          attestations:
            - predicateType: stella.ops/provcache@v1
              conditions:
                - all:
                    - key: "{{ decision.trustScore }}"
                      operator: GreaterThanOrEquals
                      value: 80
                    - key: "{{ decision.expiresAt }}"
                      operator: GreaterThan
                      value: "{{ time.Now() }}"
          attestors:
            - entries:
                - keyless:
                    issuer: https://auth.stellaops.example.com
                    subject: ".*@stellaops\\.example\\.com"

CI/CD Integration

GitHub Actions

# .github/workflows/verify-attestation.yml
name: Verify Provcache Attestation

on:
  workflow_dispatch:
    inputs:
      image:
        description: 'Image to verify'
        required: true

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Verify attestation
        run: |
          cosign verify-attestation \
            --type stella.ops/provcache@v1 \
            --certificate-identity-regexp '.*@stellaops\.example\.com' \
            --certificate-oidc-issuer https://auth.stellaops.example.com \
            ${{ inputs.image }}

      - name: Check trust score
        run: |
          TRUST_SCORE=$(cosign verify-attestation \
            --type stella.ops/provcache@v1 \
            --certificate-identity-regexp '.*@stellaops\.example\.com' \
            --certificate-oidc-issuer https://auth.stellaops.example.com \
            ${{ inputs.image }} | jq -r '.payload' | base64 -d | jq '.predicate.decision.trustScore')
          
          if [ "$TRUST_SCORE" -lt 80 ]; then
            echo "Trust score $TRUST_SCORE is below threshold (80)"
            exit 1
          fi

GitLab CI

# .gitlab-ci.yml
verify-attestation:
  stage: verify
  image: gcr.io/projectsigstore/cosign:latest
  script:
    - cosign verify-attestation
        --type stella.ops/provcache@v1
        --certificate-identity-regexp '.*@stellaops\.example\.com'
        --certificate-oidc-issuer https://auth.stellaops.example.com
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}
  rules:
    - if: $CI_COMMIT_TAG

Troubleshooting

No Attestation Found

# List all attestations on image
cosign tree registry.example.com/app:v1.2.3

# Check if attestation was pushed
crane manifest registry.example.com/app:sha256-<digest>.att

Signature Verification Failed

# Check certificate details
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  --output text \
  registry.example.com/app:v1.2.3 2>&1 | grep -A5 "Certificate"

# Verify with verbose output
COSIGN_EXPERIMENTAL=1 cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  registry.example.com/app:v1.2.3 -v

Attestation Expired

# Check expiry timestamp
cosign verify-attestation \
  --type stella.ops/provcache@v1 \
  --certificate-identity-regexp '.*@stellaops\.example\.com' \
  --certificate-oidc-issuer https://auth.stellaops.example.com \
  registry.example.com/app:v1.2.3 | \
  jq -r '.payload' | base64 -d | jq '.predicate.decision.expiresAt'

Trust Score Below Threshold

# Check trust score breakdown
stella verify attestation \
  --image registry.example.com/app:v1.2.3 \
  --type provcache \
  --output json | jq '.predicate.decision.trustScore'

# If score is low, check individual components:
# - SBOM completeness
# - VEX coverage
# - Reachability analysis
# - Policy freshness
# - Signer trust

Security Considerations

Key Management

  • Fulcio — Ephemeral certificates tied to OIDC identity; recommended for public workflows
  • Enterprise CA — Long-lived certificates for air-gapped environments
  • Self-signed — Only for development/testing; not recommended for production

Attestation Integrity

  • Attestations are signed at push time
  • Signature covers the entire predicate payload
  • Modifying any field invalidates the signature

Expiry Handling

  • Attestations have expiresAt timestamps
  • Expired attestations should be rejected by admission controllers
  • Consider re-scanning images before deployment to get fresh attestations

Verdict Reconciliation

  • Verdicts in attestation reflect state at push time
  • New vulnerabilities discovered after push won't appear
  • Use stella verify attestation --check-freshness to compare against current feeds