背景
使用KubeSphere的微服务,应用中的pod注入sidecar;网关开启微服务治理功能,即给ingress controller pod注入sidecar。结合上一篇,我们知道创建route(即ingress)后,需要加上相应upstream annotation来rewrite request header,才能正常使用分流的功能。
但是现在有个新的问题,无法获取用户的真实IP。
场景: 通过域名访问,通过获取X-Forwarded-For Header,若网关不注入sidecar,则能获取真实IP;网关注入sidecar后,无法正确获取真实IP,每次都是127.0.0.1。
问题复现
我们使用httpbin来复现,httpbin是一个python写的应用,可以获取用户的真实IP,作法就是通过获取request的X-Forwarded-Header。看源码
@app.route("/ip")
def view_origin():
"""Returns the requester's IP Address.
---
tags:
- Request inspection
produces:
- application/json
responses:
200:
description: The Requester's IP Address.
"""
return jsonify(origin=request.headers.get("X-Forwarded-For", request.remote_addr))
https://github.com/postmanlabs/httpbin/blob/master/httpbin/core.py#L313
问题:程序中通过 X-Forwarded-For header来获取用户的真实IP,如果通过抓包看到进来的请求没有这个header,是不是就无法获取到真实IP?换言之:通过curl或是网页直接请求这个地址,此时并没有主动带这个Header,那是不是就无法获取到真实IP?
答案:当手动传入这个Header的时候,程序会获取到这个Header中的IP地址,即伪造客户端地址。当没有传这个Header的时候,代码也可以获取这个Header的值,是前面流量经过的节点所有的地址。
部署应用
kubectl create deploy httpbin --image zackzhangkai/httpbin --port 80
kubectl expose deploy httpbin --port 8080 --target-port 80
直接访问这个应用:
[root@ssa3 ~]# curl 10.233.28.160:8080/ip
{
"origin": "10.233.64.1"
}
为什么是 10.233.64.1
?这个IP是service IP还是Pod IP?
其实这个IP是节点的IP,因为是在节点上直接访问httpbin service的ip及端口的。
# 看下路由,是通过lo转发的
[root@ssa3 ~]# ip r get 10.233.28.160
local 10.233.28.160 dev lo src 10.233.28.160
cache <local>
# 走回环地址转发,说明这个Ip在该内核网络栈
[root@ssa3 ~]# ip ad | grep 10.233.28.160
inet 10.233.28.160/32 brd 10.233.28.160 scope global kube-ipvs0
# 10.233.64.1是cni的地址
[root@ssa3 ~]# ip ad show cni0
8: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 8e:d2:ee:ec:57:3c brd ff:ff:ff:ff:ff:ff
inet 10.233.64.1/24 brd 10.233.64.255 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::8cd2:eeff:feec:573c/64 scope link
valid_lft forever preferred_lft forever
[root@ssa3 ~]# ip r
default via 192.168.0.1 dev eth0 proto dhcp metric 100
10.233.64.0/24 dev cni0 proto kernel scope link src 10.233.64.1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.13 metric 100
问题:为什么获取到的客户端地址是10.233.64.1?说明是通过cni0转发的,这个路由是怎么走的?留给后续思考。
网关开启sidecar,创建相应ingress
~ k -n kubesphere-controls-system get po | grep test
kubesphere-router-test-5f64fb8fb9-sslg6 2/2 Running 0 4h53m
pod 2/2说明有两个容器,其中一个是sidecar
~ kubectl get ing httpbin -oyaml
...
annotations:
nginx.ingress.kubernetes.io/upstream-vhost: httpbin.test.svc.cluster.local
spec:
rules:
- host: www.httpbin.com
http:
paths:
- backend:
serviceName: httpbin
servicePort: 8080
path: /
通过网关访问无法正确获取客户端IP
➜ ~ curl ssa3:30346/ip -HHost:www.httpbin.com
{
"origin": "127.0.0.1"
}
网关去掉sidecar,能正常获取客户IP
➜ ~ k -n kubesphere-controls-system get po kubesphere-router-test-7c8f754954-nvsjq
NAME READY STATUS RESTARTS AGE
kubesphere-router-test-7c8f754954-nvsjq 1/1 Running 0 27s
➜ ~ curl ssa3:30346/ip -HHost:www.httpbin.com
{
"origin": "10.233.64.1"
}
至此已完成复现
分析
尝试手动传入这个Header,发现传入Header的值不生效
➜ ~ curl ssa3:30346/ip -HHost:www.httpbin.com -HX-Forwarded-For:1.2.3.4
{
"origin": "10.233.64.1"
}
设置nginx 的 use_forwarded_headers: "true"
,可以解决上述问题
kubectl -n kubesphere-controls-system edit cm kubesphere-router-xxx
...
data:
use-forwarded-headers: "true"
重启ingress controller这个pod,可以看到nginx 的配置文件已经生效
kubectl -n kubesphere-controls-system exec kubesphere-router-test-xxxx -- cat /etc/nginx/nginx.conf
...
lua_ingress.set_config({
use_forwarded_headers = true,
上述的配置是设置nginx,让其把forward-headers的头全部直接转发到upstream的backend上。
➜ ~ curl ssa3:30346/ip -HHost:www.httpbin.com -HX-Forwarded-For:1.2.3.4
{
"origin": "1.2.3.4"
}
思路:如果注入sidecar后,这个值能生效,那么可以在访问全加个代理,传入这个头部
验证注入sidecar后,可以成功伪造客户端IP。
解决方法
尝试自己搭建nginx,设置proxy_pass
set_header
,发现当网关注入sidecar后,一直出现http code 426。上网查,说是http proxy 协议不对。istio文档说明需要增加一个envoyfilter来修改代理的protocol。但是没有成功。
再次梳理下问题:只需在请求时增加个代理,给请求加一个头部即可。
检查发现青云的LB已经有了这个功能。
此时借助青云Iaas上的Lb,加上这个Header:
将网关设置为这个LB的后端,此时访问能够正常获取到客户端IP:
~ curl 139.198.120.217:30099/ip -HHost:www.httpbin.com
{
"origin": "171.113.245.63"
}
此种方法是通过在lb上获取用户真实IP,加在http header里面,并透传至最后面,可以获取到
通过envoyfilter来获取用户的真实IP
Apply下这个envoyfilter
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
annotations:
name: use-remote-adress
namespace: istio-system
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_OUTBOUND
listener:
filterChain:
filter:
name: envoy.http_connection_manager
patch:
operation: MERGE
value:
typedConfig:
'@type': type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
skip_xff_append: false
use_remote_address: true
xff_num_trusted_hops: 10
效果:
curl ssa3:30346/ip -HHost:www.httpbin.com
{
"origin": "127.0.0.1,10.233.64.110"
}
此时拿到的是网关的IP
手动传入x-forwarded-for
curl ssa3:30346/ip -HHost:www.httpbin.com -HX-Forwarded-For:1.2.3.4
{
"origin": "1.2.3.4,10.233.64.111"
}
可以看到加上这个envoyfilter后可以拿到不仅可以拿到原始头中的外部IP,还可以拿到网关IP。