diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 95bb65eed4..84bd6a02d3 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -1204,6 +1204,13 @@ "title": "Trust X-Forwarded Headers", "description": "Trust the X-Forwarded-* headers from the reverse proxy. This is useful when running behind a load balancer or similar. Set this to false if you are not running behind a reverse proxy that prevents Hop-by-Hop attacks." }, + "trust_custom_headers": { + "type": "array", + "items": { "type": "string" }, + "default": [], + "title": "Trust Custom Headers", + "description": "A list of custom HTTP header names to forward from the incoming request to the upstream. Unlike trust_forwarded_headers (which controls the standard X-Forwarded-* headers), this allows forwarding arbitrary headers (e.g., X-Source-Port) set by trusted infrastructure." + }, "timeout": { "$ref": "#/definitions/serverTimeout" }, diff --git a/driver/configuration/config_keys.go b/driver/configuration/config_keys.go index 88e725ba06..562b8a8a49 100644 --- a/driver/configuration/config_keys.go +++ b/driver/configuration/config_keys.go @@ -12,6 +12,7 @@ const ( ProxyServeAddressHost Key = "serve.proxy.host" ProxyServeAddressPort Key = "serve.proxy.port" ProxyTrustForwardedHeaders Key = "serve.proxy.trust_forwarded_headers" + ProxyTrustCustomHeaders Key = "serve.proxy.trust_custom_headers" APIServeAddressHost Key = "serve.api.host" APIServeAddressPort Key = "serve.api.port" APIReadTimeout Key = "serve.api.timeout.read" diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 4293d97092..897b1d0f4d 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -44,6 +44,7 @@ type Provider interface { CORS(iface string) (cors.Options, bool) ProxyTrustForwardedHeaders() bool + ProxyTrustCustomHeaders() []string ProviderAuthenticators ProviderErrorHandlers diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index d3933eac57..5955698a18 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -390,6 +390,10 @@ func (v *KoanfProvider) ProxyTrustForwardedHeaders() bool { return v.source.Bool(ProxyTrustForwardedHeaders) } +func (v *KoanfProvider) ProxyTrustCustomHeaders() []string { + return v.source.Strings(ProxyTrustCustomHeaders) +} + func (v *KoanfProvider) AuthenticatorConfig(id string, override json.RawMessage, dest interface{}) error { return v.PipelineConfig("authenticators", id, override, dest) } diff --git a/proxy/proxy.go b/proxy/proxy.go index e21f253adb..a13b615177 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -122,6 +122,12 @@ func (d *Proxy) Rewrite(r *httputil.ProxyRequest) { } } + for _, h := range d.c.ProxyTrustCustomHeaders() { + if v := r.In.Header.Get(h); v != "" { + r.Out.Header.Set(h, v) + } + } + EnrichRequestedURL(r) rl, err := d.r.RuleMatcher().Match(r.Out.Context(), r.Out.Method, r.Out.URL, rule.ProtocolHTTP) if err != nil { diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 579919529a..c0b5df993a 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -234,6 +234,39 @@ func TestProxy(t *testing.T) { "header X-Forwarded-Host=foobar.com", }, }, + { + d: "should pass and set additional trusted forwarded headers", + prep: func(t *testing.T) { + conf.SetForTest(t, configuration.ProxyTrustCustomHeaders, []string{"X-Source-Port", "X-Custom"}) + }, + transform: func(r *http.Request) { + r.Header.Set("X-Source-Port", "1234") + r.Header.Set("X-Custom", "abc") + }, + url: ts.URL + "/authn-anon/authz-allow/cred-noop/1234", + rulesRegexp: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + rulesGlob: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]*>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + code: http.StatusOK, + messages: []string{ + "authorization=", + "url=/authn-anon/authz-allow/cred-noop/1234", + "host=" + x.ParseURLOrPanic(backend.URL).Host, + "header X-Source-Port=1234", + "header X-Custom=abc", + }, + }, { d: "should pass and remove x-forwarded headers", transform: func(r *http.Request) { diff --git a/spec/config.schema.json b/spec/config.schema.json index bb2bef34fe..8048fd8d5b 100644 --- a/spec/config.schema.json +++ b/spec/config.schema.json @@ -1204,6 +1204,13 @@ "title": "Trust X-Forwarded Headers", "description": "Trust the X-Forwarded-* headers from the reverse proxy. This is useful when running behind a load balancer or similar. Set this to false if you are not running behind a reverse proxy that prevents Hop-by-Hop attacks." }, + "trust_custom_headers": { + "type": "array", + "items": { "type": "string" }, + "default": [], + "title": "Trust Custom Headers", + "description": "A list of custom HTTP header names to forward from the incoming request to the upstream. Unlike trust_forwarded_headers (which controls the standard X-Forwarded-* headers), this allows forwarding arbitrary headers (e.g., X-Source-Port) set by trusted infrastructure." + }, "timeout": { "$ref": "#/definitions/serverTimeout" },