用 Apache 2.4 把 Subversion 主机限制给 VPN 用户
ServerName 只选择虚拟主机,并不限制谁能连接。要求 VPN 网段和真实的仓库用户,然后证明两种拒绝都生效。
把 Subversion 虚拟主机指向 VPN 地址,改变的是哪个站点回应请求,而不是谁有权发送请求。访问检查在稍后进行,依据与 URL 匹配的那一段中的规则,主机名从不参与该决定。如果 ServerName 是你唯一的改动,仓库的可达性与昨天完全一样。配置中必须回答两个独立的问题:哪个套接字接受了连接,以及请求携带了哪个身份。
ServerName 究竟决定了什么
基于名称的虚拟主机把 Host 请求头与 ServerName 和 ServerAlias 条目比对,选出一个主机,让它处理请求。连接本身此前已由属于服务器而非主机的 Listen 套接字接受。这也是看似私有的名称并不提供保护的原因:任何能到达该套接字的客户端都能发送那个 Host 头。名称只是路由键,而路由键由调用方选择。
无需猜测就能看到映射。apachectl -S 会打印每个主机、它的端口和名称,并标出对该地址而言哪个是默认主机。
RequireAll 是让两项检查都成为必需的容器
Apache 2.4 把单个段落的 Require 指令包进一个隐式的 RequireAny,因此两条裸指令意味着任意一条即足够。加上容器,含义就反过来了:
<Location /svn/repo>
AuthType Basic
AuthName "Subversion"
AuthBasicProvider file
AuthUserFile /etc/apache2/svn.htpasswd
<RequireAll>
Require ip 10.8.0.0/24
Require valid-user
</RequireAll>
</Location>RequireAll 要求两个条件同时成立:源地址位于隧道网段之内,以及仓库接受该用户。对 Subversion URL 而言,仅限 VPN 应当就是这个意思。mod_authz_core 参考文档 记录了容器规则。把该块保留在仓库原有的 Location 内,并让已经生效的 DAV 指令留在原位,因为这段片段既不替换 AuthType,也不替换密码文件,也不替换路径级授权。
源地址检查来自 mod_authz_host,所以在调试其他任何东西之前,先确认它已加载:apachectl -M | grep authz_host。
Apache 评估的是套接字地址
Require ip 比对的是 Apache 为连接记录的地址,而这并不总是客户端自以为拥有的地址。
当 VPN 不做地址转换直接路由时,Apache 看到的是隧道地址,10.8.0.0/24 便会匹配。当网关做转换时,Apache 看到的是网关地址,规则会静默地匹配不到任何东西;在主机前面终止 TLS 的负载均衡器之后也是如此。这种情况正是 mod_remoteip 存在的理由。RemoteIPHeader X-Forwarded-For 与面向均衡器的 RemoteIPTrustedProxy 一起替换记录的地址,只有这时网段才有可比较的对象。
信任任意的转发头比完全没有规则更糟。任何客户端都能发送含 10.8.0.9 的 X-Forwarded-For,从而满足一项从未锚定在可信跳点上的网段检查。
把监听器与仓库策略分层
绑定是独立的控制手段,往往也更简单。Listen 10.8.0.1:443 决定存在哪些套接字,而请求无法在从未打开的套接字上选择主机。代价是启动顺序:如果 Apache 启动时隧道接口处于关闭状态,绑定就会失败,服务起不来。把它与单元顺序配对,或绑定到一个独立于隧道而存在的地址。
路径权限仍留在 AuthzSVNAccessFile 中。站点的有效账号并不等于对项目路径的写权限,网络规则也不会改变这一点。
当该块位于被包含的文件中时,要留意段落的合并。Apache 会跨嵌套段落合并授权容器,最具体的匹配段落可能替换要求而不是追加,因此在更窄的段落定义自己的规则之后,/svn 上的规则可能到不了 /svn/repo/trunk。
在宣布完成之前证明两种拒绝
要测试四个请求,而不是一个。在 VPN 内用有效账号应当拿到仓库列表。在 VPN 内用错误密码应当拿到 401 而不是列表。从 VPN 外用有效凭据应当拿到 403,而对另一个主机名发出的同一公开请求也必须以同样方式失败。
每种情况一条命令就够了:curl -sI -u user:pass https://svn.example.com/svn/repo/,然后读取状态行。也请用真实客户端重复可用的那种情况,因为 svn info 会实际用到提交和更新将要使用的 DAV 方法。
如果四个结果中任何一个与你预期的策略不符,配置就没有完成,而重命名主机不会改变这一点。