A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
1# atcr-verify CLI Tool
2
3## Overview
4
5`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.
6
7## Features
8
9- ✅ Verify ATProto signatures via OCI Referrers API
10- ✅ DID resolution and public key extraction
11- ✅ PDS query and commit signature verification
12- ✅ Trust policy enforcement
13- ✅ Offline verification mode (with cached data)
14- ✅ Multiple output formats (human-readable, JSON, quiet)
15- ✅ Exit codes for CI/CD integration
16- ✅ Kubernetes admission controller integration
17
18## Installation
19
20### Binary Release
21
22```bash
23# Linux (x86_64)
24curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-linux-amd64 -o atcr-verify
25chmod +x atcr-verify
26sudo mv atcr-verify /usr/local/bin/
27
28# macOS (Apple Silicon)
29curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-darwin-arm64 -o atcr-verify
30chmod +x atcr-verify
31sudo mv atcr-verify /usr/local/bin/
32
33# Windows
34curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-windows-amd64.exe -o atcr-verify.exe
35```
36
37### From Source
38
39```bash
40git clone https://github.com/atcr-io/atcr.git
41cd atcr
42go install ./cmd/atcr-verify
43```
44
45### Container Image
46
47```bash
48docker pull atcr.io/atcr/verify:latest
49
50# Run
51docker run --rm atcr.io/atcr/verify:latest verify IMAGE
52```
53
54## Usage
55
56### Basic Verification
57
58```bash
59# Verify an image
60atcr-verify atcr.io/alice/myapp:latest
61
62# Output:
63# ✓ Image verified successfully
64# Signed by: alice.bsky.social (did:plc:alice123)
65# Signed at: 2025-10-31T12:34:56.789Z
66```
67
68### With Trust Policy
69
70```bash
71# Verify against trust policy
72atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml
73
74# Output:
75# ✓ Image verified successfully
76# ✓ Trust policy satisfied
77# Policy: production-images
78# Trusted DID: did:plc:alice123
79```
80
81### JSON Output
82
83```bash
84atcr-verify atcr.io/alice/myapp:latest --output json
85
86# Output:
87{
88 "verified": true,
89 "image": "atcr.io/alice/myapp:latest",
90 "digest": "sha256:abc123...",
91 "signature": {
92 "did": "did:plc:alice123",
93 "handle": "alice.bsky.social",
94 "pds": "https://bsky.social",
95 "recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123",
96 "commitCid": "bafyreih8...",
97 "signedAt": "2025-10-31T12:34:56.789Z",
98 "algorithm": "ECDSA-K256-SHA256"
99 },
100 "trustPolicy": {
101 "satisfied": true,
102 "policy": "production-images",
103 "trustedDID": true
104 }
105}
106```
107
108### Quiet Mode
109
110```bash
111# Exit code only (for scripts)
112atcr-verify atcr.io/alice/myapp:latest --quiet
113echo $? # 0 = verified, 1 = failed
114```
115
116### Offline Mode
117
118```bash
119# Export verification bundle
120atcr-verify export atcr.io/alice/myapp:latest -o bundle.json
121
122# Verify offline (in air-gapped environment)
123atcr-verify atcr.io/alice/myapp:latest --offline --bundle bundle.json
124```
125
126## Command Reference
127
128### verify
129
130Verify ATProto signature for an image.
131
132```bash
133atcr-verify verify IMAGE [flags]
134atcr-verify IMAGE [flags] # 'verify' subcommand is optional
135```
136
137**Arguments:**
138- `IMAGE` - Image reference (registry/owner/repo:tag or @digest)
139
140**Flags:**
141- `--policy FILE` - Trust policy file (default: none)
142- `--output FORMAT` - Output format: text, json, quiet (default: text)
143- `--offline` - Offline mode (requires --bundle)
144- `--bundle FILE` - Verification bundle for offline mode
145- `--cache-dir DIR` - Cache directory for DID documents (default: ~/.atcr/cache)
146- `--no-cache` - Disable caching
147- `--timeout DURATION` - Verification timeout (default: 30s)
148- `--verbose` - Verbose output
149
150**Exit Codes:**
151- `0` - Verification succeeded
152- `1` - Verification failed
153- `2` - Invalid arguments
154- `3` - Network error
155- `4` - Trust policy violation
156
157**Examples:**
158
159```bash
160# Basic verification
161atcr-verify atcr.io/alice/myapp:latest
162
163# With specific digest
164atcr-verify atcr.io/alice/myapp@sha256:abc123...
165
166# With trust policy
167atcr-verify atcr.io/alice/myapp:latest --policy production-policy.yaml
168
169# JSON output for scripting
170atcr-verify atcr.io/alice/myapp:latest --output json | jq .verified
171
172# Quiet mode for CI/CD
173if atcr-verify atcr.io/alice/myapp:latest --quiet; then
174 echo "Deploy approved"
175fi
176```
177
178### export
179
180Export verification bundle for offline verification.
181
182```bash
183atcr-verify export IMAGE [flags]
184```
185
186**Arguments:**
187- `IMAGE` - Image reference to export bundle for
188
189**Flags:**
190- `-o, --output FILE` - Output file (default: stdout)
191- `--include-did-docs` - Include DID documents in bundle
192- `--include-commit` - Include ATProto commit data
193
194**Examples:**
195
196```bash
197# Export to file
198atcr-verify export atcr.io/alice/myapp:latest -o myapp-bundle.json
199
200# Export with all verification data
201atcr-verify export atcr.io/alice/myapp:latest \
202 --include-did-docs \
203 --include-commit \
204 -o complete-bundle.json
205
206# Export for multiple images
207for img in $(cat images.txt); do
208 atcr-verify export $img -o bundles/$(echo $img | tr '/:' '_').json
209done
210```
211
212### trust
213
214Manage trust policies and trusted DIDs.
215
216```bash
217atcr-verify trust COMMAND [flags]
218```
219
220**Subcommands:**
221
222**`trust list`** - List trusted DIDs
223```bash
224atcr-verify trust list
225
226# Output:
227# Trusted DIDs:
228# - did:plc:alice123 (alice.bsky.social)
229# - did:plc:bob456 (bob.example.com)
230```
231
232**`trust add DID`** - Add trusted DID
233```bash
234atcr-verify trust add did:plc:alice123
235atcr-verify trust add did:plc:alice123 --name "Alice (DevOps)"
236```
237
238**`trust remove DID`** - Remove trusted DID
239```bash
240atcr-verify trust remove did:plc:alice123
241```
242
243**`trust policy validate`** - Validate trust policy file
244```bash
245atcr-verify trust policy validate policy.yaml
246```
247
248### version
249
250Show version information.
251
252```bash
253atcr-verify version
254
255# Output:
256# atcr-verify version 1.0.0
257# Go version: go1.21.5
258# Commit: 3b5b89b
259# Built: 2025-10-31T12:00:00Z
260```
261
262## Trust Policy
263
264Trust policies define which signatures to trust and what to do when verification fails.
265
266### Policy File Format
267
268```yaml
269version: 1.0
270
271# Global settings
272defaultAction: enforce # enforce, audit, allow
273requireSignature: true
274
275# Policies matched by image pattern (first match wins)
276policies:
277 - name: production-images
278 description: "Production images must be signed by DevOps or Security"
279 scope: "atcr.io/*/prod-*"
280 require:
281 signature: true
282 trustedDIDs:
283 - did:plc:devops-team
284 - did:plc:security-team
285 minSignatures: 1
286 maxAge: 2592000 # 30 days in seconds
287 action: enforce
288
289 - name: staging-images
290 scope: "atcr.io/*/staging-*"
291 require:
292 signature: true
293 trustedDIDs:
294 - did:plc:devops-team
295 - did:plc:developers
296 minSignatures: 1
297 action: enforce
298
299 - name: dev-images
300 scope: "atcr.io/*/dev-*"
301 require:
302 signature: false
303 action: audit # Log but don't fail
304
305# Trusted DID registry
306trustedDIDs:
307 did:plc:devops-team:
308 name: "DevOps Team"
309 validFrom: "2024-01-01T00:00:00Z"
310 expiresAt: null
311 contact: "devops@example.com"
312
313 did:plc:security-team:
314 name: "Security Team"
315 validFrom: "2024-01-01T00:00:00Z"
316 expiresAt: null
317
318 did:plc:developers:
319 name: "Developer Team"
320 validFrom: "2024-06-01T00:00:00Z"
321 expiresAt: "2025-12-31T23:59:59Z"
322```
323
324### Policy Matching
325
326Policies are evaluated in order. First match wins.
327
328**Scope patterns:**
329- `atcr.io/*/*` - All ATCR images
330- `atcr.io/myorg/*` - All images from myorg
331- `atcr.io/*/prod-*` - All images with "prod-" prefix
332- `atcr.io/myorg/myapp` - Specific repository
333- `atcr.io/myorg/myapp:v*` - Tag pattern matching
334
335### Policy Actions
336
337**`enforce`** - Reject if policy fails
338- Exit code 4
339- Blocks deployment
340
341**`audit`** - Log but allow
342- Exit code 0 (success)
343- Warning message printed
344
345**`allow`** - Always allow
346- No verification performed
347- Exit code 0
348
349### Policy Requirements
350
351**`signature: true`** - Require signature present
352
353**`trustedDIDs`** - List of trusted DIDs
354```yaml
355trustedDIDs:
356 - did:plc:alice123
357 - did:web:example.com
358```
359
360**`minSignatures`** - Minimum number of signatures required
361```yaml
362minSignatures: 2 # Require 2 signatures
363```
364
365**`maxAge`** - Maximum signature age in seconds
366```yaml
367maxAge: 2592000 # 30 days
368```
369
370**`algorithms`** - Allowed signature algorithms
371```yaml
372algorithms:
373 - ECDSA-K256-SHA256
374```
375
376## Verification Flow
377
378### 1. Image Resolution
379
380```
381Input: atcr.io/alice/myapp:latest
382 ↓
383Resolve tag to digest
384 ↓
385Output: sha256:abc123...
386```
387
388### 2. Signature Discovery
389
390```
391Query OCI Referrers API:
392 GET /v2/alice/myapp/referrers/sha256:abc123
393 ?artifactType=application/vnd.atproto.signature.v1+json
394 ↓
395Returns: List of signature artifacts
396 ↓
397Download signature metadata blobs
398```
399
400### 3. DID Resolution
401
402```
403Extract DID from signature: did:plc:alice123
404 ↓
405Query PLC directory:
406 GET https://plc.directory/did:plc:alice123
407 ↓
408Extract public key from DID document
409```
410
411### 4. PDS Query
412
413```
414Get PDS endpoint from DID document
415 ↓
416Query for manifest record:
417 GET {pds}/xrpc/com.atproto.repo.getRecord
418 ?repo=did:plc:alice123
419 &collection=io.atcr.manifest
420 &rkey=abc123
421 ↓
422Get commit CID from record
423 ↓
424Fetch commit data (includes signature)
425```
426
427### 5. Signature Verification
428
429```
430Extract signature bytes from commit
431 ↓
432Compute commit hash (SHA-256)
433 ↓
434Verify: ECDSA_K256(hash, signature, publicKey)
435 ↓
436Result: Valid or Invalid
437```
438
439### 6. Trust Policy Evaluation
440
441```
442Check if DID is in trustedDIDs list
443 ↓
444Check signature age < maxAge
445 ↓
446Check minSignatures satisfied
447 ↓
448Apply policy action (enforce/audit/allow)
449```
450
451## Integration Examples
452
453### CI/CD Pipeline
454
455**GitHub Actions:**
456```yaml
457name: Deploy
458
459on:
460 push:
461 branches: [main]
462
463jobs:
464 verify-and-deploy:
465 runs-on: ubuntu-latest
466 steps:
467 - name: Install atcr-verify
468 run: |
469 curl -L https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify-linux-amd64 -o atcr-verify
470 chmod +x atcr-verify
471 sudo mv atcr-verify /usr/local/bin/
472
473 - name: Verify image signature
474 run: |
475 atcr-verify ${{ env.IMAGE }} --policy .github/trust-policy.yaml
476
477 - name: Deploy to production
478 if: success()
479 run: kubectl set image deployment/app app=${{ env.IMAGE }}
480```
481
482**GitLab CI:**
483```yaml
484verify:
485 stage: verify
486 image: atcr.io/atcr/verify:latest
487 script:
488 - atcr-verify ${IMAGE} --policy trust-policy.yaml
489
490deploy:
491 stage: deploy
492 dependencies:
493 - verify
494 script:
495 - kubectl set image deployment/app app=${IMAGE}
496```
497
498**Jenkins:**
499```groovy
500pipeline {
501 agent any
502
503 stages {
504 stage('Verify') {
505 steps {
506 sh 'atcr-verify ${IMAGE} --policy trust-policy.yaml'
507 }
508 }
509
510 stage('Deploy') {
511 when {
512 expression { currentBuild.result == 'SUCCESS' }
513 }
514 steps {
515 sh 'kubectl set image deployment/app app=${IMAGE}'
516 }
517 }
518 }
519}
520```
521
522### Kubernetes Admission Controller
523
524**Using as webhook backend:**
525
526```go
527// webhook server
528func (h *Handler) ValidatePod(w http.ResponseWriter, r *http.Request) {
529 var admReq admissionv1.AdmissionReview
530 json.NewDecoder(r.Body).Decode(&admReq)
531
532 pod := &corev1.Pod{}
533 json.Unmarshal(admReq.Request.Object.Raw, pod)
534
535 // Verify each container image
536 for _, container := range pod.Spec.Containers {
537 cmd := exec.Command("atcr-verify", container.Image,
538 "--policy", "/etc/atcr/trust-policy.yaml",
539 "--quiet")
540
541 if err := cmd.Run(); err != nil {
542 // Verification failed
543 admResp := admissionv1.AdmissionReview{
544 Response: &admissionv1.AdmissionResponse{
545 UID: admReq.Request.UID,
546 Allowed: false,
547 Result: &metav1.Status{
548 Message: fmt.Sprintf("Image %s failed signature verification", container.Image),
549 },
550 },
551 }
552 json.NewEncoder(w).Encode(admResp)
553 return
554 }
555 }
556
557 // All images verified
558 admResp := admissionv1.AdmissionReview{
559 Response: &admissionv1.AdmissionResponse{
560 UID: admReq.Request.UID,
561 Allowed: true,
562 },
563 }
564 json.NewEncoder(w).Encode(admResp)
565}
566```
567
568### Pre-Pull Verification
569
570**Systemd service:**
571```ini
572# /etc/systemd/system/myapp.service
573[Unit]
574Description=My Application
575After=docker.service
576
577[Service]
578Type=oneshot
579ExecStartPre=/usr/local/bin/atcr-verify atcr.io/myorg/myapp:latest --policy /etc/atcr/policy.yaml
580ExecStartPre=/usr/bin/docker pull atcr.io/myorg/myapp:latest
581ExecStart=/usr/bin/docker run atcr.io/myorg/myapp:latest
582Restart=on-failure
583
584[Install]
585WantedBy=multi-user.target
586```
587
588**Docker wrapper script:**
589```bash
590#!/bin/bash
591# docker-secure-pull.sh
592
593IMAGE="$1"
594
595# Verify before pulling
596if ! atcr-verify "$IMAGE" --policy ~/.atcr/trust-policy.yaml; then
597 echo "ERROR: Image signature verification failed"
598 exit 1
599fi
600
601# Pull if verified
602docker pull "$IMAGE"
603```
604
605## Configuration
606
607### Config File
608
609Location: `~/.atcr/config.yaml`
610
611```yaml
612# Default trust policy
613defaultPolicy: ~/.atcr/trust-policy.yaml
614
615# Cache settings
616cache:
617 enabled: true
618 directory: ~/.atcr/cache
619 ttl:
620 didDocuments: 3600 # 1 hour
621 commits: 600 # 10 minutes
622
623# Network settings
624timeout: 30s
625retries: 3
626
627# Output settings
628output:
629 format: text # text, json, quiet
630 color: auto # auto, always, never
631
632# Registry settings
633registries:
634 atcr.io:
635 insecure: false
636 credentialsFile: ~/.docker/config.json
637```
638
639### Environment Variables
640
641- `ATCR_CONFIG` - Config file path
642- `ATCR_POLICY` - Default trust policy file
643- `ATCR_CACHE_DIR` - Cache directory
644- `ATCR_OUTPUT` - Output format (text, json, quiet)
645- `ATCR_TIMEOUT` - Verification timeout
646- `HTTP_PROXY` / `HTTPS_PROXY` - Proxy settings
647- `NO_CACHE` - Disable caching
648
649## Library Usage
650
651`atcr-verify` can also be used as a Go library:
652
653```go
654import "github.com/atcr-io/atcr/pkg/verify"
655
656func main() {
657 verifier := verify.NewVerifier(verify.Config{
658 Policy: policy,
659 Timeout: 30 * time.Second,
660 })
661
662 result, err := verifier.Verify(ctx, "atcr.io/alice/myapp:latest")
663 if err != nil {
664 log.Fatal(err)
665 }
666
667 if !result.Verified {
668 log.Fatal("Verification failed")
669 }
670
671 fmt.Printf("Verified by %s\n", result.Signature.DID)
672}
673```
674
675## Performance
676
677### Typical Verification Times
678
679- **First verification:** 500-1000ms
680 - OCI Referrers API: 50-100ms
681 - DID resolution: 50-150ms
682 - PDS query: 100-300ms
683 - Signature verification: 1-5ms
684
685- **Cached verification:** 50-150ms
686 - DID document cached
687 - Signature metadata cached
688
689### Optimization Tips
690
6911. **Enable caching** - DID documents change rarely
6922. **Use offline bundles** - For air-gapped environments
6933. **Parallel verification** - Verify multiple images concurrently
6944. **Local trust policy** - Avoid remote policy fetches
695
696## Troubleshooting
697
698### Verification Fails
699
700```bash
701atcr-verify atcr.io/alice/myapp:latest --verbose
702```
703
704Common issues:
705- **No signature found** - Image not signed, check Referrers API
706- **DID resolution failed** - Network issue, check PLC directory
707- **PDS unreachable** - Network issue, check PDS endpoint
708- **Signature invalid** - Tampering detected or key mismatch
709- **Trust policy violation** - DID not in trusted list
710
711### Enable Debug Logging
712
713```bash
714ATCR_LOG_LEVEL=debug atcr-verify IMAGE
715```
716
717### Clear Cache
718
719```bash
720rm -rf ~/.atcr/cache
721```
722
723## See Also
724
725- [ATProto Signatures](./ATPROTO_SIGNATURES.md) - How ATProto signing works
726- [Integration Strategy](./INTEGRATION_STRATEGY.md) - Overview of integration approaches
727- [Signature Integration](./SIGNATURE_INTEGRATION.md) - Tool-specific guides
728- [Trust Policy Examples](../examples/verification/trust-policy.yaml)