共计 2824 个字符,预计需要花费 8 分钟才能阅读完成。
背景
对于部分不能直接对公网暴露的敏感服务,我在下游 nginx 2 中使用 auth_basic
实现访问控制。
但是实际用起来频繁输入账号密码很烦人。希望在某些固定的场所访问时实现无需 auth_basic
访问控制。
例如当我在家里访问和在公司访问时,IP都是固定的。于是想到下述方案:
- 首先判断访问IP来源
- 若IP在受信任列表中,则不进行
auth_basic
校验,直接可访问。 - 若IP不在受信任列表中,进行
auth_basic
校验,需要输入账号密码才可访问。
- 若IP在受信任列表中,则不进行
我在 nginx 2 中 添加了如下配置:
# cat ***.conf
allow 1.1.1.1/32;
allow 2.2.2.2/32;
allow 192.168.0.0/16;
satisfy any;
auth_basic "Please input user and password.";
auth_basic_user_file /etc/nginx/password;
这里假定 1.1.1.1
和 2.2.2.2
是受信任的外部公网IP(公司出口公网IP);192.168.0.0/16
是受信任的内网IP段(家庭内网CIDR)。
配置解释:
-
allow 1.1.1.1/32;
允许IP地址为 1.1.1.1 的请求访问。 -
allow 2.2.2.2/32;
允许IP地址在 2.2.2.2 网段范围内的请求访问。 -
allow 192.168.0.0/16;
允许IP地址为 192.168.0.0/16 的请求访问。 -
satisfy any;
表示只要满足任意一个allow条件,就允许访问,不需要进行身份验证。 -
auth_basic "Please input user and password.";
启用HTTP基本身份验证,并设置验证时的提示信息。 -
auth_basic_user_file /etc/nginx/password;
指定存储用户名和密码的文件路径。
然而配置完毕后验证效果却发现,在 1.1.1.1
和 2.2.2.2
发起请求仍需要经过 auth_basic
验证,而在 192.168.0.0/16
访问时不需要。
不符合预期,为什么 allow 1.1.1.1/32;
和 allow 2.2.2.2/32;
不生效呢?
思路
Nginx 的 access 模块在进行访问控制时,默认使用的是$remote_addr
变量,该变量表示客户端的真实IP地址。如果 Nginx服务器 前面有还代理或负载均衡,那么$remote_addr
取得的则是上一跳设备的IP。
查看日志
从 1.1.1.1 发起请求时,观察 nginx access.log
:
1.1.1.1 - https [22/May/2024:11:06:17 +0800] "GET /_next/static/chunks/app/(main)/(mobile)/me/profile/page-8d73e05285eddb8b.js HTTP/1.1" 200 4148 "https://***.opshub.cn/sw.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" "1.1.1.1" "***.opshub.cn" "192.168.2.11:3210" "-" 0.002 0.003
可以看到日志中的确是获取到了 1.1.1.1 IP,那为什么还需要经过 auth_basic
验证?
查看日志格式相关配置:
log_format main '$http_x_real_ip - $scheme [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$host" "$upstream_addr" "$upstream_cache_status" $request_time $upstream_response_time';
到这儿我们可以发现,1.1.1.1 是 $http_x_real_ip
的值。
我们将日志配置中的 $http_x_real_ip
改为 $remote_addr
后,再次观察日志:
1.2.3.4 - https [22/May/2024:11:07:19 +0800] "GET /_next/static/chunks/app/(main)/(mobile)/me/profile/page-8d73e05285eddb8b.js HTTP/1.1" 200 4148 "https://***.opshub.cn/sw.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" "1.2.3.4" "***.opshub.cn" "1.2.3.41:3210" "-" 0.002 0.003
可以看到 IP 由 1.1.1.1 变为了 1.2.3.4。
到这儿就问题已经比较明显,在下游 nginx2 中使用 allow
进行访问控制时,默认使用的是$remote_addr
变量进行校验。
由于 $remote_addr
取得上一跳设备 nginx1 的IP:1.2.3.4
,所以不会匹配上 allow 1.1.1.1/32;
。
解决方案
$http_x_real_ip
才是客户端的真实IP,在 nginx 1 中已经正确配置将其了传递到 nginx2 中。
站内文章:Nginx 准确获取真实IP
nginx 1 配置:
proxy_set_header X-Real-IP $remote_addr;
那么现在需要在 nginx2 中,使用 ngx_http_realip_module
模块来获取并替换 $remote_addr
变量的值为真实的客户端 IP。
nginx 2 配置:
set_real_ip_from 1.2.3.4/32;
real_ip_header X-Real-IP;
allow 1.1.1.1/32;
allow 2.2.2.2/32;
allow 192.168.0.0/16;
satisfy any;
auth_basic "Please input user and password.";
auth_basic_user_file /etc/nginx/password;
配置解释:
-
set_real_ip_from 1.2.3.4/32;
用于指定可信的 IP 地址或 CIDR 范围,以确保只有受信任的上游 nginx1 服务器可以设置X-Real-IP
请求头。 -
real_ip_header X-Real-IP;
将$remote_addr
变量的值设置为X-Real-IP
请求头的值。
配置完毕后,效果符合预期。
本文属于专题:Nginx
- Nginx 准确获取真实IP
- Nginx 获取真实IP进行访问控制
- Nginx 日志分析之 Loki