rproxy: remove every X-Forwarded- header from an untrusted peer (closes #325)
check / check (push) Waiting to run
check / check (push) Waiting to run
For a peer outside SWRP_TRUSTED_PROXIES, rproxy removed a fixed list of traefik's forwarded headers, so names such as X-Forwarded-Ssl and X-Forwarded-Scheme reached the app as the client sent them. An app that reads them could take a plain request for a secure one. Now every header whose name starts with X-Forwarded- is removed before rproxy sets its three, and X-Real-IP as before. A trusted peer's headers pass on unchanged. Model: opus-5-5
This commit is contained in:
1 parent
f7e3190654
commit
35ccb256f9
3 files changed
+18
-12
No files matched your search
@@ -2310,8 +2310,9 @@ but for these headers:
|
|||||||
on.
|
on.
|
||||||
- For a peer outside `SWRP_TRUSTED_PROXIES`, its forwarded headers are its own
|
- For a peer outside `SWRP_TRUSTED_PROXIES`, its forwarded headers are its own
|
||||||
claims and are replaced. `X-Forwarded-For` names the peer, `X-Forwarded-Host`
|
claims and are replaced. `X-Forwarded-For` names the peer, `X-Forwarded-Host`
|
||||||
the host it asked for, and `X-Forwarded-Proto` plain `http`. The other headers
|
the host it asked for, and `X-Forwarded-Proto` plain `http`. Every other
|
||||||
in which traefik tells an app about a visitor are removed.
|
header whose name starts with `X-Forwarded-` is removed, and so is
|
||||||
|
`X-Real-IP`.
|
||||||
- `X-Request-ID`, which carries the fresh id.
|
- `X-Request-ID`, which carries the fresh id.
|
||||||
- `X-Client-ASN` and `X-Client-Country`, which carry what `waf` answered. A
|
- `X-Client-ASN` and `X-Client-Country`, which carry what `waf` answered. A
|
||||||
header it did not give is left out. Any of the two the visitor sent is
|
header it did not give is left out. Any of the two the visitor sent is
|
||||||
|
|||||||
@@ -105,17 +105,16 @@ func setForwardedHeaders(in, out *http.Request, peer netip.Addr, trusted bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReverseProxy has removed Forwarded and the three set below; these are the
|
// ReverseProxy has removed Forwarded and the three set below. Go's server
|
||||||
// other headers in which traefik tells the app about the visitor and its
|
// gives header names in canonical form, so the prefix matches any spelling.
|
||||||
// request.
|
for name := range out.Header {
|
||||||
for _, name := range []string{
|
if strings.HasPrefix(name, "X-Forwarded-") {
|
||||||
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
delete(out.Header, name)
|
||||||
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
}
|
||||||
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
|
||||||
} {
|
|
||||||
out.Header.Del(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.Header.Del("X-Real-IP")
|
||||||
|
|
||||||
out.Header.Set("X-Forwarded-For", forwardedFor)
|
out.Header.Set("X-Forwarded-For", forwardedFor)
|
||||||
out.Header.Set("X-Forwarded-Host", in.Host)
|
out.Header.Set("X-Forwarded-Host", in.Host)
|
||||||
out.Header.Set("X-Forwarded-Proto", "http")
|
out.Header.Set("X-Forwarded-Proto", "http")
|
||||||
|
|||||||
@@ -559,6 +559,8 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
||||||
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
||||||
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
||||||
|
"X-Forwarded-Scheme", "X-Forwarded-Ssl", "X-Forwarded-Protocol",
|
||||||
|
"X-Forwarded-Path", "X-Forwarded-Client-Cert",
|
||||||
}
|
}
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
@@ -582,6 +584,9 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
req.Header.Set(name, "claimed")
|
req.Header.Set(name, "claimed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A made-up name, sent in lower case.
|
||||||
|
req.Header["x-forwarded-made-up"] = []string{"claimed"}
|
||||||
|
|
||||||
wantGreeting(t, visit(t, req))
|
wantGreeting(t, visit(t, req))
|
||||||
|
|
||||||
header := <-seen
|
header := <-seen
|
||||||
@@ -596,7 +601,7 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range removed {
|
for _, name := range append(removed, "X-Forwarded-Made-Up") {
|
||||||
if got := header.Values(name); len(got) != 0 {
|
if got := header.Values(name); len(got) != 0 {
|
||||||
t.Errorf("the app saw %s %q, want none", name, got)
|
t.Errorf("the app saw %s %q, want none", name, got)
|
||||||
}
|
}
|
||||||
@@ -622,6 +627,7 @@ func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) {
|
|||||||
"X-Forwarded-Host": "public.example",
|
"X-Forwarded-Host": "public.example",
|
||||||
forwardedProtoHeader: "https",
|
forwardedProtoHeader: "https",
|
||||||
"X-Real-Ip": "198.51.100.1",
|
"X-Real-Ip": "198.51.100.1",
|
||||||
|
"X-Forwarded-Ssl": "on",
|
||||||
}
|
}
|
||||||
|
|
||||||
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
||||||
|
|||||||
Reference in new issue
Block a user