// Package main implements an OPA Gatekeeper External Data Provider for ATProto signature verification. package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "time" ) const ( // DefaultPort is the default HTTP port DefaultPort = "8080" // DefaultTrustPolicyPath is the default trust policy file path DefaultTrustPolicyPath = "/config/trust-policy.yaml" ) // Server is the HTTP server for the external data provider. type Server struct { verifier *Verifier port string httpServer *http.Server } // ProviderRequest is the request format from Gatekeeper. type ProviderRequest struct { Keys []string `json:"keys"` Values []string `json:"values"` } // ProviderResponse is the response format to Gatekeeper. type ProviderResponse struct { SystemError string `json:"system_error,omitempty"` Responses []map[string]any `json:"responses"` } // VerificationResult holds the result of verifying a single image. type VerificationResult struct { Image string `json:"image"` Verified bool `json:"verified"` DID string `json:"did,omitempty"` Handle string `json:"handle,omitempty"` SignedAt time.Time `json:"signedAt,omitempty"` CommitCID string `json:"commitCid,omitempty"` Error string `json:"error,omitempty"` } // NewServer creates a new provider server. func NewServer(verifier *Verifier, port string) *Server { return &Server{ verifier: verifier, port: port, } } // Start starts the HTTP server. func (s *Server) Start() error { mux := http.NewServeMux() // Provider endpoint (called by Gatekeeper) mux.HandleFunc("/provide", s.handleProvide) // Health check endpoints mux.HandleFunc("/health", s.handleHealth) mux.HandleFunc("/ready", s.handleReady) // Metrics endpoint (Prometheus) // TODO: Implement metrics // mux.HandleFunc("/metrics", s.handleMetrics) s.httpServer = &http.Server{ Addr: ":" + s.port, Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } log.Printf("Starting ATProto signature verification provider on port %s", s.port) return s.httpServer.ListenAndServe() } // Stop gracefully stops the HTTP server. func (s *Server) Stop(ctx context.Context) error { if s.httpServer != nil { return s.httpServer.Shutdown(ctx) } return nil } // handleProvide handles the provider endpoint called by Gatekeeper. func (s *Server) handleProvide(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } // Parse request var req ProviderRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Printf("ERROR: failed to parse request: %v", err) http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) return } log.Printf("INFO: received verification request for %d images", len(req.Values)) // Verify each image responses := make([]map[string]any, 0, len(req.Values)) for _, image := range req.Values { result := s.verifyImage(r.Context(), image) responses = append(responses, structToMap(result)) } // Send response resp := ProviderResponse{ Responses: responses, } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(resp); err != nil { log.Printf("ERROR: failed to encode response: %v", err) } } // verifyImage verifies a single image. func (s *Server) verifyImage(ctx context.Context, image string) VerificationResult { start := time.Now() log.Printf("INFO: verifying image: %s", image) // Call verifier verified, metadata, err := s.verifier.Verify(ctx, image) duration := time.Since(start) if err != nil { log.Printf("ERROR: verification failed for %s: %v (duration: %v)", image, err, duration) return VerificationResult{ Image: image, Verified: false, Error: err.Error(), } } if !verified { log.Printf("WARN: image %s failed verification (duration: %v)", image, duration) return VerificationResult{ Image: image, Verified: false, Error: "signature verification failed", } } log.Printf("INFO: image %s verified successfully (DID: %s, duration: %v)", image, metadata.DID, duration) return VerificationResult{ Image: image, Verified: true, DID: metadata.DID, Handle: metadata.Handle, SignedAt: metadata.SignedAt, CommitCID: metadata.CommitCID, } } // handleHealth handles health check requests. func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "ok", "version": "1.0.0", }) } // handleReady handles readiness check requests. func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) { // TODO: Check dependencies (DID resolver, PDS connectivity) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "ready", }) } // structToMap converts a struct to a map for JSON encoding. func structToMap(v any) map[string]any { data, _ := json.Marshal(v) var m map[string]any json.Unmarshal(data, &m) return m } func main() { // Load configuration port := os.Getenv("HTTP_PORT") if port == "" { port = DefaultPort } trustPolicyPath := os.Getenv("TRUST_POLICY_PATH") if trustPolicyPath == "" { trustPolicyPath = DefaultTrustPolicyPath } // Create verifier verifier, err := NewVerifier(trustPolicyPath) if err != nil { log.Fatalf("FATAL: failed to create verifier: %v", err) } // Create server server := NewServer(verifier, port) // Start server if err := server.Start(); err != nil && err != http.ErrServerClosed { log.Fatalf("FATAL: server error: %v", err) } } // TODO: Implement verifier.go with ATProto signature verification logic // TODO: Implement resolver.go with DID resolution // TODO: Implement crypto.go with K-256 signature verification