A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
1# OPA Gatekeeper External Data Provider for ATProto Signatures 2 3This is a reference implementation of an OPA Gatekeeper External Data Provider that verifies ATProto signatures on ATCR container images. 4 5## Overview 6 7Gatekeeper's External Data Provider feature allows Rego policies to call external HTTP services for data validation. This provider implements signature verification as an HTTP service that Gatekeeper can query. 8 9## Architecture 10 11``` 12Kubernetes Pod Creation 13 14OPA Gatekeeper (admission webhook) 15 16Rego Policy (constraint template) 17 18External Data Provider API call 19 20ATProto Verification Service ← This service 21 22 1. Resolve image digest 23 2. Discover signature artifacts 24 3. Parse ATProto signature metadata 25 4. Resolve DID to public key 26 5. Fetch commit from PDS 27 6. Verify K-256 signature 28 7. Check trust policy 29 30 Return: verified=true/false + metadata 31``` 32 33## Files 34 35- `main.go` - HTTP server and provider endpoints 36- `verifier.go` - ATProto signature verification logic 37- `resolver.go` - DID and PDS resolution 38- `crypto.go` - K-256 signature verification 39- `trust-policy.yaml` - Trust policy configuration 40- `Dockerfile` - Build provider service image 41- `deployment.yaml` - Kubernetes deployment manifest 42- `provider-crd.yaml` - Gatekeeper Provider custom resource 43- `constraint-template.yaml` - Rego constraint template 44- `constraint.yaml` - Policy constraint example 45 46## Prerequisites 47 48- Go 1.21+ 49- Kubernetes cluster with OPA Gatekeeper installed 50- Access to ATCR registry 51 52## Building 53 54```bash 55# Build binary 56CGO_ENABLED=0 go build -o atcr-provider \ 57 -ldflags="-w -s" \ 58 ./main.go 59 60# Build Docker image 61docker build -t atcr.io/atcr/gatekeeper-provider:latest . 62 63# Push to registry 64docker push atcr.io/atcr/gatekeeper-provider:latest 65``` 66 67## Deployment 68 69### 1. Create Trust Policy ConfigMap 70 71```bash 72kubectl create namespace gatekeeper-system 73kubectl create configmap atcr-trust-policy \ 74 --from-file=trust-policy.yaml \ 75 -n gatekeeper-system 76``` 77 78### 2. Deploy Provider Service 79 80```bash 81kubectl apply -f deployment.yaml 82``` 83 84### 3. Configure Gatekeeper Provider 85 86```bash 87kubectl apply -f provider-crd.yaml 88``` 89 90### 4. Create Constraint Template 91 92```bash 93kubectl apply -f constraint-template.yaml 94``` 95 96### 5. Create Constraint 97 98```bash 99kubectl apply -f constraint.yaml 100``` 101 102### 6. Test 103 104```bash 105# Try to create pod with signed image (should succeed) 106kubectl run test-signed --image=atcr.io/alice/myapp:latest 107 108# Try to create pod with unsigned image (should fail) 109kubectl run test-unsigned --image=atcr.io/malicious/fake:latest 110 111# Check constraint status 112kubectl get constraint atcr-signatures-required -o yaml 113``` 114 115## API Specification 116 117### Provider Endpoint 118 119**POST /provide** 120 121Request: 122```json 123{ 124 "keys": ["image"], 125 "values": [ 126 "atcr.io/alice/myapp:latest", 127 "atcr.io/bob/webapp:v1.0" 128 ] 129} 130``` 131 132Response: 133```json 134{ 135 "responses": [ 136 { 137 "image": "atcr.io/alice/myapp:latest", 138 "verified": true, 139 "did": "did:plc:alice123", 140 "handle": "alice.bsky.social", 141 "signedAt": "2025-10-31T12:34:56Z", 142 "commitCid": "bafyreih8..." 143 }, 144 { 145 "image": "atcr.io/bob/webapp:v1.0", 146 "verified": false, 147 "error": "no signature found" 148 } 149 ] 150} 151``` 152 153### Health Check 154 155**GET /health** 156 157Response: 158```json 159{ 160 "status": "ok", 161 "version": "1.0.0" 162} 163``` 164 165## Configuration 166 167### Trust Policy Format 168 169```yaml 170# trust-policy.yaml 171version: 1.0 172 173trustedDIDs: 174 did:plc:alice123: 175 name: "Alice (DevOps)" 176 validFrom: "2024-01-01T00:00:00Z" 177 expiresAt: null 178 179 did:plc:bob456: 180 name: "Bob (Security)" 181 validFrom: "2024-06-01T00:00:00Z" 182 expiresAt: "2025-12-31T23:59:59Z" 183 184policies: 185 - name: production 186 scope: "atcr.io/*/prod-*" 187 require: 188 signature: true 189 trustedDIDs: 190 - did:plc:alice123 191 - did:plc:bob456 192 action: enforce 193``` 194 195### Provider Configuration 196 197Environment variables: 198- `TRUST_POLICY_PATH` - Path to trust policy file (default: `/config/trust-policy.yaml`) 199- `HTTP_PORT` - HTTP server port (default: `8080`) 200- `LOG_LEVEL` - Log level: debug, info, warn, error (default: `info`) 201- `CACHE_ENABLED` - Enable caching (default: `true`) 202- `CACHE_TTL` - Cache TTL in seconds (default: `300`) 203- `DID_RESOLVER_TIMEOUT` - DID resolution timeout (default: `10s`) 204- `PDS_TIMEOUT` - PDS XRPC timeout (default: `10s`) 205 206## Rego Policy Examples 207 208### Simple Verification 209 210```rego 211package atcrsignatures 212 213import future.keywords.contains 214import future.keywords.if 215import future.keywords.in 216 217provider := "atcr-verifier" 218 219violation[{"msg": msg}] { 220 container := input.review.object.spec.containers[_] 221 startswith(container.image, "atcr.io/") 222 223 # Call external provider 224 response := external_data({ 225 "provider": provider, 226 "keys": ["image"], 227 "values": [container.image] 228 }) 229 230 # Check verification result 231 not response[_].verified == true 232 233 msg := sprintf("Image %v has no valid ATProto signature", [container.image]) 234} 235``` 236 237### Advanced Verification with DID Trust 238 239```rego 240package atcrsignatures 241 242import future.keywords.contains 243import future.keywords.if 244import future.keywords.in 245 246provider := "atcr-verifier" 247 248trusted_dids := [ 249 "did:plc:alice123", 250 "did:plc:bob456" 251] 252 253violation[{"msg": msg}] { 254 container := input.review.object.spec.containers[_] 255 startswith(container.image, "atcr.io/") 256 257 # Call external provider 258 response := external_data({ 259 "provider": provider, 260 "keys": ["image"], 261 "values": [container.image] 262 }) 263 264 # Get response for this image 265 result := response[_] 266 result.image == container.image 267 268 # Check if verified 269 not result.verified == true 270 msg := sprintf("Image %v failed signature verification: %v", [container.image, result.error]) 271} 272 273violation[{"msg": msg}] { 274 container := input.review.object.spec.containers[_] 275 startswith(container.image, "atcr.io/") 276 277 # Call external provider 278 response := external_data({ 279 "provider": provider, 280 "keys": ["image"], 281 "values": [container.image] 282 }) 283 284 # Get response for this image 285 result := response[_] 286 result.image == container.image 287 result.verified == true 288 289 # Check DID is trusted 290 not result.did in trusted_dids 291 msg := sprintf("Image %v signed by untrusted DID: %v", [container.image, result.did]) 292} 293``` 294 295### Namespace-Specific Policies 296 297```rego 298package atcrsignatures 299 300import future.keywords.contains 301import future.keywords.if 302import future.keywords.in 303 304provider := "atcr-verifier" 305 306# Production namespaces require signatures 307production_namespaces := ["production", "prod", "staging"] 308 309violation[{"msg": msg}] { 310 # Only apply to production namespaces 311 input.review.object.metadata.namespace in production_namespaces 312 313 container := input.review.object.spec.containers[_] 314 startswith(container.image, "atcr.io/") 315 316 # Call external provider 317 response := external_data({ 318 "provider": provider, 319 "keys": ["image"], 320 "values": [container.image] 321 }) 322 323 # Check verification result 324 not response[_].verified == true 325 326 msg := sprintf("Production namespace requires signed images. Image %v is not signed", [container.image]) 327} 328``` 329 330## Performance Considerations 331 332### Caching 333 334The provider caches: 335- Signature verification results (TTL: 5 minutes) 336- DID documents (TTL: 5 minutes) 337- PDS endpoints (TTL: 5 minutes) 338- Public keys (TTL: 5 minutes) 339 340Enable/disable via `CACHE_ENABLED` environment variable. 341 342### Timeouts 343 344- `DID_RESOLVER_TIMEOUT` - DID resolution timeout (default: 10s) 345- `PDS_TIMEOUT` - PDS XRPC calls timeout (default: 10s) 346- HTTP client timeout: 30s total 347 348### Horizontal Scaling 349 350The provider is stateless and can be scaled horizontally: 351 352```yaml 353apiVersion: apps/v1 354kind: Deployment 355spec: 356 replicas: 3 # Scale up for high traffic 357``` 358 359### Rate Limiting 360 361Consider implementing rate limiting for: 362- Gatekeeper → Provider requests 363- Provider → DID resolver 364- Provider → PDS 365 366## Monitoring 367 368### Metrics 369 370The provider exposes Prometheus metrics at `/metrics`: 371 372``` 373# Request metrics 374atcr_provider_requests_total{status="success|failure"} 375atcr_provider_request_duration_seconds 376 377# Verification metrics 378atcr_provider_verifications_total{result="verified|failed|error"} 379atcr_provider_verification_duration_seconds 380 381# Cache metrics 382atcr_provider_cache_hits_total 383atcr_provider_cache_misses_total 384``` 385 386### Logging 387 388Structured JSON logging with fields: 389- `image` - Image being verified 390- `did` - Signer DID (if found) 391- `duration` - Verification duration 392- `error` - Error message (if failed) 393 394### Health Checks 395 396```bash 397# Liveness probe 398curl http://localhost:8080/health 399 400# Readiness probe 401curl http://localhost:8080/ready 402``` 403 404## Troubleshooting 405 406### Provider Not Reachable 407 408```bash 409# Check provider pod status 410kubectl get pods -n gatekeeper-system -l app=atcr-provider 411 412# Check service 413kubectl get svc -n gatekeeper-system atcr-provider 414 415# Test connectivity from Gatekeeper pod 416kubectl exec -n gatekeeper-system deployment/gatekeeper-controller-manager -- \ 417 curl http://atcr-provider.gatekeeper-system/health 418``` 419 420### Verification Failing 421 422```bash 423# Check provider logs 424kubectl logs -n gatekeeper-system deployment/atcr-provider 425 426# Test verification manually 427kubectl run test-curl --rm -it --image=curlimages/curl -- \ 428 curl -X POST http://atcr-provider.gatekeeper-system/provide \ 429 -H "Content-Type: application/json" \ 430 -d '{"keys":["image"],"values":["atcr.io/alice/myapp:latest"]}' 431``` 432 433### Policy Not Enforcing 434 435```bash 436# Check Gatekeeper logs 437kubectl logs -n gatekeeper-system deployment/gatekeeper-controller-manager 438 439# Check constraint status 440kubectl get constraint atcr-signatures-required -o yaml 441 442# Test policy manually with conftest 443conftest test -p constraint-template.yaml pod.yaml 444``` 445 446## Security Considerations 447 448### Network Policies 449 450Restrict network access: 451 452```yaml 453apiVersion: networking.k8s.io/v1 454kind: NetworkPolicy 455metadata: 456 name: atcr-provider 457 namespace: gatekeeper-system 458spec: 459 podSelector: 460 matchLabels: 461 app: atcr-provider 462 ingress: 463 - from: 464 - podSelector: 465 matchLabels: 466 control-plane: controller-manager # Gatekeeper 467 ports: 468 - port: 8080 469 egress: 470 - to: # PLC directory 471 - namespaceSelector: {} 472 ports: 473 - port: 443 474``` 475 476### Authentication 477 478The provider should only be accessible from Gatekeeper. Options: 479- Network policies (recommended for Kubernetes) 480- Mutual TLS 481- API tokens 482 483### Trust Policy Management 484 485- Store trust policy in version control 486- Use GitOps (Flux, ArgoCD) for updates 487- Review DID changes carefully 488- Audit policy modifications 489 490## See Also 491 492- [Gatekeeper Documentation](https://open-policy-agent.github.io/gatekeeper/) 493- [External Data Provider](https://open-policy-agent.github.io/gatekeeper/website/docs/externaldata/) 494- [ATCR Signature Integration](../../../docs/SIGNATURE_INTEGRATION.md) 495- [ATCR Integration Strategy](../../../docs/INTEGRATION_STRATEGY.md) 496 497## Support 498 499For issues or questions: 500- GitHub Issues: https://github.com/atcr-io/atcr/issues 501- Gatekeeper GitHub: https://github.com/open-policy-agent/gatekeeper