请求到达时目标地址是隧道 IP,离开同一个容器时目标已是相邻服务的地址。在这两个瞬间之间,DNAT 改写了目的地址,数据包不再是 VPN 容器的输入流量。它现在是转发流量,由另一条规则判定。这一转变解释了为什么对隧道地址看似正确的防火墙规则,对翻译后的连接毫无作用。

跟踪转换之后的报文

取客户端地址池 10.90.0.0/24 和隧道地址 10.90.0.1。两个容器位于同一个网桥上:192.0.2.2 运行 VPN,192.0.2.3 运行应用。客户端连接 10.90.0.1:8080,VPN 容器命名空间中的 DNAT 规则会把它改写为应用的真实地址和端口。随后一条路由把数据包送过网桥。这些地址仅作示例,不是部署配方。

应用实际监听的端口可能与宿主机上发布的任何端口都不同。请读取容器内部的监听者,而不要假定两个数字相同。

许可属于转发路径

VPN 容器需要开启 IP 转发,并需要一条限定在客户端池、应用地址和目标端口上的转发决策。已建立的回复需要各自的放行规则。匹配原始隧道地址的规则在 DNAT 之后不会匹配,因为目的地址已经改变。对于不再指向本主机的流量,INPUT 放行是错误的钩子。

把范围收窄:一个源地址段、一个目的地址、一个服务。宽松的转发规则写起来容易,日后却难以为它辩护。

docker exec vpn-container ip route get 192.0.2.3
docker exec vpn-container iptables -t nat -S
docker exec vpn-container iptables -S FORWARD

Docker 的链不是你的容器的链

容器命名空间内的规则不是 Docker 宿主机的规则。当流量从隧道接口跨到网桥时,宿主机的网桥防火墙可能做出另一个决定。在 Docker 的 iptables 后端下,文档所述的转发链和 DNAT 之后的匹配点正是该决定出现的地方。

Docker 防火墙参考 描述了这些链以及 DNAT 之后的匹配。不要通过关闭 Docker 的防火墙管理或让每条链都宽松来修补缺失的放行,因为那会拆掉你正想使用的边界。

把回程路径变成有意的选择

如果保留源地址,应用看到的是 10.90.0.x,就必须让回复经 VPN 容器返回。否则其常规 Docker 网关会把回复发给宿主机,而宿主机并不拥有该隧道。另一种做法是在 VPN 容器上做范围很窄的源地址转换,这简化了回复,却让应用看不到客户端。请有意识地选择,并写下你选了哪一种。

从普通客户端测试一条新的被允许的连接,然后确认无关端口仍然关闭。当数据包消失时,检查 VPN 命名空间、应用命名空间以及应用自身的日志。对网桥网关做 ping 并不是服务的验收测试。

把规则放在容器支持的启动配置里,因为交互式修改会在容器重建时消失。宿主机端口发布是另一条暴露路径,并不因为服务能经隧道访问就必须启用。

相关文章