# 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 ```json { "_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 ```bash # Install cosign brew install cosign # macOS # or go install github.com/sigstore/cosign/v2/cmd/cosign@latest ``` ### Basic Verification ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```yaml # 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() } ``` ```yaml # 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 ```yaml 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 ```yaml # .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 ```yaml # .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 ```bash # 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-.att ``` ### Signature Verification Failed ```bash # 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 ```bash # 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 ```bash # 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 --- ## Related Documentation - [Provcache Module README](./README.md) — Core concepts - [Provcache Metrics and Alerting](./metrics-alerting.md) — Observability - [Signer Module](../signer/architecture.md) — Signing infrastructure - [Attestor Module](../attestor/architecture.md) — Attestation generation - [OCI Artifact Spec](https://github.com/opencontainers/image-spec) — OCI standards - [In-toto Attestation Spec](https://github.com/in-toto/attestation) — Attestation format - [Sigstore Documentation](https://docs.sigstore.dev/) — Cosign and Fulcio