Image Signing with ATProto#
ATCR provides cryptographic verification of container images through ATProto's native signature system. Every manifest stored in a PDS is cryptographically signed, providing tamper-proof image verification.
Overview#
Key Fact: Every image pushed to ATCR is automatically signed via ATProto's repository commit signing. No additional signing tools or steps are required.
When you push an image:
- Manifest stored in your PDS as an
io.atcr.manifestrecord - PDS signs the repository commit containing the manifest (ECDSA K-256)
- Signature is part of the ATProto repository chain
- Verification proves the manifest came from your DID and hasn't been tampered with
This document explains:
- How ATProto signatures work for ATCR images
- How to verify signatures using standard and custom tools
- Integration options for different use cases
- When to use optional X.509 certificates (Hold-as-CA)
ATProto Signature Model#
How It Works#
ATProto uses a repository commit signing model similar to Git:
1. docker push atcr.io/alice/myapp:latest
↓
2. AppView stores manifest in alice's PDS as io.atcr.manifest record
↓
3. PDS creates repository commit containing the new record
↓
4. PDS signs commit with alice's private key (ECDSA K-256)
↓
5. Commit becomes part of alice's cryptographically signed repo chain
What this proves:
- ✅ Manifest came from alice's PDS (DID-based identity)
- ✅ Manifest content hasn't been tampered with
- ✅ Manifest was created at a specific time (commit timestamp)
- ✅ Manifest is part of alice's verifiable repository history
Trust model:
- Public keys distributed via DID documents (PLC directory, did:web)
- Signatures use ECDSA K-256 (secp256k1)
- Verification is decentralized (no central CA required)
- Users control their own DIDs and can rotate keys
Signature Metadata#
In addition to ATProto's native commit signatures, ATCR creates ORAS signature artifacts that bridge ATProto signatures to the OCI ecosystem:
{
"$type": "io.atcr.atproto.signature",
"version": "1.0",
"subject": {
"digest": "sha256:abc123...",
"mediaType": "application/vnd.oci.image.manifest.v1+json"
},
"atproto": {
"did": "did:plc:alice123",
"handle": "alice.bsky.social",
"pdsEndpoint": "https://bsky.social",
"recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123",
"commitCid": "bafyreih8...",
"signedAt": "2025-10-31T12:34:56.789Z"
},
"signature": {
"algorithm": "ECDSA-K256-SHA256",
"keyId": "did:plc:alice123#atproto",
"publicKeyMultibase": "zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z"
}
}
Stored as:
- OCI artifact with
artifactType: application/vnd.atproto.signature.v1+json - Linked to image manifest via OCI Referrers API
- Discoverable by standard OCI tools (ORAS, Cosign, Crane)
Verification#
Quick Verification (Shell Script)#
For manual verification, use the provided shell scripts:
# Verify an image
./examples/verification/atcr-verify.sh atcr.io/alice/myapp:latest
# Output shows:
# - DID and handle of signer
# - PDS endpoint
# - ATProto record URI
# - Signature verification status
See: examples/verification/README.md for complete examples including:
- Standalone verification script
- Secure pull wrapper (verify before pull)
- Kubernetes webhook deployment
- CI/CD integration examples
Standard Tools (Discovery Only)#
Standard OCI tools can discover ATProto signature artifacts but cannot verify them (different signature format):
# Discover signatures with ORAS
oras discover atcr.io/alice/myapp:latest \
--artifact-type application/vnd.atproto.signature.v1+json
# Fetch signature metadata
oras pull atcr.io/alice/myapp@sha256:sig789...
# View with Cosign (discovery only)
cosign tree atcr.io/alice/myapp:latest
Note: Cosign/Notary cannot verify ATProto signatures directly because they use a different signature format and trust model. Use integration plugins or the atcr-verify CLI tool instead.
Integration Options#
ATCR supports multiple integration approaches depending on your use case:
1. Plugins (Recommended for Kubernetes) ⭐#
Build plugins for existing policy/verification engines:
Ratify Verifier Plugin:
- Integrates with OPA Gatekeeper
- Verifies ATProto signatures using Ratify's plugin interface
- Policy-based enforcement for Kubernetes
OPA Gatekeeper External Provider:
- HTTP service that verifies ATProto signatures
- Rego policies call external provider
- Flexible and easy to deploy
Containerd 2.0 Bindir Plugin:
- Verifies signatures at containerd level
- Works with any CRI-compatible runtime
- No Kubernetes required
See: docs/SIGNATURE_INTEGRATION.md for complete plugin implementation examples
2. CLI Tool (atcr-verify)#
Standalone CLI tool for signature verification:
# Install
go install github.com/atcr-io/atcr/cmd/atcr-verify@latest
# Verify image
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml
# Use in CI/CD
atcr-verify $IMAGE --quiet && kubectl apply -f deployment.yaml
Features:
- Trust policy management (which DIDs to trust)
- Multiple output formats (text, JSON, SARIF)
- Offline verification with cached DID documents
- Library usage for custom integrations
See: docs/ATCR_VERIFY_CLI.md for complete CLI specification
3. External Services#
Deploy verification as a service:
GitHub Actions:
- name: Verify image signature
uses: atcr-io/atcr-verify-action@v1
with:
image: atcr.io/alice/myapp:${{ github.sha }}
policy: .atcr/trust-policy.yaml
GitLab CI, Jenkins, CircleCI:
- Use
atcr-verifyCLI in pipeline - Fail build if verification fails
- Enforce signature requirements before deployment
4. X.509 Certificates (Hold-as-CA) ⚠️#
Optional approach where hold services issue X.509 certificates based on ATProto signatures:
Use cases:
- Enterprise environments requiring PKI compliance
- Tools that only support X.509 (legacy systems)
- Notation integration (P-256 certificates)
Trade-offs:
- ❌ Introduces centralization (hold acts as CA)
- ❌ Trust shifts from DIDs to hold operator
- ❌ Requires hold service infrastructure
See: docs/HOLD_AS_CA.md for complete architecture and security considerations
Integration Strategy Decision Matrix#
Choose the right integration approach:
| Use Case | Recommended Approach | Priority |
|---|---|---|
| Kubernetes admission control | Ratify plugin or Gatekeeper provider | HIGH |
| CI/CD verification | atcr-verify CLI or GitHub Actions | HIGH |
| Docker/containerd | Containerd bindir plugin | MEDIUM |
| Policy enforcement | OPA Gatekeeper + external provider | HIGH |
| Manual verification | Shell scripts or atcr-verify CLI | LOW |
| Enterprise PKI compliance | Hold-as-CA (X.509 certificates) | OPTIONAL |
| Legacy tool support | Hold-as-CA or external bridge service | OPTIONAL |
See: docs/INTEGRATION_STRATEGY.md for complete integration planning guide including:
- Architecture layers and data flow
- Tool compatibility matrix (16+ tools)
- Implementation roadmap (4 phases)
- When to use each approach
Trust Policies#
Define which signatures you trust:
# trust-policy.yaml
version: 1.0
trustedDIDs:
did:plc:alice123:
name: "Alice (DevOps Lead)"
validFrom: "2024-01-01T00:00:00Z"
expiresAt: null
did:plc:bob456:
name: "Bob (Security Team)"
validFrom: "2024-06-01T00:00:00Z"
expiresAt: "2025-12-31T23:59:59Z"
policies:
- name: production-images
scope: "atcr.io/*/prod-*"
require:
signature: true
trustedDIDs:
- did:plc:alice123
- did:plc:bob456
minSignatures: 1
action: enforce # reject if policy fails
- name: dev-images
scope: "atcr.io/*/dev-*"
require:
signature: false
action: audit # log but don't reject
Use with:
atcr-verifyCLI:atcr-verify IMAGE --policy trust-policy.yaml- Kubernetes webhooks: ConfigMap with policy
- CI/CD pipelines: Fail build if policy not met
Security Considerations#
What ATProto Signatures Prove#
✅ Identity: Manifest signed by specific DID (e.g., did:plc:alice123)
✅ Integrity: Manifest content hasn't been tampered with
✅ Timestamp: When the manifest was signed
✅ Authenticity: Signature created with private key for that DID
What They Don't Prove#
❌ Vulnerability-free: Signature doesn't mean image is safe ❌ Authorization: DID ownership doesn't imply permission to deploy ❌ Key security: Private key could be compromised ❌ PDS trustworthiness: Malicious PDS could create fake records
Trust Dependencies#
When verifying signatures, you're trusting:
- DID resolution (PLC directory, did:web) - public key is correct for DID
- PDS integrity - PDS serves correct records and doesn't forge signatures
- Cryptographic primitives - ECDSA K-256 remains secure
- Your trust policy - DIDs you've chosen to trust are legitimate
Best Practices#
1. Use Trust Policies Don't blindly trust all signatures - define which DIDs you trust:
trustedDIDs:
- did:plc:your-org-team
- did:plc:your-ci-system
2. Monitor Signature Coverage Track which images have signatures:
atcr-verify --check-coverage namespace/production
3. Enforce in Production Use Kubernetes admission control to block unsigned images:
# Ratify + Gatekeeper or custom webhook
enforceSignatures: true
failurePolicy: Fail
4. Verify in CI/CD Never deploy unsigned images:
# GitHub Actions
- name: Verify signature
run: atcr-verify $IMAGE || exit 1
5. Plan for Compromised Keys
- Rotate DID keys periodically
- Monitor DID documents for unexpected key changes
- Have incident response plan for key compromise
Implementation Status#
✅ Available Now#
- ATProto signatures: All manifests automatically signed by PDS
- ORAS artifacts: Signature metadata stored as OCI artifacts
- OCI Referrers API: Discovery via standard OCI endpoints
- Shell scripts: Manual verification examples
- Documentation: Complete integration guides
🔄 In Development#
- atcr-verify CLI: Standalone verification tool
- Ratify plugin: Kubernetes integration
- Gatekeeper provider: OPA policy enforcement
- GitHub Actions: CI/CD integration
📋 Planned#
- Containerd plugin: Runtime-level verification
- Hold-as-CA: X.509 certificate generation (optional)
- Web UI: Signature viewer in AppView
- Offline bundles: Air-gapped verification
Comparison with Other Signing Solutions#
| Feature | ATCR (ATProto) | Cosign (Sigstore) | Notation (Notary v2) |
|---|---|---|---|
| Signing | Automatic (PDS) | Manual or keyless | Manual |
| Keys | K-256 (secp256k1) | P-256 or RSA | P-256, P-384, P-521 |
| Trust | DID-based | OIDC + Fulcio CA | X.509 PKI |
| Storage | ATProto PDS | OCI registry | OCI registry |
| Centralization | Decentralized | Centralized (Fulcio) | Configurable |
| Transparency Log | ATProto firehose | Rekor | Configurable |
| Verification | Custom tools/plugins | Cosign CLI | Notation CLI |
| Kubernetes | Plugins (Ratify) | Policy Controller | Policy Controller |
ATCR advantages:
- ✅ Decentralized trust (no CA required)
- ✅ Automatic signing (no extra tools)
- ✅ DID-based identity (portable, self-sovereign)
- ✅ Transparent via ATProto firehose
ATCR trade-offs:
- ⚠️ Requires custom verification tools/plugins
- ⚠️ K-256 not supported by Notation (needs Hold-as-CA)
- ⚠️ Smaller ecosystem than Cosign/Notation
Why Not Use Cosign Directly?#
Question: Why not just integrate with Cosign's keyless signing (OIDC + Fulcio)?
Answer: ATProto and Cosign use incompatible authentication models:
| Requirement | Cosign Keyless | ATProto |
|---|---|---|
| Identity protocol | OIDC | ATProto OAuth + DPoP |
| Token format | JWT from OIDC provider | DPoP-bound access token |
| CA | Fulcio (Sigstore CA) | None (DID-based PKI) |
| Infrastructure | Fulcio + Rekor + TUF | PDS + DID resolver |
To make Cosign work, we'd need to:
- Deploy Fulcio (certificate authority)
- Deploy Rekor (transparency log)
- Deploy TUF (metadata distribution)
- Build OIDC provider bridge for ATProto OAuth
- Maintain all this infrastructure
Instead: We leverage ATProto's existing signatures and build lightweight plugins/tools for verification. This is simpler, more decentralized, and aligns with ATCR's design philosophy.
For tools that need X.509 certificates: See Hold-as-CA for an optional centralized approach.
Getting Started#
Verify Your First Image#
# 1. Check if image has ATProto signature
oras discover atcr.io/alice/myapp:latest \
--artifact-type application/vnd.atproto.signature.v1+json
# 2. Pull signature metadata
oras pull atcr.io/alice/myapp@sha256:sig789...
# 3. Verify with shell script
./examples/verification/atcr-verify.sh atcr.io/alice/myapp:latest
# 4. Use atcr-verify CLI (when available)
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml
Deploy Kubernetes Verification#
# 1. Choose an approach
# Option A: Ratify plugin (recommended)
# Option B: Gatekeeper external provider
# Option C: Custom admission webhook
# 2. Follow integration guide
# See docs/SIGNATURE_INTEGRATION.md for step-by-step
# 3. Enable for namespace
kubectl label namespace production atcr-verify=enabled
# 4. Test with sample pod
kubectl run test --image=atcr.io/alice/myapp:latest -n production
Integrate with CI/CD#
# GitHub Actions
- name: Verify signature
run: |
curl -LO https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify
chmod +x atcr-verify
./atcr-verify ${{ env.IMAGE }} --policy .atcr/trust-policy.yaml
# GitLab CI
verify_image:
script:
- wget https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify
- chmod +x atcr-verify
- ./atcr-verify $IMAGE --policy .atcr/trust-policy.yaml
Documentation#
Core Documentation#
- ATProto Signatures - Technical deep-dive into signature format and verification
- Signature Integration - Tool-specific integration guides (Ratify, Gatekeeper, Containerd)
- Integration Strategy - High-level overview and decision matrix
- atcr-verify CLI - CLI tool specification and usage
- Hold-as-CA - Optional X.509 certificate approach
Examples#
- examples/verification/ - Shell scripts, Kubernetes configs, trust policies
- examples/plugins/ - Plugin skeletons for Ratify, Gatekeeper, Containerd
External References#
- ATProto: https://atproto.com/specs/repository (repository commit signing)
- ORAS: https://oras.land/ (artifact registry)
- OCI Referrers API: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers
- Ratify: https://ratify.dev/ (verification framework)
- OPA Gatekeeper: https://open-policy-agent.github.io/gatekeeper/
Support#
For questions or issues:
- GitHub Issues: https://github.com/atcr-io/atcr/issues
- Documentation: https://docs.atcr.io
- Security: security@atcr.io
Summary#
Key Points:
- Automatic signing: Every ATCR image is automatically signed via ATProto's native signature system
- No additional tools: Signing happens transparently when you push images
- Decentralized trust: DID-based signatures, no central CA required
- Standard discovery: ORAS artifacts and OCI Referrers API for signature metadata
- Custom verification: Use plugins, CLI tools, or shell scripts (not Cosign directly)
- Multiple integrations: Kubernetes (Ratify, Gatekeeper), CI/CD (atcr-verify), containerd
- Optional X.509: Hold-as-CA for enterprise PKI compliance (centralized)
Next Steps:
- Read examples/verification/README.md for practical examples
- Choose integration approach from INTEGRATION_STRATEGY.md
- Implement plugin or deploy CLI tool from SIGNATURE_INTEGRATION.md
- Define trust policy for your organization
- Deploy to test environment first, then production