• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Kubernetes Pod 获取真实 IP 地址

武飞扬头像
风清扬&
帮助1

1. 准备

1.1 链路介绍

7 层转发链路

Client --> Nginx --> K8s Nginx Ingress

4 层转发链路:

Client --> 公有云 SLB(或 F5、LVS、Haproxy 等)--> K8s Nginx Ingress

实际业务可能会串联更多层级的转发。例如 WAF、CDN、API Gateway 一般都是 7 层转发,LB、LVS 一般是 4 层 TCP 转发。

1.2 准备 Whoami 探针

whomai 是一个 go 编写的调试探针工具,回显 http 头信息。

  1.  
    apiVersion: apps/v1
  2.  
    kind: Deployment
  3.  
    metadata:
  4.  
    name: whoami
  5.  
    namespace: default
  6.  
    labels:
  7.  
    app: whoami
  8.  
    spec:
  9.  
    replicas: 1
  10.  
    selector:
  11.  
    matchLabels:
  12.  
    app: whoami
  13.  
    template:
  14.  
    metadata:
  15.  
    labels:
  16.  
    app: whoami
  17.  
    spec:
  18.  
    containers:
  19.  
    - image: containous/whoami
  20.  
    imagePullPolicy: Always
  21.  
    name: whoami
  22.  
    ports:
  23.  
    - containerPort: 80
  24.  
    name: 80tcp02
  25.  
    protocol: TCP
  26.  
    dnsPolicy: ClusterFirst
  27.  
    restartPolicy: Always
  28.  
    ---
  29.  
    kind: Service
  30.  
    apiVersion: v1
  31.  
    metadata:
  32.  
    name: whoami
  33.  
    namespace: default
  34.  
    labels:
  35.  
    app: whoami
  36.  
    spec:
  37.  
    selector:
  38.  
    app: whoami
  39.  
    ports:
  40.  
    - name: whoami
  41.  
    port: 80
  42.  
    targetPort: 80
  43.  
    ---
  44.  
    apiVersion: networking.k8s.io/v1
  45.  
    kind: Ingress
  46.  
    metadata:
  47.  
    name: whoami
  48.  
    namespace: default
  49.  
    spec:
  50.  
    rules:
  51.  
    - host: whoami.oook.com
  52.  
    http:
  53.  
    paths:
  54.  
    - backend:
  55.  
    service:
  56.  
    name: whoami
  57.  
    port:
  58.  
    number: 80
  59.  
    path: /
  60.  
    pathType: ImplementationSpecific
学新通

客户端访问,回显 HTTP 头显示:

  1.  
    Hostname: whoami-57cb797c4f-dxlxx
  2.  
    IP: 127.0.0.1
  3.  
    IP: 10.244.5.76
  4.  
    RemoteAddr: 10.244.0.9:42634
  5.  
    GET / HTTP/1.1
  6.  
    Host: whoami.test.com
  7.  
    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
  8.  
    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
  9.  
    Accept-Encoding: gzip, deflate
  10.  
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
  11.  
    Cache-Control: no-cache
  12.  
    Pragma: no-cache
  13.  
    Upgrade-Insecure-Requests: 1
  14.  
    X-Forwarded-For: 10.244.0.1
  15.  
    X-Forwarded-Host: whoami.test.com
  16.  
    X-Forwarded-Port: 80
  17.  
    X-Forwarded-Proto: http
  18.  
    X-Original-Uri: /
  19.  
    X-Real-Ip: 10.244.0.1
  20.  
    X-Request-Id: a367b1746e215adffe4da8a73f3452cf
  21.  
    X-Scheme: http
学新通

2. 解决方法

2.1 7 层 HTTP 头 X-Forwarded-For 透传

HTTP 工作在网络第 7 层,头中有个 X-Forwarded-For 字段。

大部分 CDN、WAF、LB 用 X-Forwarded-For 字段来存客户端 IP,也有用 X-Real-Ip 字段,cloudflare、百度云加速还扩展了 CF-Connecting-IP 字段。

  1.  
    # 第一个 ip 是客户端 ip,后面的 proxy 为路过一层就加一层的 ip
  2.  
    # 这里的 proxy 可以是 WAF、CDN、LB、API Gateway 等
  3.  
    X-Forwareded-For:Client,proxy1,proxy2,proxy3......

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包头,导致无法成功建立连接。

3. 7 层透传中 Kubernetes 配置

3.1 Nginx Ingress Controller X-Forwarded-For 配置

查看 NGINX Ingress Controller 的 ConfigMap 文档中有以下配置:

use-forwarded-headers

如果为 true,Nginx 会将传入的 X-Forwarded-* 头传递给 upstreams。当 Nginx 位于另一个正在设置这些标头的 L7 proxy/load balancer 之后时,请使用此选项。

如果为 false,Nginx 会忽略传入的 X-Forwarded-* 头,用它看到的请求信息填充它们。如果 Nginx 直接暴露在互联网上,或者它在基于 L3/packet-based load balancer 后面,并且不改变数据包中的源 IP,请使用此选项。

Nginx Ingress Controller 直接暴露互联网也就是 Edge 模式不能开启为 true,否则会有伪造 IP 的安全问题。也就是 k8s 有公网 IP,直接让客户端访问,本配置不要设为 true。

forwarded-for-header

设置标头字段以标识客户端的原始IP地址。 默认为 X-Forwarded-For。

如果 Nginx Ingress Controller 在 CDN、WAF、LB 等后面,设置从头的哪个字段获取 IP,默认是 X-Forwarded-For。这个配置应该和 use-forwarded-headers 配合使用。

compute-full-forwarded-for

将远程地址附加到 X-Forwarded-For 标头,而不是替换它。 启用此选项后,upstreams 应用程序将根据其自己的受信任代理列表提取客户端 IP。

实际配置如下:

  1.  
    # kubectl -n ingress-nginx edit cm nginx-configuration
  2.  
    # 添加
  3.  
    data:
  4.  
    compute-full-forwarded-for: "true"
  5.  
    forwarded-for-header: "X-Forwarded-For"
  6.  
    use-forwarded-headers: "true"

3.2 使用 externalTrafficPolicy: Local 保留报文的源地址

Kubernetes 将在 Pod 所在 Node 上针对 nodePort 下发 DNAT 规则:

  1.  
    client
  2.  
    \ ^
  3.  
    \ \
  4.  
    v \
  5.  
    node 1 <--- node 2
  6.  
    | ^ SNAT
  7.  
    | | --->
  8.  
    v |
  9.  
    endpoint
  • 客户端发送数据包到 node2:nodePort;
  • node2 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT);
  • node2 使用 pod IP 地址替换数据包的目的 IP 地址;
  • 数据包被路由到 node 1,然后交给 endpoint;
  • Pod 的回复被路由回 node2;
  • Pod 的回复被发送回给客户端。

这样就无法返回正确的客户端 IP,返回的 IP 为集群内部的 IP 地址。

为了防止这种情况发生,Kubernetes 提供了一个特性来保留客户端的源 IP 地址,设置 service.spec.externalTrafficPolicy 的值为 Local,请求就只会被代理到本地 endpoints 而不会被转发到其它节点。这样就保留了最初的源 IP 地址。如果没有本地 endpoints,发送到这个节点的数据包将会被丢弃。这样在应用到数据包的任何包处理规则下,你都能依赖这个正确的 source-ip 使数据包通过并到达 endpoint。

设置 service.spec.externalTrafficPolicy 字段如下:

kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

这样就可以获取到真实的 IP:

  1.  
    client
  2.  
    ^ / \
  3.  
    / / \
  4.  
    / v X
  5.  
    node 1 node 2
  6.  
    ^ |
  7.  
    | |
  8.  
    | v
  9.  
    endpoint
  • 客户端发送数据包到 node2:nodePort,它没有任何 endpoints;
  • 数据包被丢弃;
  • 客户端发送数据包到 node1:nodePort,它有 endpoints;
  • node1 使用正确的源 IP 地址将数据包路由到 endpoint。

实际配置如下:

  1.  
    apiVersion: v1
  2.  
    kind: Service
  3.  
    metadata:
  4.  
    labels:
  5.  
    app.kubernetes.io/name: ingress-nginx
  6.  
    app.kubernetes.io/part-of: ingress-nginx
  7.  
    name: ingress-nginx
  8.  
    namespace: ingress-nginx
  9.  
    spec:
  10.  
    externalTrafficPolicy: Local
  11.  
    ports:
  12.  
    - name: http
  13.  
    nodePort: 30080
  14.  
    port: 80
  15.  
    protocol: TCP
  16.  
    targetPort: 80
  17.  
    - name: https
  18.  
    nodePort: 30443
  19.  
    port: 443
  20.  
    protocol: TCP
  21.  
    targetPort: 443
  22.  
    selector:
  23.  
    app.kubernetes.io/name: ingress-nginx
  24.  
    app.kubernetes.io/part-of: ingress-nginx
  25.  
    type: NodePort
学新通

3.3 Nginx 作为边缘节点配置

作为 Edge 需要重写 remote_addr,保证客户端 IP 不会被伪造。

  • 必须:X-Forwarded-For 重写为 $remote_addr
  • 非必须扩展:X-Real-IP 重写为 $remote_addr
  1.  
    upstream k8s {
  2.  
    server 10.10.115.21:443;
  3.  
    server 10.10.115.22:443;
  4.  
    server 10.10.115.23:443;
  5.  
    }
  6.  
     
  7.  
    map $http_upgrade $connection_upgrade {
  8.  
    default Upgrade;
  9.  
    '' close;
  10.  
    }
  11.  
    server {
  12.  
    if ($http_x_forwarded_proto = '') {
  13.  
    set $http_x_forwarded_proto $scheme;
  14.  
    }
  15.  
    location / {
  16.  
     
  17.  
    proxy_set_header Host $http_host;
  18.  
    proxy_set_header X-Forwarded-Proto $scheme;
  19.  
    proxy_set_header X-Forwarded-Port $server_port;
  20.  
    #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  21.  
    proxy_set_header X-Forwarded-For $remote_addr;
  22.  
    proxy_set_header X-Real-IP $remote_addr;
  23.  
     
  24.  
    proxy_pass https://k8s;
  25.  
    proxy_http_version 1.1;
  26.  
    proxy_set_header Upgrade $http_upgrade;
  27.  
    proxy_set_header Connection $connection_upgrade;
  28.  
    proxy_read_timeout 900s;
  29.  
    proxy_buffering off;
  30.  
    }
  31.  
    }
学新通

3.4 X-Forwarded-For 是否可以伪造

客户端是否能伪造 IP,取决于边缘节点(Edge)是如何处理 X-Forwarded-For 字段的。

客户端直接连接的首个 Proxy 节点都叫做边缘节点(Edge),不管是网关、CDN、LB 等只要这一层是直接接入客户端访问的,那么他就是一个边缘节点。

不重写-不安全的边缘节点(Edge)

边缘节点如果是透传 HTTP 头中的 X-Forwarded-For 字段,那么这个就是不安全的,客户端可以在 HTTP 中实现包含 X-Forwarded-For 字段值,这个值又被透传了。

  1.  
    # 不安全
  2.  
    X-Forwareded-For:Client(Edge不重写,只透传),proxy1,proxy2,proxy3……

# 不安全
X-Forwareded-For:Client(Edge不重写,只透传),proxy1,proxy2,proxy3……

  1.  
    #安全
  2.  
    X-Forwareded-For:Client(Edge获取的remote_addr),proxy1,proxy2,proxy3……

4. 4 层透传 Kubernetes 配置

4.1 Nginx Ingress Controller Proxy Protocol 配置

查看 NGINX Ingress Controller 的 ConfigMap 文档中有以下配置:

use-proxy-protocol

启用或禁用 Proxy Protocol,以接收通过代理服务器和负载均衡器(例如 HAProxy 和Amazon Elastic Load Balancer(ELB))传递的客户端连接(真实IP地址)信息。

Nginx  Ingress Controller 作为 receiver 角色 Proxy Protocol 配置:

  1.  
    data:
  2.  
    use-proxy-protocol: "true"

实际配置如下:

  1.  
    # kubectl -n ingress-nginx edit cm nginx-configuration
  2.  
    # 添加
  3.  
    data:
  4.  
    use-proxy-protocol: "true"

注意

需要上一层 LB 支持 Proxy Protocol,才能这么配置,否则会导致无法连接。

同样需要将  service.spec.externalTrafficPolicy 的值设置为 Local。

4.2 Haproxy 支持 Proxy Protocol 配置

Haproxy 是 proxy protocol 的亲爹,使用非常方便。配置 send-proxy 和 accept-proxy 两个参数即可。

  1.  
    frontend nginx-ingress-http
  2.  
    bind 0.0.0.0:80
  3.  
    bind 127.0.0.1:80
  4.  
    # bind *:80 # accept-proxy 作为 receiver
  5.  
    mode tcp
  6.  
    option tcplog
  7.  
    tcp-request inspect-delay 5s
  8.  
    default_backend nginx-ingress-http
  9.  
     
  10.  
    backend nginx-ingress-http
  11.  
    mode tcp
  12.  
    option tcplog
  13.  
    option tcp-check
  14.  
    balance roundrobin
  15.  
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 40960 maxqueue 256 weight 100
  16.  
    server nginx-ingress-1 10.10.115.21:30080 send-proxy check
  17.  
    server nginx-ingress-2 10.10.115.22:30080 send-proxy check
  18.  
    server nginx-ingress-3 10.10.115.23:30080 send-proxy check
  19.  
     
  20.  
    frontend nginx-ingress-https
  21.  
    bind 0.0.0.0:443
  22.  
    bind 127.0.0.1:443
  23.  
    # bind *:443 # accept-proxy 作为 receiver
  24.  
    mode tcp
  25.  
    option tcplog
  26.  
    tcp-request inspect-delay 5s
  27.  
    default_backend nginx-ingress-https
  28.  
     
  29.  
    backend nginx-ingress-https
  30.  
    mode tcp
  31.  
    option tcplog
  32.  
    option tcp-check
  33.  
    balance roundrobin
  34.  
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 40960 maxqueue 256 weight 100
  35.  
    server nginx-ingress-1 10.10.115.21:30443 send-proxy check
  36.  
    server nginx-ingress-2 10.10.115.22:30443 send-proxy check
  37.  
    server nginx-ingress-3 10.10.115.23:30443 send-proxy check
学新通

总结

上游如果是 7 层用 X-Forwarded-For,如果是 4 层用 Proxy Protocol。

如果链路的边缘节点(Edge)X-Forwarded-For 字段是安全的,建议用 X-Forwarded-For。

如果链路 Proxy 全路径都支持 Proxy Protocol,那么建议用 Proxy Protocol。

如果有 4 层 TCP 业务应用,那么获取客户端 IP 就的用 Proxy Protocol。

参考

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhagfh
系列文章
更多 icon
同类精品
更多 icon
继续加载