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

This commit is contained in:
Chris Seufert 2025-10-16 13:47:32 +11:00 committed by GitHub
parent 7fb39ec1e5
commit d7185fd002
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 0 deletions

View file

@ -51,6 +51,7 @@ type serverOptions struct {
StrictSNIHost *bool StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int TrustedProxiesStrict int
TrustedProxiesUnix bool
ClientIPHeaders []string ClientIPHeaders []string
ShouldLogCredentials bool ShouldLogCredentials bool
Metrics *caddyhttp.Metrics Metrics *caddyhttp.Metrics
@ -251,6 +252,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
serverOpts.TrustedProxiesStrict = 1 serverOpts.TrustedProxiesStrict = 1
case "trusted_proxies_unix":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.TrustedProxiesUnix = true
case "client_ip_headers": case "client_ip_headers":
headers := d.RemainingArgs() headers := d.RemainingArgs()
for _, header := range headers { for _, header := range headers {
@ -342,6 +349,7 @@ func applyServerOptions(
server.TrustedProxiesRaw = opts.TrustedProxiesRaw server.TrustedProxiesRaw = opts.TrustedProxiesRaw
server.ClientIPHeaders = opts.ClientIPHeaders server.ClientIPHeaders = opts.ClientIPHeaders
server.TrustedProxiesStrict = opts.TrustedProxiesStrict server.TrustedProxiesStrict = opts.TrustedProxiesStrict
server.TrustedProxiesUnix = opts.TrustedProxiesUnix
server.Metrics = opts.Metrics server.Metrics = opts.Metrics
if opts.ShouldLogCredentials { if opts.ShouldLogCredentials {
if server.Logs == nil { if server.Logs == nil {

View file

@ -0,0 +1,59 @@
{
servers {
trusted_proxies_unix
}
}
example.com {
reverse_proxy https://local:8080
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http",
"tls": {}
},
"upstreams": [
{
"dial": "local:8080"
}
]
}
]
}
]
}
],
"terminal": true
}
],
"trusted_proxies_unix": true
}
}
}
}
}

View file

@ -202,6 +202,13 @@ type Server struct {
// This option is disabled by default. // This option is disabled by default.
TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"` TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"`
// If greater than zero, enables trusting socket connections
// (e.g. Unix domain sockets) as coming from a trusted
// proxy.
//
// This option is disabled by default.
TrustedProxiesUnix bool `json:"trusted_proxies_unix,omitempty"`
// Enables access logging and configures how access logs are handled // Enables access logging and configures how access logs are handled
// in this server. To minimally enable access logs, simply set this // in this server. To minimally enable access logs, simply set this
// to a non-null, empty struct. // to a non-null, empty struct.
@ -941,6 +948,17 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) {
return false, "" return false, ""
} }
if s.TrustedProxiesUnix && r.RemoteAddr == "@" {
if s.TrustedProxiesStrict > 0 {
ipRanges := []netip.Prefix{}
if s.trustedProxies != nil {
ipRanges = s.trustedProxies.GetIPRanges(r)
}
return true, strictUntrustedClientIp(r, s.ClientIPHeaders, ipRanges, "@")
} else {
return true, trustedRealClientIP(r, s.ClientIPHeaders, "@")
}
}
// Parse the remote IP, ignore the error as non-fatal, // Parse the remote IP, ignore the error as non-fatal,
// but the remote IP is required to continue, so we // but the remote IP is required to continue, so we
// just return early. This should probably never happen // just return early. This should probably never happen

View file

@ -297,6 +297,39 @@ func TestServer_DetermineTrustedProxy_TrustedLoopback(t *testing.T) {
assert.Equal(t, clientIP, "31.40.0.10") assert.Equal(t, clientIP, "31.40.0.10")
} }
func TestServer_DetermineTrustedProxy_UnixSocket(t *testing.T) {
server := &Server{
ClientIPHeaders: []string{"X-Forwarded-For"},
TrustedProxiesUnix: true,
}
req := httptest.NewRequest("GET", "/", nil)
req.RemoteAddr = "@"
req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3")
trusted, clientIP := determineTrustedProxy(req, server)
assert.True(t, trusted)
assert.Equal(t, "2.2.2.2", clientIP)
}
func TestServer_DetermineTrustedProxy_UnixSocketStrict(t *testing.T) {
server := &Server{
ClientIPHeaders: []string{"X-Forwarded-For"},
TrustedProxiesUnix: true,
TrustedProxiesStrict: 1,
}
req := httptest.NewRequest("GET", "/", nil)
req.RemoteAddr = "@"
req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3")
trusted, clientIP := determineTrustedProxy(req, server)
assert.True(t, trusted)
assert.Equal(t, "3.3.3.3", clientIP)
}
func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) { func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) {
loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8")