最近把 DNS 轉到 Cloudflare 使用, 原本在主機上使用 HAProxy 把 SSL 的封包轉到個別對應的軟體是使用 Proxy Protocol 將來源 IP 通知處理的軟體. 在改用 Cloudflare 之後, 這個 IP 就變成 Cloudflare 的 IP 了. 這個在需要知道來源 IP 的軟體上, 就反而造成問題.
通常會走 Cloudflare 進來的, 也都是網頁的服務 (其他同樣使用 SSL 走 443 的服務, 也不需要透過 Cloudflare 處理, 通常可以在 DNS 上另外設定來避開). 依據 Cloudflare 的作法, 是應該使用 HTTP 的 header CF-Connecting-IP 來取得真實的來源 IP. 不過.... 由於我這邊是先透過 HAProxy 處理過, 混雜了來自 Cloudflare 與沒經過 Cloudflare 的封包 (例如內部網路的機器), 這時, 就變成使用 CF-Connecting-IP 時, 非來自 Cloudflare 的 IP 變成 HAProxy 的 127.0.0.1, 如果使用 Proxy Protocol 時, 那些透過 Cloudflare 的封包, 來源 IP 又全變成 Cloudflare 的 IP 了.
看了一下 Nginx 的 real_ip 模組設定, real_ip_header 就只能設定一種, 不能有多重的設定, 所以就只好看一下 source, 自己動手來修改.
--- nginx-1.23.3/src/http/modules/ngx_http_realip_module.c.orig 2023-03-19 11:18:38.033783262 +0800
+++ nginx-1.23.3/src/http/modules/ngx_http_realip_module.c 2023-03-19 11:17:30.029338872 +0800
@@ -157,7 +157,8 @@
case NGX_HTTP_REALIP_XREALIP:
if (r->headers_in.x_real_ip == NULL) {
- return NGX_DECLINED;
+ //return NGX_DECLINED;
+ goto try_proxy_protocol;
}
value = &r->headers_in.x_real_ip->value;
@@ -170,7 +171,8 @@
xfwd = r->headers_in.x_forwarded_for;
if (xfwd == NULL) {
- return NGX_DECLINED;
+ //return NGX_DECLINED;
+ goto try_proxy_protocol;
}
value = NULL;
@@ -178,6 +180,7 @@
break;
case NGX_HTTP_REALIP_PROXY:
+try_proxy_protocol:
if (r->connection->proxy_protocol == NULL) {
return NGX_DECLINED;
}
@@ -219,7 +222,8 @@
}
}
- return NGX_DECLINED;
+ //return NGX_DECLINED;
+ goto try_proxy_protocol;
}
found:
修改的方式其實不難, 這邊的處理雖然是用 switch case 來獨立處理, 不過可以發現其實是不是透過 Proxy Protocol 處理過, 是有方法可以判斷的, 所以我們只要在其中每一種用來取得 real_ip 的 case 中, 如果最後沒有處理到, 就轉給 Proxy Protocol 那一段來處理就可以.
這樣子修改後, 我們依舊可以在 Nginx 中設定
real_ip_header CF-Connecting-IP;
就算是沒有這個 header 的來源, 一樣仍透過 Proxy Protocol 處理, 就算也沒透過 Proxy Protocol, 一樣可以判斷出來而不會造成問題.