A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.

ATCR Signature Verification Integration Strategy#

Overview#

This document provides a comprehensive overview of how to integrate ATProto signature verification into various tools and workflows. ATCR uses a layered approach that provides maximum compatibility while maintaining ATProto's decentralized philosophy.

Architecture Layers#

┌─────────────────────────────────────────────────────────┐
│ Layer 4: Applications & Workflows                      │
│  - CI/CD pipelines                                      │
│  - Kubernetes admission control                         │
│  - Runtime verification                                 │
│  - Security scanning                                    │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Integration Methods                           │
│  - Plugins (Ratify, Gatekeeper, Containerd)            │
│  - CLI tools (atcr-verify)                              │
│  - External services (webhooks, APIs)                   │
│  - (Optional) X.509 certificates (hold-as-CA)           │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Layer 2: Signature Discovery                           │
│  - OCI Referrers API (GET /v2/.../referrers/...)        │
│  - ORAS artifact format                                 │
│  - artifactType: application/vnd.atproto.signature...   │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Layer 1: ATProto Signatures (Foundation)               │
│  - Manifests signed by PDS (K-256)                      │
│  - Signatures in ATProto repository commits             │
│  - Public keys in DID documents                         │
│  - DID-based identity                                   │
└─────────────────────────────────────────────────────────┘

Integration Approaches#

Best for: Kubernetes, standard tooling, production deployments

Integrate through plugin systems of existing tools:

Ratify Verifier Plugin#

  • Use case: Kubernetes admission control via Gatekeeper
  • Effort: 2-3 weeks to build
  • Maturity: CNCF Sandbox project, growing adoption
  • Benefits:
    • ✅ Standard plugin interface
    • ✅ Works with existing Ratify deployments
    • ✅ Policy-based enforcement
    • ✅ Multi-verifier support (can combine with Notation, Cosign)

Implementation:

// Ratify plugin interface
type ReferenceVerifier interface {
    VerifyReference(
        ctx context.Context,
        subjectRef common.Reference,
        referenceDesc ocispecs.ReferenceDescriptor,
        store referrerStore.ReferrerStore,
    ) (VerifierResult, error)
}

Deployment:

apiVersion: config.ratify.deislabs.io/v1beta1
kind: Verifier
metadata:
  name: atcr-verifier
spec:
  name: atproto
  artifactType: application/vnd.atproto.signature.v1+json
  parameters:
    trustedDIDs:
      - did:plc:alice123

See Ratify Integration Guide


OPA Gatekeeper External Provider#

  • Use case: Kubernetes admission control with OPA policies
  • Effort: 2-3 weeks to build
  • Maturity: Very stable, widely adopted
  • Benefits:
    • ✅ Rego-based policies (flexible)
    • ✅ External data provider API (standard)
    • ✅ Can reuse existing Gatekeeper deployments

Implementation:

// External data provider
type Provider struct {
    verifier *atproto.Verifier
}

func (p *Provider) Provide(ctx context.Context, req ProviderRequest) (*ProviderResponse, error) {
    image := req.Keys["image"]
    result, err := p.verifier.Verify(ctx, image)
    return &ProviderResponse{
        Data: map[string]bool{"verified": result.Verified},
    }, nil
}

Policy:

package verify

violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]
  startswith(container.image, "atcr.io/")

  response := external_data({
    "provider": "atcr-verifier",
    "keys": ["image"],
    "values": [container.image]
  })

  response.verified != true
  msg := sprintf("Image %v has no valid ATProto signature", [container.image])
}

See Gatekeeper Integration Guide


Containerd 2.0 Image Verifier Plugin#

  • Use case: Runtime verification at image pull time
  • Effort: 1-2 weeks to build
  • Maturity: New in Containerd 2.0 (Nov 2024)
  • Benefits:
    • ✅ Runtime enforcement (pull-time verification)
    • ✅ Works for Docker, nerdctl, ctr
    • ✅ Transparent to users
    • ✅ No Kubernetes required

Limitation: CRI plugin integration still maturing

Implementation:

#!/bin/bash
# /usr/local/bin/containerd-verifiers/atcr-verifier
# Binary called by containerd on image pull

# Containerd passes image info via stdin
read -r INPUT

IMAGE=$(echo "$INPUT" | jq -r '.reference')
DIGEST=$(echo "$INPUT" | jq -r '.descriptor.digest')

# Verify signature
if atcr-verify "$IMAGE@$DIGEST" --quiet; then
  exit 0  # Verified
else
  exit 1  # Failed
fi

Configuration:

# /etc/containerd/config.toml
[plugins."io.containerd.image-verifier.v1.bindir"]
  bin_dir = "/usr/local/bin/containerd-verifiers"
  max_verifiers = 5
  per_verifier_timeout = "10s"

See Containerd Integration Guide


Best for: CI/CD, scripts, general-purpose verification

Use atcr-verify CLI tool directly in workflows:

Command-Line Verification#

# Basic verification
atcr-verify atcr.io/alice/myapp:latest

# With trust policy
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml

# JSON output for scripting
atcr-verify atcr.io/alice/myapp:latest --output json

# Quiet mode for exit codes
atcr-verify atcr.io/alice/myapp:latest --quiet && echo "Verified"

CI/CD Integration#

GitHub Actions:

- name: Verify image
  run: atcr-verify ${{ env.IMAGE }} --policy .github/trust-policy.yaml

GitLab CI:

verify:
  image: atcr.io/atcr/verify:latest
  script:
    - atcr-verify ${IMAGE} --policy trust-policy.yaml

Universal Container:

docker run --rm atcr.io/atcr/verify:latest verify IMAGE

Benefits:

  • ✅ Works everywhere (not just Kubernetes)
  • ✅ Simple integration (single binary)
  • ✅ No plugin installation required
  • ✅ Offline mode support

See atcr-verify CLI Documentation


Approach 3: External Services#

Best for: Custom admission controllers, API-based verification

Build verification as a service that tools can call:

Webhook Service#

// HTTP endpoint for verification
func (h *Handler) VerifyImage(w http.ResponseWriter, r *http.Request) {
    image := r.URL.Query().Get("image")

    result, err := h.verifier.Verify(r.Context(), image)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    json.NewEncoder(w).Encode(map[string]interface{}{
        "verified": result.Verified,
        "did": result.Signature.DID,
        "signedAt": result.Signature.SignedAt,
    })
}

Usage from Kyverno#

verifyImages:
- imageReferences:
  - "atcr.io/*/*"
  attestors:
  - entries:
    - api:
        url: http://atcr-verify.kube-system/verify?image={{ image }}

Benefits:

  • ✅ Flexible integration
  • ✅ Centralized verification logic
  • ✅ Caching and rate limiting
  • ✅ Can add additional checks (vulnerability scanning, etc.)

Approach 4: Hold-as-CA (OPTIONAL, ENTERPRISE ONLY)#

Best for: Enterprise X.509 PKI compliance requirements

⚠️ WARNING: This approach introduces centralization trade-offs. Only use if you have specific X.509 compliance requirements.

Hold services act as Certificate Authorities that issue X.509 certificates for users, enabling standard Notation verification.

When to use:

  • Enterprise requires standard X.509 PKI
  • Cannot deploy custom plugins
  • Accept centralization trade-off for tool compatibility

When NOT to use:

  • Default deployments (use plugins instead)
  • Maximum decentralization required
  • Don't need X.509 compliance

See Hold-as-CA Architecture for complete details and security implications.


Tool Compatibility Matrix#

Tool Discover Verify Integration Method Priority Effort
Kubernetes
OPA Gatekeeper External provider HIGH 2-3 weeks
Ratify Verifier plugin HIGH 2-3 weeks
Kyverno ⚠️ External service MEDIUM 2 weeks
Portieris N/A (deprecated) NONE -
Runtime
Containerd 2.0 Bindir plugin MED-HIGH 1-2 weeks
CRI-O ⚠️ ⚠️ Upstream contribution MEDIUM 3-4 weeks
Podman ⚠️ ⚠️ Upstream contribution MEDIUM 3-4 weeks
CI/CD
GitHub Actions Custom action HIGH 1 week
GitLab CI Container image HIGH 1 week
Jenkins/CircleCI Container image HIGH 1 week
Scanners
Trivy N/A (not verifier) NONE -
Snyk N/A (not verifier) NONE -
Anchore N/A (not verifier) NONE -
Registries
Harbor ⚠️ UI integration LOW -
OCI Tools
ORAS CLI Already works Document -
Notation ⚠️ ⚠️ Hold-as-CA OPTIONAL 3-4 weeks
Cosign Not compatible NONE -
Crane Already works Document -
Skopeo ⚠️ ⚠️ Upstream contribution LOW 3-4 weeks

Legend:

  • ✅ Works / Feasible
  • ⚠️ Partial / Requires changes
  • ❌ Not applicable / Not feasible

Implementation Roadmap#

Phase 1: Foundation (4-5 weeks) ⭐#

Goal: Core verification capability

  1. atcr-verify CLI tool (Week 1-2)

    • ATProto signature verification
    • Trust policy support
    • Multiple output formats
    • Offline mode
  2. OCI Referrers API (Week 2-3)

    • AppView endpoint implementation
    • ORAS artifact serving
    • Integration with existing SBOM pattern
  3. CI/CD Container Image (Week 3)

    • Universal verification image
    • Documentation for GitHub Actions, GitLab CI
    • Example workflows
  4. Documentation (Week 4-5)

    • Integration guides
    • Trust policy examples
    • Troubleshooting guides

Deliverables:

  • atcr-verify binary (Linux, macOS, Windows)
  • atcr.io/atcr/verify:latest container image
  • OCI Referrers API implementation
  • Complete documentation

Phase 2: Kubernetes Integration (3-4 weeks)#

Goal: Production-ready Kubernetes admission control

  1. OPA Gatekeeper Provider (Week 1-2)

    • External data provider service
    • Helm chart for deployment
    • Example policies
  2. Ratify Plugin (Week 2-3)

    • Verifier plugin implementation
    • Testing with Ratify
    • Documentation
  3. Kubernetes Examples (Week 4)

    • Deployment manifests
    • Policy examples
    • Integration testing

Deliverables:

  • atcr-gatekeeper-provider service
  • Ratify plugin binary
  • Kubernetes deployment examples
  • Production deployment guide

Phase 3: Runtime Verification (2-3 weeks)#

Goal: Pull-time verification

  1. Containerd Plugin (Week 1-2)

    • Bindir verifier implementation
    • Configuration documentation
    • Testing with Docker, nerdctl
  2. CRI-O/Podman Integration (Week 3, optional)

    • Upstream contribution (if accepted)
    • Policy.json extension
    • Documentation

Deliverables:

  • Containerd verifier binary
  • Configuration guides
  • Runtime verification examples

Phase 4: Optional Features (2-3 weeks)#

Goal: Enterprise features (if demanded)

  1. Hold-as-CA (Week 1-2, optional)

    • Certificate generation
    • Notation signature creation
    • Trust store distribution
    • Only if enterprise customers request
  2. Advanced Features (Week 3, as needed)

    • Signature transparency log
    • Multi-signature support
    • Hardware token integration

Deliverables:

  • Hold co-signing implementation (if needed)
  • Advanced feature documentation

Decision Matrix#

Which Integration Approach Should I Use?#

┌─────────────────────────────────────────────────┐
│ Are you using Kubernetes?                      │
└───────────────┬─────────────────────────────────┘
                │
       ┌────────┴────────┐
       │                 │
      YES               NO
       │                 │
       ↓                 ↓
┌──────────────┐  ┌──────────────┐
│ Using        │  │ CI/CD        │
│ Gatekeeper?  │  │ Pipeline?    │
└──────┬───────┘  └──────┬───────┘
       │                 │
  ┌────┴────┐       ┌────┴────┐
 YES       NO      YES       NO
  │         │       │         │
  ↓         ↓       ↓         ↓
External  Ratify  GitHub   Universal
Provider  Plugin  Action   CLI Tool

Use OPA Gatekeeper Provider if:#

  • ✅ Already using Gatekeeper
  • ✅ Want Rego-based policies
  • ✅ Need flexible policy logic

Use Ratify Plugin if:#

  • ✅ Using Ratify (or planning to)
  • ✅ Want standard plugin interface
  • ✅ Need multi-verifier support (Notation + Cosign + ATProto)

Use atcr-verify CLI if:#

  • ✅ CI/CD pipelines
  • ✅ Local development
  • ✅ Non-Kubernetes environments
  • ✅ Want simple integration

Use Containerd Plugin if:#

  • ✅ Need runtime enforcement
  • ✅ Want pull-time verification
  • ✅ Using Containerd 2.0+

Use Hold-as-CA if:#

  • ⚠️ Enterprise X.509 PKI compliance required
  • ⚠️ Cannot deploy plugins
  • ⚠️ Accept centralization trade-off

Best Practices#

1. Start Simple#

Begin with CLI tool integration in CI/CD:

# Add to .github/workflows/deploy.yml
- run: atcr-verify $IMAGE --policy .github/trust-policy.yaml

2. Define Trust Policies#

Create trust policies early:

# trust-policy.yaml
policies:
  - name: production
    scope: "atcr.io/*/prod-*"
    require:
      signature: true
      trustedDIDs: [did:plc:devops-team]
    action: enforce

3. Progressive Rollout#

  1. Week 1: Add verification to CI/CD (audit mode)
  2. Week 2: Enforce in CI/CD
  3. Week 3: Add Kubernetes admission control (audit mode)
  4. Week 4: Enforce in Kubernetes

4. Monitor and Alert#

Track verification metrics:

  • Verification success/failure rates
  • Policy violations
  • Signature coverage (% of images signed)

5. Plan for Key Rotation#

  • Document DID key rotation procedures
  • Test key rotation in non-production
  • Monitor for unexpected key changes

Common Patterns#

Pattern 1: Multi-Layer Defense#

1. CI/CD verification (atcr-verify)
   ↓ (blocks unsigned images from being pushed)
2. Kubernetes admission (Gatekeeper/Ratify)
   ↓ (blocks unsigned images from running)
3. Runtime verification (Containerd plugin)
   ↓ (blocks unsigned images from being pulled)

Pattern 2: Trust Policy Inheritance#

# Global policy
trustedDIDs:
  - did:plc:security-team  # Always trusted

# Environment-specific policies
staging:
  trustedDIDs:
    - did:plc:developers  # Additional trust for staging

production:
  trustedDIDs: []  # Only global trust (security-team)

Pattern 3: Offline Verification#

# Build environment (online)
atcr-verify export $IMAGE -o bundle.json

# Air-gapped environment (offline)
atcr-verify $IMAGE --offline --bundle bundle.json

Migration Guide#

From Docker Content Trust (DCT)#

DCT is deprecated. Migrate to ATCR signatures:

Old (DCT):

export DOCKER_CONTENT_TRUST=1
docker push myimage:latest

New (ATCR):

# Signatures created automatically on push
docker push atcr.io/myorg/myimage:latest

# Verify in CI/CD
atcr-verify atcr.io/myorg/myimage:latest

From Cosign#

Cosign and ATCR signatures can coexist:

Dual signing:

# Push to ATCR (ATProto signature automatic)
docker push atcr.io/myorg/myimage:latest

# Also sign with Cosign (if needed)
cosign sign atcr.io/myorg/myimage:latest

Verification:

# Verify ATProto signature
atcr-verify atcr.io/myorg/myimage:latest

# Or verify Cosign signature
cosign verify atcr.io/myorg/myimage:latest --key cosign.pub

Troubleshooting#

Signatures Not Found#

Symptom: atcr-verify reports "no signature found"

Diagnosis:

# Check if Referrers API works
curl "https://atcr.io/v2/OWNER/REPO/referrers/DIGEST"

# Check if signature artifact exists
oras discover atcr.io/OWNER/REPO:TAG

Solutions:

  1. Verify Referrers API is implemented
  2. Re-push image to generate signature
  3. Check AppView logs for signature creation errors

DID Resolution Fails#

Symptom: Cannot resolve DID to public key

Diagnosis:

# Test DID resolution
curl https://plc.directory/did:plc:XXXXXX

# Check DID document has verificationMethod
curl https://plc.directory/did:plc:XXXXXX | jq .verificationMethod

Solutions:

  1. Check internet connectivity
  2. Verify DID is valid
  3. Ensure DID document contains public key

Policy Violations#

Symptom: Verification fails with "trust policy violation"

Diagnosis:

# Verify with verbose output
atcr-verify IMAGE --policy policy.yaml --verbose

Solutions:

  1. Add DID to trustedDIDs list
  2. Check signature age vs. maxAge
  3. Verify policy scope matches image

See Also#