通过 Squid 放行一个 VPN 端点:一条收得很窄的 CONNECT 例外
只让一个获批源站经由 Squid 访问 1194 端口上的一个 VPN 网关,保留全局端口防线,并证明没有打开别的通路。
Squid 用第一条匹配的 http_access 规则回应 CONNECT 请求,而这条规则可以是拒绝。仅此一点就解释了为什么 VPN 端点的例外通常第一次就失败:请求根本走不到别人加在末尾的放行规则。收窄的改法保留既有端口防线,并让防线多认识一组经过复核的源、目的地址和端口。代理上的其余行为与从前完全一致。
Squid 如何判定一个 CONNECT 请求
CONNECT 请求给出主机和端口,Squid 按顺序逐条比对 http_access,直到有一条匹配。匹配到的拒绝会立即结束求值,客户端从代理拿到 403。这就是规则位置比措辞更重要的原因。把同一条规则上移一档,结果可能彻底改变。
常规防线拒绝 Safe_ports 之外的一切,也拒绝 SSL_ports 之外的 CONNECT。这些名字是你们自己配置里定义的 ACL,内容归你们而不归协议。某些发行版把范围很宽的端口列为 Safe_ports,却另外限制 CONNECT。先读实际取值,再假定某个端口是按设计封死的。
确认拒绝请求的组件就是代理。Squid 生成的 403 与 VPN 服务器的 TLS 告警完全不同,把两者混为一个问题会赔掉第一个小时。查看该请求的访问日志行,它同时记录了动作和产生该动作的规则。
用一条组合匹配取代三条松散 ACL
用三件事实搭出例外:一个获批源网段、一个获批目的地址,以及端点监听的唯一端口。两条松散 ACL 加一条组合匹配会得出截然不同的策略,因为被求值的是这个组合。组合 ACL 按它允许什么命名,而不是按它承载的方法命名。
下面的写法使用文档保留网段,必须替换为复核过的取值。Squid 并不保留这些名字,选什么、保持一致都是你们自己的事。这份配方只适用于 Squid 6 和 7,它们的文档化配置支持 all-of 组。当前参考文档把这些指令标为在 Squid 8 中不可用,因此请把它当作与版本绑定的配方,而不是迁移路径,并在套用前对照你自己版本的参考文档核实。
acl pvpn_src src 192.0.2.128/25
acl pvpn_dst dst 198.51.100.77/32
acl pvpn_port port 1194
acl pvpn_ok all-of CONNECT pvpn_port pvpn_dst pvpn_srcall-of 组只有在每个成员都匹配时才匹配,也就是源、目的和端口在同一行里一起检查。组本身不授予任何权限,只满足三件事实中的两件无法让它成立。这一性质使得日后有人增加第二个网关时,例外依然很窄。要么新目的被有意加入,要么请求回落到原有防线。
弯折端口防线而不删除它
在原有位置上,这两条常规防线看起来是这样。它们通常位于认证之后、允许一切的策略行之前。
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports给每一条加上取反的组合匹配,让匹配的例外得以穿过本来会拒绝它的防线。只有防线行改变,每条只多一个条件。
http_access deny CONNECT !SSL_ports !pvpn_ok
http_access deny !Safe_ports !pvpn_ok同一访问行上的条件是合并的,因此这一行现在只拒绝同时未通过原有检查和例外的请求。发往获批端点的请求通过,其余请求保持旧的端口行为。这 比把端口加进全局 SSL_ports 列表更窄,后者会把这个端口放给所有目的地址。如果端口已经在该列表里,这条 ACL 什么也没收紧,旧的更宽许可仍适用于其他目的地址,所以在声称例外打开了端口之前,请先读实际列表。它同样可回退,因为两行会恢复原样。访问控制参考记录了这一改动所依赖的顺序和常规防线。
例外不该放的位置
诱人的做法是在防线之上加一条放行,然后收工。那个位置同时跳过了通常靠近可用配置顶部的认证与本地目的地址保护。结果不是收窄的例外,而是上面挂着注释的一个洞。任何新放行都应与客户策略的其余部分处于同一个复核环节。
检查端口防线之后请求还需要什么。已认证的账号、被允许的客户端地址和目的地址策略都仍然生效,例外不改变其中任何一项。如果你的代理早已让该获批账号访问任意目的地址,那么端口防线从来不是真正的控制点。
考虑端点到底是不是 TCP。CONNECT 例外承载 TCP 流,使用 UDP 的 OpenVPN 配置经由它不会变得可达。只有当服务器确实这样监听、且运维方接受行为差异时,才把配置改为 TCP。
用解析器和一次真实请求验证边界
重载之前先解析配置,同时保持另一个会话开着。重载时的语法错误可能让代理留在旧配置上,或直接拒绝新配置。解析器会报出被拒的行,这比客户端错误告诉你的更多。
然后重载,并从获批源站经代理发一条真实的 CONNECT。先跑解析器,因为带着笔误重载学不到任何关于策略的东西。
squid -k parse
squid -k reconfigure
curl -v -p -x http://proxy.lab.internal:3128 https://vpn.lab.internal:1194/ --max-time 5测三种情况:获批源站与账号、未获批源站,以及同一端口上的另一个目的地址。第三种才能证明例外是窄的,因为它使用了端口却没有目的地址匹配。三种情况都去读访问日志,而不是相信客户端提示。成功的 CONNECT 只建立一条 TCP 流,仅此而已,VPN 握手仍是另一项测试。