一个 URL 兼顾办公室与 VPN?白名单神话为何站不住
VPN 连接并不会改变被允许的网站所看到的地址。在新增 URL 之前,先核查真实出口、受信任的代理跳点和 IPv6 路径。
连上隧道,打开同一个书签,被允许的网站给出的回应与在办公室时一模一样。这是大多数远程访问方案悄悄依赖的假设,而它只在三件事同时成立时才站得住。这三件事没有一件是 VPN 客户端的功能,也没有任何 DNS 记录能提供其中任何一件。当书签在家里失败时,浏览器只会给出同一个笼统错误,无论原因是缺少路由、未获批准的出口地址,还是替换了它所上报来源的代理。把这三者当成一个问题,正是小改动变成整整一周扩白名单的原因。
经得起真实办公网络考验的设计,比这个假设更窄:一张明确的三行决策表、一个无法绕过的执行点,以及一项能证明点什么的拒绝测试。先拆掉这个假设,因为其后的一切都取决于知道是哪一层在错误地表述来源。
神话:连上不等于走对路由
隧道建立起来,客户端为某些前缀安装了路由。连接能做的就这些。一个不相关的 HTTPS 请求是否经过隧道、到达时看起来来自哪个公网地址,是之后由其他组件做出的决定:路由表选路径,出口选来源。
所以,同一台笔记本、同一份浏览器配置、同一个书签,在办公室和在餐桌上结果不同。在办公室,请求经由网站运营方认可的、有人维护的公网地址发出。走全隧道时,它可能从 VPN 出口出去,那是另一个地址,在没人把它加进去之前属于未获批准。走拆分隧道时,它可能根本不进隧道。这三条路径里只有两条曾是站点所有者约定的一部分,第三条不是靠多加一条 allow 就能补上的缺陷。
三种请求状态,两个获批出口
动防火墙之前先把矩阵写下来,因为表才是设计,规则只是它的一种实现。下面这些地址属于文档保留段;请换成你自己维护的值,并放在站点所有者能审计的地方。
| Request path | Source the site sees | Intended decision |
|---|---|---|
| Office, no tunnel | Office public exit | Admit to the application login |
| Remote, approved full tunnel | Approved VPN egress | Admit to the same login |
| Remote, ordinary connection | Neither approved address | Deny at the network boundary |
恰恰是中间那一行让这些方案大多失效。算数的是执行设备看到的地址,不是笔记本在隧道内持有的私有地址,也不是它的局域网地址。10.8.0.6 这样的值对公网站点毫无意义,192.168.1.44 也一样。隧道地址只是隧道编址的细节,不是站点能验证的凭据。
如果你的 VPN 提供不了稳定且有记录可查的出口,矩阵就没有有效的中间一行,再多白名单也造不出来。此时诚实的选项是:经由明确的私有路由才能到达的私有站点,或者在应用前面加一道识别身份的门禁。两者与公网白名单是另一种约定,也都比一份长到碰巧能用的名单更容易向审计者解释。
在做出决定的地方读取来源
把限制放在终结请求的那台设备上,并确认没有任何备用源路径绕过它。Nginx 访问模块按顺序评估 allow 与 deny 指令,第一条匹配者胜出,而从外围块继承的规则在 location 内依然生效。同一个位置里窄 allow 挨着宽 allow,通常意味着由宽的那条决定。改动其中哪怕一行之前,先读完整份生效配置。
server {
listen 443 ssl;
server_name intranet.example.com;
set_real_ip_from 10.20.0.10;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location /app/ {
allow 203.0.113.24; # office public exit
allow 198.51.100.77; # approved VPN egress
deny all;
proxy_pass http://app_backend;
}
}
在负载均衡器或反向代理后面,这条规则看到的是代理,除非你另有说明。只有通过受支持的受信任代理机制、并附一份明确的代理地址清单,才能还原调用方地址。声称某个获批地址的请求头并不是调用方拥有它的证据,而在公网监听上,未经校验的转发头会让白名单沦为装饰。
接着检查另一个地址族。只覆盖 IPv4 的规则集,对一个同样在 AAAA 上应答的主机名来说是不完整的决定;有可用 IPv6 的客户端会沿一条你从未审阅过的路径到达同一个应用。
拆分隧道:根本进不来的请求
拆分隧道只承载它清单上的前缀。办公内网网段通常在清单上,公网站点不在。浏览器解析域名,把选中的地址与路由表比对,仍然发现本地默认网关挡在前面。
于是请求从用户家里的线路进来、被拒绝,报告说 VPN 坏了,而隧道其实完全按配置在做事。两种修法都正当:把该目的地导进隧道,让获批出口承载它;或者承认站点是直连的,给远程用户另一条获批路径,比如门禁。把站点的公网白名单扩到某个家庭地址碰巧被包含进来,两者都不是,而且它抹掉了设计本该守住的边界。前两种让矩阵保持诚实;第三种则悄悄删掉其中一行。
先证明拒绝,再证明恢复
站在表格每一行所代表的客户端上,用真正触发该决定的请求去测。对某个不相关的查地址页面做可达性检查证明不了什么,因为那个目的地可能走与应用不同的路径。
curl -sS -o /dev/null -w 'status=%{http_code}
' https://intranet.example.com/app/login
# repeat with the tunnel up, then with it down
tail -f /var/log/nginx/access.log | grep ' /app/'
把边界上的访问记录与表格的预判对照。然后把隧道降下来,发一条理应被拒绝的新请求;已经加载的页面或已经打开的连接什么都测不出来。如果没出现拒绝,执行点就不在你以为的位置。
在网络准入之后仍要保留应用认证,因为办公出口和 VPN 出口各自代表许多人,都不识别具体个人。给用户的说明保持简短:先连上获批隧道,再重试。这正是让一个 URL 保持便利、同时让来源规则仍是判断而非猜测的原因。