@marcel91200 / caddy Mirror · read-only / commits / d7185fd0
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 19:45 UTC

caddyhttp: Add `trusted_proxies_unix` for trusting unix socket `X-Forwarded-*` headers (#7265)

Chris Seufert committed Oct 16, 2025 at 13:47 UTC d7185fd002a2c57d20a98d3e94442da7b259b3ad
4 files changed +118
caddyconfig/httpcaddyfile/serveroptions.go
+8
@@ -51,6 +51,7 @@ type serverOptions struct {
51 StrictSNIHost *bool
52 TrustedProxiesRaw json.RawMessage
53 TrustedProxiesStrict int
54 + TrustedProxiesUnix bool
55 ClientIPHeaders []string
56 ShouldLogCredentials bool
57 Metrics *caddyhttp.Metrics
@@ -251,6 +252,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
252 }
253 serverOpts.TrustedProxiesStrict = 1
254
255 + case "trusted_proxies_unix":
256 + if d.NextArg() {
257 + return nil, d.ArgErr()
258 + }
259 + serverOpts.TrustedProxiesUnix = true
260 +
261 case "client_ip_headers":
262 headers := d.RemainingArgs()
263 for _, header := range headers {
@@ -342,6 +349,7 @@ func applyServerOptions(
349 server.TrustedProxiesRaw = opts.TrustedProxiesRaw
350 server.ClientIPHeaders = opts.ClientIPHeaders
351 server.TrustedProxiesStrict = opts.TrustedProxiesStrict
352 + server.TrustedProxiesUnix = opts.TrustedProxiesUnix
353 server.Metrics = opts.Metrics
354 if opts.ShouldLogCredentials {
355 if server.Logs == nil {
caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies_unix.caddyfiletest new
+59
@@ -0,0 +1,59 @@
1 +{
2 + servers {
3 + trusted_proxies_unix
4 + }
5 +}
6 +
7 +example.com {
8 + reverse_proxy https://local:8080
9 +}
10 +----------
11 +{
12 + "apps": {
13 + "http": {
14 + "servers": {
15 + "srv0": {
16 + "listen": [
17 + ":443"
18 + ],
19 + "routes": [
20 + {
21 + "match": [
22 + {
23 + "host": [
24 + "example.com"
25 + ]
26 + }
27 + ],
28 + "handle": [
29 + {
30 + "handler": "subroute",
31 + "routes": [
32 + {
33 + "handle": [
34 + {
35 + "handler": "reverse_proxy",
36 + "transport": {
37 + "protocol": "http",
38 + "tls": {}
39 + },
40 + "upstreams": [
41 + {
42 + "dial": "local:8080"
43 + }
44 + ]
45 + }
46 + ]
47 + }
48 + ]
49 + }
50 + ],
51 + "terminal": true
52 + }
53 + ],
54 + "trusted_proxies_unix": true
55 + }
56 + }
57 + }
58 + }
59 +}
modules/caddyhttp/server.go
+18
@@ -202,6 +202,13 @@ type Server struct {
202 // This option is disabled by default.
203 TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"`
204
205 + // If greater than zero, enables trusting socket connections
206 + // (e.g. Unix domain sockets) as coming from a trusted
207 + // proxy.
208 + //
209 + // This option is disabled by default.
210 + TrustedProxiesUnix bool `json:"trusted_proxies_unix,omitempty"`
211 +
212 // Enables access logging and configures how access logs are handled
213 // in this server. To minimally enable access logs, simply set this
214 // to a non-null, empty struct.
@@ -941,6 +948,17 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) {
948 return false, ""
949 }
950
951 + if s.TrustedProxiesUnix && r.RemoteAddr == "@" {
952 + if s.TrustedProxiesStrict > 0 {
953 + ipRanges := []netip.Prefix{}
954 + if s.trustedProxies != nil {
955 + ipRanges = s.trustedProxies.GetIPRanges(r)
956 + }
957 + return true, strictUntrustedClientIp(r, s.ClientIPHeaders, ipRanges, "@")
958 + } else {
959 + return true, trustedRealClientIP(r, s.ClientIPHeaders, "@")
960 + }
961 + }
962 // Parse the remote IP, ignore the error as non-fatal,
963 // but the remote IP is required to continue, so we
964 // just return early. This should probably never happen
modules/caddyhttp/server_test.go
+33
@@ -297,6 +297,39 @@ func TestServer_DetermineTrustedProxy_TrustedLoopback(t *testing.T) {
297 assert.Equal(t, clientIP, "31.40.0.10")
298 }
299
300 +func TestServer_DetermineTrustedProxy_UnixSocket(t *testing.T) {
301 + server := &Server{
302 + ClientIPHeaders: []string{"X-Forwarded-For"},
303 + TrustedProxiesUnix: true,
304 + }
305 +
306 + req := httptest.NewRequest("GET", "/", nil)
307 + req.RemoteAddr = "@"
308 + req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3")
309 +
310 + trusted, clientIP := determineTrustedProxy(req, server)
311 +
312 + assert.True(t, trusted)
313 + assert.Equal(t, "2.2.2.2", clientIP)
314 +}
315 +
316 +func TestServer_DetermineTrustedProxy_UnixSocketStrict(t *testing.T) {
317 + server := &Server{
318 + ClientIPHeaders: []string{"X-Forwarded-For"},
319 + TrustedProxiesUnix: true,
320 + TrustedProxiesStrict: 1,
321 + }
322 +
323 + req := httptest.NewRequest("GET", "/", nil)
324 + req.RemoteAddr = "@"
325 + req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3")
326 +
327 + trusted, clientIP := determineTrustedProxy(req, server)
328 +
329 + assert.True(t, trusted)
330 + assert.Equal(t, "3.3.3.3", clientIP)
331 +}
332 +
333 func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) {
334 loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8")
335