Container Attestations: A Complete Guide
Learn how to implement software attestations to verify the integrity and provenance of your container images and build processes.
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:
- Start Small: Begin with basic image signing
- Automate: Integrate into CI/CD pipelines
- Monitor: Track coverage and compliance
- 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.