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

rename example go files for documentation

evan.jarrett.net ff7bc131 2d720e41

verified
-439
-225
examples/plugins/gatekeeper-provider/main.go
··· 1 - // Package main implements an OPA Gatekeeper External Data Provider for ATProto signature verification. 2 - package main 3 - 4 - import ( 5 - "context" 6 - "encoding/json" 7 - "fmt" 8 - "log" 9 - "net/http" 10 - "os" 11 - "time" 12 - ) 13 - 14 - const ( 15 - // DefaultPort is the default HTTP port 16 - DefaultPort = "8080" 17 - 18 - // DefaultTrustPolicyPath is the default trust policy file path 19 - DefaultTrustPolicyPath = "/config/trust-policy.yaml" 20 - ) 21 - 22 - // Server is the HTTP server for the external data provider. 23 - type Server struct { 24 - verifier *Verifier 25 - port string 26 - httpServer *http.Server 27 - } 28 - 29 - // ProviderRequest is the request format from Gatekeeper. 30 - type ProviderRequest struct { 31 - Keys []string `json:"keys"` 32 - Values []string `json:"values"` 33 - } 34 - 35 - // ProviderResponse is the response format to Gatekeeper. 36 - type ProviderResponse struct { 37 - SystemError string `json:"system_error,omitempty"` 38 - Responses []map[string]interface{} `json:"responses"` 39 - } 40 - 41 - // VerificationResult holds the result of verifying a single image. 42 - type VerificationResult struct { 43 - Image string `json:"image"` 44 - Verified bool `json:"verified"` 45 - DID string `json:"did,omitempty"` 46 - Handle string `json:"handle,omitempty"` 47 - SignedAt time.Time `json:"signedAt,omitempty"` 48 - CommitCID string `json:"commitCid,omitempty"` 49 - Error string `json:"error,omitempty"` 50 - } 51 - 52 - // NewServer creates a new provider server. 53 - func NewServer(verifier *Verifier, port string) *Server { 54 - return &Server{ 55 - verifier: verifier, 56 - port: port, 57 - } 58 - } 59 - 60 - // Start starts the HTTP server. 61 - func (s *Server) Start() error { 62 - mux := http.NewServeMux() 63 - 64 - // Provider endpoint (called by Gatekeeper) 65 - mux.HandleFunc("/provide", s.handleProvide) 66 - 67 - // Health check endpoints 68 - mux.HandleFunc("/health", s.handleHealth) 69 - mux.HandleFunc("/ready", s.handleReady) 70 - 71 - // Metrics endpoint (Prometheus) 72 - // TODO: Implement metrics 73 - // mux.HandleFunc("/metrics", s.handleMetrics) 74 - 75 - s.httpServer = &http.Server{ 76 - Addr: ":" + s.port, 77 - Handler: mux, 78 - ReadTimeout: 10 * time.Second, 79 - WriteTimeout: 30 * time.Second, 80 - IdleTimeout: 60 * time.Second, 81 - } 82 - 83 - log.Printf("Starting ATProto signature verification provider on port %s", s.port) 84 - return s.httpServer.ListenAndServe() 85 - } 86 - 87 - // Stop gracefully stops the HTTP server. 88 - func (s *Server) Stop(ctx context.Context) error { 89 - if s.httpServer != nil { 90 - return s.httpServer.Shutdown(ctx) 91 - } 92 - return nil 93 - } 94 - 95 - // handleProvide handles the provider endpoint called by Gatekeeper. 96 - func (s *Server) handleProvide(w http.ResponseWriter, r *http.Request) { 97 - if r.Method != http.MethodPost { 98 - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 99 - return 100 - } 101 - 102 - // Parse request 103 - var req ProviderRequest 104 - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 105 - log.Printf("ERROR: failed to parse request: %v", err) 106 - http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 107 - return 108 - } 109 - 110 - log.Printf("INFO: received verification request for %d images", len(req.Values)) 111 - 112 - // Verify each image 113 - responses := make([]map[string]interface{}, 0, len(req.Values)) 114 - for _, image := range req.Values { 115 - result := s.verifyImage(r.Context(), image) 116 - responses = append(responses, structToMap(result)) 117 - } 118 - 119 - // Send response 120 - resp := ProviderResponse{ 121 - Responses: responses, 122 - } 123 - 124 - w.Header().Set("Content-Type", "application/json") 125 - if err := json.NewEncoder(w).Encode(resp); err != nil { 126 - log.Printf("ERROR: failed to encode response: %v", err) 127 - } 128 - } 129 - 130 - // verifyImage verifies a single image. 131 - func (s *Server) verifyImage(ctx context.Context, image string) VerificationResult { 132 - start := time.Now() 133 - log.Printf("INFO: verifying image: %s", image) 134 - 135 - // Call verifier 136 - verified, metadata, err := s.verifier.Verify(ctx, image) 137 - duration := time.Since(start) 138 - 139 - if err != nil { 140 - log.Printf("ERROR: verification failed for %s: %v (duration: %v)", image, err, duration) 141 - return VerificationResult{ 142 - Image: image, 143 - Verified: false, 144 - Error: err.Error(), 145 - } 146 - } 147 - 148 - if !verified { 149 - log.Printf("WARN: image %s failed verification (duration: %v)", image, duration) 150 - return VerificationResult{ 151 - Image: image, 152 - Verified: false, 153 - Error: "signature verification failed", 154 - } 155 - } 156 - 157 - log.Printf("INFO: image %s verified successfully (DID: %s, duration: %v)", 158 - image, metadata.DID, duration) 159 - 160 - return VerificationResult{ 161 - Image: image, 162 - Verified: true, 163 - DID: metadata.DID, 164 - Handle: metadata.Handle, 165 - SignedAt: metadata.SignedAt, 166 - CommitCID: metadata.CommitCID, 167 - } 168 - } 169 - 170 - // handleHealth handles health check requests. 171 - func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { 172 - w.Header().Set("Content-Type", "application/json") 173 - json.NewEncoder(w).Encode(map[string]string{ 174 - "status": "ok", 175 - "version": "1.0.0", 176 - }) 177 - } 178 - 179 - // handleReady handles readiness check requests. 180 - func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) { 181 - // TODO: Check dependencies (DID resolver, PDS connectivity) 182 - w.Header().Set("Content-Type", "application/json") 183 - json.NewEncoder(w).Encode(map[string]string{ 184 - "status": "ready", 185 - }) 186 - } 187 - 188 - // structToMap converts a struct to a map for JSON encoding. 189 - func structToMap(v interface{}) map[string]interface{} { 190 - data, _ := json.Marshal(v) 191 - var m map[string]interface{} 192 - json.Unmarshal(data, &m) 193 - return m 194 - } 195 - 196 - func main() { 197 - // Load configuration 198 - port := os.Getenv("HTTP_PORT") 199 - if port == "" { 200 - port = DefaultPort 201 - } 202 - 203 - trustPolicyPath := os.Getenv("TRUST_POLICY_PATH") 204 - if trustPolicyPath == "" { 205 - trustPolicyPath = DefaultTrustPolicyPath 206 - } 207 - 208 - // Create verifier 209 - verifier, err := NewVerifier(trustPolicyPath) 210 - if err != nil { 211 - log.Fatalf("FATAL: failed to create verifier: %v", err) 212 - } 213 - 214 - // Create server 215 - server := NewServer(verifier, port) 216 - 217 - // Start server 218 - if err := server.Start(); err != nil && err != http.ErrServerClosed { 219 - log.Fatalf("FATAL: server error: %v", err) 220 - } 221 - } 222 - 223 - // TODO: Implement verifier.go with ATProto signature verification logic 224 - // TODO: Implement resolver.go with DID resolution 225 - // TODO: Implement crypto.go with K-256 signature verification
-214
examples/plugins/ratify-verifier/verifier.go
··· 1 - // Package atproto implements a Ratify verifier plugin for ATProto signatures. 2 - package atproto 3 - 4 - import ( 5 - "context" 6 - "encoding/json" 7 - "fmt" 8 - "time" 9 - 10 - "github.com/ratify-project/ratify/pkg/common" 11 - "github.com/ratify-project/ratify/pkg/ocispecs" 12 - "github.com/ratify-project/ratify/pkg/referrerstore" 13 - "github.com/ratify-project/ratify/pkg/verifier" 14 - ) 15 - 16 - const ( 17 - // VerifierName is the name of this verifier 18 - VerifierName = "atproto" 19 - 20 - // VerifierType is the type of this verifier 21 - VerifierType = "atproto" 22 - 23 - // ATProtoSignatureArtifactType is the OCI artifact type for ATProto signatures 24 - ATProtoSignatureArtifactType = "application/vnd.atproto.signature.v1+json" 25 - ) 26 - 27 - // ATProtoVerifier implements the Ratify ReferenceVerifier interface for ATProto signatures. 28 - type ATProtoVerifier struct { 29 - name string 30 - config ATProtoConfig 31 - resolver *Resolver 32 - verifier *SignatureVerifier 33 - trustStore *TrustStore 34 - } 35 - 36 - // ATProtoConfig holds configuration for the ATProto verifier. 37 - type ATProtoConfig struct { 38 - // TrustPolicyPath is the path to the trust policy YAML file 39 - TrustPolicyPath string `json:"trustPolicyPath"` 40 - 41 - // DIDResolverTimeout is the timeout for DID resolution 42 - DIDResolverTimeout time.Duration `json:"didResolverTimeout"` 43 - 44 - // PDSTimeout is the timeout for PDS XRPC calls 45 - PDSTimeout time.Duration `json:"pdsTimeout"` 46 - 47 - // CacheEnabled enables caching of DID documents and public keys 48 - CacheEnabled bool `json:"cacheEnabled"` 49 - 50 - // CacheTTL is the cache TTL for DID documents and public keys 51 - CacheTTL time.Duration `json:"cacheTTL"` 52 - } 53 - 54 - // ATProtoSignature represents the ATProto signature metadata stored in the OCI artifact. 55 - type ATProtoSignature struct { 56 - Type string `json:"$type"` 57 - Version string `json:"version"` 58 - Subject struct { 59 - Digest string `json:"digest"` 60 - MediaType string `json:"mediaType"` 61 - } `json:"subject"` 62 - ATProto struct { 63 - DID string `json:"did"` 64 - Handle string `json:"handle"` 65 - PDSEndpoint string `json:"pdsEndpoint"` 66 - RecordURI string `json:"recordUri"` 67 - CommitCID string `json:"commitCid"` 68 - SignedAt time.Time `json:"signedAt"` 69 - } `json:"atproto"` 70 - Signature struct { 71 - Algorithm string `json:"algorithm"` 72 - KeyID string `json:"keyId"` 73 - PublicKeyMultibase string `json:"publicKeyMultibase"` 74 - } `json:"signature"` 75 - } 76 - 77 - // NewATProtoVerifier creates a new ATProto verifier instance. 78 - func NewATProtoVerifier(name string, config ATProtoConfig) (*ATProtoVerifier, error) { 79 - // Load trust policy 80 - trustStore, err := LoadTrustStore(config.TrustPolicyPath) 81 - if err != nil { 82 - return nil, fmt.Errorf("failed to load trust policy: %w", err) 83 - } 84 - 85 - // Create resolver with caching 86 - resolver := NewResolver(config.DIDResolverTimeout, config.CacheEnabled, config.CacheTTL) 87 - 88 - // Create signature verifier 89 - verifier := NewSignatureVerifier(config.PDSTimeout) 90 - 91 - return &ATProtoVerifier{ 92 - name: name, 93 - config: config, 94 - resolver: resolver, 95 - verifier: verifier, 96 - trustStore: trustStore, 97 - }, nil 98 - } 99 - 100 - // Name returns the name of this verifier. 101 - func (v *ATProtoVerifier) Name() string { 102 - return v.name 103 - } 104 - 105 - // Type returns the type of this verifier. 106 - func (v *ATProtoVerifier) Type() string { 107 - return VerifierType 108 - } 109 - 110 - // CanVerify returns true if this verifier can verify the given artifact type. 111 - func (v *ATProtoVerifier) CanVerify(artifactType string) bool { 112 - return artifactType == ATProtoSignatureArtifactType 113 - } 114 - 115 - // VerifyReference verifies an ATProto signature artifact. 116 - func (v *ATProtoVerifier) VerifyReference( 117 - ctx context.Context, 118 - subjectRef common.Reference, 119 - referenceDesc ocispecs.ReferenceDescriptor, 120 - store referrerstore.ReferrerStore, 121 - ) (verifier.VerifierResult, error) { 122 - // 1. Fetch signature blob from store 123 - sigBlob, err := store.GetBlobContent(ctx, subjectRef, referenceDesc.Digest) 124 - if err != nil { 125 - return v.failureResult(fmt.Sprintf("failed to fetch signature blob: %v", err)), err 126 - } 127 - 128 - // 2. Parse ATProto signature metadata 129 - var sigData ATProtoSignature 130 - if err := json.Unmarshal(sigBlob, &sigData); err != nil { 131 - return v.failureResult(fmt.Sprintf("failed to parse signature metadata: %v", err)), err 132 - } 133 - 134 - // Validate signature format 135 - if err := v.validateSignature(&sigData); err != nil { 136 - return v.failureResult(fmt.Sprintf("invalid signature format: %v", err)), err 137 - } 138 - 139 - // 3. Check trust policy first (fail fast if DID not trusted) 140 - if !v.trustStore.IsTrusted(sigData.ATProto.DID, time.Now()) { 141 - return v.failureResult(fmt.Sprintf("DID %s not in trusted list", sigData.ATProto.DID)), 142 - fmt.Errorf("untrusted DID") 143 - } 144 - 145 - // 4. Resolve DID to public key 146 - pubKey, err := v.resolver.ResolveDIDToPublicKey(ctx, sigData.ATProto.DID) 147 - if err != nil { 148 - return v.failureResult(fmt.Sprintf("failed to resolve DID: %v", err)), err 149 - } 150 - 151 - // 5. Fetch repository commit from PDS 152 - commit, err := v.verifier.FetchCommit(ctx, sigData.ATProto.PDSEndpoint, 153 - sigData.ATProto.DID, sigData.ATProto.CommitCID) 154 - if err != nil { 155 - return v.failureResult(fmt.Sprintf("failed to fetch commit: %v", err)), err 156 - } 157 - 158 - // 6. Verify K-256 signature 159 - if err := v.verifier.VerifySignature(pubKey, commit); err != nil { 160 - return v.failureResult(fmt.Sprintf("signature verification failed: %v", err)), err 161 - } 162 - 163 - // 7. Success - return detailed result 164 - return verifier.VerifierResult{ 165 - IsSuccess: true, 166 - Name: v.name, 167 - Type: v.Type(), 168 - Message: fmt.Sprintf("Successfully verified ATProto signature for DID %s", sigData.ATProto.DID), 169 - Extensions: map[string]interface{}{ 170 - "did": sigData.ATProto.DID, 171 - "handle": sigData.ATProto.Handle, 172 - "signedAt": sigData.ATProto.SignedAt, 173 - "commitCid": sigData.ATProto.CommitCID, 174 - "pdsEndpoint": sigData.ATProto.PDSEndpoint, 175 - }, 176 - }, nil 177 - } 178 - 179 - // validateSignature validates the signature metadata format. 180 - func (v *ATProtoVerifier) validateSignature(sig *ATProtoSignature) error { 181 - if sig.Type != "io.atcr.atproto.signature" { 182 - return fmt.Errorf("invalid signature type: %s", sig.Type) 183 - } 184 - if sig.ATProto.DID == "" { 185 - return fmt.Errorf("missing DID") 186 - } 187 - if sig.ATProto.PDSEndpoint == "" { 188 - return fmt.Errorf("missing PDS endpoint") 189 - } 190 - if sig.ATProto.CommitCID == "" { 191 - return fmt.Errorf("missing commit CID") 192 - } 193 - if sig.Signature.Algorithm != "ECDSA-K256-SHA256" { 194 - return fmt.Errorf("unsupported signature algorithm: %s", sig.Signature.Algorithm) 195 - } 196 - return nil 197 - } 198 - 199 - // failureResult creates a failure result with the given message. 200 - func (v *ATProtoVerifier) failureResult(message string) verifier.VerifierResult { 201 - return verifier.VerifierResult{ 202 - IsSuccess: false, 203 - Name: v.name, 204 - Type: v.Type(), 205 - Message: message, 206 - Extensions: map[string]interface{}{ 207 - "error": message, 208 - }, 209 - } 210 - } 211 - 212 - // TODO: Implement resolver.go with DID resolution logic 213 - // TODO: Implement crypto.go with K-256 signature verification 214 - // TODO: Implement config.go with trust policy loading