Envoy:經過envoy代理后獲取客戶端真實IP


在envoy作為前端代理時,用戶ip的獲取很重要,一般獲取ip的方式。都是通過Header中的 X-Forward-ForX-Real-IPRemote addr 等屬性獲取,但是如果確保Envoy可以獲取到的ip是真實的用戶ip呢?本篇繼續解密!

概念說明

  • Remote Address
    是nginx與客戶端進行TCP連接過程中,獲得的客戶端真實地址。Remote Address 無法偽造,因為建立 TCP 連接需要三次握手,如果偽造了源 IP,無法建立 TCP 連接,更不會有后面的 HTTP 請求。
    一般情況下,在Envoy作為最外層代理時,此IP為真實的IP客戶端IP

  • X-Real-IP
    是一個自定義頭。X-Real-Ip 通常被 HTTP 代理用來表示與它產生 TCP 連接的設備 IP,這個設備可能是其他代理,也可能是真正的請求端。X-Real-Ip 目前並不屬於任何標准,代理和 Web 應用之間可以約定用任何自定義頭來傳遞這個信息。

  • X-Forwarded-For
    X-Forwarded-For 是一個擴展頭。HTTP/1.1(RFC 2616)協議並沒有對它的定義,它最開始是由 Squid 這個緩存代理軟件引入,用來表示 HTTP 請求端真實 IP,現在已經成為事實上的標准,被各大 HTTP 代理、負載均衡等轉發服務廣泛使用,並被寫入 RFC 7239(Forwarded HTTP Extension)標准之中。通常,X-Forwarded-For可被偽造,並且使用CDN會被重寫

Envoy中如何獲取真實IP

在Envoy中,涉及到客戶端IP的配置如下:
use_remote_address: 默認值false,設置為true,使用客戶端連接的真實遠程地址,false是使用x-forwarded-for
skip_xff_append: 設置為true,則不會將遠程地址附加到x-forwarded-for中
request_headers_to_add 添加請求頭
request_headers_to_remove 刪除一個請求頭

實驗環境配置准備

admin:
  access_log_path: /dev/null
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_80
    address:
      socket_address: { address: 0.0.0.0, port_value: 80 }
    access_log:
   
    filter_chains:
    - filters:
      - name: envoy_http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          access_log:
          - name: envoy.listener.accesslog
            typed_config: 
              "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
              path: /var/log/envoy.log
              log_format: 
                text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\"\n"
          http_filters:
          - name: envoy.filters.http.router
          use_remote_address: true
          skip_xff_append: false
          xff_num_trusted_hops: 0
          stat_prefix: local_route
          codec_type: AUTO
          route_config:
            name: local_route
            #request_headers_to_remove: "X-Forwarded-For"
            request_headers_to_add:
              header: 
                key: "X-Forwarded-For"
                value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
                #value: "%REQ(REMOTE_ADDR)%"
              append: true
            virtual_hosts:
            - name: split_traffic
              domains: [ "*" ]
              routes:
              - match: 
                  prefix: "/"
                route:
                  cluster: version_v1
                  request_mirror_policies:
                    cluster: version_v2
                    runtime_fraction:
                      default_value:
                        numerator: 10
                        denominator: HUNDRED
                      runtime_key: routing.request_mirror.version
  clusters:
  - name: version_v1
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: version_v1
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: version1, port_value: 90 }
    health_checks:
      timeout: 3s
      interval: 30s
      unhealthy_threshold: 2
      healthy_threshold: 2
      http_health_check:
        path: /ping
        expected_statuses: { start: 200, end: 201 }
  - name: version_v2
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: version_v2
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: version2, port_value: 90 }
    health_checks:
      timeout: 3s
      interval: 30s
      unhealthy_threshold: 2
      healthy_threshold: 2
      http_health_check:
        path: /ping
        expected_statuses: { start: 200, end: 201 }

docker-compose

version: '3'
services:
  envoy:
    image: envoyproxy/envoy-alpine:v1.15-latest
    environment: 
    - ENVOY_UID=0
    ports:
    - 80:80
    - 443:443
    - 82:9901
    volumes:
    - ./envoy.yaml:/etc/envoy/envoy.yaml
    networks:
      envoymesh:
        aliases:
        - envoy
    depends_on:
    - webserver1
    - webserver2
    - webserver3
    - webserver4
  
  webserver1:
    image: sealloong/envoy-end:latest
    networks:
      envoymesh:
        aliases:
        - version1
    environment: 
    - VERSION=v1
    - COLORFUL=blue
    expose:
    - 90
  webserver2:
    image: sealloong/envoy-end:latest
    networks:
      envoymesh:
        aliases:
        - version1
    environment: 
    - VERSION=v1
    - COLORFUL=blue
    expose:
    - 90
  webserver3:
    image: sealloong/envoy-end:latest
    networks:
      envoymesh:
        aliases:
        - version2
    environment: 
    - VERSION=v2
    - COLORFUL=red
    expose:
    - 90
  webserver4:
    image: sealloong/envoy-end:latest
    environment: 
    - VERSION=v2
    - COLORFUL=red
    networks:
      envoymesh:
        aliases:
        - version2
    expose:
    - 90
networks:
  envoymesh: {}

實際使用Envoy作為代理時的外在環境

環境1:客戶端直接和Envoy通信

當一個正常請求時,此處可以正常獲得客戶端IP,實際上envoy拿的值是 X-Forwarded-For

后端日志

在偽造或者重寫X-Forwarded-For后實際上是獲取的偽造的值。



在Envoy直接作為外層代理時,可以使用如下參數,在不管如何偽造,都可以拿到對應的參數。

name: local_route
request_headers_to_remove: "X-Forwarded-For"  # 怕X-Forwarded-For為偽造值,可以刪除此值,
request_headers_to_add:   # 刪除后還需要向后端傳遞,故還需要添加上此值
  header: 
    key: "X-Forwarded-For"
    value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"  # 獲取 remote_addr,此值無法偽造,為Envoy變量,表示 下游主機真實IP不加端口,即remote_addr 無端口
  append: true  # 表面值是追加還是重寫

可以看到envoy獲取的為真實的ip並非偽造的請求

環境2:Envoy前段存在代理(無CDN)

此環境下,前端存在代理,如f5、nginx等。這種情況下不能使用remote_addr 這樣獲取的為前端代理的IP並非真實IP

前端存在f5或nginx,可以在f5中配置irule傳遞真實的remote_addr,替換為真實的客戶端IP,又前端代理重寫配置,可自定義值。

request_headers_to_remove: "X-Forwarded-For"
request_headers_to_add:
  header: 
    key: "X-Forwarded-For"
    value: "%REQ(custom_header)%"

環境3:Envoy前段存在代理(單CDN)

此環境下,前端存在代理,並且使用了CDN,應為每個CDN廠商獲取客戶真實IP的方式並不一致,這里需要找到cdn廠商找到獲取真實IP的方法,在按照步驟2進行。

舉例:
阿里雲cdn獲取真實IP方法
加速樂獲取真實IP方法

環境4:Envoy前段存在代理(多CDN)

由於各CDN的帶寬、價格、使用場景等因素,在實際情況下,可能使用多種CDN;如:正常情況下使用cdn加速,遇到攻擊時切換安全防御高的CDN。一般僅加速的CDN價格比帶防御的要便宜很多。

此處Enovy待更新,后端應用可根據CDN的http頭正常獲取IP

環境5:內部代理

無特殊需求可無需配置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM