我有一个多用户设置 (Ubuntu)(在不同的 TTY 上同时登录用户)。
当我使用一个帐户连接到 NordVPN 时nordvpn connect
,所有用户现在都通过该 VPN 连接到互联网。
如何以某种方式分离用户的网络,这意味着当我连接到 VPN 时,只有当前用户受到影响,并且该用户的所有连接都应该使用 VPN ?
nordvpn
只是一个包装器openvpn
,可以使用 直接连接openvpn
,因此纯openvpn
解决方案也会有所帮助。- 用户通过 .root 访问权限
sudo
。 - 我对脚本解决方案很满意。
答案1
如何为不同用户单独使用VPN:
用户使用不同用户空间的情况:
默认情况下,用户空间将网络分开(网络),因此nordvpn connect
不会影响其他用户空间,但这不是linux下用户系统的默认功能,您需要设置不同的用户空间以隔离每个用户的网络;此外,网络接口只能存在于单个名称空间桥或 veth 接口上,然后用于在用户空间之间传输流量。
用户使用同一用户空间的情况:
Linux 用户系统保持在同一网络系统下,如果一个用户连接到 wifi,另一用户将从该连接中受益,因为网卡是在根级别设置的,因此为使用默认设置的网络的每个人共享。
VPN 连接是通过新的虚拟接口(tun 或 tap)完成的,并链接到主网络接口(wifi 或 eth0)...当 VPN 连接初始化时,会创建 tun/tap 接口,然后连接到 VPN 服务器并创建隧道,但这并不意味着所有连接都通过 VPN 接口进行隧道传输,为了拥有经典的工作 VPN 连接,首先使用虚拟接口初始化连接,然后使用路线添加以强制所有连接通过 VPN 接口,这称为路由。
了解这些信息后,解决方案将无需启动 VPN 连接即可启动路由然后为每个用户单独设置路由。不需要VPN的用户无需更改;对于需要使用VPN的用户,需要通过iptables/ip-route添加一个特殊的路由。换句话说,VPN 接口将被设置,但它不会是默认接口(因为默认 VPN 路由规则不会被推送)
VPN默认情况:[Connect Command] > [Create-Tun/Tap] > [Connect Tun/Tap] > [将 Tun/Tap 设置为默认接口的路由]
无路由VPN:[连接命令] > [Create-Tun/Tap] > [连接 Tun/Tap]
具有自定义路由的 VPN:[连接命令] > [Create-Tun/Tap] > [连接 Tun/Tap]然后手动或自动添加【自定义路由】
不使用“路由”步骤连接到您的 VPN,然后推送/设置自定义路由,这可以使用 iptables/ip-route 或 VPN conf 设置文件来完成。
如何在不推送默认网关/路由的情况下配置 openvpn:
编辑您的 VPN 配置文件并添加route-nopull
指令。 (如果nordvpn
使用命令可访问 openvpn conf 文件,您可以根据需要编辑它们,否则您将需要使用 openvpn 或网络管理器来连接到您的 VPN)
为特定的 Linux 用户使用特定的接口:
答案2
部分答案(需要更多详细信息):
区分“正常”互联网连接和 NordVPN 互联网连接的一种方法是创建一个网络命名空间,nordvpn
在此命名空间中启动,然后启动应在此命名空间中使用此 VPN 的所有进程。
详细信息取决于您想要如何使用它:
如果您有一个始终启动 NordVPN 的用户,您可以在登录时创建该网络命名空间,并在该网络命名空间中为该用户启动所有进程。因此,该用户永远不会有“正常”的互联网连接。
如果您有多个用户都想要使用“正常”互联网连接和 NordVPN 连接,您可以编写一个脚本来创建该命名空间,在其中启动 NordVPN,并为用户提供一个可以启动其他应用程序的终端,或者甚至可能已经在这个命名空间中启动了像网络浏览器这样的应用程序。该脚本将替换该
connect
命令。
可能还有更多方法可以做到这一点,具体取决于您的要求。因此,请编辑问题并描述您的要求/情况。
您需要 root 权限才能创建命名空间。这意味着您需要将脚本设置为 setuid-root(如果脚本有错误,这可能是一个安全问题),您可能需要授予用户sudo
访问权限等。
答案3
我认为这个问题可以通过多种方式解决,具体取决于整体预先存在的设置、更改提供商自己的配置的可能性、要支持的实际 VPN 类型以及是否愿意进行或多或少复杂的设置。
我还想说,对于特定于 NordVPN 服务的解决方案,您也可以尝试请求他们支持,因为该nordvpn
命令不一定使用 OpenVPN,它可能更愿意使用 IPSec,甚至 PPTP/L2TP。
然而,对于您所说的与您的 NordVPN 服务完全兼容的通用 OpenVPN 解决方案,我认为最好的方法可能是使用策略路由,可能有一些变体。
但首先,看到您对名称空间的兴趣,我想谈谈我对使用这些名称空间的可能方法的看法:
当然,通过使用命名空间,人们将获得用户之间网络的真正隔离,其主要且显着的优势是成为任何类型 VPN 的通用解决方案,而不仅仅是 OpenVPN。
但是许多 Linux 发行版通常不会安装设置所需的工具,因为所讨论的问题似乎不能简单地通过使用unshare
或ip netns exec
命令来解决,因为这些命令仅允许这些进程看到新的命名空间从那时起,在该命令会话下运行,而不是在用户的整个预先存在的会话中运行。
此外,即使通过安装所需的工具来按照应有的方式进行设置,系统也需要仔细设置,以便将每个用户分离到自己的网络命名空间中,同时使用相同的物理网络设备并共享 UNIX-为本地主机运行的域套接字和服务。设置网桥(或 macvlan)、子网、分配地址、可能还有 NAT 以及其他复杂操作。这可能是可行的,但可能并不简单,而且我认为比原来的问题复杂得多。
因此,回到路由策略方法,据我所知,基本解决方案,可能是最简单的可行解决方案,可能足以满足大多数常见用例,或者作为冒险进入更复杂的变体之前的起点,将使用单独的根据用户的 UID 通过路由策略查找每个用户隧道的路由表。
它将具有以下主要特征:
- 需要(相当)标准版本的 OpenVPN(即提供商未进行大量定制)
- 需要一个
iptables
伴随规则来根据用户的UID号标记相关流量 - 如果与预先存在的防火墙或路由规则一起使用,可能需要仔细集成
- 但是,无法识别用户通过
sudo
(因为这些以 UID 0 运行)或 setuid 命令运行的进程或以 root 身份运行的应用程序。这些情况大多不是典型桌面场景的情况,但很可能会发生并且不会按预期处理(即该部分流量不会进入 VPN)
一切都可以通过两个帮助程序脚本来处理。一个要使用的经过OpenVPN,其他要用的跑步 openvpn
本身。
标准 OpenVPN 使用该ip
命令在 tun/tap 设备上进行设置并安装 VPN 提供商推送的路由。问题是,默认情况下,该ip
命令在主路由表上运行,该路由表由整个系统使用,因此包括其所有用户。
然而,OpenVPN 允许指定不同的命令用于它需要应用的设置,因此我们可以提供一个ip
简单地在不同路由表上操作的包装器。
这样的(暂定名为myip.sh
)包装器脚本可能类似于:
#!/bin/bash -
my_session="$(ps -p $$ -o sid --no-header)" || exit 1
real_uid="$(ps -p $$ -o ouid --no-header || ps -p $my_session -o ruid --no-header || echo 0)"
real_ip_cmd=/sbin/ip
if [ $real_uid -ne 0 ] && [[ "$1" = ro* ]] ; then
! [ $2 ] && exec $real_ip_cmd "$1" list table $real_uid
[[ "$2" =~ ^([adcfls]|rep).*$ ]] && exec $real_ip_cmd "$1" "$2" table $real_uid "${@:3}"
fi
exec $real_ip_cmd "$@"
前两行用于尝试检索运行 OpenVPN 的真实 UID。我们使用该 UID 作为表 id。这样,每个用户在启动 VPN 时都会有自己的路由表。
该if-then-fi
块捕获对route
设置的调用(模仿ip
命令接受缩短但明确的关键字的能力),并简单地在 openvpn 传递的所有此类命令前面加上一个选项,该table
选项携带的 id 等于用户的真实 UID。所有其他ip
命令(即非命令route
)均不受影响地通过。
另一个脚本包装该openvpn
命令,同时进行设置以使用户生成的流量根据其不同的表进行路由。该包装器脚本的要点可以包含在提供商自己的配置文件中,从而实现更好的无缝集成。如果您想以非交互方式建立 VPN(例如在启动时),这将有所帮助。然而,通过使用这个包装脚本,就不需要摆弄配置文件,这就是我选择展示这个版本的原因。
因此,这样的单个包装器脚本可能如下所示:
#!/bin/bash
my_session="$(ps -p $$ -o sid --no-header)" || exit 1
real_uid="$(ps -p $$ -o ouid --no-header || ps -p $my_session -o ruid --no-header || echo 0)"
[ $real_uid -eq 0 ] && { echo "will not run for UID 0" >&2 ; exit 1; }
remove_tagging() {
iptables -t mangle -D OUTPUT -m owner --uid $real_uid -j MARK --set-mark $real_uid
ip rule del fwmark $real_uid
ip route flush table $real_uid
} 2>/dev/null
trap 'remove_tagging' EXIT
source <(ip route | sed "s/^/ip route add table ${real_uid} /")
(
ip -o monitor | grep -qm 1 '^[0-9]\+: tun[0-9]\+[[:blank:]]\+inet '
ip rule add fwmark $real_uid lookup $real_uid
iptables -t mangle -A OUTPUT -m owner --uid $real_uid -j MARK --set-mark $real_uid
) &
openvpn --iproute myip.sh --config tunnel-config.ovpn
前三行再次尝试检索用户的真实 UID,如果无法获取,则不会继续。
然后我们有一个在脚本退出时运行的函数。该函数删除以下子 shell 进行的设置。
但首先我们将整个当前主路由表复制到用户的不同表中。
然后,我们运行子 shell,等待tunX
OpenVPN 设置设备,然后添加路由规则和iptables
伴随规则,标记用户从那时起生成的流量。我们在后台运行它,因为我们需要在前台运行 OpenVPN,以防它需要询问用户名和密码。
命令source
、子 shell(不带管道ip -o monitor
)和函数是可以作为(分别)和选项remove_tagging
放入 vpn 配置文件中的部分。这样做可以完全摆脱这个包装脚本。up
route-up
route-pre-down
最后我们运行实际的openvpn
命令,告诉它使用我们自己的myip.sh
脚本作为命令来设置网络。
一个可能的轻微变体可能是使用更新版本的 iproute2 软件包(它提供命令ip
),这些软件包可以选择在路由规则中uidrange
使用而不是使用。fwmark
因此,这种变体将消除iptables
命令,并ip rule fwmark ..
用ip rule uidrange ${real_uid}-${real_uid} lookup $real_uid
命令替换命令(其各自的命令也是如此ip rule del ..
)。
支持某些命令运行的另一种变体sudo
是基于用户组 (GID) 而不是 UID 来制定策略路由。这将有额外的基本要求:
- 用户存在的唯一 GID,这在许多 Linux 发行版(包括 Ubuntu)上很常见
sudo
使用额外选项(-g <user> -u root
或 中的等效配置sudoers
)运行以保留原始 GID 号
但是,它仍然无法识别用户在此类之外运行的 UID 0 进程sudo
。
要真正解决“UID 0 进程”问题,可以使用 cgroup,但这自然还有其他要求:
iptables
使用 iptables 自己的特定“cgroup”模块标记相关流量的配套规则,该模块在 Ubuntu 14.04 等系统上尚不可用- 可能与已经存在的现有 cgroup 设置冲突
使用 cgroups,设置 net-class-id 可以通过一个简单的脚本来很好地处理pam_exec.so
#!/bin/bash -e
uid=$(id -ru "${PAM_USER}")
mkdir -p /sys/fs/cgroup/net_cls/user/${uid} && echo $uid > /sys/fs/cgroup/net_cls/user/${uid}/net_cls.classid
echo $$ > /sys/fs/cgroup/net_cls/user/${uid}/cgroup.procs
然后将以下行添加到所需的配置文件中/etc/pam.d
:
session optional pam_exec.so /root/set-net-cls-id.sh
上面的行可能会放入传统/etc/pam.d/common-session
文件中,以涵盖通过任何交互式服务(例如ssh
控制台登录、桌面环境等)发起的所有会话。
通过此设置,iptables
包装器脚本中使用的命令将是:
iptables -t mangle -A OUTPUT -m cgroup --cgroup $real_uid -j MARK --set-mark $real_uid
及其各自的删除命令用-D
代替-A
。
cgroup 解决方案的一个可能的变体可能是将其集成到systemd
,以便人们可以使用systemd
直接处理 cgroup 的能力,而无需使用pam_exec
.
笔记但是,此 cgroup 设置无法在 Ubuntu 14.04 上正常工作,因为该版本用于为每个用户会话设置 cgroup,因此会覆盖此解决方案所设置的设置。您宁愿需要设置为每个会话动态创建的 cgroup 的 classid 值,其名称可以通过解析cat /proc/self/cgroup
输出轻松获得。