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

Configure Feed

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

rename example go files for documentation

-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