1. 导言
绝大多数业务场景都是需要知道客户端 IP 的
在 k8s 中运行的业务项目, 如何获取到客户端真实 IP?
本文总结了通行的 2 种方式
要答案的直接看方式一, 方式二和总结
SEO 关键字
nginx ingress 客户端真实 ip
kubernets 获取客户端真实 ip
rke 获取客户端真实 ip
rancher 获取客户端真实 ip
本文由 www.iamle.com 流水理鱼 原创, wx 公众号同名
1.1 流量链路介绍
7 层转发链路 Client(客户端)> Nginx> K8s Ingress(Nginx ingress)
4 层转发链路 Client(客户端)> 公有云 LB> K8s Ingress(Nginx ingress)
ps: 实际业务会串联更多层级的转发. WAF,CDN,API Gateway 一般是 http 7 层转发, LB 一般是 4 层 tcp 转发
1.2 准备 whoami 探针
whomai 是一个 go 编写的调试探针工具, 回显 http 头信息
在 k8s 中部署一个 containous/whoami 用来作为探针, 配置好 ingress 公网和访问, 这样客户端 web 访问可以看到基本的 http 头信息, 方便调试
- kubectl apply -f - <<EOF
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: whoami
- namespace: default
- labels:
- App: whoami
- spec:
- replicas: 1
- selector:
- matchLabels:
- App: whoami
- template:
- metadata:
- labels:
- App: whoami
- spec:
- containers:
- - image: containous/whoami
- imagePullPolicy: Always
- name: whoami
- ports:
- - containerPort: 80
- name: 80tcp02
- protocol: TCP
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- EOF
ps:ingress 自行增加
客户端 Web 访问, 回显 http 头示例
- Hostname: whoami-65b8cc4b-6vwns
- IP: 127.0.0.1
- IP: 10.42.2.12
- RemoteAddr: 10.42.1.0:47850
- GET / HTTP/1.1
- Host: whoami.iamle.com
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/80.0.3987.162 Safari/537.36
- Accept: text/HTML,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
- Accept-Encoding: gzip, deflate, br
- Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,la;q=0.5
- Cookie: _ga=GA1.2.30707523.1570429261;
- Dnt: 1
- Sec-Fetch-Dest: document
- Sec-Fetch-Mode: navigate
- Sec-Fetch-Site: cross-site
- Sec-Fetch-User: ?1
- Upgrade-Insecure-Requests: 1
- X-Forwarded-For: 8.8.8.8, 10.0.0.1
- X-Forwarded-Host: whoami.iamle.com
- X-Forwarded-Port: 443
- X-Forwarded-Proto: https
- X-Original-Forwarded-For: 8.8.8.8
- X-Original-Uri: /
- X-Real-Ip: 8.8.8.8
- X-Request-Id: 3852c9780589ffba4c1f9f2785691d5f
- X-Scheme: https
2. 两种方式 7 层 http 头 X-Forwarded-For 透传 和 4 层 Proxy Protocol 透传
获得客户端真实 IP 有针对 7 层和针对 4 层两种方式
2.1 7 层 http 头 X-Forwarded-For 透传介绍
http 工作在网络第 7 层, http 中有个 X-Forwarded-For 字段
大部分 CDN,WAF,LB 用 X-Forwarded-For 字段来存客户端 IP, 也有用 X-Real-Ip 字段, cloudflare, 百度云加速还扩展了 CF-Connecting-IP 字段
标准数据为
X-Forwareded-For:Client,proxy1,proxy2,proxy3......
第一个 ip 是客户端 ip, 后面的 proxy 为路过一层就加一层的 ip
这里的 proxy 可以是 WAF,CDN,LB,API Gateway 等
2.2 4 层 Proxy Protocol 透传
tcp 工作在网络第 4 层, Proxy Protocol 就是在 tcp 中增加一个小的报头, 用来存储额外的信息
代理协议即 Proxy Protocol, 是 haproxy 的作者 Willy Tarreau 于 2010 年开发和设计的一个 Internet 协议, 通过为 tcp 添加一个很小的头信息, 来方便的传递客户端信息(协议栈, 源 IP, 目的 IP, 源端口, 目的端口等), 在网络情况复杂又需要获取客户 IP 时非常有用.
其本质是在三次握手结束后由代理在连接中插入了一个携带了原始连接四元组信息的数据包.
目前 proxy protocol 有两个版本, v1 仅支持 human-readable 报头格式(ASCIII 码),v2 需同时支持 human-readable 和二进制格式, 即需要兼容 v1 格式
proxy protocol 的接收端必须在接收到完整有效的 proxy protocol 头部后才能开始处理连接数据. 因此对于服务器的同一个监听端口, 不存在兼容带 proxy protocol 包的连接和不带 proxy protocol 包的连接. 如果服务器接收到的第一个数据包不符合 proxy protocol 的格式, 那么服务器会直接终止连接.
Proxy protocol 是比较新的协议, 但目前已经有很多软件支持, 如 haproxy,nginx,apache,squid,MySQL 等等, 要使用 proxy protocol 需要两个角色 sender 和 receiver,sender 在与 receiver 之间建立连接后, 会先发送一个带有客户信息的 tcp header, 因为更改了 tcp 协议头, 需 receiver 也支持 proxy protocol, 否则不能识别 tcp 包头, 导致无法成功建立连接.
nginx 是从 1.5.12 起开始支持的
3. 方式一 X-Forwarded-For 配置
适用于 7 层 http 转发
3.1 NGINX Ingress Controller X-Forwarded-For 配置
查看 NGINX Ingress Controller 的 ConfigMaps 配置文档, 可以找到以下配置项
use-forwarded-headers
如果为 true,NGINX 会将传入的 X-Forwarded-* 头传递给 upstreams. 当 NGINX 位于另一个正在设置这些标头的 L7 proxy / load balancer 之后时, 请使用此选项.
如果为 false,NGINX 会忽略传入的 X-Forwarded-* 头, 用它看到的请求信息填充它们. 如果 NGINX 直接暴露在互联网上, 或者它在基于 L3/packet-based load balancer 后面, 并且不改变数据包中的源 IP, 请使用此选项.
ps: NGINX Ingress Controller 直接暴露互联网也就是 Edge 模式不能开启为 true, 否则会有伪造 ip 的安全问题. 也就是 k8s 有公网 ip, 直接让客户端访问, 本配置不要设为 true!
forwarded-for-header
设置标头字段以标识客户端的原始 IP 地址. 默认: X-Forwarded-For
ps: 如果 NGINX Ingress Controller 在 CDN,WAF,LB 等后面, 设置从头的哪个字段获取 IP, 默认是 X-Forwarded-For
这个配置应该和 use-forwarded-headers 配合使用
compute-full-forwarded-for
将远程地址附加到 X-Forwarded-For 标头, 而不是替换它. 启用此选项后, upstreams 应用程序将根据其自己的受信任代理列表提取客户端 IP
修改 configmap nginx-configuration 配置
kubectl -n ingress-nginx edit cm nginx-configuration
在 apiVersion: v1 下, kind: ConfigMap 上加入
- data:
- compute-full-forwarded-for: "true"
- forwarded-for-header: "X-Forwarded-For"
- use-forwarded-headers: "true"
或者直接 apply 附加配置
- kubectl apply -f - <<EOF
- apiVersion: v1
- data:
- compute-full-forwarded-for: "true"
- forwarded-for-header: X-Forwarded-For
- use-forwarded-headers: "true"
- kind: ConfigMap
- metadata:
- labels:
- App: ingress-nginx
- name: nginx-configuration
- namespace: ingress-nginx
- EOF
ps: 如果 nginx-configuration 不在 namespace ingress-nginx 中就在 namespace kube-system 中找
3.2 Nginx 作为边缘节点 (Edge) 配置
作为 Edge 需要重写 remote_addr, 保证了客户端 IP 不会被伪造
必须: X-Forwarded-For 重写为 $remote_addr
非必须扩展: X-Real-IP 重写为 $remote_addr
- upstream wwek-k8s {
- server 8.8.8.8:443;
- server 8.8.8.7:443;
- server 8.8.8.6:443;
- }
- map $http_upgrade $connection_upgrade {
- default Upgrade;
- '' close;
- }
- server {
- if ($http_x_forwarded_proto = '') {
- set $http_x_forwarded_proto $scheme;
- }
- location / {
- proxy_set_header Host $http_host;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Forwarded-Port $server_port;
- #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_pass https://wwek-k8s;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- proxy_read_timeout 900s;
- proxy_buffering off;
- }
- }
3.3 X-Forwarded-For 是否可以伪造
客户端是否能伪造 IP, 取决于边缘节点 (Edge) 是如何处理 X-Forwarded-For 字段的.
客户端直接连接的首个 proxy 节点都叫做边缘节点(Edge), 不管是网关, CDN,LB 等只要这一层是直接接入客户端访问的, 那么他就是一个边缘节点.
不重写 - 不安全的边缘节点(Edge)
边缘节点如果是透传 http 头中的 X-Forwarded-For 字段, 那么这个就是不安全的, 客户端可以在 http 中实现包含 X-Forwarded-For 字段值, 这个值又被透传了.
- # 不安全
- X-Forwareded-For:Client(Edge 不重写, 只透传),proxy1,proxy2,proxy3......
重写 - 安全的边缘节点(Edge)
边缘节点 (Edge) 如果重写 remote_addr 到 X-Forwarded-For, 那么这就是安全的. 边缘节点 (Edge) 获取的 remote_addr 就是客户端的真实 IP
- # 安全
- X-Forwareded-For:Client(Edge 获取的 remote_addr),proxy1,proxy2,proxy3......
4. 方式二 Proxy Protocol 配置实例
适用于 4 层 tcp 转发
公有云的负载均衡 LB 一般都支持 Proxy Protocol
查看 NGINX Ingress Controller 的 ConfigMaps 配置文档, 可以找到如何配置 Proxy Protocol
use-proxy-protocol
启用或禁用 roxy Protocol, 以接收通过代理服务器和负载均衡器 (例如 HAProxy 和 Amazon Elastic Load Balancer(ELB)) 传递的客户端连接 (真实 IP 地址) 信息.
NGINX Ingress Controller 作为 receiver 角色 Proxy Protocol 配置
kubectl -n ingress-nginx edit cm nginx-configuration
在 apiVersion: v1 下, kind: ConfigMap 上加入
- data:
- use-proxy-protocol: "true"
或者直接 apply 附加配置
- kubectl apply -f - <<EOF
- apiVersion: v1
- data:
- use-proxy-protocol: "true"
- kind: ConfigMap
- metadata:
- labels:
- App: ingress-nginx
- name: nginx-configuration
- namespace: ingress-nginx
- EOF
ps: 注意需要上一层 LB 支持 Proxy Protocol, 才能这么配置, 否则会导致无法链接
5. 总结
7 层 http 头 X-Forwarded-For 透传
链路 proxy 有透传 X-Forwarded-For
访问链路上多层 proxy, 任意一个节点不支持 Proxy Protocol
4 层协议 Proxy Protocol 透传
上下游可控都支持 Proxy Protocol 协议
链路 proxy 中丢失了 http 头
https 反向代理 http(某些情况下由于 Keep-alive 导致不是每次请求都传递 x-forword-for
应该用那种方式?
7 层用 X-Forwarded-For,4 层用 Proxy Protocol
如果链路的边缘节点(Edge)X-Forwarded-For 字段是安全的, 建议用 X-Forwarded-For
如果链路 proxy 全路径都支持 Proxy Protocol, 那么建议用 Proxy Protocol
如果有 4 层 tcp 业务应用, 那么获取客户端 IP 就的用 Proxy Protocol
总之搞清楚了这 2 种方式的原理按照场景选择
5. 参考
- HTTP HeadersX-Forwarded-For https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Forwarded-For
- [ Proxy Protocol Introduction https://www.haproxy.com/blog/haproxy/Proxy Protocol/ ]( https://www.haproxy.com/blog/haproxy/Proxy Protocol/)
- NGINX Ingress Controller ConfigMaps https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers
- Configuring NGINX to Accept the PROXY Protocol https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#configuring-nginx-to-accept-the-proxy-protocol
- nginx Module ngx_http_realip_module https://nginx.org/en/docs/http/ngx_http_realip_module.html
- rancher K8s Ingress Controllers https://rancher.com/docs/rke/latest/en/config-options/add-ons/ingress-controllers/
- rancher Set Up Load Balancer and Ingress Controller within Rancher https://rancher.com/docs/rancher/v2.x/en/k8s-in-rancher/load-balancers-and-ingress/
aliyun 负载均衡 如何获取客户端真实 IP https://help.aliyun.com/document_detail/54007.html https://help.aliyun.com/document_detail/54007.html
aliyun 全站加速 获取客户端真实 IP https://help.aliyun.com/document_detail/119658.html
腾讯云高防 IP 获取客户端真实 IP https://cloud.tencent.com/document/product/1014/31124
How does Cloudflare handle HTTP Request headers?
来源: https://www.cnblogs.com/wwek/p/12639700.html