1. 準(zhǔn)備
1.1 鏈路介紹
7 層轉(zhuǎn)發(fā)鏈路:
Client --> Nginx --> K8s Nginx Ingress
4 層轉(zhuǎn)發(fā)鏈路:
Client --> 公有云 SLB(或 F5、LVS、Haproxy 等)--> K8s Nginx Ingress
實(shí)際業(yè)務(wù)可能會(huì)串聯(lián)更多層級(jí)的轉(zhuǎn)發(fā)。例如 WAF、CDN、API Gateway 一般都是 7 層轉(zhuǎn)發(fā),LB、LVS 一般是 4 層 TCP 轉(zhuǎn)發(fā)。
1.2 準(zhǔn)備 Whoami 探針
whomai 是一個(gè) go 編寫的調(diào)試探針工具,回顯 http 頭信息。
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
---
kind: Service
apiVersion: v1
metadata:
name: whoami
namespace: default
labels:
app: whoami
spec:
selector:
app: whoami
ports:
- name: whoami
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami
namespace: default
spec:
rules:
- host: whoami.oook.com
http:
paths:
- backend:
service:
name: whoami
port:
number: 80
path: /
pathType: ImplementationSpecific
客戶端訪問(wèn),回顯 HTTP 頭顯示:
Hostname: whoami-57cb797c4f-dxlxx
IP: 127.0.0.1
IP: 10.244.5.76
RemoteAddr: 10.244.0.9:42634
GET / HTTP/1.1
Host: whoami.test.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 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
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Pragma: no-cache
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 10.244.0.1
X-Forwarded-Host: whoami.test.com
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Original-Uri: /
X-Real-Ip: 10.244.0.1
X-Request-Id: a367b1746e215adffe4da8a73f3452cf
X-Scheme: http
2. 解決方法
2.1 7 層 HTTP 頭 X-Forwarded-For 透?jìng)?/h4>
HTTP 工作在網(wǎng)絡(luò)第 7 層,頭中有個(gè) X-Forwarded-For 字段。
大部分 CDN、WAF、LB 用 X-Forwarded-For 字段來(lái)存客戶端 IP,也有用 X-Real-Ip 字段,cloudflare、百度云加速還擴(kuò)展了 CF-Connecting-IP 字段。
# 第一個(gè) ip 是客戶端 ip,后面的 proxy 為路過(guò)一層就加一層的 ip
# 這里的 proxy 可以是 WAF、CDN、LB、API Gateway 等
X-Forwareded-For:Client,proxy1,proxy2,proxy3......
2.2 4 層 Proxy Protocol 透?jìng)?/h4>
TCP 工作在網(wǎng)絡(luò)第 4 層,Proxy Protocol 就是在 TCP 中增加一個(gè)小的報(bào)頭,用來(lái)存儲(chǔ)額外的信息。
代理協(xié)議即 Proxy Protocol,是 haproxy 的作者 Willy Tarreau 于 2010 年開(kāi)發(fā)和設(shè)計(jì)的一個(gè) Internet 協(xié)議,通過(guò)為 TCP 添加一個(gè)很小的頭信息,來(lái)方便的傳遞客戶端信息(協(xié)議棧、源IP、目的IP、源端口、目的端口等),在網(wǎng)絡(luò)情況復(fù)雜又需要獲取客戶 IP 時(shí)非常有用。
其本質(zhì)是在三次握手結(jié)束后由代理在連接中插入了一個(gè)攜帶了原始連接四元組信息的數(shù)據(jù)包。
目前 Proxy Protocol 有兩個(gè)版本,v1 僅支持 human-readable 報(bào)頭格式(ASCIII 碼),v2 需同時(shí)支持 human-readable 和二進(jìn)制格式,即需要兼容 v1 格式。
Proxy Protocol 的接收端必須在接收到完整有效的 Proxy Protocol 頭部后才能開(kāi)始處理連接數(shù)據(jù)。因此對(duì)于服務(wù)器的同一個(gè)監(jiān)聽(tīng)端口,不存在兼容帶 Proxy Protocol 包的連接和不帶 Proxy Protocol 包的連接。如果服務(wù)器接收到的第一個(gè)數(shù)據(jù)包不符合 Proxy Protocol的格式,那么服務(wù)器會(huì)直接終止連接。
Proxy Protocol 是比較新的協(xié)議,但目前已經(jīng)有很多軟件支持,如 Haproxy、Nginx、Apache、Squid、MySQL 等等。
要使用 Proxy Protocol 需要兩個(gè)角色 Sender 和 Receiver,Sender 在與 Receiver 之間建立連接后,會(huì)先發(fā)送一個(gè)帶有客戶信息的 TCP Header,因?yàn)楦牧?TCP 協(xié)議頭,需 Receiver 也支持 Proxy Protocol,否則不能識(shí)別 TCP包頭,導(dǎo)致無(wú)法成功建立連接。
3. 7 層透?jìng)髦?Kubernetes 配置
3.1 Nginx?Ingress Controller X-Forwarded-For 配置
查看 NGINX Ingress Controller 的 ConfigMap 文檔中有以下配置:
use-forwarded-headers
如果為 true,Nginx 會(huì)將傳入的 X-Forwarded-* 頭傳遞給 upstreams。當(dāng) Nginx 位于另一個(gè)正在設(shè)置這些標(biāo)頭的 L7 proxy/load balancer 之后時(shí),請(qǐng)使用此選項(xiàng)。
如果為 false,Nginx 會(huì)忽略傳入的 X-Forwarded-* 頭,用它看到的請(qǐng)求信息填充它們。如果 Nginx 直接暴露在互聯(lián)網(wǎng)上,或者它在基于 L3/packet-based load balancer 后面,并且不改變數(shù)據(jù)包中的源 IP,請(qǐng)使用此選項(xiàng)。
Nginx Ingress Controller 直接暴露互聯(lián)網(wǎng)也就是 Edge 模式不能開(kāi)啟為 true,否則會(huì)有偽造 IP 的安全問(wèn)題。也就是 k8s 有公網(wǎng) IP,直接讓客戶端訪問(wèn),本配置不要設(shè)為 true。
forwarded-for-header
設(shè)置標(biāo)頭字段以標(biāo)識(shí)客戶端的原始IP地址。 默認(rèn)為 X-Forwarded-For。
如果 Nginx Ingress Controller 在 CDN、WAF、LB 等后面,設(shè)置從頭的哪個(gè)字段獲取 IP,默認(rèn)是 X-Forwarded-For。這個(gè)配置應(yīng)該和 use-forwarded-headers 配合使用。
compute-full-forwarded-for
將遠(yuǎn)程地址附加到 X-Forwarded-For 標(biāo)頭,而不是替換它。 啟用此選項(xiàng)后,upstreams 應(yīng)用程序?qū)⒏鶕?jù)其自己的受信任代理列表提取客戶端 IP。
實(shí)際配置如下:
# kubectl -n ingress-nginx edit cm nginx-configuration
# 添加
data:
compute-full-forwarded-for: "true"
forwarded-for-header: "X-Forwarded-For"
use-forwarded-headers: "true"
3.2 使用?externalTrafficPolicy: Local 保留報(bào)文的源地址
Kubernetes 將在 Pod 所在 Node 上針對(duì) nodePort 下發(fā) DNAT 規(guī)則:
client
\ ^
\ \
v \
node 1 <--- node 2
| ^ SNAT
| | --->
v |
endpoint
- 客戶端發(fā)送數(shù)據(jù)包到 node2:nodePort;
- node2 使用它自己的 IP 地址替換數(shù)據(jù)包的源 IP 地址(SNAT);
- node2 使用 pod IP 地址替換數(shù)據(jù)包的目的 IP 地址;
- 數(shù)據(jù)包被路由到 node 1,然后交給 endpoint;
- Pod 的回復(fù)被路由回 node2;
- Pod 的回復(fù)被發(fā)送回給客戶端。
這樣就無(wú)法返回正確的客戶端 IP,返回的 IP 為集群內(nèi)部的 IP 地址。
為了防止這種情況發(fā)生,Kubernetes 提供了一個(gè)特性來(lái)保留客戶端的源 IP 地址,設(shè)置 service.spec.externalTrafficPolicy 的值為 Local,請(qǐng)求就只會(huì)被代理到本地 endpoints 而不會(huì)被轉(zhuǎn)發(fā)到其它節(jié)點(diǎn)。這樣就保留了最初的源 IP 地址。如果沒(méi)有本地 endpoints,發(fā)送到這個(gè)節(jié)點(diǎn)的數(shù)據(jù)包將會(huì)被丟棄。這樣在應(yīng)用到數(shù)據(jù)包的任何包處理規(guī)則下,你都能依賴這個(gè)正確的 source-ip 使數(shù)據(jù)包通過(guò)并到達(dá) endpoint。
設(shè)置?service.spec.externalTrafficPolicy 字段如下:
kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'
這樣就可以獲取到真實(shí)的 IP:
client
^ / \
/ / \
/ v X
node 1 node 2
^ |
| |
| v
endpoint
- 客戶端發(fā)送數(shù)據(jù)包到 node2:nodePort,它沒(méi)有任何 endpoints;
- 數(shù)據(jù)包被丟棄;
- 客戶端發(fā)送數(shù)據(jù)包到 node1:nodePort,它有 endpoints;
- node1 使用正確的源 IP 地址將數(shù)據(jù)包路由到 endpoint。
實(shí)際配置如下:
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
name: ingress-nginx
namespace: ingress-nginx
spec:
externalTrafficPolicy: Local
ports:
- name: http
nodePort: 30080
port: 80
protocol: TCP
targetPort: 80
- name: https
nodePort: 30443
port: 443
protocol: TCP
targetPort: 443
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
type: NodePort
3.3 Nginx 作為邊緣節(jié)點(diǎn)配置
作為 Edge 需要重寫 remote_addr,保證客戶端 IP 不會(huì)被偽造。
- 必須:X-Forwarded-For 重寫為 $remote_addr
- 非必須擴(kuò)展:X-Real-IP 重寫為 $remote_addr
upstream k8s {
server 10.10.115.21:443;
server 10.10.115.22:443;
server 10.10.115.23: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://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.4 X-Forwarded-For 是否可以偽造
客戶端是否能偽造 IP,取決于邊緣節(jié)點(diǎn)(Edge)是如何處理 X-Forwarded-For 字段的。
客戶端直接連接的首個(gè) Proxy 節(jié)點(diǎn)都叫做邊緣節(jié)點(diǎn)(Edge),不管是網(wǎng)關(guān)、CDN、LB 等只要這一層是直接接入客戶端訪問(wèn)的,那么他就是一個(gè)邊緣節(jié)點(diǎn)。
不重寫-不安全的邊緣節(jié)點(diǎn)(Edge)
邊緣節(jié)點(diǎn)如果是透?jìng)?HTTP 頭中的 X-Forwarded-For 字段,那么這個(gè)就是不安全的,客戶端可以在 HTTP 中實(shí)現(xiàn)包含 X-Forwarded-For 字段值,這個(gè)值又被透?jìng)髁恕?/p>
# 不安全
X-Forwareded-For:Client(Edge不重寫,只透?jìng)?,proxy1,proxy2,proxy3……
# 不安全
X-Forwareded-For:Client(Edge不重寫,只透?jìng)?,proxy1,proxy2,proxy3……
#安全
X-Forwareded-For:Client(Edge獲取的remote_addr),proxy1,proxy2,proxy3……
4. 4 層透?jìng)?Kubernetes 配置
4.1 Nginx?Ingress Controller?Proxy Protocol 配置
查看 NGINX Ingress Controller 的 ConfigMap 文檔中有以下配置:
use-proxy-protocol
啟用或禁用 Proxy Protocol,以接收通過(guò)代理服務(wù)器和負(fù)載均衡器(例如 HAProxy 和Amazon Elastic Load Balancer(ELB))傳遞的客戶端連接(真實(shí)IP地址)信息。
Nginx??Ingress Controller 作為 receiver 角色 Proxy Protocol 配置:
data:
use-proxy-protocol: "true"
實(shí)際配置如下:
# kubectl -n ingress-nginx edit cm nginx-configuration
# 添加
data:
use-proxy-protocol: "true"
注意
需要上一層 LB 支持 Proxy Protocol,才能這么配置,否則會(huì)導(dǎo)致無(wú)法連接。
同樣需要將??service.spec.externalTrafficPolicy 的值設(shè)置為 Local。
4.2 Haproxy 支持 Proxy Protocol 配置
Haproxy 是 proxy protocol 的親爹,使用非常方便。配置 send-proxy 和 accept-proxy 兩個(gè)參數(shù)即可。
frontend nginx-ingress-http
bind 0.0.0.0:80
bind 127.0.0.1:80
# bind *:80 # accept-proxy 作為 receiver
mode tcp
option tcplog
tcp-request inspect-delay 5s
default_backend nginx-ingress-http
backend nginx-ingress-http
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 40960 maxqueue 256 weight 100
server nginx-ingress-1 10.10.115.21:30080 send-proxy check
server nginx-ingress-2 10.10.115.22:30080 send-proxy check
server nginx-ingress-3 10.10.115.23:30080 send-proxy check
frontend nginx-ingress-https
bind 0.0.0.0:443
bind 127.0.0.1:443
# bind *:443 # accept-proxy 作為 receiver
mode tcp
option tcplog
tcp-request inspect-delay 5s
default_backend nginx-ingress-https
backend nginx-ingress-https
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 40960 maxqueue 256 weight 100
server nginx-ingress-1 10.10.115.21:30443 send-proxy check
server nginx-ingress-2 10.10.115.22:30443 send-proxy check
server nginx-ingress-3 10.10.115.23:30443 send-proxy check
總結(jié)
上游如果是 7 層用 X-Forwarded-For,如果是 4 層用 Proxy Protocol。
如果鏈路的邊緣節(jié)點(diǎn)(Edge)X-Forwarded-For 字段是安全的,建議用 X-Forwarded-For。
如果鏈路 Proxy 全路徑都支持 Proxy Protocol,那么建議用 Proxy Protocol。
如果有 4 層 TCP 業(yè)務(wù)應(yīng)用,那么獲取客戶端 IP 就的用 Proxy Protocol。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-604447.html
參考
●How does Cloudflare handle HTTP Request headers?
●NGINX Ingress Controller ConfigMaps
●HTTP Headers X-Forwarded-For
●Nginx Module ngx_http_realip_module
●Haproxy Proxy Protocol
●Kubernetes Using Source IP文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-604447.html
到了這里,關(guān)于Kubernetes Pod 獲取真實(shí) IP 地址的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!