Skip to content

Commit aef5ab4

Browse files
feat: addd API key authentication to prover server (#1880)
Enables authentication via X-API-Key or Bearer token headers. Public /health endpoint remains accessible while all other routes require authentication when PROVER_API_KEY env var is set.
1 parent 0f49ddf commit aef5ab4

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed
File renamed without changes.

prover/server/scripts/test_auth.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
3+
# Test script for API key authentication on prover server
4+
# This script demonstrates how to test the API key functionality
5+
6+
echo "Testing Prover Server API Key Authentication"
7+
echo "============================================="
8+
9+
# Test variables
10+
SERVER_URL="http://localhost:3001"
11+
API_KEY="test-api-key-12345"
12+
13+
echo ""
14+
echo "1. Testing /health endpoint (should work without API key):"
15+
curl -s -o /dev/null -w "%{http_code}" $SERVER_URL/health
16+
echo ""
17+
18+
echo ""
19+
echo "2. Testing /prove endpoint without API key (should return 401):"
20+
curl -s -o /dev/null -w "%{http_code}" -X POST $SERVER_URL/prove -H "Content-Type: application/json" -d '{"circuit_type": "inclusion"}'
21+
echo ""
22+
23+
echo ""
24+
echo "3. Testing /prove endpoint with X-API-Key header (should work if server has API key):"
25+
curl -s -o /dev/null -w "%{http_code}" -X POST $SERVER_URL/prove -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{"circuit_type": "inclusion"}'
26+
echo ""
27+
28+
echo ""
29+
echo "4. Testing /prove endpoint with Authorization Bearer header (should work if server has API key):"
30+
curl -s -o /dev/null -w "%{http_code}" -X POST $SERVER_URL/prove -H "Content-Type: application/json" -H "Authorization: Bearer $API_KEY" -d '{"circuit_type": "inclusion"}'
31+
echo ""
32+
33+
echo ""
34+
echo "To run this test:"
35+
echo "1. Set PROVER_API_KEY environment variable: export PROVER_API_KEY=test-api-key-12345"
36+
echo "2. Start the prover server
37+
echo "3. Run this script: bash test_auth.sh"
38+
echo ""
39+
echo "Expected results:"
40+
echo "- /health: 200 (always accessible)"
41+
echo "- /prove without key: 401 (if API key is set)"
42+
echo "- /prove with key: 400 or other (depends on valid circuit data)"

prover/server/server/auth.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package server
2+
3+
import (
4+
"crypto/subtle"
5+
"light/light-prover/logging"
6+
"net/http"
7+
"os"
8+
"strings"
9+
)
10+
11+
type authMiddleware struct {
12+
next http.Handler
13+
apiKey string
14+
}
15+
16+
func NewAPIKeyMiddleware(apiKey string) func(http.Handler) http.Handler {
17+
return func(next http.Handler) http.Handler {
18+
return &authMiddleware{
19+
next: next,
20+
apiKey: apiKey,
21+
}
22+
}
23+
}
24+
25+
func (m *authMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
26+
if !m.isAuthenticated(r) {
27+
logging.Logger().Warn().
28+
Str("remote_addr", r.RemoteAddr).
29+
Str("path", r.URL.Path).
30+
Str("method", r.Method).
31+
Msg("Unauthorized API request - missing or invalid API key")
32+
33+
unauthorizedError := &Error{
34+
StatusCode: http.StatusUnauthorized,
35+
Code: "unauthorized",
36+
Message: "Invalid or missing API key. Please provide a valid API key in the Authorization header as 'Bearer <api-key>' or in the X-API-Key header.",
37+
}
38+
unauthorizedError.send(w)
39+
return
40+
}
41+
42+
m.next.ServeHTTP(w, r)
43+
}
44+
45+
func (m *authMiddleware) isAuthenticated(r *http.Request) bool {
46+
if m.apiKey == "" {
47+
return true
48+
}
49+
50+
providedKey := m.extractAPIKey(r)
51+
if providedKey == "" {
52+
return false
53+
}
54+
55+
return subtle.ConstantTimeCompare([]byte(m.apiKey), []byte(providedKey)) == 1
56+
}
57+
58+
func (m *authMiddleware) extractAPIKey(r *http.Request) string {
59+
if apiKey := r.Header.Get("X-API-Key"); apiKey != "" {
60+
return apiKey
61+
}
62+
63+
if authHeader := r.Header.Get("Authorization"); authHeader != "" {
64+
if strings.HasPrefix(authHeader, "Bearer ") {
65+
return strings.TrimPrefix(authHeader, "Bearer ")
66+
}
67+
}
68+
69+
return ""
70+
}
71+
72+
func getAPIKeyFromEnv() string {
73+
return os.Getenv("PROVER_API_KEY")
74+
}
75+
76+
func requiresAuthentication(path string) bool {
77+
publicPaths := []string{
78+
"/health",
79+
}
80+
81+
for _, publicPath := range publicPaths {
82+
if path == publicPath {
83+
return false
84+
}
85+
}
86+
87+
return true
88+
}
89+
90+
func conditionalAuthMiddleware(apiKey string) func(http.Handler) http.Handler {
91+
return func(next http.Handler) http.Handler {
92+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93+
if requiresAuthentication(r.URL.Path) {
94+
authHandler := NewAPIKeyMiddleware(apiKey)(next)
95+
authHandler.ServeHTTP(w, r)
96+
} else {
97+
next.ServeHTTP(w, r)
98+
}
99+
})
100+
}
101+
}

prover/server/server/server.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ func RunWithQueue(config *Config, redisQueue *RedisQueue, circuits []string, run
453453
}
454454

455455
func RunEnhanced(config *EnhancedConfig, redisQueue *RedisQueue, circuits []string, runMode prover.RunMode, provingSystemsV1 []*prover.ProvingSystemV1, provingSystemsV2 []*prover.ProvingSystemV2) RunningJob {
456+
apiKey := getAPIKeyFromEnv()
457+
if apiKey != "" {
458+
logging.Logger().Info().Msg("API key authentication enabled for prover server")
459+
} else {
460+
logging.Logger().Warn().Msg("No API key configured - server will accept all requests. Set PROVER_API_KEY environment variable to enable authentication.")
461+
}
456462
metricsMux := http.NewServeMux()
457463
metricsServer := &http.Server{Addr: config.MetricsAddress, Handler: metricsMux}
458464
metricsJob := spawnServerJob(metricsServer, "metrics server")
@@ -531,14 +537,16 @@ func RunEnhanced(config *EnhancedConfig, redisQueue *RedisQueue, circuits []stri
531537
"X-Requested-With",
532538
"Content-Type",
533539
"Authorization",
540+
"X-API-Key",
534541
"X-Async",
535542
"X-Sync",
536543
}),
537544
handlers.AllowedOrigins([]string{"*"}),
538545
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
539546
)
540547

541-
proverServer := &http.Server{Addr: config.ProverAddress, Handler: corsHandler(proverMux)}
548+
authHandler := conditionalAuthMiddleware(apiKey)
549+
proverServer := &http.Server{Addr: config.ProverAddress, Handler: corsHandler(authHandler(proverMux))}
542550
proverJob := spawnServerJob(proverServer, "prover server")
543551

544552
if redisQueue != nil {

0 commit comments

Comments
 (0)