# 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 ```bash # 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 ```bash git clone https://github.com/atcr-io/atcr.git cd atcr go install ./cmd/atcr-verify ``` ### Container Image ```bash docker pull atcr.io/atcr/verify:latest # Run docker run --rm atcr.io/atcr/verify:latest verify IMAGE ``` ## Usage ### Basic Verification ```bash # 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 ```bash # 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 ```bash 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 ```bash # Exit code only (for scripts) atcr-verify atcr.io/alice/myapp:latest --quiet echo $? # 0 = verified, 1 = failed ``` ### Offline Mode ```bash # 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. ```bash 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:** ```bash # 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. ```bash 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:** ```bash # 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. ```bash atcr-verify trust COMMAND [flags] ``` **Subcommands:** **`trust list`** - List trusted DIDs ```bash 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 ```bash atcr-verify trust add did:plc:alice123 atcr-verify trust add did:plc:alice123 --name "Alice (DevOps)" ``` **`trust remove DID`** - Remove trusted DID ```bash atcr-verify trust remove did:plc:alice123 ``` **`trust policy validate`** - Validate trust policy file ```bash atcr-verify trust policy validate policy.yaml ``` ### version Show version information. ```bash 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 ```yaml 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 ```yaml trustedDIDs: - did:plc:alice123 - did:web:example.com ``` **`minSignatures`** - Minimum number of signatures required ```yaml minSignatures: 2 # Require 2 signatures ``` **`maxAge`** - Maximum signature age in seconds ```yaml maxAge: 2592000 # 30 days ``` **`algorithms`** - Allowed signature algorithms ```yaml 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:** ```yaml 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:** ```yaml 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:** ```groovy 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:** ```go // 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:** ```ini # /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:** ```bash #!/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` ```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: ```go 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 ```bash 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 ```bash ATCR_LOG_LEVEL=debug atcr-verify IMAGE ``` ### Clear Cache ```bash rm -rf ~/.atcr/cache ``` ## See Also - [ATProto Signatures](./ATPROTO_SIGNATURES.md) - How ATProto signing works - [Integration Strategy](./INTEGRATION_STRATEGY.md) - Overview of integration approaches - [Signature Integration](./SIGNATURE_INTEGRATION.md) - Tool-specific guides - [Trust Policy Examples](../examples/verification/trust-policy.yaml)