7 min read

Container Attestations: A Complete Guide

Learn how to implement software attestations to verify the integrity and provenance of your container images and build processes.

AttestationsSupply ChainContainer SecuritySigstore

Container attestations provide cryptographic proof about your software's provenance, integrity, and compliance with security policies. In this comprehensive guide, we'll explore how to implement attestations in your container workflows.

What are Software Attestations?

Software attestations are cryptographically signed statements about software artifacts. They answer critical questions like:

  • Who built this software?
  • When was it built?
  • How was it built?
  • What went into building it?

For containers, attestations can cover:

  • Build provenance (source code, build environment, dependencies)
  • Security scan results
  • Code review status
  • Compliance attestations
  • SBOM attestations

Why Attestations Matter

Supply Chain Attacks

Recent attacks like SolarWinds and CodeCov demonstrate the critical need for software provenance:

# Without attestations, you can't verify:
# - Was this image built from the expected source?
# - Did it come from your trusted CI/CD system?
# - Has it been tampered with since creation?

# With attestations, you get cryptographic proof
cosign verify-attestation myregistry/myapp:v1.0.0 \
  --policy policy.yaml \
  --certificate-oidc-issuer https://github.com/actions

Compliance Requirements

Many frameworks now require attestations:

  • SLSA (Supply-chain Levels for Software Artifacts)
  • NIST SSDF (Secure Software Development Framework)
  • EU Cyber Resilience Act

Types of Container Attestations

1. Provenance Attestations

Provenance attestations document the build process:

{
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "subject": [
    {
      "name": "myregistry/myapp",
      "digest": {
        "sha256": "abc123..."
      }
    }
  ],
  "predicate": {
    "builder": {
      "id": "https://github.com/actions"
    },
    "buildType": "https://github.com/actions/runner",
    "invocation": {
      "configSource": {
        "uri": "https://github.com/myorg/myapp",
        "digest": {
          "sha256": "def456..."
        }
      }
    }
  }
}

2. SBOM Attestations

Attach SBOMs as signed attestations:

# Generate SBOM
syft myregistry/myapp:v1.0.0 -o spdx-json > sbom.spdx.json

# Create SBOM attestation
cosign attest --predicate sbom.spdx.json \
  --type spdx \
  myregistry/myapp:v1.0.0

3. Vulnerability Scan Attestations

Attach security scan results:

# Run security scan
grype myregistry/myapp:v1.0.0 -o json > scan-results.json

# Create scan attestation
cosign attest --predicate scan-results.json \
  --type vuln \
  myregistry/myapp:v1.0.0

Implementing Attestations with Sigstore

Sigstore provides keyless signing for software supply chains:

Basic Setup

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

# Sign a container image
cosign sign myregistry/myapp:v1.0.0

# Verify signature
cosign verify myregistry/myapp:v1.0.0 \
  --certificate-identity="user@example.com" \
  --certificate-oidc-issuer="https://github.com/actions"

GitHub Actions Integration

name: Build and Attest
on: [push]

permissions:
  contents: read
  id-token: write
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
      
      - name: Install Cosign
        uses: sigstore/cosign-installer@v3
      
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/${{ github.repository }}:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json
      
      - name: Attest SBOM
        run: |
          cosign attest --yes --predicate sbom.spdx.json \
            --type spdx \
            ghcr.io/${{ github.repository }}:${{ github.sha }}
      
      - name: Generate Provenance
        uses: actions/attest-build-provenance@v1
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: ${{ steps.build.outputs.digest }}

Advanced Attestation Workflows

Custom Attestation Types

Create domain-specific attestations:

# Create custom compliance attestation
cat <<EOF > compliance.json
{
  "compliantWith": ["SOC2", "ISO27001"],
  "auditDate": "2024-01-15T10:30:00Z",
  "auditor": "security-team@company.com",
  "findings": []
}
EOF

# Attach as attestation
cosign attest --predicate compliance.json \
  --type "https://company.com/compliance/v1" \
  myregistry/myapp:v1.0.0

Multi-Stage Attestations

For multi-stage builds, attest each stage:

ARG BUILDKIT_SBOM_SCAN_STAGE=true
# Multi-stage Dockerfile
FROM node:18 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install

FROM deps AS builder
COPY . .
RUN npm run build

FROM node:18-slim AS runtime
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]
# Build with attestations for each stage
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --provenance true \
  --sbom true \
  -t myregistry/myapp:v1.0.0 \
  --push .

The argument BUILDKIT_SBOM_SCAN_STAGE=true enables SBOM generation for each build stage during the multi-stage build. The attestation is pushed to the registry along with the image.

Policy-Based Verification

Use OPA (Open Policy Agent) for attestation policies:

# policy.rego
package sigstore

import rego.v1

# Require SBOM attestation
required_attestations := ["https://spdx.dev/Document"]

# Require GitHub Actions provenance
trusted_builders := ["https://github.com/actions"]

allow if {
    # Check for required attestations
    count(required_attestations) == count([att |
        att := required_attestations[_]
        att in input.predicateTypes
    ])
    
    # Verify builder
    input.predicate.builder.id in trusted_builders
    
    # Ensure recent build
    build_time := time.parse_rfc3339_ns(input.predicate.metadata.buildFinishedOn)
    now := time.now_ns()
    (now - build_time) < (7 * 24 * 60 * 60 * 1000000000) # 7 days
}

Verifying Attestations

At Deployment Time

#!/bin/bash
# verify-attestations.sh

IMAGE="$1"
POLICY_FILE="policy.yaml"

echo "Verifying attestations for $IMAGE..."

# Verify image signature
if ! cosign verify "$IMAGE" --certificate-oidc-issuer="https://github.com/actions"; then
    echo "❌ Image signature verification failed"
    exit 1
fi

# Verify SBOM attestation exists
if ! cosign verify-attestation "$IMAGE" --type=spdx --policy="$POLICY_FILE"; then
    echo "❌ SBOM attestation verification failed"
    exit 1
fi

# Verify provenance attestation
if ! cosign verify-attestation "$IMAGE" --type=slsaprovenance --policy="$POLICY_FILE"; then
    echo "❌ Provenance attestation verification failed"
    exit 1
fi

echo "✅ All attestations verified successfully"

Kubernetes Integration

Use admission controllers to verify attestations:

# Admission controller policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-attestations
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: check-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "*"
      attestors:
      - entries:
        - keyless:
            subject: "https://github.com/myorg/*"
            issuer: "https://github.com/actions"
      attestations:
      - predicateType: "https://slsa.dev/provenance/v0.2"
      - predicateType: "https://spdx.dev/Document"

Monitoring and Analytics

Track attestation coverage across your infrastructure:

# Script to audit attestation coverage
#!/bin/bash

REGISTRY="myregistry"
NAMESPACE="production"

echo "Auditing attestation coverage..."

# Get all images in namespace
kubectl get pods -n "$NAMESPACE" -o jsonpath='{.items[*].spec.containers[*].image}' | \
tr ' ' '\n' | sort -u | \
while read image; do
    echo "Checking $image..."
    
    # Check for signature
    if cosign verify "$image" >/dev/null 2>&1; then
        echo "  ✅ Signature verified"
    else
        echo "  ❌ No valid signature"
    fi
    
    # Check for SBOM attestation
    if cosign verify-attestation "$image" --type=spdx >/dev/null 2>&1; then
        echo "  ✅ SBOM attestation found"
    else
        echo "  ❌ No SBOM attestation"
    fi
    
    # Check for provenance attestation
    if cosign verify-attestation "$image" --type=slsaprovenance >/dev/null 2>&1; then
        echo "  ✅ Provenance attestation found"
    else
        echo "  ❌ No provenance attestation"
    fi
done

Best Practices

1. Implement Gradually

Start with signing, then add attestations:

# Phase 1: Basic signing
cosign sign myregistry/myapp:v1.0.0

# Phase 2: Add SBOM attestations
cosign attest --predicate sbom.json --type spdx myregistry/myapp:v1.0.0

# Phase 3: Add provenance
# (via GitHub Actions or similar)

# Phase 4: Add custom attestations
cosign attest --predicate compliance.json --type custom myregistry/myapp:v1.0.0

2. Use Keyless Signing

Leverage OIDC identity for keyless workflows:

# GitHub Actions - automatically uses keyless signing
- name: Sign image
  run: cosign sign --yes ${{ env.IMAGE }}
  env:
    COSIGN_EXPERIMENTAL: 1

3. Store Attestations Properly

Use transparency logs and distributed storage:

# Verify attestation was logged in Rekor
cosign verify-attestation myregistry/myapp:v1.0.0 \
  --type=spdx \
  --rekor-url=https://rekor.sigstore.dev

Troubleshooting Common Issues

Verification Failures

# Debug verification issues
cosign verify-attestation myregistry/myapp:v1.0.0 \
  --type=spdx \
  --certificate-identity="user@example.com" \
  --certificate-oidc-issuer="https://github.com/actions" \
  --verbose

Missing Attestations

# List all attestations for an image
cosign tree myregistry/myapp:v1.0.0

Next Steps

Attestations are a powerful tool for supply chain security. Consider:

  1. Start Small: Begin with basic image signing
  2. Automate: Integrate into CI/CD pipelines
  3. Monitor: Track coverage and compliance
  4. Evolve: Add custom attestation types as needed

In our next post, we'll explore policy enforcement and how to use attestations for admission control in Kubernetes.

Resources

This is an excerpt from "Docker & Kubernetes Security" - get the full book for complete coverage of attestations, SBOMs, and container security best practices.