Files
at-container-registry/examples/plugins/gatekeeper-provider/main.go.temp
2026-01-04 21:10:29 -06:00

226 lines
5.9 KiB
Plaintext

// 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