Description
Describe the bug
X-Forwarded-For
header isn't sanitized before being forwarded by ingress gateway, which means that malicious client can easily spoof its source address by sending X-Forwarded-For
header with fake IP address, and while the fake IP address won't be used by ingress gateway (because of the use_remote_address: true
setting), it might be accepted by workloads trusting ingress to pass client's IP using this header.
Note that the presence of spoofed X-Forwarded-For
header results in either X-Envoy-Internal
or X-Envoy-External-Address
being forwarded to the workload.
Furthermore, even without spoofing, both X-Forwarded-For
and X-Envoy-External-Address
headers contain IP address of kube-proxy
(e.g. 10.128.0.5
) and not client's IP, rendering it pretty useless.
External request:
curl -v -H"Host: httpbin.example.com" http://$INGRESS_HOST:$INGRESS_PORT/get?show_env=1
< HTTP/1.1 200 OK
< server: envoy
< date: Tue, 07 Aug 2018 00:29:34 GMT
< content-type: application/json
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 534
< x-envoy-upstream-service-time: 6
<
{
"args": {
"show_env": "1"
},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.example.com",
"User-Agent": "curl/7.60.0",
"X-B3-Sampled": "1",
"X-B3-Spanid": "f318863530d246b3",
"X-B3-Traceid": "f318863530d246b3",
"X-Envoy-Internal": "true",
"X-Forwarded-For": "10.128.0.5",
"X-Forwarded-Proto": "http",
"X-Request-Id": "a53a017d-dc07-9784-a4ec-5957a1915800"
},
"origin": "10.128.0.5",
"url": "http://httpbin.example.com/get?show_env=1"
}
External request with spoofed X-Forwarded-For
header:
curl -v -H"Host: httpbin.example.com" -H"X-Forwarded-For: 1.2.3.4" http://$INGRESS_HOST:$INGRESS_PORT/get?show_env=1
< HTTP/1.1 200 OK
< server: envoy
< date: Tue, 07 Aug 2018 00:29:50 GMT
< content-type: application/json
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 566
< x-envoy-upstream-service-time: 13
<
{
"args": {
"show_env": "1"
},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.example.com",
"User-Agent": "curl/7.60.0",
"X-B3-Sampled": "1",
"X-B3-Spanid": "384e7075880e36c8",
"X-B3-Traceid": "384e7075880e36c8",
"X-Envoy-External-Address": "10.128.0.5",
"X-Forwarded-For": "1.2.3.4, 10.128.0.5",
"X-Forwarded-Proto": "http",
"X-Request-Id": "38bab7c1-5691-9825-ba76-a3a654b83842"
},
"origin": "1.2.3.4, 10.128.0.5",
"url": "http://httpbin.example.com/get?show_env=1"
}
Expected behavior
X-Forwarded-For
header should contain valid client's IP, and ingress gateway should sanitize and/or strip untrusted values before forwarding requests to backend services.
Note that we should account for trusted load balancers being deployed in front of ingress gateway and forwarding valid client's IP using this header.
Steps to reproduce the bug
Deploy sample httpbin
service and expose it via ingress gateway from the official guide and issue curl
commands mentioned above.
Version
$ istioctl version
Version: 1.0.0
GitRevision: 3a136c90ec5e308f236e0d7ebb5c4c5e405217f4
User: root@71a9470ea93c
Hub: gcr.io/istio-release
GolangVersion: go1.10.1
BuildStatus: Clean
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"11", GitVersion:"v1.11.1", GitCommit:"b1b29978270dc22fecc592ac55d903350454310a", GitTreeState:"clean", BuildDate:"2018-07-17T18:53:20Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"10+", GitVersion:"v1.10.5-gke.3", GitCommit:"6265b9797fc8680c8395abeab12c1e3bad14069a", GitTreeState:"clean", BuildDate:"2018-07-19T23:02:51Z", GoVersion:"go1.9.3b4", Compiler:"gc", Platform:"linux/amd64"}
Is Istio Auth enabled or not?
n/a
Environment
GKE
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Activity
kyessenov commentedon Aug 7, 2018
Ingress gateways should run with externalTrafficPolicy set to Local (see #7328)
PiotrSikora commentedon Aug 7, 2018
@kyessenov Good find, thanks! Indeed, changing
externalTrafficPolicy
fromCluster
toLocal
results in real client IP being passed viaX-Forwarded-For
header. This is also whatingress-nginx
seems to be using in order to preserve client IP.That leaves us with sanitization that won't break
X-Forwarded-For
passed by load balancers.mandarjog commentedon Aug 7, 2018
It depends on which lbs are in the front of ingress. If there is a trusted lb like gcp lb or elb is in front, then you want the left most address set by in xff, if the fronting lb already sanitizes. this should be configurable by deployment.
Is there a way to configure trusted downstream in envoy?
rshriram commentedon Aug 8, 2018
There is a load balancer source ip range in kube. Not sure if that helps. Else we can look into ip tagging filter or add a source subnet field in the filter chain match so that we can enable it via api (users specify source ip ranges in the gateway spec).
rshriram commentedon Aug 8, 2018
I think source subnet match is easiest way forward.
PiotrSikora commentedon Aug 8, 2018
@mandarjog you can configure number of trusted proxies (xff_num_trusted_hops), which, while not perfect, works for the "behind cloud load balancer" use case.
exiaohao commentedon Aug 9, 2018
@kyessenov
I've changed
externalTrafficPolicy
fromCluster
toLocal
, Ingressgateway seems stopped working. envoy is still listening port but the port doesn't response any request.my istio-ingressgateway config
andraxylia commentedon Aug 14, 2018
We can add a helm deployment option in the istio-ingressgateway proxy, where the user can specify if the gateway is an edge proxy, or it is located behind a trusted load balancer. This will get passed via proxy node Metadata to Pilot, which will generate the correct Envoy config with the correct combination of use_remote_address and xff_num_trusted_hops as described here:
https://www.envoyproxy.io/docs/envoy/latest/configuration/http_conn_man/headers#config-http-conn-man-headers-x-forwarded-for
Let me know if you see issues with this approach for 1.0.1.
duderino commentedon Aug 14, 2018
@rshriram's proposal to filter on source IP with a CIDR or similar would handle cases where an ingress gateway wants to strip an xff header from some but not all of its clients. The current approach is all or nothing - the ingress gateway trusts all of its clients or none of its clients.
Is that flexible enough?
andraxylia commentedon Aug 14, 2018
@duderino Envoy does not have support for filtering x-forwarded-for , it would need to be added.
What are the best practices in the industry to prevent spoofing and how do other proxies handle it? I wonder why Envoy went with the num_hops solely. @PiotrSikora
andraxylia commentedon Aug 14, 2018
filtering based on IP is discussed here also as a possible fix: cloudfoundry/gorouter#179
rkpagadala commentedon Aug 15, 2018
@PiotrSikora We want to cut a build on Friday, what is the status of this bug
65 remaining items