在企業實際運用中,真實業務服務器往往都不是直接對外提供服務的,前面大多都會添加反代服務器以及防火牆,
但這樣以來核心的業務服務器如何獲取到用戶的真實IP往往是運維考慮的一個核心問題,今天我給大家分享一下
企業真實企業真實環境下核心web服務器如何獲取用戶的真實IP。由於線上大多使用硬件防火牆,我們這裏
使用CentOS自帶的iptables來進行模擬,也方便大家閱讀本文進行實踐。
環境展示:
虛擬化平臺: Vmware Workstation
操作系統: CentOS 7.5
防火牆: CentOS系統的iptables做DNAT+SNAT
反代服務器: nginx的七層反代模塊
web服務器: nginx或者httpd
此次的模擬過程我們用四臺虛擬機來實現:
webserver 一臺單獨的虛機 192.168.10.254
proxyserver 一臺單獨的虛機 192.168.10.100
proxyserver2 一臺單獨的虛機 192.168.10.50
firewall 一臺單獨的虛機 內網IP:192.168.10.8 外網IP:10.0.0.10
四臺服務器同時禁用和停止SELinux和默認的firewalld服務,實際企業環境中,防火牆以內的服務器也很少會啓用此兩項。
webserver安裝nginx和php-fpm
proxyserver安裝nginx
firewall默認就可以使用iptables,如果要想使用更完備的iptables功能,可以安裝iptables-services
webserver的nginx主配置文件
grep -v '[[:space:]]*#' /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /data/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '
'$remote_user [$time_local] "$request" $status $body_bytes_sent '
'$upstream_addr $request_time $upstream_response_time '
'"$http_referer" "$http_user_agent" ';
access_log /data/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
webserver的nginx虛擬主機配置文件
grep -v '[[:space:]]*#' /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name www.test.com;
root /data/website;
index index.php index.html index.htm;
access_log /data/log/nginx/test.access.log main;
error_log /data/log/nginx/test.error.log warn;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
測試頁面php代碼如下:
/data/website/index.php
<?php
foreach ($_SERVER as $key=>$value) {
echo $key.'='.$value.'<br />';
}
?>
使用宿主機的瀏覽器訪問核心業務服務器192.168.10.254,頁面輸出如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.1
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.1
REMOTE_PORT=57829
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=192.168.10.254
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539053341.08
REQUEST_TIME=1539053341
webserver的nginx日誌內容如下
cat /data/log/nginx/test.access.log
192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:10:54:34 +0800] "GET / HTTP/1.1" 200 1146 127.0.0.1:9000 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
由於宿主機的ip都是本網段的第一個ip,所以我們ip是192.168.10.1是真實的客戶端ip。
爲了方便查看HTTP_X_FORWARDED_FOR的變化,我們直接使用fastcgi_param參數嚮應用服務器傳遞一個HTTP_X_FORWARDED_FOR變量
location ~ \.php$ {
#root /data/website;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for; #添加了此行
include fastcgi_params;
}
此時再次刷新訪問頁面192.168.10.254,輸出內容如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=192.168.10.1
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.1
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.1
REMOTE_PORT=59095
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=192.168.10.254
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539054347.9258
REQUEST_TIME=1539054347
HTTP_X_FORWARDED_FOR=192.168.10.1,可以明確的看到fastcgi向後端應用服務器傳遞的HTTP_X_FORWARDED_FOR的值
webserver的nginx日誌內容如下
192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:05:47 +0800] "GET / HTTP/1.1" 200 1185 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
接下來我們給webserver前端添加第一層反向代理
proxyserver的nginx主配如下:
user nginx;
worker_processes auto;
error_log /data/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '
'$remote_user [$time_local] "$request" $status $body_bytes_sent '
'$upstream_addr $request_time $upstream_response_time '
'"$http_referer" "$http_user_agent" ';
access_log /data/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
proxyserver的nginx小配如下:
upstream backend_server {
server 192.168.10.254;
}
server {
listen 80;
server_name proxy.test.com;
access_log /data/log/nginx/proxy.access.log main;
error_log /data/log/nginx/proxy.error.log warn;
location / {
proxy_pass http://backend_server;
}
}
此時使用宿主機的瀏覽器訪問proxyserver的ip 192.168.10.100,輸出內容如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=192.168.10.100
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.0
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.100
REMOTE_PORT=44866
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=backend_server
HTTP_CONNECTION=close
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539055499.4538
REQUEST_TIME=1539055499
此時可以看到proxyserver響應的頁面中HTTP_X_FORWARDED_FOR=192.168.10.100,由原來記錄的用戶的真實IP變成了反代proxyserver自己的IP。REMOTE_ADDR=192.168.10.100由原來需要訪問核心業務服務器變成了訪問proxyserver的IP,從而起到了隱藏核心業務服務器的作用。
proxyserver的nginx日誌輸出如下:
192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:25:03 +0800] "GET / HTTP/1.1" 200 1150 192.168.10.254:80 0.002 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
webserver的nginx日誌輸出如下:
192.168.10.100 "-" "192.168.10.100" - [09/Oct/2018:11:24:59 +0800] "GET / HTTP/1.0" 200 1138 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
在這裏能夠很清晰的看到前端proxyserver能夠拿到用戶的真實訪問IP,但核心業務服務器webserver看到所有的訪問是來自前端反代服務器的,
這樣以來,用戶的真實IP就獲取不到了。這是無法企業的需求的,那麼接下就要給大家分享在前端有反向代理服務器時如何獲取用戶的真實IP。
修改proxyserver的nginx小配文件
location / {
proxy_set_header Host $host; #增加內容
proxy_set_header X-Real-IP $remote_addr; #增加內容
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #增加內容
proxy_pass http://backend_server;
}
reload nginx後重新訪問192.168.10.100,頁面輸出內容如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.100
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.0
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.100
REMOTE_PORT=44870
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=192.168.10.100
HTTP_X_REAL_IP=192.168.10.1
HTTP_CONNECTION=close
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539057291.9947
REQUEST_TIME=1539057291
HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.100,可以看到HTTP_X_FORWARDED_FOR變量中保存了所有經過的服務器,這裏我們只經過了一層反向服務器,如果有多層的話,會依次追加至真實客戶端IP的後面。
此時我們再來查看一下proxyserver的nginx訪問日誌
192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:54:55 +0800] "GET / HTTP/1.1" 200 1231 192.168.10.254:80 0.001 0.003 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
可以看出和之前一樣
webserver的nginx訪問日誌
192.168.10.100 "192.168.10.1" "192.168.10.1, 192.168.10.100" - [09/Oct/2018:11:54:51 +0800] "GET / HTTP/1.0" 200 1219 127.0.0.1:9000 0.000 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
但核心業務服務器日誌的HTTP_X_FORWARDED_FOR字段的值就是用戶端的真實IP "192.168.10.1"。但這只是針對單層反向代碼的情況,如果核心業務服務器前端有多層反向代理時,HTTP_X_FORWARDED_FOR字段可能不上一個IP了,是會同時攜帶最接近用戶真實IP層面的幾層IP信息。
爲了能夠讓大家看到效果,我們繼續給proxyserver 192.168.10.100的前端再添加一層反代服務器,使用192.168.10.50這臺服務器也安裝nginx,並配置成爲proxyserver2,做爲192.168.10.100的前端反向代理。
proxyserver2的nginx主配置文件
user nginx;
worker_processes auto;
error_log /data/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '
'$remote_user [$time_local] "$request" $status $body_bytes_sent '
'$upstream_addr $request_time $upstream_response_time '
'"$http_referer" "$http_user_agent" ';
access_log /data/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
proxyserver2的nginx小配文件
upstream backend_server {
server 192.168.10.100;
}
server {
listen 80;
server_name proxy.test.com;
access_log /data/log/nginx/proxy.access.log main;
error_log /data/log/nginx/proxy.error.log warn;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_server;
}
}
此時訪問最前端反代服務器proxyserver2的ip,192.168.10.50,輸出內容如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.50, 192.168.10.100
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.0
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.100
REMOTE_PORT=44880
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=192.168.10.50
HTTP_X_REAL_IP=192.168.10.50
HTTP_CONNECTION=close
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539059555.4869
REQUEST_TIME=1539059555
proxyserver2服務器nginx的訪問日誌
192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:12:32:39 +0800] "GET / HTTP/1.1" 200 1246 192.168.10.100:80 0.003 0.004 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
由於這一層在最前端,獲取到是用戶的真實IP。
proxyserver的nginx訪問日誌
192.168.10.50 "192.168.10.1" "192.168.10.1, 192.168.10.50" - [09/Oct/2018:12:32:39 +0800] "GET / HTTP/1.0" 200 1234 192.168.10.254:80 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
能能夠看到用戶的真實ip是被傳遞給了HTTP_X_FORWARDED_FOR變量的,但也是源於proxyserver2的反向代理層面添加了如下三行配置:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
否則proxyserver層是獲取不到用戶端的真實IP的
再看一下核心層webserver服務器nginx訪問日誌
192.168.10.100 "192.168.10.1, 192.168.10.50" "192.168.10.1, 192.168.10.50, 192.168.10.100" - [09/Oct/2018:12:32:35 +0800] "GET / HTTP/1.0" 200 1234 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
此時可以看到核心業務服務器HTTP_X_FORWARDED_FOR變量的值就不止一個IP了,包含了用戶的真實IP和離用戶最近一層的反向代理服務器的IP。
在這種多層反向代理的環境,如果要想從HTTP_X_FORWARDED_FOR變量中取到用戶真實IP,就得寫代碼從HTTP_X_FORWARDED_FOR變量中取第一個,號前的內容了。如果想讓REMOTE_ADDR字段直接保存用戶的真實IP,還需要進一步的配置。
在這裏通過筆者的實踐得出了兩種配置思路:
第一種,只在核心業務器webserver上配置(前端反代不用關心)
需要在server段中添加如下配置
set_real_ip_from 192.168.10.50;
set_real_ip_from 192.168.10.100;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
也即把所有外層反代服務器的ip地址全部使用set_real_ip_from羅列出來,並指定用戶真實IP從X-Forwarded-For字段中取值,
並且啓用真實IP遞歸解析(也即從X-Forwarded-For字段的最右側開始,排除方式取出第一個,號之前IP)
第二種,每層反代理都配置set_real_ip_from 上層反代IP;(上文中的其配置都可省略)
此種思路配置較爲複雜,但每層反代都可以使用REMOTE_ADDR字段保存用戶的真實IP,
如果反代服務器不是自己的資源時具有較多的不可操作性。
首先,我們看一下第一種方案配置後的日誌記錄
訪問最外層反代proxyserver2的ip,頁面輸出如下
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.50, 192.168.10.1
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.0
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=192.168.10.1
REMOTE_PORT=
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=192.168.10.50
HTTP_X_REAL_IP=192.168.10.50
HTTP_CONNECTION=close
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539090819.6079
REQUEST_TIME=1539090819
proxyserver2的nginx日誌如下:
192.168.10.1 "-" "192.168.10.1" - [10/Oct/2018:19:36:53 +0800] "GET / HTTP/1.1" 200 1237 192.168.10.100:80 0.307 0.308 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
最外層記錄了用戶的真實訪問IP,並向PROXY_ADD_X_FORWARDED_FOR變量中插入用戶的真實IP
proxyserver的nginx日誌如下:
192.168.10.50 "192.168.10.1" "192.168.10.1, 192.168.10.50" - [09/Oct/2018:21:13:07 +0800] "GET / HTTP/1.0" 200 1225 192.168.10.254:80 0.307 0.307 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
中間層沒有使用set_real_ip_from指令,所以REMOTE_ADDR字段還是存放的上層反代服務器的訪問IP,但HTTP_X_FORWARDED_FOR變量的第一段已經有了用戶的真實訪問IP,如果這裏想獲取用戶的真實訪問IP就得通過代碼來實現。
webserver的nginx日誌如下:
192.168.10.1 "192.168.10.1, 192.168.10.50" "192.168.10.1, 192.168.10.50, 192.168.10.1" - [09/Oct/2018:21:13:39 +0800] "GET / HTTP/1.0" 200 1225 127.0.0.1:9000 0.306 0.306 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
核心層的nginx配置中添加過了REAL_IP的相關配置,所以REMOTE_ADDR字段直接保存了用戶的真實訪問IP,X-Forwarded-For字段保存了用戶的真實IP和離用戶最近的上層代理,proxy_add_x_forwarded_for變量中的最後一段也是用戶的真實IP,這個有待於研究。
至此,我們核心業務服務器在多層反向代理的環境下,已經可以獲取用戶的真實訪問IP了。如果想模擬在最外層有防火牆的情況,請繼續往下。
此時我們啓用firewall這臺虛機,需要另外添加一張外網網卡,內網使用eth0,配置IP:192.168.10.8,外網使用eth1,配置IP:10.0.0.10,
如果要想做爲網絡防火牆使用,必須打開內核轉發參數,也即net.ipv4.ip_forward = 1
添加相應外網到內網的映射規則,也即DNAT規則
iptables -t nat -I PREROUTING -p tcp -d 10.0.0.10 --dport 80 -j DNAT --to-destination 192.168.10.50:80
此條規則是將所有訪問防火牆的外網接口10.0.0.10的80端口的數據修改目標地址及端口後轉發到內網服務器192.168.10.50的80端口,
而我們內網服務器192.168.10.50也不是真實web服務器,而是多層反向代理的最外層反代服務器。所以這整個鏈路的層次還是比較多的。
僅有DNAT規則,只能將外網用戶的請求轉發到內網的反代服務器,所以還需要SNAT規則將反代服務器的響應數據修改源地及端口後轉發給用戶
iptables -t nat -I POSTROUTING -p tcp -s 192.168.10.50 --sport 80 -j SNAT --to-source 10.0.0.10:80
跟着做的朋友可能會發現現在DNAT和SNAT都做好了,但爲什麼還是訪問10.0.0.10還是不正常了,原因在於內網的反代服務器響應數據時找不到通向10.0網絡的路由,所以需要在內網服務器上配置防火牆192.168.10.8爲自己的網關。
需要修改proxyserver2的網絡配置
GATEWAY=192.168.10.8
此時使用宿主機的瀏覽器訪問10.0.0.10,頁面輸出內容如下:
USER=apache
HOME=/usr/share/httpd
FCGI_ROLE=RESPONDER
SCRIPT_FILENAME=/data/website/index.php
HTTP_X_FORWARDED_FOR=10.0.0.1, 192.168.10.50, 10.0.0.1
QUERY_STRING=
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/index.php
REQUEST_URI=/
DOCUMENT_URI=/index.php
DOCUMENT_ROOT=/data/website
SERVER_PROTOCOL=HTTP/1.0
REQUEST_SCHEME=http
GATEWAY_INTERFACE=CGI/1.1
SERVER_SOFTWARE=nginx/1.14.0
REMOTE_ADDR=10.0.0.1
REMOTE_PORT=
SERVER_ADDR=192.168.10.254
SERVER_PORT=80
SERVER_NAME=www.test.com
REDIRECT_STATUS=200
HTTP_HOST=10.0.0.10
HTTP_X_REAL_IP=192.168.10.50
HTTP_CONNECTION=close
HTTP_CACHE_CONTROL=max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9
PHP_SELF=/index.php
REQUEST_TIME_FLOAT=1539111297.9179
REQUEST_TIME=1539111297
proxyserver2的nginx日誌如下:
10.0.0.1 "-" "10.0.0.1" - [11/Oct/2018:01:18:11 +0800] "GET / HTTP/1.1" 200 1221 192.168.10.100:80 0.004 0.005 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
proxyserver的nginx日誌如下:
192.168.10.50 "10.0.0.1" "10.0.0.1, 192.168.10.50" - [10/Oct/2018:02:54:26 +0800] "GET / HTTP/1.0" 200 1209 192.168.10.254:80 0.002 0.003 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
webserver的nginx日誌如下:
10.0.0.1 "10.0.0.1, 192.168.10.50" "10.0.0.1, 192.168.10.50, 10.0.0.1" - [10/Oct/2018:02:54:57 +0800] "GET / HTTP/1.0" 200 1209 127.0.0.1:9000 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
接下來我們再爲大家展示一下層層代理都配置後得到的日誌效果
proxyserver2是最外層的反向代理,所以不需要使用set_real_ip指定上級反代的真實IP
proxyserver2的nginx日誌如下:
10.0.0.1 "-" "10.0.0.1" - [11/Oct/2018:02:05:00 +0800] "GET / HTTP/1.1" 200 1211 192.168.10.100:80 0.004 0.005 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
proxyserver是中間反代服務器,需要使用set_real_ip指定上級反代的真實IP
需要在nginx小配文件中增加如下配置
set_real_ip_from 192.168.10.50;
proxyserver的nginx日誌如下:
10.0.0.1 "10.0.0.1" "10.0.0.1, 10.0.0.1" - [10/Oct/2018:03:41:14 +0800] "GET / HTTP/1.0" 200 1199 192.168.10.254:80 0.002 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
webserver的nginx也需要使用set_real_ip指定上級反代的真實IP
set_real_ip_from 192.168.10.100;
webserver的nginx日誌如下:
10.0.0.1 "10.0.0.1, 10.0.0.1" "10.0.0.1, 10.0.0.1, 10.0.0.1" - [10/Oct/2018:03:41:46 +0800] "GET / HTTP/1.0" 200 1199 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
從核心業務服務器日誌的proxy_add_x_forwarded_for的變量值來看,每經過一次處理,都會把用戶的真實IP解析出來並向
proxy_add_x_forwarded_for值。
至此,我們的環境是最前端防火牆,防火牆過後穿越兩層反向代理,用戶請求才到達核心業務服務器,即便是如此複雜的網絡環境,依然能夠保證我們的核心業務服務器能夠獲取到用戶的真實IP。