How do proxy handle the IP source of requests?

代理伺服器與後端服務如何處理請求的 IP來源?

前言

在服務面前擺 Nginx 進行反向代理是常見的操作,但當我們要做應用層的限流機制、紀錄 Log、一切與來源請求 IP 相關的功能時要如何拿到正確的資訊?

記錄一下我根據Go Gin 受信任代理文件🔗設置的最佳範例。

X-Forwarded-For 可以被偽造

通常透過反向代理的請求會在 X-Forwarded-ForX-Real-Ip 等標頭中轉發原始請求的 IP 位址,讓我驚訝的是 Gin 預設會信任所有代理,也就是說通常用 Context.ClientIP() 獲取請求 IP 是不安全的,隨便都能假冒修改。

Terminal window
curl -s -H "X-Forwarded-For: 203.0.113.196" http://localhost:8080/ping

解決方法一:不信任所有,使用 RemoteAddr

Gin 文件中提到 Engine.SetTrustedProxies(nil) 等同不信任任何來源,使用 Request.RemoteAddr 作為結果(TCP 連接訊息中獲取),也能少掉一些判斷,不過這麼做在沒有用到請求 IP 的情況下還行,萬一用到代理伺服器呢?所有的請求來源將都會變成自己的代理伺服器

解決方法二:正確設置信任的代理

更好的做法是明確告訴 Gin 哪些 IP 或 CIDR 範圍是可信任的代理,這樣 ClientIP() 只會在請求來自這些來源時才採信 X-Forwarded-For 標頭。

Nginx 負責將真實客戶端 IP 寫入標頭:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

Gin 只信任來自 Nginx 所在網段的請求標頭:

r.SetTrustedProxies([]string{"172.29.0.0/16"})
r.GET("/ping", func(c *gin.Context) {
// 只有當請求來自受信任的代理時,才會從 X-Forwarded-For 取得真實 IP
clientIP := c.ClientIP()
})

Docker Compose 為 Nginx 與 App 分配固定網段,並透過環境變數傳入信任的 CIDR:

services:
app:
environment:
- TRUSTED_PROXY=172.29.0.0/16
networks:
proxy-network:
ipv4_address: 172.29.0.10
nginx:
networks:
proxy-network:
ipv4_address: 172.29.0.20
networks:
proxy-network:
ipam:
config:
- subnet: 172.29.0.0/16

這樣即使攻擊者在請求中偽造 X-Forwarded-For,因為請求不是來自受信任的代理網段,Gin 會直接使用 TCP 連線的 RemoteAddr 而非標頭內容,偽造就無效了。

動態設置信任網段

上述方案將 CIDR 寫死,更方便的做法是讓 app 在啟動時自動讀取建立信任清單:

func localCIDRs() []string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
var cidrs []string
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
cidrs = append(cidrs, ipNet.String())
}
}
return cidrs
}
// 啟動時自動信任與自己同網段的所有來源
r.SetTrustedProxies(localCIDRs())

net.Interfaces() 會列出容器內所有網卡,取出 CIDR(如 172.18.0.2/16)。因為 Nginx 和 app 在同一個 Docker network 內,Nginx 的 IP 必然落在同一個子網段,所以會被自動信任。

docker-compose.yml 也不再需要指定固定 IP 或 ipam,完全交給 Docker 分配:

services:
app:
build: .
networks:
- proxy-network
nginx:
image: nginx:alpine
networks:
- proxy-network
networks:
proxy-network:

延伸閱讀