差異處
這裏顯示兩個版本的差異處。
| 下次修改 | 前次修改 | ||
| tech:http_realip [2025/10/03 19:00] – 建立 jonathan | tech:http_realip [2025/10/03 22:57] (目前版本) – [以下透過一個實驗環境(Cloudflare Tunnel、NPM 與 Docker Swarm 架構)的架構案例進行說明] jonathan | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| ====== 網站取得用戶真實 IP 的方法 ====== | ====== 網站取得用戶真實 IP 的方法 ====== | ||
| - | * 目前網站的網路架構愈來愈複雜, | + | * 目前網站的網路架構愈來愈複雜, |
| - | * Client (1.2.3.4) --(HTTPS)-> | + | |
| + | ==== 通用原則:掌握 HTTP Real IP 標頭 ==== | ||
| + | * 取得真實 IP 的核心在於: | ||
| + | - 識別上游代理伺服器傳來的 真實 IP 標頭(例如:**X-Forwarded-For、CF-Connecting-IP、X-Real-IP**)。 | ||
| + | - 使用 **set_real_ip_from** 信任該標頭的來源 IP 範圍。 | ||
| + | - 使用 **real_ip_header** 指定要讀取的標頭名稱。 | ||
| + | |||
| + | ==== 以下透過一個實驗環境(Cloudflare Tunnel、NPM 與 Docker Swarm 的架構)案例進行說明 | ||
| < | < | ||
| - | graph LR | + | graph TD |
| - | | + | |
| - | | + | |
| - | | + | B{Cloudflare CDN} |
| - | B[NginxProxyManager:80] | + | C[Host A - 192.168.1.254 - Cloudflared] |
| - | style B fill:# | + | D[Host B - 192.168.1.100: |
| - | end | + | |
| - | subgraph | + | F[Backend Service - dokuwiki_dokuwiki: |
| - | D[(dokuwiki_dokuwiki: | + | |
| - | style D fill:# | + | %% 2. 結構化 - 邏輯分區 |
| - | end | + | |
| - | | + | A -- HTTPS --> |
| - | | + | |
| end | end | ||
| - | |||
| - | A[Client (1.2.3.4)] | ||
| - | C{{Cloudflare CDN}} | ||
| - | E[Host A (192.168.1.1) - Cloudflared] | ||
| | | ||
| - | A -- HTTPS --> C | + | |
| - | C -- Encrypted Tunnel | + | %% Host A 到 Host B 的連線 |
| - | | + | C -- HTTP - LAN 192.168.1.x (X-Forwarded-For 開始累積,CF-Connecting-IP 帶有真實 IP) --> |
| + | %% NPM 經由 Overlay Network 到後端 | ||
| + | D -- HTTP - rproxy-net 10.0.8.x | ||
| + | end | ||
| | | ||
| - | | + | |
| - | style C fill:# | + | E -- rproxy-net 接入 --> F |
| - | | + | |
| | | ||
| - | %% 標籤解釋 | + | %% 4. 補充細節/ |
| - | | + | |
| - | classDef cdn fill:# | + | |
| - | classDef tunnel fill:# | + | |
| - | classDef proxy fill:# | + | |
| - | classDef backend fill:# | + | |
| </ | </ | ||
| + | 如果沒有特別設定, | ||
| + | |||
| + | ===== 1. NPM 必須要將 Port Mode 改成 Host ====== | ||
| + | * NPM 的 Stack 部署設定, | ||
| + | services: | ||
| + | nginx-proxy-manager: | ||
| + | image: jc21/ | ||
| + | ports: | ||
| + | - target: 80 | ||
| + | published: 80 | ||
| + | mode: host # 重要:使用 host mode | ||
| + | - target: 443 | ||
| + | published: 443 | ||
| + | mode: host | ||
| + | - target: 81 | ||
| + | published: 81 | ||
| + | mode: host | ||
| + | volumes: | ||
| + | - npm-data:/ | ||
| + | - npm-letsencrypt:/ | ||
| + | networks: | ||
| + | - rproxy-net | ||
| + | deploy: | ||
| + | replicas: 1 # NPM 本身不適合多副本 | ||
| + | placement: | ||
| + | constraints: | ||
| + | - node.labels.rproxy == true # 只部署在有 rproxy label 的節點 | ||
| + | </ | ||
| + | * 因為是 host 模式, 所以需要在特定 Swarm Host 上設定 Label Exp. swarm-100(192.168.1.100)< | ||
| + | * 所以除了這個 IP 之外, 其餘 Swarm Host 的 IP 就無不能連上 NPM 服務 | ||
| + | * 在 Cloudflare -> Zero Trust -> Networks -> Tunnels -> Your-cf-tunnel -> Published application routes 就只能設定這個 IP Exp. www.ichiayi.com -> 192.168.1.100: | ||
| + | |||
| + | ===== 2. NPM 內對 Backend Service 網站的設定 ===== | ||
| + | * Domain Names : www.ichiayi.com | ||
| + | * Scheme : http | ||
| + | * Forward Hostname / IP : dokuwiki_dokuwiki (假設 Backend Service 的 Stack Name : dokuwiki , Service Name : dokuwiki) | ||
| + | * Forward Port : 80 | ||
| + | * {{: | ||
| + | ==== Advanced 的設定說明 ==== | ||
| + | * Host 都在 192.168.1.0/ | ||
| + | # ======================================== | ||
| + | # 真實 IP 處理 | ||
| + | # ======================================== | ||
| + | |||
| + | set_real_ip_from 192.168.1.0/ | ||
| + | |||
| + | # 使用 Cloudflare 的 CF-Connecting-IP header | ||
| + | real_ip_header CF-Connecting-IP; | ||
| + | real_ip_recursive on; | ||
| + | |||
| + | # ======================================== | ||
| + | # Proxy Headers - 傳遞真實 IP 到後端 | ||
| + | # ======================================== | ||
| + | |||
| + | # 真實 IP (現在 $remote_addr 已經是真實 IP) | ||
| + | proxy_set_header X-Real-IP $remote_addr; | ||
| + | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| + | proxy_set_header X-Forwarded-Proto $scheme; | ||
| + | proxy_set_header X-Forwarded-Host $host; | ||
| + | proxy_set_header Host $host; | ||
| + | |||
| + | # 保留原始 Cloudflare headers | ||
| + | proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; | ||
| + | proxy_set_header CF-Ray $http_cf_ray; | ||
| + | proxy_set_header CF-IPCountry $http_cf_ipcountry; | ||
| + | proxy_set_header CF-Visitor $http_cf_visitor; | ||
| + | </ | ||
| + | * 此時在 NPM 看存取 www.ichiayi.com 的 log 應該就可看到類似以下的紀錄, | ||
| + | : | ||
| + | [03/ | ||
| + | : | ||
| + | </ | ||
| + | < | ||
| + | 當設定 real_ip_recursive on 時,Nginx 會從 X-Forwarded-For 列表的最右邊開始,依序移除屬於 set_real_ip_from 信任範圍內的 IP,直到遇到第一個非信任 IP。由於我們這裡直接使用 real_ip_header CF-Connecting-IP,因此它會被視為真實 IP 並覆蓋 $remote_addr。」 | ||
| + | </ | ||
| + | ===== 3. Backend Service 內的 Nginx Config 設定 ===== | ||
| + | * 因為這 Backend Service 是 dokuwiki 使用的 image 為 lscr.io/ | ||
| + | * 假設 Docker Networks 的 rproxy-net (由 NPM -> Dokuwiki 的 overlay network) 網段是 10.0.8.0/24 | ||
| + | * 在 custom-cont-init.d 內增加 10-nginx-realip.sh < | ||
| + | #!/bin/sh | ||
| + | |||
| + | # 腳本名稱: | ||
| + | # 目的: 修正 Real IP 設定,將其插入到實際的 Nginx 虛擬主機設定檔 (default.conf) 中。 | ||
| + | |||
| + | # Nginx 實際的虛擬主機設定檔路徑 | ||
| + | SITE_CONF="/ | ||
| + | |||
| + | echo ">> | ||
| + | |||
| + | # Real IP 配置內容 | ||
| + | REAL_IP_CONFIG=$(cat <<EOF | ||
| + | # | ||
| + | # Real IP 設定 - 信任 Docker Overlay 網路 (NPM IP) | ||
| + | # | ||
| + | set_real_ip_from 10.0.8.0/ | ||
| + | |||
| + | # 讀取 NPM 傳來的 X-Real-IP 標頭 | ||
| + | real_ip_header X-Real-IP; | ||
| + | |||
| + | EOF | ||
| + | ) | ||
| + | |||
| + | # 檢查配置是否已經存在 | ||
| + | if grep -q " | ||
| + | echo ">> | ||
| + | else | ||
| + | # 這次我們在 ' | ||
| + | SERVER_START=' | ||
| + | |||
| + | # 使用 sed 在 ' | ||
| + | # 使用 \n 和 \t 來處理換行和縮排 | ||
| + | REAL_IP_CONFIG_ESCAPED=$(echo " | ||
| + | |||
| + | if sed -i "/ | ||
| + | echo ">> | ||
| + | else | ||
| + | echo ">> | ||
| + | exit 1 | ||
| + | fi | ||
| + | fi | ||
| + | |||
| + | # 賦予腳本執行權限 | ||
| + | chmod +x " | ||
| + | </ | ||
| + | * 需要將這個 script 檔案設定執行權限 < | ||
| + | * 重新啟動 dokuwiki 這個 stack | ||
| + | * 查看 dokuwiki 啟動的容器 log 應該就會出現類似以下的訊息 < | ||
| + | : | ||
| + | **** Permissions could not be set. This is probably because your volume mounts are remote or read-only. **** | ||
| + | **** The app may not work properly and we will not provide support for it. **** | ||
| + | Existing install found, deleting install.php. | ||
| + | [custom-init] Files found, executing | ||
| + | [custom-init] 10-nginx-realip.sh: | ||
| + | >> [custom-init] Configuring Nginx Real IP in / | ||
| + | >> [custom-init] Real IP configuration already exists in site-conf. Skipping. | ||
| + | [custom-init] 10-nginx-realip.sh: | ||
| + | [ls.io-init] done. | ||
| + | : | ||
| + | </ | ||
| + | * 接下來就可以查看 dokuwiki 的 nginx access log 可以出現用戶 IP Exp. <cli> | ||
| + | : | ||
| + | 15.235.145.214 - - [03/ | ||
| + | 15.235.145.214 - - [03/ | ||
| + | 15.235.145.214 - - [03/ | ||
| + | 217.113.196.92 - - [03/ | ||
| + | 82.97.199.97 - - [03/ | ||
| + | : | ||
| + | </ | ||
| + | |||
| + | {{tag> | ||