Linux 容器/使用 VPN

来自 ArchWiki

本文介绍如何设置 Linux 容器以运行多种 VPN 协议,并使用“kill switch”(终止开关)来确保安全/私密的互联网使用。 这样做相比于使用 VirtualBoxQEMU 等全功能虚拟化,具有明显的优势,因为资源开销相对较小,并且能够在低功耗设备上运行。

容器设置

需要 Linux 容器的基本设置和理解。 本文假设读者已具备基本的 LXC 设置并使其正常运行。

服务器模式下的 OpenVPN

本小节详细介绍了在容器中提供 OpenVPN 服务所需的一些额外设置。 希望使用提供的 OpenVPN 配置文件的用户无需阅读本小节。

主机设置

  1. 主机操作系统需要桥接以太网设置,以允许容器运行。 请参阅 Linux Containers#主机网络配置 获取相关信息。
  2. 需要启用数据包转发。 请参阅 Internet sharing#启用数据包转发 获取相关信息。
  3. 虽然不是严格必需,但强烈建议使用防火墙。

客户端模式下的 OpenVPN

需要修改容器的配置以使用 OpenVPN,如下所示:

/var/lib/lxc/playtime/config
...

## for OpenVPN
lxc.mount.entry = /dev/net dev/net none bind,create=dir
lxc.cgroup2.devices.allow = c 10:200 rwm

安装 openvpn。 如果使用容器连接到第三方 VPN 提供商,只需将配置文件 foo.conf 放置在 /etc/openvpn/client/foo.conf 中即可使用。 要验证容器内的 OpenVPN 功能,通过 openvpn-client@foo.service 启动 OpenVPN,并在满意后启用它以在启动时运行。

对于其他用例和设置,请参阅 OpenVPN

注意: 在非特权容器中运行 OpenVPN 的用户将需要创建一个自定义的 systemd 单元以在容器内启动它。 在替换单元文件中,注释掉以 LimitNPROC... 开头的行。

WireGuard

安装 wireguard-tools。 用户要么拥有第三方 VPN 服务提供的 WireGuard 配置文件,要么将设置 WireGuard 以在此角色中提供服务。 如果使用容器连接到 VPN 提供商,只需将配置文件 foo.conf 放置在 /etc/wireguard/ 中即可使用。

要验证容器内的 WireGuard 功能,通过 wg-quick@foo.service 启动 WireGuard,并在满意后启用它以在启动时运行。

对于其他用例,请参阅 WireGuard

容器内的防火墙配置

强烈建议在容器内运行配置正确的防火墙。 容器内防火墙的作用有两个:

  1. 提供一个功能性的“kill switch”(终止开关),以在 VPN 连接失败时维护隐私。
  2. 阻止恶意内容进入。

本指南使用易于配置的 ufw,但当然也可以使用其他示例。

提示: 要完全重置 ufw 的配置文件,请使用 reset 参数调用它:ufw reset

功能性“kill switch”(终止开关)的策略很简单,就是设置拒绝策略,然后仅允许 VPN 设备上的特定服务和流量。 这样,如果该设备的连接中断,则不会有本地回退。

注意: 下面显示的方法的一个限制是,VPN 配置文件不能使用域名,例如 www.myvpn.com,它们需要使用相应的 IP 地址。 如上所述,当 VPN 未连接时,容器 DNS 解析将按设计禁用。 因此,为了连接,必须提供数字 IP 地址。

编辑 /etc/default/ufw 并将 DEFAULT_OUTPUT_POLICY 从 “ACCEPT” 更改为 “DROP”。

/etc/default/ufw
DEFAULT_OUTPUT_POLICY="DROP"
注意: 以下调用 ufw 的命令需要以 root 用户身份执行; 为了方便干净地复制/粘贴到终端中,已省略了按照标准 wiki 符号在这些命令前添加的 “#” 符号。

设置拒绝策略

ufw default deny outgoing
ufw default deny incoming

可选地添加在文件中定义的任何预定义或自定义规则,例如 /etc/ufw/applications.d/custom

ufw allow ssh
ufw allow from my-custom-app1
ufw allow from my-custom-app2

可选地进一步限制来自内部 LAN IP 范围甚至单个 IP 地址的访问。

ufw allow from 192.168.1.0/24

WireGuard 用户将创建一个接口,该接口的名称与相应的配置文件相同,例如 /etc/wireguard/foo.conf,而 OpenVPN 用户可能正在使用 tun0。 在下面的行中,将 ‘foo’ 替换为 WireGuard 配置的名称(省略 .conf 后缀),或者如果使用 OpenVPN,则将 ‘foo’ 替换为 tun0 或正在使用的任何设备。

ufw allow out on foo from any to any

最后,允许访问 VPN 提供商的 IP 地址,使用预期的端口并定义预期的协议。 在下面的行中,有三个变量需要考虑,定义如下:

  • ‘xxx’ 代表 WireGuard 对等方/OpenVPN 服务器的 IP 地址。 它将在 VPN 提供商提供的相应配置文件中定义。
  • ‘yyy’ 代表通信将要发生的端口。 同样,这将位于配置文件中。
  • ‘zzz’ 代表要使用的协议,可以从 udp 或 tcp 中选择。 请注意,WireGuard 仅支持 udp,而 OpenVPN 支持两者。
ufw allow out to xxx port yyy proto zzz
注意: 如果预期使用多个服务器,请为 VPN 提供商定义的每个 IP 地址 (xxx) 重复此操作。

启动 ufw 并启用 ufw.service 以在启动时运行。

使用 VPN 域名在配置文件中的一种不太优雅的解决方法

如果希望在 VPN 配置文件中使用域名,主机上的 shell 脚本可以预先将其解析为数字 IP,然后通过将其存储在写入容器内文件的变量中,将该 IP 地址传递给容器。 然后,修改后的 VPN systemd 服务可以读取该文件。 它可以工作,但有点不太优雅。

编辑这两个变量以匹配您的用例的容器名称和服务器名称。

在主机上

安装 bind(dig 所需)并创建以下脚本:

/path/to/container-start.sh
#!/bin/bash
# this script should be called as root
container=foo
server=www.myvpnserver.org
 
if ! systemctl is-active lxc@"$container" &>/dev/null; then
  ToUse=$(dig +short "$server")
  [[ -d /var/lib/lxc/$container/rootfs/etc/conf.d ]] || mkdir -p /var/lib/lxc/$container/rootfs/etc/conf.d
  echo "SERVER=$ToUse" > /var/lib/lxc/$container/rootfs/etc/conf.d/server.hack.txt
  systemctl start lxc@"$container"
fi

从现在开始,调用该脚本以启动容器。 它将使用 dig 从域名获取 IP 地址,然后启动容器。

从容器内部

修改启动 VPN 的 systemd 服务,并创建一个骨架配置文件,该文件可以使用我们刚刚创建的脚本在 /var/lib/lxc/$container/rootfs/etc/conf.d/server.hack.txt 中定义的 IP 地址进行修改。

要创建骨架配置文件,只需将实际使用的配置文件重命名为另一个名称。

例如,使用 WireGuard

mv /etc/wireguard/foo.conf /etc/wireguard/foo.skel

现在编辑 /etc/wireguard/foo.skel 以将 Endpoint = www.myvpnserver.org 替换为 @@@,例如。

Endpoint = @@@:51820

或者,如果使用 OpenVPN

mv /etc/openvpn/client/foo.conf /etc/openvpn/client/foo.skel

编辑 /etc/openvpn/client/foo.skel 以将 remote www.myvpnserver.org 替换为 @@@,例如。

remote @@@

最后,创建一个 drop-in 文件,以读取 IP 并将其替换为实际的配置文件。

使用 WireGuard 的示例

/etc/systemd/system/wg-quick@foo.service.d/override.conf
[Service]
EnvironmentFile=-/etc/conf.d/server.hack.txt
ExecStartPre=/bin/bash -ac "sed s/@@@/$SERVER/ </etc/wireguard/foo.skel >/etc/wireguard/foo.conf"

使用 OpenVPN 的示例

/etc/systemd/system/openvpn-client@foo.service.d/override.conf
[Service]
EnvironmentFile=-/etc/conf.d/server.hack.txt
ExecStartPre=/bin/bash -ac "sed s/@@@/$SERVER/ </etc/openvpn/client/foo.skel >/etc/openvpn/client/foo.conf"

测试服务

从正在运行的容器内(通过 ssh 或通过 lxc-attach -n playtime 连接),通过将浏览器导出到主机机器的 X server 来测试设置。

$ DISPLAY=:0 firefox
提示: 通过 ssh 连接将需要允许本地显示器接受连接。 通过 xhost +SI:localuser:yourusername 执行此操作,然后通过 ssh 连接到容器。

结果应该是在主机 X server 中显示一个标题为 “Mozilla Firefox (playtime)” 的 firefox 窗口。 许多网站可用于验证 IP 地址和 DNS 条目的状态。 其中一个网站是 ipleak dot net

此时,应仅显示与配置文件中定义的那些相对应的 DNS 条目。