1package middleware
2
3import (
4 "crypto/subtle"
5 "net/http"
6
7 "github.com/rs/zerolog/log"
8)
9
10// RegistryAuth middleware provides Docker Registry authentication (optional)
11func RegistryAuth(username, password string) func(http.Handler) http.Handler {
12 return func(next http.Handler) http.Handler {
13 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 // Require auth for all other registry operations
15 if !isAuthenticated(r, username, password) {
16 w.Header().Set("WWW-Authenticate", `Basic realm="Gordon Registry"`)
17 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
18 http.Error(w, "Unauthorized", http.StatusUnauthorized)
19
20 log.Warn().
21 Str("method", r.Method).
22 Str("path", r.URL.Path).
23 Str("remote_addr", r.RemoteAddr).
24 Msg("Unauthorized registry access attempt")
25 return
26 }
27
28 next.ServeHTTP(w, r)
29 })
30 }
31}
32
33// Helper functions
34
35func isAuthenticated(r *http.Request, expectedUsername, expectedPassword string) bool {
36 // Debug: Log raw Authorization header
37 authHeader := r.Header.Get("Authorization")
38 log.Debug().
39 Str("method", r.Method).
40 Str("path", r.URL.Path).
41 Str("authorization_header", authHeader).
42 Msg("Processing authentication request")
43
44 username, password, ok := r.BasicAuth()
45 if !ok {
46 log.Debug().
47 Str("method", r.Method).
48 Str("path", r.URL.Path).
49 Str("authorization_header", authHeader).
50 Msg("No basic auth provided")
51 return false
52 }
53
54 // Use constant-time comparison to prevent timing attacks
55 usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(expectedUsername)) == 1
56 passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(expectedPassword)) == 1
57
58 authenticated := usernameMatch && passwordMatch
59 if !authenticated {
60 log.Debug().
61 Str("provided_username", username).
62 Str("expected_username", expectedUsername).
63 Str("method", r.Method).
64 Str("path", r.URL.Path).
65 Str("authorization_header", authHeader).
66 Msg("Authentication failed")
67 } else {
68 log.Debug().
69 Str("provided_username", username).
70 Str("method", r.Method).
71 Str("path", r.URL.Path).
72 Msg("Authentication successful")
73 }
74
75 return authenticated
76}