我可以使用 HAProxy 的新“捕获”功能将远程地址保存在 TCP 前端,并将其用作 HTTP 后端中的“X-Forwarded-For”标头吗?

kvz*_*kvz 6 ssl haproxy packet-capture

使用 HAProxy 1.6 和一个聪明的 hack,我现在有一个 HAProxy tcp 模式前端,它检测浏览器是否支持 SNI,并基于此,路由到一个强加密的 SSL 终止后端,或更弱的一个。这确保了 SSL 实验室的 A+ 等级,同时仍然允许除 IE6 之外的所有浏览器使用 SSL。

这是我的配置。它有一些模板变量应该是不言自明的,但不在与我的问题相关的领域:

frontend https_incoming
 bind 0.0.0.0:443
 mode tcp
 option tcplog
 tcp-request inspect-delay 5s
 tcp-request content accept if { req.ssl_hello_type 1 }
 use_backend https_strong if { req.ssl_sni -m end .transloadit.com }
 default_backend https_weak

backend https_strong
 mode tcp
 option tcplog
 server https_strong 127.0.0.1:1665

frontend https_strong
 bind 127.0.0.1:1665 ssl crt ${DM_ROOT_DIR}/envs/ssl/haproxy-dh2048.pem no-sslv3 no-tls-tickets ciphers ${strongCiphers}
 mode http
 option httplog
 option httpclose
 option forwardfor if-none except 127.0.0.1
 http-response add-header Strict-Transport-Security max-age=31536000
 reqadd X-Forwarded-Proto:\ https
 reqadd FRONT_END_HTTPS:\ on
 use_backend http_incoming

backend https_weak
 mode tcp
 option tcplog
 server https_weak 127.0.0.1:1667

frontend https_weak
 bind 127.0.0.1:1667 ssl crt ${DM_ROOT_DIR}/envs/ssl/haproxy.pem no-sslv3 ciphers ${weakCiphers}
 mode http
 option httplog
 option httpclose
 option forwardfor if-none except 127.0.0.1
 http-response add-header Strict-Transport-Security max-age=31536000
 reqadd X-Forwarded-Proto:\ https
 reqadd FRONT_END_HTTPS:\ on
 use_backend http_incoming
Run Code Online (Sandbox Code Playgroud)

问题:https_incoming前端知道客户端 IP,但由于它在 中mode tcp,因此无法将此信息保存在mode http X-Forwarded-For标头中。option forwardfor在 TCP 模式下无效。

另一个关于 serverfault 的问题 我已经发现我可以使用:

  • LVS
  • 代理协议

因此,据X-Forwarded-For我所知,甚至不再需要标头,在 LVS 的情况下:数据包被欺骗,因此源成为客户端 IP,而在 PROXY 的情况下:数据包被封装以携带客户端 IP。

这两个似乎都可以工作。然而,LVS 对我们来说似乎是一种心脏手术,可能会产生副作用,而 PROXY 的缺点是代理/应用程序上游/下游,可能尚不完全兼容。

我真的希望有更轻量级的东西,那时我发现了HAProxy 1.6的新“捕获”功能,因为它提到:

您可以声明捕获槽,在其中存储数据并在会话期间随时使用它。

它继续显示以下示例:

defaults 
 mode http

frontend f_myapp
 bind :9001
 declare capture request len 32 # id=0 to store Host header
 declare capture request len 64 # id=1 to store User-Agent header
 http-request capture req.hdr(Host) id 0
 http-request capture req.hdr(User-Agent) id 1
 default_backend b_myapp

backend b_myapp
 http-response set-header Your-Host %[capture.req.hdr(0)]
 http-response set-header Your-User-Agent %[capture.req.hdr(1)]
 server s1 10.0.0.3:4444 check
Run Code Online (Sandbox Code Playgroud)

在我看来,信息存储在前端,然后在后端使用,所以也许我可以在 TCP 模式下获取客户端 IP,保存它,然后稍后使用它,可能像这样:

http-response set-header X-Forwarded-For %[capture.req.hdr(0)]
Run Code Online (Sandbox Code Playgroud)

我查看了捕获文档,似乎捕获更面向 http 模式标头,但后来我还看到一个邮件列表对话成功演示了tcp-request capture.

我尝试了几件事,其中包括:

tcp-request capture req.hdr(RemoteAddr) id 0
# or
tcp-request content capture req.hdr(RemoteHost) id 0
Run Code Online (Sandbox Code Playgroud)

但是正如您所看到的,我不知道语法应该是什么以及该信息在哪个键下可用,我也无法在(我认为)相关文档中找到它。

问题:是否可以在 TCP 模式下捕获客户端 IP,然后X-Forwarded-For在 HTTP 模式下将此信息写入标头?如果是这样,它的语法是什么?

kvz*_*kvz 3

要回答我自己的问题,这似乎不可能,因为流量“离开”HAProxy:

       TCP                             HTTP
frontend->backend (->leaving->) frontend->backend
Run Code Online (Sandbox Code Playgroud)

因此上下文丢失并且捕获无法保留。相反,正如“PiBa-NL”昨天在 Freenode 的 #haproxy 上的 IRC 上建议的那样:

[5:29pm] PiBa-NL: kvz, use proxy-protocol between back and front
[5:54pm] kvz: PiBa-NL: Thanks, does this mean my app also needs to understand 
         the proxy protocol, or will it be 'stripped' once it reaches the 
         backend. I don't think my node.js server could handle it without  
         significant changes to its stack
[6:07pm] kvz: Or let me rephrase: could I enable the proxy protocol on the first 
         frontend, then 'unwrap' it in the second frontend, taking the client ip 
         and putting it into the http header - so that my app would not have to 
         be proxy protocol compatible, and it would just be means to carry the 
         client ip from first frontend to the second?
[6:49pm] PiBa-NL: kvz, the last part you can still use the x-forwarded-for header
[6:50pm] PiBa-NL: but between haproxy backend and frontend you would use the 
         proxyprotocol to make the second frontent 'know' the original client ip
[6:50pm] PiBa-NL: server https_strong 127.0.0.1:1665 send-proxy
[6:50pm] PiBa-NL: bind 127.0.0.1:1665 ssl crt .... accept-proxy
[6:52pm] PiBa-NL: the second frontend can then still use the  
         'option forwardfor', and it should insert the wanted header
[6:53pm] PiBa-NL: so basically 'yes' 
Run Code Online (Sandbox Code Playgroud)

这意味着 PROXY 协议仅用于将两个前端粘合在一起,封装客户端 IP,但第二个前端将其解开并将其保存在 header X-Forwarded-Forvia中option forwardfor,以便其后端可以向我的应用程序发送无 PROXY 协议的请求服务器,这意味着我不必担心上/下游的兼容性问题。