detached 487e687

fix: normalize client_ip to clean IP without port in all log fields

- Add trustedNets param to RegistryAuthV2 and helper functions
  (sendForbidden, sendUnauthorizedMsg, sendUnauthorized) so they can
  call GetClientIP() instead of using raw r.RemoteAddr
- Add trustedNets field to proxy Handler and update NewHandler constructor
  to accept trustedNets; replace r.RemoteAddr with GetClientIP()
- Update appendRegistryAuthMiddleware to thread trustedNets through
- Update all test call sites to pass nil for trustedNets
commit
487e6873fdd4bd620681b2d20b0cf201cb26ff62
author
brice · · 2026-04-03 15:05 UTC
parent
18b726b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +42 -37
M internal/adapters/in/http/middleware/auth.go +13 -12
@@ -5,6 +5,7 @@ import (
55 "crypto/subtle"
66 "encoding/json"
77 "errors"
8+ "net"
89 "net/http"
910 "strings"
1011
@@ -54,7 +55,7 @@ type InternalRegistryAuth struct {
5455
5556 // RegistryAuthV2 middleware provides enhanced Docker Registry authentication
5657 // supporting both password and token-based authentication.
57-func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, log zerowrap.Logger) func(http.Handler) http.Handler {
58+func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, trustedNets []*net.IPNet, log zerowrap.Logger) func(http.Handler) http.Handler {
5859 return func(next http.Handler) http.Handler {
5960 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
6061 // Allow localhost requests only with internal instance credentials.
@@ -64,7 +65,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
6465 Str(zerowrap.FieldAdapter, "http").
6566 Str(zerowrap.FieldMethod, r.Method).
6667 Str(zerowrap.FieldPath, r.URL.Path).
67- Str(zerowrap.FieldClientIP, r.RemoteAddr).
68+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
6869 Msg("localhost request with internal auth - skipping auth")
6970 next.ServeHTTP(w, r)
7071 return
@@ -77,7 +78,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
7778 Str(zerowrap.FieldAdapter, "http").
7879 Str(zerowrap.FieldMethod, r.Method).
7980 Str(zerowrap.FieldPath, r.URL.Path).
80- Str(zerowrap.FieldClientIP, r.RemoteAddr).
81+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
8182 Msg("registry auth over insecure HTTP connection")
8283 }
8384
@@ -96,9 +97,9 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
9697
9798 if authErr != nil {
9899 if errors.Is(authErr, domain.ErrLongLivedToken) {
99- sendUnauthorizedMsg(w, authSvc.GetAuthType(), r.Host, log, r, domain.ErrLongLivedToken.Error())
100+ sendUnauthorizedMsg(w, authSvc.GetAuthType(), r.Host, log, trustedNets, r, domain.ErrLongLivedToken.Error())
100101 } else {
101- sendUnauthorized(w, authSvc.GetAuthType(), r.Host, log, r)
102+ sendUnauthorized(w, authSvc.GetAuthType(), r.Host, log, trustedNets, r)
102103 }
103104 return
104105 }
@@ -106,7 +107,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
106107 // SECURITY: Check scopes for token auth (per-repo access control)
107108 if tokenClaims != nil {
108109 if !checkScopeAccess(r, tokenClaims, log) {
109- sendForbidden(w, log, r)
110+ sendForbidden(w, log, trustedNets, r)
110111 return
111112 }
112113 // Store claims in context for downstream handlers that need access to token metadata
@@ -290,7 +291,7 @@ func checkScopeAccess(r *http.Request, claims *domain.TokenClaims, log zerowrap.
290291 }
291292
292293 // sendForbidden sends an HTTP 403 response.
293-func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, r *http.Request) {
294+func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request) {
294295 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
295296 w.Header().Set("Content-Type", "application/json")
296297 w.WriteHeader(http.StatusForbidden)
@@ -301,12 +302,12 @@ func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, r *http.Request)
301302 Str(zerowrap.FieldAdapter, "http").
302303 Str(zerowrap.FieldMethod, r.Method).
303304 Str(zerowrap.FieldPath, r.URL.Path).
304- Str(zerowrap.FieldClientIP, r.RemoteAddr).
305+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
305306 Msg("forbidden: insufficient scope for operation")
306307 }
307308
308309 // sendUnauthorizedMsg sends an HTTP 401 response with a custom error message.
309-func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, r *http.Request, msg string) {
310+func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request, msg string) {
310311 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
311312
312313 switch authType {
@@ -332,12 +333,12 @@ func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host s
332333 Str(zerowrap.FieldAdapter, "http").
333334 Str(zerowrap.FieldMethod, r.Method).
334335 Str(zerowrap.FieldPath, r.URL.Path).
335- Str(zerowrap.FieldClientIP, r.RemoteAddr).
336+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
336337 Msg("unauthorized registry access attempt")
337338 }
338339
339340 // sendUnauthorized sends an HTTP 401 response with appropriate headers.
340-func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, r *http.Request) {
341+func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request) {
341342 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
342343
343344 switch authType {
@@ -375,6 +376,6 @@ func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host stri
375376 Str(zerowrap.FieldAdapter, "http").
376377 Str(zerowrap.FieldMethod, r.Method).
377378 Str(zerowrap.FieldPath, r.URL.Path).
378- Str(zerowrap.FieldClientIP, r.RemoteAddr).
379+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
379380 Msg("unauthorized registry access attempt")
380381 }
@@ -5,6 +5,7 @@ import (
5 "crypto/subtle"5 "crypto/subtle"
6 "encoding/json"6 "encoding/json"
7 "errors"7 "errors"
8+ "net"
8 "net/http"9 "net/http"
9 "strings"10 "strings"
10 11
@@ -54,7 +55,7 @@ type InternalRegistryAuth struct {
54 55
55 // RegistryAuthV2 middleware provides enhanced Docker Registry authentication56 // RegistryAuthV2 middleware provides enhanced Docker Registry authentication
56 // supporting both password and token-based authentication.57 // supporting both password and token-based authentication.
57-func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, log zerowrap.Logger) func(http.Handler) http.Handler {58+func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, trustedNets []*net.IPNet, log zerowrap.Logger) func(http.Handler) http.Handler {
58 return func(next http.Handler) http.Handler {59 return func(next http.Handler) http.Handler {
59 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {60 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60 // Allow localhost requests only with internal instance credentials.61 // Allow localhost requests only with internal instance credentials.
@@ -64,7 +65,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
64 Str(zerowrap.FieldAdapter, "http").65 Str(zerowrap.FieldAdapter, "http").
65 Str(zerowrap.FieldMethod, r.Method).66 Str(zerowrap.FieldMethod, r.Method).
66 Str(zerowrap.FieldPath, r.URL.Path).67 Str(zerowrap.FieldPath, r.URL.Path).
67- Str(zerowrap.FieldClientIP, r.RemoteAddr).68+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
68 Msg("localhost request with internal auth - skipping auth")69 Msg("localhost request with internal auth - skipping auth")
69 next.ServeHTTP(w, r)70 next.ServeHTTP(w, r)
70 return71 return
@@ -77,7 +78,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
77 Str(zerowrap.FieldAdapter, "http").78 Str(zerowrap.FieldAdapter, "http").
78 Str(zerowrap.FieldMethod, r.Method).79 Str(zerowrap.FieldMethod, r.Method).
79 Str(zerowrap.FieldPath, r.URL.Path).80 Str(zerowrap.FieldPath, r.URL.Path).
80- Str(zerowrap.FieldClientIP, r.RemoteAddr).81+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
81 Msg("registry auth over insecure HTTP connection")82 Msg("registry auth over insecure HTTP connection")
82 }83 }
83 84
@@ -96,9 +97,9 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
96 97
97 if authErr != nil {98 if authErr != nil {
98 if errors.Is(authErr, domain.ErrLongLivedToken) {99 if errors.Is(authErr, domain.ErrLongLivedToken) {
99- sendUnauthorizedMsg(w, authSvc.GetAuthType(), r.Host, log, r, domain.ErrLongLivedToken.Error())100+ sendUnauthorizedMsg(w, authSvc.GetAuthType(), r.Host, log, trustedNets, r, domain.ErrLongLivedToken.Error())
100 } else {101 } else {
101- sendUnauthorized(w, authSvc.GetAuthType(), r.Host, log, r)102+ sendUnauthorized(w, authSvc.GetAuthType(), r.Host, log, trustedNets, r)
102 }103 }
103 return104 return
104 }105 }
@@ -106,7 +107,7 @@ func RegistryAuthV2(authSvc in.AuthService, internalAuth InternalRegistryAuth, l
106 // SECURITY: Check scopes for token auth (per-repo access control)107 // SECURITY: Check scopes for token auth (per-repo access control)
107 if tokenClaims != nil {108 if tokenClaims != nil {
108 if !checkScopeAccess(r, tokenClaims, log) {109 if !checkScopeAccess(r, tokenClaims, log) {
109- sendForbidden(w, log, r)110+ sendForbidden(w, log, trustedNets, r)
110 return111 return
111 }112 }
112 // Store claims in context for downstream handlers that need access to token metadata113 // Store claims in context for downstream handlers that need access to token metadata
@@ -290,7 +291,7 @@ func checkScopeAccess(r *http.Request, claims *domain.TokenClaims, log zerowrap.
290 }291 }
291 292
292 // sendForbidden sends an HTTP 403 response.293 // sendForbidden sends an HTTP 403 response.
293-func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, r *http.Request) {294+func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request) {
294 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")295 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
295 w.Header().Set("Content-Type", "application/json")296 w.Header().Set("Content-Type", "application/json")
296 w.WriteHeader(http.StatusForbidden)297 w.WriteHeader(http.StatusForbidden)
@@ -301,12 +302,12 @@ func sendForbidden(w http.ResponseWriter, log zerowrap.Logger, r *http.Request)
301 Str(zerowrap.FieldAdapter, "http").302 Str(zerowrap.FieldAdapter, "http").
302 Str(zerowrap.FieldMethod, r.Method).303 Str(zerowrap.FieldMethod, r.Method).
303 Str(zerowrap.FieldPath, r.URL.Path).304 Str(zerowrap.FieldPath, r.URL.Path).
304- Str(zerowrap.FieldClientIP, r.RemoteAddr).305+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
305 Msg("forbidden: insufficient scope for operation")306 Msg("forbidden: insufficient scope for operation")
306 }307 }
307 308
308 // sendUnauthorizedMsg sends an HTTP 401 response with a custom error message.309 // sendUnauthorizedMsg sends an HTTP 401 response with a custom error message.
309-func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, r *http.Request, msg string) {310+func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request, msg string) {
310 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")311 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
311 312
312 switch authType {313 switch authType {
@@ -332,12 +333,12 @@ func sendUnauthorizedMsg(w http.ResponseWriter, authType domain.AuthType, host s
332 Str(zerowrap.FieldAdapter, "http").333 Str(zerowrap.FieldAdapter, "http").
333 Str(zerowrap.FieldMethod, r.Method).334 Str(zerowrap.FieldMethod, r.Method).
334 Str(zerowrap.FieldPath, r.URL.Path).335 Str(zerowrap.FieldPath, r.URL.Path).
335- Str(zerowrap.FieldClientIP, r.RemoteAddr).336+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
336 Msg("unauthorized registry access attempt")337 Msg("unauthorized registry access attempt")
337 }338 }
338 339
339 // sendUnauthorized sends an HTTP 401 response with appropriate headers.340 // sendUnauthorized sends an HTTP 401 response with appropriate headers.
340-func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, r *http.Request) {341+func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host string, log zerowrap.Logger, trustedNets []*net.IPNet, r *http.Request) {
341 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")342 w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
342 343
343 switch authType {344 switch authType {
@@ -375,6 +376,6 @@ func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host stri
375 Str(zerowrap.FieldAdapter, "http").376 Str(zerowrap.FieldAdapter, "http").
376 Str(zerowrap.FieldMethod, r.Method).377 Str(zerowrap.FieldMethod, r.Method).
377 Str(zerowrap.FieldPath, r.URL.Path).378 Str(zerowrap.FieldPath, r.URL.Path).
378- Str(zerowrap.FieldClientIP, r.RemoteAddr).379+ Str(zerowrap.FieldClientIP, GetClientIP(r, trustedNets)).
379 Msg("unauthorized registry access attempt")380 Msg("unauthorized registry access attempt")
380 }381 }
M internal/adapters/in/http/middleware/auth_test.go +6 -6
@@ -34,7 +34,7 @@ func TestRegistryAuthV2_LocalhostBypassRequiresInternalAuth(t *testing.T) {
3434 Password: "secret",
3535 }
3636
37- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
37+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
3838 w.WriteHeader(http.StatusOK)
3939 }))
4040
@@ -65,7 +65,7 @@ func TestRegistryAuthV2_LocalhostWrongInternalAuthFallsBack(t *testing.T) {
6565 Password: "secret",
6666 }
6767
68- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
68+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
6969 w.WriteHeader(http.StatusOK)
7070 }))
7171
@@ -97,7 +97,7 @@ func TestRegistryAuthV2_NonLocalhostIgnoresInternalAuth(t *testing.T) {
9797 Password: "secret",
9898 }
9999
100- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
100+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
101101 w.WriteHeader(http.StatusOK)
102102 }))
103103
@@ -359,7 +359,7 @@ func TestRegistryAuthV2_RejectsLongLivedToken(t *testing.T) {
359359 Password: "secret",
360360 }
361361
362- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
362+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
363363 w.WriteHeader(http.StatusOK)
364364 }))
365365
@@ -393,7 +393,7 @@ func TestRegistryAuthV2_AcceptsEphemeralToken(t *testing.T) {
393393 }
394394
395395 handlerCalled := false
396- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
396+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
397397 handlerCalled = true
398398 w.WriteHeader(http.StatusOK)
399399 }))
@@ -427,7 +427,7 @@ func TestRegistryAuthV2_RejectsLongLivedTokenAsPassword(t *testing.T) {
427427 Password: "secret",
428428 }
429429
430- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
430+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
431431 w.WriteHeader(http.StatusOK)
432432 }))
433433
@@ -34,7 +34,7 @@ func TestRegistryAuthV2_LocalhostBypassRequiresInternalAuth(t *testing.T) {
34 Password: "secret",34 Password: "secret",
35 }35 }
36 36
37- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {37+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
38 w.WriteHeader(http.StatusOK)38 w.WriteHeader(http.StatusOK)
39 }))39 }))
40 40
@@ -65,7 +65,7 @@ func TestRegistryAuthV2_LocalhostWrongInternalAuthFallsBack(t *testing.T) {
65 Password: "secret",65 Password: "secret",
66 }66 }
67 67
68- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {68+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
69 w.WriteHeader(http.StatusOK)69 w.WriteHeader(http.StatusOK)
70 }))70 }))
71 71
@@ -97,7 +97,7 @@ func TestRegistryAuthV2_NonLocalhostIgnoresInternalAuth(t *testing.T) {
97 Password: "secret",97 Password: "secret",
98 }98 }
99 99
100- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {100+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
101 w.WriteHeader(http.StatusOK)101 w.WriteHeader(http.StatusOK)
102 }))102 }))
103 103
@@ -359,7 +359,7 @@ func TestRegistryAuthV2_RejectsLongLivedToken(t *testing.T) {
359 Password: "secret",359 Password: "secret",
360 }360 }
361 361
362- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {362+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
363 w.WriteHeader(http.StatusOK)363 w.WriteHeader(http.StatusOK)
364 }))364 }))
365 365
@@ -393,7 +393,7 @@ func TestRegistryAuthV2_AcceptsEphemeralToken(t *testing.T) {
393 }393 }
394 394
395 handlerCalled := false395 handlerCalled := false
396- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {396+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
397 handlerCalled = true397 handlerCalled = true
398 w.WriteHeader(http.StatusOK)398 w.WriteHeader(http.StatusOK)
399 }))399 }))
@@ -427,7 +427,7 @@ func TestRegistryAuthV2_RejectsLongLivedTokenAsPassword(t *testing.T) {
427 Password: "secret",427 Password: "secret",
428 }428 }
429 429
430- handler := RegistryAuthV2(authSvc, internalAuth, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {430+ handler := RegistryAuthV2(authSvc, internalAuth, nil, log)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
431 w.WriteHeader(http.StatusOK)431 w.WriteHeader(http.StatusOK)
432 }))432 }))
433 433
M internal/adapters/in/http/proxy/forwarder_test.go +6 -6
@@ -58,7 +58,7 @@ func TestForwardToTarget_H2CEndToEnd(t *testing.T) {
5858 }, nil)
5959 proxySvc.EXPECT().TrackInFlight("grpc-1").Return(func() {})
6060
61- handler := NewHandler(proxySvc, zerowrap.Default())
61+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
6262
6363 req := httptest.NewRequest(http.MethodGet, "http://grpc.example.com/test", nil)
6464 req.Host = "grpc.example.com"
@@ -90,7 +90,7 @@ func TestForwardToTarget_HTTP1StillWorks(t *testing.T) {
9090 }, nil)
9191 proxySvc.EXPECT().TrackInFlight("web-1").Return(func() {})
9292
93- handler := NewHandler(proxySvc, zerowrap.Default())
93+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
9494
9595 req := httptest.NewRequest(http.MethodGet, "http://web.example.com/", nil)
9696 req.Host = "web.example.com"
@@ -103,7 +103,7 @@ func TestForwardToTarget_HTTP1StillWorks(t *testing.T) {
103103 }
104104
105105 func TestTransportForTarget_SelectsCorrectTransport(t *testing.T) {
106- handler := NewHandler(inmocks.NewMockProxyService(t), zerowrap.Default())
106+ handler := NewHandler(inmocks.NewMockProxyService(t), nil, zerowrap.Default())
107107
108108 h2cTarget := &domain.ProxyTarget{Protocol: "h2c"}
109109 httpTarget := &domain.ProxyTarget{Protocol: ""}
@@ -135,7 +135,7 @@ func TestForwardToTarget_OriginalHostPreserved(t *testing.T) {
135135 }, nil)
136136 proxySvc.EXPECT().TrackInFlight("").Return(func() {})
137137
138- handler := NewHandler(proxySvc, zerowrap.Default())
138+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
139139
140140 req := httptest.NewRequest(http.MethodGet, "http://external.example.com/", nil)
141141 req.Host = "external.example.com"
@@ -159,7 +159,7 @@ func TestForwardToTarget_BackendDown_Returns503(t *testing.T) {
159159 }, nil)
160160 proxySvc.EXPECT().TrackInFlight("c-down").Return(func() {})
161161
162- handler := NewHandler(proxySvc, zerowrap.Default())
162+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
163163
164164 req := httptest.NewRequest(http.MethodGet, "http://down.example.com/", nil)
165165 req.Host = "down.example.com"
@@ -189,7 +189,7 @@ func TestForwardToTarget_ProxyHeaderSet(t *testing.T) {
189189 }, nil)
190190 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
191191
192- handler := NewHandler(proxySvc, zerowrap.Default())
192+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
193193
194194 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
195195 req.Host = "app.example.com"
@@ -58,7 +58,7 @@ func TestForwardToTarget_H2CEndToEnd(t *testing.T) {
58 }, nil)58 }, nil)
59 proxySvc.EXPECT().TrackInFlight("grpc-1").Return(func() {})59 proxySvc.EXPECT().TrackInFlight("grpc-1").Return(func() {})
60 60
61- handler := NewHandler(proxySvc, zerowrap.Default())61+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
62 62
63 req := httptest.NewRequest(http.MethodGet, "http://grpc.example.com/test", nil)63 req := httptest.NewRequest(http.MethodGet, "http://grpc.example.com/test", nil)
64 req.Host = "grpc.example.com"64 req.Host = "grpc.example.com"
@@ -90,7 +90,7 @@ func TestForwardToTarget_HTTP1StillWorks(t *testing.T) {
90 }, nil)90 }, nil)
91 proxySvc.EXPECT().TrackInFlight("web-1").Return(func() {})91 proxySvc.EXPECT().TrackInFlight("web-1").Return(func() {})
92 92
93- handler := NewHandler(proxySvc, zerowrap.Default())93+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
94 94
95 req := httptest.NewRequest(http.MethodGet, "http://web.example.com/", nil)95 req := httptest.NewRequest(http.MethodGet, "http://web.example.com/", nil)
96 req.Host = "web.example.com"96 req.Host = "web.example.com"
@@ -103,7 +103,7 @@ func TestForwardToTarget_HTTP1StillWorks(t *testing.T) {
103 }103 }
104 104
105 func TestTransportForTarget_SelectsCorrectTransport(t *testing.T) {105 func TestTransportForTarget_SelectsCorrectTransport(t *testing.T) {
106- handler := NewHandler(inmocks.NewMockProxyService(t), zerowrap.Default())106+ handler := NewHandler(inmocks.NewMockProxyService(t), nil, zerowrap.Default())
107 107
108 h2cTarget := &domain.ProxyTarget{Protocol: "h2c"}108 h2cTarget := &domain.ProxyTarget{Protocol: "h2c"}
109 httpTarget := &domain.ProxyTarget{Protocol: ""}109 httpTarget := &domain.ProxyTarget{Protocol: ""}
@@ -135,7 +135,7 @@ func TestForwardToTarget_OriginalHostPreserved(t *testing.T) {
135 }, nil)135 }, nil)
136 proxySvc.EXPECT().TrackInFlight("").Return(func() {})136 proxySvc.EXPECT().TrackInFlight("").Return(func() {})
137 137
138- handler := NewHandler(proxySvc, zerowrap.Default())138+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
139 139
140 req := httptest.NewRequest(http.MethodGet, "http://external.example.com/", nil)140 req := httptest.NewRequest(http.MethodGet, "http://external.example.com/", nil)
141 req.Host = "external.example.com"141 req.Host = "external.example.com"
@@ -159,7 +159,7 @@ func TestForwardToTarget_BackendDown_Returns503(t *testing.T) {
159 }, nil)159 }, nil)
160 proxySvc.EXPECT().TrackInFlight("c-down").Return(func() {})160 proxySvc.EXPECT().TrackInFlight("c-down").Return(func() {})
161 161
162- handler := NewHandler(proxySvc, zerowrap.Default())162+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
163 163
164 req := httptest.NewRequest(http.MethodGet, "http://down.example.com/", nil)164 req := httptest.NewRequest(http.MethodGet, "http://down.example.com/", nil)
165 req.Host = "down.example.com"165 req.Host = "down.example.com"
@@ -189,7 +189,7 @@ func TestForwardToTarget_ProxyHeaderSet(t *testing.T) {
189 }, nil)189 }, nil)
190 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})190 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
191 191
192- handler := NewHandler(proxySvc, zerowrap.Default())192+ handler := NewHandler(proxySvc, nil, zerowrap.Default())
193 193
194 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)194 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
195 req.Host = "app.example.com"195 req.Host = "app.example.com"
M internal/adapters/in/http/proxy/handler.go +6 -2
@@ -2,11 +2,13 @@
22 package proxy
33
44 import (
5+ "net"
56 "net/http"
67 "sync/atomic"
78
89 "github.com/bnema/zerowrap"
910
11+ "github.com/bnema/gordon/internal/adapters/in/http/middleware"
1012 "github.com/bnema/gordon/internal/boundaries/in"
1113 )
1214
@@ -17,6 +19,7 @@ import (
1719 type Handler struct {
1820 proxySvc in.ProxyService
1921 log zerowrap.Logger
22+ trustedNets []*net.IPNet
2023 appTransport http.RoundTripper
2124 h2cTransport http.RoundTripper
2225 registryTransport http.RoundTripper
@@ -24,10 +27,11 @@ type Handler struct {
2427 }
2528
2629 // NewHandler creates a new proxy HTTP handler.
27-func NewHandler(proxySvc in.ProxyService, log zerowrap.Logger) *Handler {
30+func NewHandler(proxySvc in.ProxyService, trustedNets []*net.IPNet, log zerowrap.Logger) *Handler {
2831 return &Handler{
2932 proxySvc: proxySvc,
3033 log: log,
34+ trustedNets: trustedNets,
3135 appTransport: newAppTransport(),
3236 h2cTransport: newH2CTransport(),
3337 registryTransport: newRegistryTransport(),
@@ -61,7 +65,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6165 zerowrap.FieldMethod: r.Method,
6266 zerowrap.FieldPath: r.URL.Path,
6367 zerowrap.FieldHost: r.Host,
64- zerowrap.FieldClientIP: r.RemoteAddr,
68+ zerowrap.FieldClientIP: middleware.GetClientIP(r, h.trustedNets),
6569 })
6670 r = r.WithContext(ctx)
6771 log := zerowrap.FromCtx(ctx)
@@ -2,11 +2,13 @@
2 package proxy2 package proxy
3 3
4 import (4 import (
5+ "net"
5 "net/http"6 "net/http"
6 "sync/atomic"7 "sync/atomic"
7 8
8 "github.com/bnema/zerowrap"9 "github.com/bnema/zerowrap"
9 10
11+ "github.com/bnema/gordon/internal/adapters/in/http/middleware"
10 "github.com/bnema/gordon/internal/boundaries/in"12 "github.com/bnema/gordon/internal/boundaries/in"
11 )13 )
12 14
@@ -17,6 +19,7 @@ import (
17 type Handler struct {19 type Handler struct {
18 proxySvc in.ProxyService20 proxySvc in.ProxyService
19 log zerowrap.Logger21 log zerowrap.Logger
22+ trustedNets []*net.IPNet
20 appTransport http.RoundTripper23 appTransport http.RoundTripper
21 h2cTransport http.RoundTripper24 h2cTransport http.RoundTripper
22 registryTransport http.RoundTripper25 registryTransport http.RoundTripper
@@ -24,10 +27,11 @@ type Handler struct {
24 }27 }
25 28
26 // NewHandler creates a new proxy HTTP handler.29 // NewHandler creates a new proxy HTTP handler.
27-func NewHandler(proxySvc in.ProxyService, log zerowrap.Logger) *Handler {30+func NewHandler(proxySvc in.ProxyService, trustedNets []*net.IPNet, log zerowrap.Logger) *Handler {
28 return &Handler{31 return &Handler{
29 proxySvc: proxySvc,32 proxySvc: proxySvc,
30 log: log,33 log: log,
34+ trustedNets: trustedNets,
31 appTransport: newAppTransport(),35 appTransport: newAppTransport(),
32 h2cTransport: newH2CTransport(),36 h2cTransport: newH2CTransport(),
33 registryTransport: newRegistryTransport(),37 registryTransport: newRegistryTransport(),
@@ -61,7 +65,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
61 zerowrap.FieldMethod: r.Method,65 zerowrap.FieldMethod: r.Method,
62 zerowrap.FieldPath: r.URL.Path,66 zerowrap.FieldPath: r.URL.Path,
63 zerowrap.FieldHost: r.Host,67 zerowrap.FieldHost: r.Host,
64- zerowrap.FieldClientIP: r.RemoteAddr,68+ zerowrap.FieldClientIP: middleware.GetClientIP(r, h.trustedNets),
65 })69 })
66 r = r.WithContext(ctx)70 r = r.WithContext(ctx)
67 log := zerowrap.FromCtx(ctx)71 log := zerowrap.FromCtx(ctx)
M internal/adapters/in/http/proxy/handler_test.go +7 -7
@@ -28,7 +28,7 @@ func TestHandler_ConcurrentConnectionLimit_503WhenFull(t *testing.T) {
2828 MaxConcurrentConns: 1,
2929 })
3030
31- handler := NewHandler(proxySvc, testLogger())
31+ handler := NewHandler(proxySvc, nil, testLogger())
3232
3333 // Simulate an in-flight connection.
3434 handler.activeConns.Add(1)
@@ -51,7 +51,7 @@ func TestHandler_RoutesToRegistry(t *testing.T) {
5151 proxySvc.EXPECT().TrackRegistryRequest().Return()
5252 proxySvc.EXPECT().ReleaseRegistryRequest().Return()
5353
54- handler := NewHandler(proxySvc, testLogger())
54+ handler := NewHandler(proxySvc, nil, testLogger())
5555
5656 req := httptest.NewRequest(http.MethodGet, "http://registry.example.com/v2/", nil)
5757 req.Host = "registry.example.com"
@@ -68,7 +68,7 @@ func TestHandler_Returns404WhenNoTarget(t *testing.T) {
6868 proxySvc.EXPECT().IsRegistryDomain("unknown.example.com").Return(false)
6969 proxySvc.EXPECT().GetTarget(mock.Anything, "unknown.example.com").Return(nil, domain.ErrNoTargetAvailable)
7070
71- handler := NewHandler(proxySvc, testLogger())
71+ handler := NewHandler(proxySvc, nil, testLogger())
7272
7373 req := httptest.NewRequest(http.MethodGet, "http://unknown.example.com/", nil)
7474 req.Host = "unknown.example.com"
@@ -101,7 +101,7 @@ func TestHandler_ProxiesToTarget(t *testing.T) {
101101 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(target, nil)
102102 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
103103
104- handler := NewHandler(proxySvc, testLogger())
104+ handler := NewHandler(proxySvc, nil, testLogger())
105105
106106 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
107107 req.Host = "app.example.com"
@@ -135,7 +135,7 @@ func TestHandler_ReadsMaxBodySizeConfig(t *testing.T) {
135135 }, nil)
136136 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
137137
138- handler := NewHandler(proxySvc, testLogger())
138+ handler := NewHandler(proxySvc, nil, testLogger())
139139
140140 // Send a body larger than the 1024-byte limit.
141141 largeBody := bytes.NewReader(make([]byte, 2048))
@@ -156,7 +156,7 @@ func TestHandler_UpdatedConfigReflected(t *testing.T) {
156156 proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false).Once()
157157 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(nil, domain.ErrNoTargetAvailable).Once()
158158
159- handler := NewHandler(proxySvc, testLogger())
159+ handler := NewHandler(proxySvc, nil, testLogger())
160160
161161 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
162162 req.Host = "app.example.com"
@@ -199,7 +199,7 @@ func TestHandler_NoConcurrencyLimitWhenZero(t *testing.T) {
199199 }, nil)
200200 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
201201
202- handler := NewHandler(proxySvc, testLogger())
202+ handler := NewHandler(proxySvc, nil, testLogger())
203203
204204 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
205205 req.Host = "app.example.com"
@@ -28,7 +28,7 @@ func TestHandler_ConcurrentConnectionLimit_503WhenFull(t *testing.T) {
28 MaxConcurrentConns: 1,28 MaxConcurrentConns: 1,
29 })29 })
30 30
31- handler := NewHandler(proxySvc, testLogger())31+ handler := NewHandler(proxySvc, nil, testLogger())
32 32
33 // Simulate an in-flight connection.33 // Simulate an in-flight connection.
34 handler.activeConns.Add(1)34 handler.activeConns.Add(1)
@@ -51,7 +51,7 @@ func TestHandler_RoutesToRegistry(t *testing.T) {
51 proxySvc.EXPECT().TrackRegistryRequest().Return()51 proxySvc.EXPECT().TrackRegistryRequest().Return()
52 proxySvc.EXPECT().ReleaseRegistryRequest().Return()52 proxySvc.EXPECT().ReleaseRegistryRequest().Return()
53 53
54- handler := NewHandler(proxySvc, testLogger())54+ handler := NewHandler(proxySvc, nil, testLogger())
55 55
56 req := httptest.NewRequest(http.MethodGet, "http://registry.example.com/v2/", nil)56 req := httptest.NewRequest(http.MethodGet, "http://registry.example.com/v2/", nil)
57 req.Host = "registry.example.com"57 req.Host = "registry.example.com"
@@ -68,7 +68,7 @@ func TestHandler_Returns404WhenNoTarget(t *testing.T) {
68 proxySvc.EXPECT().IsRegistryDomain("unknown.example.com").Return(false)68 proxySvc.EXPECT().IsRegistryDomain("unknown.example.com").Return(false)
69 proxySvc.EXPECT().GetTarget(mock.Anything, "unknown.example.com").Return(nil, domain.ErrNoTargetAvailable)69 proxySvc.EXPECT().GetTarget(mock.Anything, "unknown.example.com").Return(nil, domain.ErrNoTargetAvailable)
70 70
71- handler := NewHandler(proxySvc, testLogger())71+ handler := NewHandler(proxySvc, nil, testLogger())
72 72
73 req := httptest.NewRequest(http.MethodGet, "http://unknown.example.com/", nil)73 req := httptest.NewRequest(http.MethodGet, "http://unknown.example.com/", nil)
74 req.Host = "unknown.example.com"74 req.Host = "unknown.example.com"
@@ -101,7 +101,7 @@ func TestHandler_ProxiesToTarget(t *testing.T) {
101 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(target, nil)101 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(target, nil)
102 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})102 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
103 103
104- handler := NewHandler(proxySvc, testLogger())104+ handler := NewHandler(proxySvc, nil, testLogger())
105 105
106 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)106 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
107 req.Host = "app.example.com"107 req.Host = "app.example.com"
@@ -135,7 +135,7 @@ func TestHandler_ReadsMaxBodySizeConfig(t *testing.T) {
135 }, nil)135 }, nil)
136 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})136 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
137 137
138- handler := NewHandler(proxySvc, testLogger())138+ handler := NewHandler(proxySvc, nil, testLogger())
139 139
140 // Send a body larger than the 1024-byte limit.140 // Send a body larger than the 1024-byte limit.
141 largeBody := bytes.NewReader(make([]byte, 2048))141 largeBody := bytes.NewReader(make([]byte, 2048))
@@ -156,7 +156,7 @@ func TestHandler_UpdatedConfigReflected(t *testing.T) {
156 proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false).Once()156 proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false).Once()
157 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(nil, domain.ErrNoTargetAvailable).Once()157 proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(nil, domain.ErrNoTargetAvailable).Once()
158 158
159- handler := NewHandler(proxySvc, testLogger())159+ handler := NewHandler(proxySvc, nil, testLogger())
160 160
161 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)161 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
162 req.Host = "app.example.com"162 req.Host = "app.example.com"
@@ -199,7 +199,7 @@ func TestHandler_NoConcurrencyLimitWhenZero(t *testing.T) {
199 }, nil)199 }, nil)
200 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})200 proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
201 201
202- handler := NewHandler(proxySvc, testLogger())202+ handler := NewHandler(proxySvc, nil, testLogger())
203 203
204 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)204 req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
205 req.Host = "app.example.com"205 req.Host = "app.example.com"
M internal/app/run.go +4 -4
@@ -1632,7 +1632,7 @@ func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Ha
16321632 registerAdminRoutes(registryMux, svc, cfg, trustedNets, log)
16331633
16341634 // Proxy handler
1635- proxyHandler := proxyadapter.NewHandler(svc.proxySvc, log)
1635+ proxyHandler := proxyadapter.NewHandler(svc.proxySvc, trustedNets, log)
16361636
16371637 // HTTP proxy handler chain: HTTPS redirect for non-proxy clients, then CIDR allowlist
16381638 proxyAllowedNets, proxyCIDRMiddleware := buildProxyCIDRAllowlistMiddleware(cfg, trustedNets, log)
@@ -1775,7 +1775,7 @@ func buildRegistryHandlerWithMiddleware(
17751775 rateLimitMiddleware := buildRegistryRateLimitMiddleware(cfg, log)
17761776 registryMiddlewares = append(registryMiddlewares, rateLimitMiddleware)
17771777
1778- appendRegistryAuthMiddleware(&registryMiddlewares, svc, cfg, log)
1778+ appendRegistryAuthMiddleware(&registryMiddlewares, svc, cfg, trustedNets, log)
17791779
17801780 registryWithOtel := otelhttp.NewHandler(
17811781 middleware.Chain(registryMiddlewares...)(registryHandler),
@@ -1866,13 +1866,13 @@ func buildRegistryRateLimitMiddleware(cfg Config, log zerowrap.Logger) func(http
18661866 return registry.RateLimitMiddleware(nil, nil, nil, log)
18671867 }
18681868
1869-func appendRegistryAuthMiddleware(registryMiddlewares *[]func(http.Handler) http.Handler, svc *services, cfg Config, log zerowrap.Logger) {
1869+func appendRegistryAuthMiddleware(registryMiddlewares *[]func(http.Handler) http.Handler, svc *services, cfg Config, trustedNets []*net.IPNet, log zerowrap.Logger) {
18701870 if svc.authSvc != nil {
18711871 internalAuth := middleware.InternalRegistryAuth{
18721872 Username: svc.internalRegUser,
18731873 Password: svc.internalRegPass,
18741874 }
1875- *registryMiddlewares = append(*registryMiddlewares, middleware.RegistryAuthV2(svc.authSvc, internalAuth, log))
1875+ *registryMiddlewares = append(*registryMiddlewares, middleware.RegistryAuthV2(svc.authSvc, internalAuth, trustedNets, log))
18761876 return
18771877 }
18781878
@@ -1632,7 +1632,7 @@ func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Ha
1632 registerAdminRoutes(registryMux, svc, cfg, trustedNets, log)1632 registerAdminRoutes(registryMux, svc, cfg, trustedNets, log)
1633 1633
1634 // Proxy handler1634 // Proxy handler
1635- proxyHandler := proxyadapter.NewHandler(svc.proxySvc, log)1635+ proxyHandler := proxyadapter.NewHandler(svc.proxySvc, trustedNets, log)
1636 1636
1637 // HTTP proxy handler chain: HTTPS redirect for non-proxy clients, then CIDR allowlist1637 // HTTP proxy handler chain: HTTPS redirect for non-proxy clients, then CIDR allowlist
1638 proxyAllowedNets, proxyCIDRMiddleware := buildProxyCIDRAllowlistMiddleware(cfg, trustedNets, log)1638 proxyAllowedNets, proxyCIDRMiddleware := buildProxyCIDRAllowlistMiddleware(cfg, trustedNets, log)
@@ -1775,7 +1775,7 @@ func buildRegistryHandlerWithMiddleware(
1775 rateLimitMiddleware := buildRegistryRateLimitMiddleware(cfg, log)1775 rateLimitMiddleware := buildRegistryRateLimitMiddleware(cfg, log)
1776 registryMiddlewares = append(registryMiddlewares, rateLimitMiddleware)1776 registryMiddlewares = append(registryMiddlewares, rateLimitMiddleware)
1777 1777
1778- appendRegistryAuthMiddleware(&registryMiddlewares, svc, cfg, log)1778+ appendRegistryAuthMiddleware(&registryMiddlewares, svc, cfg, trustedNets, log)
1779 1779
1780 registryWithOtel := otelhttp.NewHandler(1780 registryWithOtel := otelhttp.NewHandler(
1781 middleware.Chain(registryMiddlewares...)(registryHandler),1781 middleware.Chain(registryMiddlewares...)(registryHandler),
@@ -1866,13 +1866,13 @@ func buildRegistryRateLimitMiddleware(cfg Config, log zerowrap.Logger) func(http
1866 return registry.RateLimitMiddleware(nil, nil, nil, log)1866 return registry.RateLimitMiddleware(nil, nil, nil, log)
1867 }1867 }
1868 1868
1869-func appendRegistryAuthMiddleware(registryMiddlewares *[]func(http.Handler) http.Handler, svc *services, cfg Config, log zerowrap.Logger) {1869+func appendRegistryAuthMiddleware(registryMiddlewares *[]func(http.Handler) http.Handler, svc *services, cfg Config, trustedNets []*net.IPNet, log zerowrap.Logger) {
1870 if svc.authSvc != nil {1870 if svc.authSvc != nil {
1871 internalAuth := middleware.InternalRegistryAuth{1871 internalAuth := middleware.InternalRegistryAuth{
1872 Username: svc.internalRegUser,1872 Username: svc.internalRegUser,
1873 Password: svc.internalRegPass,1873 Password: svc.internalRegPass,
1874 }1874 }
1875- *registryMiddlewares = append(*registryMiddlewares, middleware.RegistryAuthV2(svc.authSvc, internalAuth, log))1875+ *registryMiddlewares = append(*registryMiddlewares, middleware.RegistryAuthV2(svc.authSvc, internalAuth, trustedNets, log))
1876 return1876 return
1877 }1877 }
1878 1878

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close