-
Notifications
You must be signed in to change notification settings - Fork 38.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Append X-Forwarded-For in proxy handler #46126
Conversation
@@ -78,6 +78,8 @@ func NewUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, | |||
|
|||
// ServeHTTP handles the proxy request | |||
func (h *UpgradeAwareProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | |||
utilnet.PrepareForwardedFor(req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question to reviewers:
Is it safe to modify the request headers here? Or do we need to copy the request first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like these modifications should be applied to newReq not req. Even if modifying req is currently safe I don't think you can rely on it remaining safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
/cc @lavalamp |
} | ||
|
||
// Try the X-Real-Ip header. | ||
hdrRealIp := hdr.Get("X-Real-Ip") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the first I've heard of this header...
- how is it determined and trusted?
It seems odd to both append it toedit: maybe not... we only insert this into X-Forwarded-For when there is no existing X-Forwarded-For header?X-Forwarded-For
and not strip the header when forwarding. If other proxies did what we're doing here, wouldn't the X-Real-IP value get interleaved in X-Forward-For multiple times?- which should take priority, the remote IP obtained from GetRemoteIP() or this header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is it determined and trusted?
Some proxies set it. I don't think it's very well defined... It can't be trusted. That's why we still append the RemoteAddr
IP below.
we only insert this into X-Forwarded-For when there is no existing X-Forwarded-For header?
Right. See above comment.
which should take priority, the remote IP obtained from GetRemoteIP() or this header?
The logic in PrepareForwardedFor
is based off the existing logic in GetClientIP
, which gives preference to the X-Real-IP
header. Note that for audit logging, we're going to log the full X-Forwarded-For
chain, since there's nothing stopping a client from adding bogus IPs to the front of the chain, or spoofing X-Real-IP
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe any originating IP address can be trusted. That having been said X-Real-Ip is meant to be the originating IP. I don't believe its clear whether GetRemoteIP() is the originating IP, the last IP in the proxy chain or something in between. This is exacerbated by systems line HAProxy and NGINX which are configurable to do either. X-Forwarded-For is nice because while its no more trust worthy it should have all but the proxy IP in its list (assuming that no one has improperly mucked with it). Of course it assumes that the last proxy IP should be the result of calling GetRemoteIP().
// First check the X-Forwarded-For header for requests via proxy. | ||
hdrForwardedFor := hdr.Get("X-Forwarded-For") | ||
if hdrForwardedFor != "" { | ||
// Append the remote IP if necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're ignoring X-Real-IP in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for the reason you described below. I couldn't find much on the X-Real-IP
header, but it looks like most (all?) proxies that set it also set X-Forwarded-For
{"1.2.3.4:8000", "1.2.3.5", "", "1.2.3.5,1.2.3.4"}, | ||
{"1.2.3.4:8000", "1.2.3.5", "8.8.8.8", "8.8.8.8,1.2.3.4"}, | ||
{"1.2.3.4:8000", "1.2.3.5", "8.8.8.8,", "8.8.8.8,1.2.3.4"}, | ||
{"1.2.3.4:8000", "1.2.3.5", "foo,bar", "foo,bar,1.2.3.4"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this never tests preexisting multiple IPs in forwarded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
parts = append(parts[:i+1], remoteIP.String()) | ||
hdr.Set("X-Forwarded-For", strings.Join(parts, ",")) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning at the end of the main for loop seems a but clumsy. Can we find a nicer way to strip trailing commas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
// Try the X-Real-Ip header. | ||
hdrRealIp := hdr.Get("X-Real-Ip") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe any originating IP address can be trusted. That having been said X-Real-Ip is meant to be the originating IP. I don't believe its clear whether GetRemoteIP() is the originating IP, the last IP in the proxy chain or something in between. This is exacerbated by systems line HAProxy and NGINX which are configurable to do either. X-Forwarded-For is nice because while its no more trust worthy it should have all but the proxy IP in its list (assuming that no one has improperly mucked with it). Of course it assumes that the last proxy IP should be the result of calling GetRemoteIP().
@@ -78,6 +78,8 @@ func NewUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, | |||
|
|||
// ServeHTTP handles the proxy request | |||
func (h *UpgradeAwareProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | |||
utilnet.PrepareForwardedFor(req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like these modifications should be applied to newReq not req. Even if modifying req is currently safe I don't think you can rely on it remaining safe.
Thansk, addressed all feedback. PTAL. |
So actually it turns out the go's reverse proxy already does this for us (https://golang.org/src/net/http/httputil/reverseproxy.go?s=5526:5923) but we weren't plumbing the client IP through correctly, due to the way we create request copies. This PR needs an overhaul to account for this, I'll ping when it's ready. |
Ok, updated. Changes include:
I also noticed #46156 while debugging this... PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
@@ -189,6 +189,21 @@ func GetClientIP(req *http.Request) net.IP { | |||
return net.ParseIP(req.RemoteAddr) | |||
} | |||
|
|||
// Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's | |||
// IP address to the X-Forwarded-For chain. | |||
func PrepareForwardedFor(req *http.Request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/PrepareForwardedFor/AppendRemoteAddrToForwardedFor/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about AppendForwardedForHeader
?
@@ -224,6 +218,8 @@ func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Reque | |||
if !httpstream.IsUpgradeRequest(req) { | |||
return false | |||
} | |||
net.PrepareForwardedFor(newReq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this happen when newReq
is created above in ServeHTTP
? Here it's only the upgrade code path, isn't it? If httputil.NewSingleHostReverseProxy
does this already internally, please add comment here and/or before httputil.NewSingleHostReverseProxy
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Some nits, otherwise lgtm. |
Addressed feedback, rebased & squashed. |
@k8s-bot pull-kubernetes-federation-e2e-gce test this |
@k8s-bot gce etcd3 e2e test this |
Lgtm. Only the common federation flake is biting us. |
/lgtm |
/approve |
@deads2k approved? (kube-aggregator) |
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: deads2k, sttts, timstclair
Needs approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
Trivial rebase. Reapplying LGTM. |
@timstclair: The following test(s) failed:
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
@k8s-bot pull-kubernetes-e2e-gce-etcd3 test this |
Automatic merge from submit-queue (batch tested with PRs 42042, 46139, 46126, 46258, 46312) |
Append the request sender's IP to the
X-Forwarded-For
header chain when proxying requests. This is important for audit logging (kubernetes/enhancements#22) in order to capture the client IP (specifically in the case of federation or kube-aggregator)./cc @liggitt @deads2k @ericchiang @ihmccreery @soltysh