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