网关自建管理页:走内网地址还是公网地址
网关自身的服务需要一个隧道能够到达的内网入口。以私有方式发布它,然后确认公网监听没有继续开着。
管理页在手机移动数据下能打开,在已接入 VPN 的笔记本上却超时。隧道本身没有坏,因为请求根本没有进入隧道。该主机名解析到网关的公网地址,而这个地址只能从它所终结的隧道之外到达。解决办法是给服务一个内网入口,而不是去搬动外层端点。
页面从外部加载,而不是经隧道加载
这里比较的是两个不同的请求。走移动数据时,浏览器直接到达公网地址。在隧道内部,同一个名字仍解析到同一个公网地址,于是数据包必须离开隧道,才能到达隧道自己远端一侧的地址。同样的名字、同样的地址,位置却完全不同。
目标路由选的是地址,不是端口。路由表并不知道 443 端口用于管理、1194 端口用于传输,因此两者被当作同一个目标。
这就是页面时好时坏的原因:取决于客户端用了哪个名字、哪张网络。这是路径问题,不是认证问题。两个客户端的认证都没有问题。
保持外层端点可达
全隧道配置需要为 VPN 端点保留一条例外路由,OpenVPN 的 redirect-gateway 选项正是这么写的:远端端点保留一条经既有网关的路由,这样隧道就不会路由到自身。去掉这条例外,加密传输就会跟随隧道,会话便在环路中结束。端点地址能从隧道内部到达,原因正是这条例外。
结果是公网地址从客户端依旧可达,这也解释了为什么发往该地址的浏览器请求会沿同一条例外出去,而没有进入隧道。例外在履行它的职责,造成损害的是名字。
动手之前先读路由判定,而不是接口清单。下面的命令打印的是判定,而不是清单。请在出问题的客户端上执行。
ip -4 route get 203.0.113.77
ip -4 rule show
ip -4 route show table all如果查询显示端点路由走的是物理网关,那行为就是正确的,管理页只是被送上了错误的路径。名字解析正是在这一步成为修复手段。
给服务一个私有名字
服务需要一个在隧道内解析到网关地址的名字,同时保留应用所期望的证书与主机名语义。为管理名添加一条私有区域记录是常规做法,传输用的名字则原样不动。
不要把传输名重新指向内网地址,因为下一次建立隧道时就会去访问一个只存在于隧道内部的地址。这个错误造出的 VPN 只能成功一次。
两类地址族都要检查,因为残留的 AAAA 记录会把浏览器送到私有方案从未覆盖的地方。
dig +short admin.hq.internal
dig +short -t AAAA admin.hq.internal
curl -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' https://admin.hq.internal/让策略与进程实际运行的位置相匹配
在网关上监听的本地进程走的是本地输入路径,因此输入链和套接字绑定决定请求是否被接受。转发规则和 IP 转发不会让本地监听应答。在输入策略中放行隧道来源,并确认进程绑定在隧道能够到达的地址上。
发布在主机端口上的容器走另一条路径,因为数据包会做目的地址转换再被转发。这类请求会经过主机的转发策略和 Docker 自身的规则,所以能工作的本地监听可以与拒绝同一端口的容器并存。
两种情况都要注意启动顺序,因为绑定一个只在隧道起来后才出现的地址,会在开机时静默失败。
检查私有访问与被拒绝的公网路径
在保留应用自身身份校验的前提下,从授权客户端发起一次新的具名请求。然后从隧道外部访问同一服务,确认它不应答。私有名字只是便利,不是限制;监听仍绑定在公网地址上,就仍然暴露。最后这项检查恰恰是大多数评审会跳过的。
读绑定和过滤状态,而不是用户拿到的那个主机名。暴露是套接字的属性,不是名字的属性。这个区别在任何一次 DNS 变更后都成立。
ss -lntp | grep ':443'
nft list ruleset | grep -n 443
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://203.0.113.77/当内网请求返回页面、外部请求超时,服务就有了一条刻意保留的私有路径,传输也保持可达。当外部请求仍然应答,就必须收窄监听或已发布的容器条目,之后才能把这项服务称作仅限内部。