Skip to content

Commit ee4f497

Browse files
authored
feature: add auto for X-Forwarded-Proto and X-FOrwarded-Port (#3878)
closes #3871 Signed-off-by: patrickdk <patrickdk@patrickdk.com>
1 parent 2b0d598 commit ee4f497

5 files changed

Lines changed: 133 additions & 8 deletions

File tree

config/config.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,8 @@ func NewConfig() *Config {
503503
"X-Forwarded-Host sets X-Forwarded-Host value to the request host\n"+
504504
"X-Forwarded-Method sets X-Forwarded-Method value to the request method\n"+
505505
"X-Forwarded-Uri sets X-Forwarded-Uri value to the requestURI\n"+
506-
"X-Forwarded-Port=<port> sets X-Forwarded-Port value\n"+
507-
"X-Forwarded-Proto=<http|https> sets X-Forwarded-Proto value")
506+
"X-Forwarded-Port=<port|auto> sets X-Forwarded-Port value, use 'auto' to detect from the listener address\n"+
507+
"X-Forwarded-Proto=<http|https|auto> sets X-Forwarded-Proto value, use 'auto' to detect from TLS state")
508508
flag.Var(cfg.ForwardedHeadersExcludeCIDRList, "forwarded-headers-exclude-cidrs", "disables addition of forwarded headers for the remote host IPs from the comma separated list of CIDRs")
509509

510510
flag.BoolVar(&cfg.NormalizeHost, "normalize-host", false, "converts request host to lowercase and removes port and trailing dot if any")
@@ -1297,6 +1297,8 @@ func (c *Config) parseForwardedHeaders() error {
12971297
c.ForwardedHeaders.Proto = "http"
12981298
case header == "X-Forwarded-Proto=https":
12991299
c.ForwardedHeaders.Proto = "https"
1300+
case header == "X-Forwarded-Proto=auto":
1301+
c.ForwardedHeaders.Proto = "auto"
13001302
default:
13011303
return fmt.Errorf("invalid forwarded header: %s", header)
13021304
}

config/config_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,60 @@ func Test_Validate(t *testing.T) {
347347
}
348348
}
349349

350+
func Test_parseForwardedHeadersAutoDetect(t *testing.T) {
351+
for _, tt := range []struct {
352+
name string
353+
headers []string
354+
wantProto string
355+
wantPort string
356+
wantErr bool
357+
}{
358+
{
359+
name: "X-Forwarded-Proto=auto sets proto to auto",
360+
headers: []string{"X-Forwarded-Proto=auto"},
361+
wantProto: "auto",
362+
},
363+
{
364+
name: "X-Forwarded-Proto=http sets static proto",
365+
headers: []string{"X-Forwarded-Proto=http"},
366+
wantProto: "http",
367+
},
368+
{
369+
name: "X-Forwarded-Proto=https sets static proto",
370+
headers: []string{"X-Forwarded-Proto=https"},
371+
wantProto: "https",
372+
},
373+
{
374+
name: "X-Forwarded-Port=auto sets port to auto",
375+
headers: []string{"X-Forwarded-Port=auto"},
376+
wantPort: "auto",
377+
},
378+
{
379+
name: "X-Forwarded-Port=8080 sets static port",
380+
headers: []string{"X-Forwarded-Port=8080"},
381+
wantPort: "8080",
382+
},
383+
} {
384+
t.Run(tt.name, func(t *testing.T) {
385+
cfg := NewConfig()
386+
cfg.ForwardedHeadersList = commaListFlag(tt.headers...)
387+
for _, h := range tt.headers {
388+
cfg.ForwardedHeadersList.Set(h)
389+
}
390+
err := validate(cfg)
391+
if (err != nil) != tt.wantErr {
392+
t.Fatalf("config.NewConfig() error = %v, wantErr %v", err, tt.wantErr)
393+
}
394+
if cfg.ForwardedHeaders.Proto != tt.wantProto {
395+
t.Errorf("Failed to get wanted Proto, got: %v, want: %v", cfg.ForwardedHeaders.Proto, tt.wantProto)
396+
}
397+
if cfg.ForwardedHeaders.Port != tt.wantPort {
398+
t.Errorf("Failed to get wanted Port, got: %v, want: %v", cfg.ForwardedHeaders.Port, tt.wantPort)
399+
}
400+
})
401+
}
402+
}
403+
350404
func Test_NewConfigWithArgs(t *testing.T) {
351405
for _, tt := range []struct {
352406
name string

docs/operation/operation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,8 +1555,8 @@ Skipper can be configured to add [`X-Forwarded-*` headers](https://en.wikipedia.
15551555
comma separated list of headers to add to the incoming request before routing
15561556
X-Forwarded-For sets or appends with comma the remote IP of the request to the X-Forwarded-For header value
15571557
X-Forwarded-Host sets X-Forwarded-Host value to the request host
1558-
X-Forwarded-Port=<port> sets X-Forwarded-Port value
1559-
X-Forwarded-Proto=<http|https> sets X-Forwarded-Proto value
1558+
X-Forwarded-Port=<port> sets X-Forwarded-Port value, or set to auto to use the port of the listener
1559+
X-Forwarded-Proto=<http|https|auto> sets X-Forwarded-Proto value, or set to auto to use http or https depending on the listener tls state
15601560
-forwarded-headers-exclude-cidrs value
15611561
disables addition of forwarded headers for the remote host IPs from the comma separated list of CIDRs
15621562
```

net/headers.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ type ForwardedHeaders struct {
1818
Method bool
1919
// Uri sets the path and query as X-Forwarded-Uri header to the request header
2020
Uri bool
21-
// Sets X-Forwarded-Port value
21+
// Port sets X-Forwarded-Port value, use "auto" to detect from the listener address
2222
Port string
23-
// Sets X-Forwarded-Proto value
23+
// Proto sets X-Forwarded-Proto value, use "auto" to detect from TLS state
2424
Proto string
2525
}
2626

@@ -54,11 +54,23 @@ func (h *ForwardedHeaders) Set(req *http.Request) {
5454
req.Header.Set("X-Forwarded-Uri", req.RequestURI)
5555
}
5656

57-
if h.Port != "" {
57+
if h.Port == "auto" {
58+
if addr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
59+
if _, port, err := net.SplitHostPort(addr.String()); err == nil {
60+
req.Header.Set("X-Forwarded-Port", port)
61+
}
62+
}
63+
} else if h.Port != "" {
5864
req.Header.Set("X-Forwarded-Port", h.Port)
5965
}
6066

61-
if h.Proto != "" {
67+
if h.Proto == "auto" {
68+
if req.TLS != nil {
69+
req.Header.Set("X-Forwarded-Proto", "https")
70+
} else {
71+
req.Header.Set("X-Forwarded-Proto", "http")
72+
}
73+
} else if h.Proto != "" {
6274
req.Header.Set("X-Forwarded-Proto", h.Proto)
6375
}
6476
}

net/headers_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package net
22

33
import (
44
"bytes"
5+
"context"
6+
"crypto/tls"
7+
"net"
58
"net/http"
69
"net/http/httptest"
710
"reflect"
@@ -20,6 +23,8 @@ func TestForwardedHeaders(t *testing.T) {
2023
header http.Header
2124
forwarded ForwardedHeaders
2225
expected http.Header
26+
tls bool
27+
localAddr string
2328
}{
2429
{
2530
name: "no change when disabled",
@@ -133,6 +138,48 @@ func TestForwardedHeaders(t *testing.T) {
133138
"X-Forwarded-Proto": []string{"https"},
134139
},
135140
},
141+
{
142+
name: "proto auto detect with TLS",
143+
remoteAddr: "1.2.3.4:56",
144+
header: http.Header{},
145+
forwarded: ForwardedHeaders{Proto: "auto"},
146+
tls: true,
147+
expected: http.Header{
148+
"X-Forwarded-Proto": []string{"https"},
149+
},
150+
},
151+
{
152+
name: "proto auto detect without TLS",
153+
remoteAddr: "1.2.3.4:56",
154+
header: http.Header{},
155+
forwarded: ForwardedHeaders{Proto: "auto"},
156+
tls: false,
157+
expected: http.Header{
158+
"X-Forwarded-Proto": []string{"http"},
159+
},
160+
},
161+
{
162+
name: "port auto detect",
163+
remoteAddr: "1.2.3.4:56",
164+
header: http.Header{},
165+
forwarded: ForwardedHeaders{Port: "auto"},
166+
localAddr: "0.0.0.0:8443",
167+
expected: http.Header{
168+
"X-Forwarded-Port": []string{"8443"},
169+
},
170+
},
171+
{
172+
name: "proto and port auto detect together",
173+
remoteAddr: "1.2.3.4:56",
174+
header: http.Header{},
175+
forwarded: ForwardedHeaders{Proto: "auto", Port: "auto"},
176+
tls: true,
177+
localAddr: "0.0.0.0:443",
178+
expected: http.Header{
179+
"X-Forwarded-Proto": []string{"https"},
180+
"X-Forwarded-Port": []string{"443"},
181+
},
182+
},
136183
} {
137184
t.Run(ti.name, func(t *testing.T) {
138185
r := &http.Request{
@@ -143,6 +190,16 @@ func TestForwardedHeaders(t *testing.T) {
143190
RequestURI: ti.requestURI,
144191
}
145192

193+
if ti.tls {
194+
r.TLS = &tls.ConnectionState{}
195+
}
196+
197+
if ti.localAddr != "" {
198+
addr, _ := net.ResolveTCPAddr("tcp", ti.localAddr)
199+
ctx := context.WithValue(r.Context(), http.LocalAddrContextKey, addr)
200+
r = r.WithContext(ctx)
201+
}
202+
146203
ti.forwarded.Set(r)
147204

148205
if !reflect.DeepEqual(ti.expected, r.Header) {

0 commit comments

Comments
 (0)