A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
72
fork

Configure Feed

Select the types of activity you want to include in your feed.

atcr-verify CLI Tool#

Overview#

atcr-verify is a command-line tool for verifying ATProto signatures on container images stored in ATCR. It provides cryptographic verification of image manifests using ATProto's DID-based trust model.

Features#

  • ✅ Verify ATProto signatures via OCI Referrers API
  • ✅ DID resolution and public key extraction
  • ✅ PDS query and commit signature verification
  • ✅ Trust policy enforcement
  • ✅ Offline verification mode (with cached data)
  • ✅ Multiple output formats (human-readable, JSON, quiet)
  • ✅ Exit codes for CI/CD integration
  • ✅ Kubernetes admission controller integration

Installation#

Binary Release#

# Linux (x86_64)
curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-linux-amd64 -o atcr-verify
chmod +x atcr-verify
sudo mv atcr-verify /usr/local/bin/

# macOS (Apple Silicon)
curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-darwin-arm64 -o atcr-verify
chmod +x atcr-verify
sudo mv atcr-verify /usr/local/bin/

# Windows
curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-windows-amd64.exe -o atcr-verify.exe

From Source#

git clone https://github.com/atcr-io/atcr.git
cd atcr
go install ./cmd/atcr-verify

Container Image#

docker pull atcr.io/atcr/verify:latest

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

Usage#

Basic Verification#

# Verify an image
atcr-verify atcr.io/alice/myapp:latest

# Output:
# ✓ Image verified successfully
# Signed by: alice.bsky.social (did:plc:alice123)
# Signed at: 2025-10-31T12:34:56.789Z

With Trust Policy#

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

# Output:
# ✓ Image verified successfully
# ✓ Trust policy satisfied
# Policy: production-images
# Trusted DID: did:plc:alice123

JSON Output#

atcr-verify atcr.io/alice/myapp:latest --output json

# Output:
{
  "verified": true,
  "image": "atcr.io/alice/myapp:latest",
  "digest": "sha256:abc123...",
  "signature": {
    "did": "did:plc:alice123",
    "handle": "alice.bsky.social",
    "pds": "https://bsky.social",
    "recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123",
    "commitCid": "bafyreih8...",
    "signedAt": "2025-10-31T12:34:56.789Z",
    "algorithm": "ECDSA-K256-SHA256"
  },
  "trustPolicy": {
    "satisfied": true,
    "policy": "production-images",
    "trustedDID": true
  }
}

Quiet Mode#

# Exit code only (for scripts)
atcr-verify atcr.io/alice/myapp:latest --quiet
echo $?  # 0 = verified, 1 = failed

Offline Mode#

# Export verification bundle
atcr-verify export atcr.io/alice/myapp:latest -o bundle.json

# Verify offline (in air-gapped environment)
atcr-verify atcr.io/alice/myapp:latest --offline --bundle bundle.json

Command Reference#

verify#

Verify ATProto signature for an image.

atcr-verify verify IMAGE [flags]
atcr-verify IMAGE [flags]  # 'verify' subcommand is optional

Arguments:

  • IMAGE - Image reference (registry/owner/repo:tag or @digest)

Flags:

  • --policy FILE - Trust policy file (default: none)
  • --output FORMAT - Output format: text, json, quiet (default: text)
  • --offline - Offline mode (requires --bundle)
  • --bundle FILE - Verification bundle for offline mode
  • --cache-dir DIR - Cache directory for DID documents (default: ~/.atcr/cache)
  • --no-cache - Disable caching
  • --timeout DURATION - Verification timeout (default: 30s)
  • --verbose - Verbose output

Exit Codes:

  • 0 - Verification succeeded
  • 1 - Verification failed
  • 2 - Invalid arguments
  • 3 - Network error
  • 4 - Trust policy violation

Examples:

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

# With specific digest
atcr-verify atcr.io/alice/myapp@sha256:abc123...

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

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

# Quiet mode for CI/CD
if atcr-verify atcr.io/alice/myapp:latest --quiet; then
  echo "Deploy approved"
fi

export#

Export verification bundle for offline verification.

atcr-verify export IMAGE [flags]

Arguments:

  • IMAGE - Image reference to export bundle for

Flags:

  • -o, --output FILE - Output file (default: stdout)
  • --include-did-docs - Include DID documents in bundle
  • --include-commit - Include ATProto commit data

Examples:

# Export to file
atcr-verify export atcr.io/alice/myapp:latest -o myapp-bundle.json

# Export with all verification data
atcr-verify export atcr.io/alice/myapp:latest \
  --include-did-docs \
  --include-commit \
  -o complete-bundle.json

# Export for multiple images
for img in $(cat images.txt); do
  atcr-verify export $img -o bundles/$(echo $img | tr '/:' '_').json
done

trust#

Manage trust policies and trusted DIDs.

atcr-verify trust COMMAND [flags]

Subcommands:

trust list - List trusted DIDs

atcr-verify trust list

# Output:
# Trusted DIDs:
# - did:plc:alice123 (alice.bsky.social)
# - did:plc:bob456 (bob.example.com)

trust add DID - Add trusted DID

atcr-verify trust add did:plc:alice123
atcr-verify trust add did:plc:alice123 --name "Alice (DevOps)"

trust remove DID - Remove trusted DID

atcr-verify trust remove did:plc:alice123

trust policy validate - Validate trust policy file

atcr-verify trust policy validate policy.yaml

version#

Show version information.

atcr-verify version

# Output:
# atcr-verify version 1.0.0
# Go version: go1.21.5
# Commit: 3b5b89b
# Built: 2025-10-31T12:00:00Z

Trust Policy#

Trust policies define which signatures to trust and what to do when verification fails.

Policy File Format#

version: 1.0

# Global settings
defaultAction: enforce  # enforce, audit, allow
requireSignature: true

# Policies matched by image pattern (first match wins)
policies:
  - name: production-images
    description: "Production images must be signed by DevOps or Security"
    scope: "atcr.io/*/prod-*"
    require:
      signature: true
      trustedDIDs:
        - did:plc:devops-team
        - did:plc:security-team
      minSignatures: 1
      maxAge: 2592000  # 30 days in seconds
    action: enforce

  - name: staging-images
    scope: "atcr.io/*/staging-*"
    require:
      signature: true
      trustedDIDs:
        - did:plc:devops-team
        - did:plc:developers
      minSignatures: 1
    action: enforce

  - name: dev-images
    scope: "atcr.io/*/dev-*"
    require:
      signature: false
    action: audit  # Log but don't fail

# Trusted DID registry
trustedDIDs:
  did:plc:devops-team:
    name: "DevOps Team"
    validFrom: "2024-01-01T00:00:00Z"
    expiresAt: null
    contact: "devops@example.com"

  did:plc:security-team:
    name: "Security Team"
    validFrom: "2024-01-01T00:00:00Z"
    expiresAt: null

  did:plc:developers:
    name: "Developer Team"
    validFrom: "2024-06-01T00:00:00Z"
    expiresAt: "2025-12-31T23:59:59Z"

Policy Matching#

Policies are evaluated in order. First match wins.

Scope patterns:

  • atcr.io/*/* - All ATCR images
  • atcr.io/myorg/* - All images from myorg
  • atcr.io/*/prod-* - All images with "prod-" prefix
  • atcr.io/myorg/myapp - Specific repository
  • atcr.io/myorg/myapp:v* - Tag pattern matching

Policy Actions#

enforce - Reject if policy fails

  • Exit code 4
  • Blocks deployment

audit - Log but allow

  • Exit code 0 (success)
  • Warning message printed

allow - Always allow

  • No verification performed
  • Exit code 0

Policy Requirements#

signature: true - Require signature present

trustedDIDs - List of trusted DIDs

trustedDIDs:
  - did:plc:alice123
  - did:web:example.com

minSignatures - Minimum number of signatures required

minSignatures: 2  # Require 2 signatures

maxAge - Maximum signature age in seconds

maxAge: 2592000  # 30 days

algorithms - Allowed signature algorithms

algorithms:
  - ECDSA-K256-SHA256

Verification Flow#

1. Image Resolution#

Input: atcr.io/alice/myapp:latest
  ↓
Resolve tag to digest
  ↓
Output: sha256:abc123...

2. Signature Discovery#

Query OCI Referrers API:
  GET /v2/alice/myapp/referrers/sha256:abc123
  ?artifactType=application/vnd.atproto.signature.v1+json
  ↓
Returns: List of signature artifacts
  ↓
Download signature metadata blobs

3. DID Resolution#

Extract DID from signature: did:plc:alice123
  ↓
Query PLC directory:
  GET https://plc.directory/did:plc:alice123
  ↓
Extract public key from DID document

4. PDS Query#

Get PDS endpoint from DID document
  ↓
Query for manifest record:
  GET {pds}/xrpc/com.atproto.repo.getRecord
    ?repo=did:plc:alice123
    &collection=io.atcr.manifest
    &rkey=abc123
  ↓
Get commit CID from record
  ↓
Fetch commit data (includes signature)

5. Signature Verification#

Extract signature bytes from commit
  ↓
Compute commit hash (SHA-256)
  ↓
Verify: ECDSA_K256(hash, signature, publicKey)
  ↓
Result: Valid or Invalid

6. Trust Policy Evaluation#

Check if DID is in trustedDIDs list
  ↓
Check signature age < maxAge
  ↓
Check minSignatures satisfied
  ↓
Apply policy action (enforce/audit/allow)

Integration Examples#

CI/CD Pipeline#

GitHub Actions:

name: Deploy

on:
  push:
    branches: [main]

jobs:
  verify-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Install atcr-verify
        run: |
          curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-linux-amd64 -o atcr-verify
          chmod +x atcr-verify
          sudo mv atcr-verify /usr/local/bin/

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

      - name: Deploy to production
        if: success()
        run: kubectl set image deployment/app app=${{ env.IMAGE }}

GitLab CI:

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

deploy:
  stage: deploy
  dependencies:
    - verify
  script:
    - kubectl set image deployment/app app=${IMAGE}

Jenkins:

pipeline {
  agent any

  stages {
    stage('Verify') {
      steps {
        sh 'atcr-verify ${IMAGE} --policy trust-policy.yaml'
      }
    }

    stage('Deploy') {
      when {
        expression { currentBuild.result == 'SUCCESS' }
      }
      steps {
        sh 'kubectl set image deployment/app app=${IMAGE}'
      }
    }
  }
}

Kubernetes Admission Controller#

Using as webhook backend:

// webhook server
func (h *Handler) ValidatePod(w http.ResponseWriter, r *http.Request) {
    var admReq admissionv1.AdmissionReview
    json.NewDecoder(r.Body).Decode(&admReq)

    pod := &corev1.Pod{}
    json.Unmarshal(admReq.Request.Object.Raw, pod)

    // Verify each container image
    for _, container := range pod.Spec.Containers {
        cmd := exec.Command("atcr-verify", container.Image,
            "--policy", "/etc/atcr/trust-policy.yaml",
            "--quiet")

        if err := cmd.Run(); err != nil {
            // Verification failed
            admResp := admissionv1.AdmissionReview{
                Response: &admissionv1.AdmissionResponse{
                    UID: admReq.Request.UID,
                    Allowed: false,
                    Result: &metav1.Status{
                        Message: fmt.Sprintf("Image %s failed signature verification", container.Image),
                    },
                },
            }
            json.NewEncoder(w).Encode(admResp)
            return
        }
    }

    // All images verified
    admResp := admissionv1.AdmissionReview{
        Response: &admissionv1.AdmissionResponse{
            UID: admReq.Request.UID,
            Allowed: true,
        },
    }
    json.NewEncoder(w).Encode(admResp)
}

Pre-Pull Verification#

Systemd service:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=docker.service

[Service]
Type=oneshot
ExecStartPre=/usr/local/bin/atcr-verify atcr.io/myorg/myapp:latest --policy /etc/atcr/policy.yaml
ExecStartPre=/usr/bin/docker pull atcr.io/myorg/myapp:latest
ExecStart=/usr/bin/docker run atcr.io/myorg/myapp:latest
Restart=on-failure

[Install]
WantedBy=multi-user.target

Docker wrapper script:

#!/bin/bash
# docker-secure-pull.sh

IMAGE="$1"

# Verify before pulling
if ! atcr-verify "$IMAGE" --policy ~/.atcr/trust-policy.yaml; then
    echo "ERROR: Image signature verification failed"
    exit 1
fi

# Pull if verified
docker pull "$IMAGE"

Configuration#

Config File#

Location: ~/.atcr/config.yaml

# Default trust policy
defaultPolicy: ~/.atcr/trust-policy.yaml

# Cache settings
cache:
  enabled: true
  directory: ~/.atcr/cache
  ttl:
    didDocuments: 3600  # 1 hour
    commits: 600  # 10 minutes

# Network settings
timeout: 30s
retries: 3

# Output settings
output:
  format: text  # text, json, quiet
  color: auto  # auto, always, never

# Registry settings
registries:
  atcr.io:
    insecure: false
    credentialsFile: ~/.docker/config.json

Environment Variables#

  • ATCR_CONFIG - Config file path
  • ATCR_POLICY - Default trust policy file
  • ATCR_CACHE_DIR - Cache directory
  • ATCR_OUTPUT - Output format (text, json, quiet)
  • ATCR_TIMEOUT - Verification timeout
  • HTTP_PROXY / HTTPS_PROXY - Proxy settings
  • NO_CACHE - Disable caching

Library Usage#

atcr-verify can also be used as a Go library:

import "github.com/atcr-io/atcr/pkg/verify"

func main() {
    verifier := verify.NewVerifier(verify.Config{
        Policy: policy,
        Timeout: 30 * time.Second,
    })

    result, err := verifier.Verify(ctx, "atcr.io/alice/myapp:latest")
    if err != nil {
        log.Fatal(err)
    }

    if !result.Verified {
        log.Fatal("Verification failed")
    }

    fmt.Printf("Verified by %s\n", result.Signature.DID)
}

Performance#

Typical Verification Times#

  • First verification: 500-1000ms

    • OCI Referrers API: 50-100ms
    • DID resolution: 50-150ms
    • PDS query: 100-300ms
    • Signature verification: 1-5ms
  • Cached verification: 50-150ms

    • DID document cached
    • Signature metadata cached

Optimization Tips#

  1. Enable caching - DID documents change rarely
  2. Use offline bundles - For air-gapped environments
  3. Parallel verification - Verify multiple images concurrently
  4. Local trust policy - Avoid remote policy fetches

Troubleshooting#

Verification Fails#

atcr-verify atcr.io/alice/myapp:latest --verbose

Common issues:

  • No signature found - Image not signed, check Referrers API
  • DID resolution failed - Network issue, check PLC directory
  • PDS unreachable - Network issue, check PDS endpoint
  • Signature invalid - Tampering detected or key mismatch
  • Trust policy violation - DID not in trusted list

Enable Debug Logging#

ATCR_LOG_LEVEL=debug atcr-verify IMAGE

Clear Cache#

rm -rf ~/.atcr/cache

See Also#