SSH VPN 进阶:基于用户策略精细化 sshuttle 网络访问
嘿,各位技术爱好者们!今天我们来聊一个大家在使用 sshuttle 搭建 SSH VPN 时可能会遇到的进阶需求:如何为不同的登录用户设置不同的网络访问权限?这在需要精细化管理用户访问范围的场景下非常有用,比如企业内部根据员工角色限制其可访问的内网资源。
在 Ubuntu 24.04 环境下,虽然 sshuttle 本身并没有直接提供基于用户的网络策略配置选项,但我们可以巧妙地结合 Linux 系统的防火墙工具(如 iptables 或 nftables)以及一些脚本来实现这一目标。
核心思路
我们的核心思路是:当用户通过 SSH 连接到服务器后,根据该用户的身份,动态地应用预设的防火墙规则,从而限制其通过 sshuttle 转发的网络流量所能访问的目标。
听起来有点复杂?别担心,这里会一步步了解如何实现。
实现步骤
1. 准备工作:用户和用户组规划
首先,我们需要规划好我们的用户和他们对应的网络访问策略。一个好的实践是使用用户组来管理权限。例如,我们可以创建不同的用户组,每个用户组对应一套网络访问规则。
# 创建用户组
sudo groupadd group_allow_web_only
sudo groupadd group_allow_all_internal
# 创建用户并添加到指定用户组
sudo useradd -m -s /bin/bash user_web_only
sudo usermod -aG group_allow_web_only user_web_only
sudo useradd -m -s /bin/bash user_internal_access
sudo usermod -aG group_allow_all_internal user_internal_access
2. 配置防火墙规则(以 iptables 为例)
接下来,我们需要为不同的用户组定义相应的 iptables 规则。这里我们假设 sshuttle 会将流量转发到 tun0 这样的虚拟网卡上(具体网卡名称可能因 sshuttle 版本和配置而异,请自行确认)。
我们的策略是:默认情况下,禁止所有来自 sshuttle 的转发流量,然后根据用户组,有选择地放行。
重要提示: 在操作 iptables 规则之前,请务必小心,错误的配置可能导致我们失去服务器的访问权限。建议在测试环境中进行,或者确保我们有其他方式访问服务器(如物理控制台)。
# 首先,清空现有的 FORWARD 链规则 (谨慎操作!)
# sudo iptables -F FORWARD
# 设置默认的 FORWARD 策略为 DROP (如果我们的服务器还有其他转发需求,请谨慎评估)
# sudo iptables -P FORWARD DROP
# 创建自定义链来管理不同用户组的规则
sudo iptables -N SSHUTTLE_WEB_ONLY
sudo iptables -N SSHUTTLE_INTERNAL_ALL
# 针对 SSHUTTLE_WEB_ONLY 组的规则 (示例:只允许访问特定网站的 80 和 443 端口)
# 注意:这里的目标 IP 需要根据我们的实际情况修改
sudo iptables -A SSHUTTLE_WEB_ONLY -o <your_primary_network_interface> -d www.example.com -p tcp --dport 80 -j ACCEPT
sudo iptables -A SSHUTTLE_WEB_ONLY -o <your_primary_network_interface> -d www.example.com -p tcp --dport 443 -j ACCEPT
# 其他流量丢弃 (或者我们可以设置一个更通用的 REJECT 或 DROP 规则在链的末尾)
sudo iptables -A SSHUTTLE_WEB_ONLY -j DROP
# 针对 SSHUTTLE_INTERNAL_ALL 组的规则 (示例:允许访问所有内网 IP 段)
# 注意:这里的内网 IP 段需要根据我们的实际情况修改
sudo iptables -A SSHUTTLE_INTERNAL_ALL -o <your_primary_network_interface> -d 192.168.1.0/24 -j ACCEPT
sudo iptables -A SSHUTTLE_INTERNAL_ALL -o <your_primary_network_interface> -d 10.0.0.0/8 -j ACCEPT
# 其他流量丢弃
sudo iptables -A SSHUTTLE_INTERNAL_ALL -j DROP
# 在 FORWARD 链中,我们将根据用户动态跳转到相应的自定义链
# 初始情况下,可以先不添加具体的跳转规则,由后续脚本动态添加和删除
<your_primary_network_interface> 通常是我们的服务器连接外网的网卡,例如 eth0 或 ens33。
使用 nftables: 如果我们的 Ubuntu 24.04 默认使用 nftables,相应的配置思路类似,只是语法不同。我们需要创建 nftables 的 table、chain 和 rule。
3. 编写 SSH 登录和退出时的钩子脚本
这是实现动态策略的核心。我们需要在用户 SSH 登录成功后,根据其用户组,应用相应的 iptables 规则;在用户退出 SSH 时,清除这些规则。
我们可以利用 SSH 的 ForceCommand 配置或者 PAM (Pluggable Authentication Modules) 来实现。这里我们以一个更简单直观的方式,通过修改用户的 shell 配置文件(如 .bashrc 或 .profile)或者使用 SSH 的 pam_exec.so 模块来在登录和退出时执行脚本。
更健壮的方法是使用 SSH 的 ForceCommand 结合一个包装脚本。这个包装脚本会首先执行我们的权限设置脚本,然后再启动用户请求的 shell 或命令。
一个更通用的方法是利用 pam_script 模块(如果系统支持)或者在 /etc/ssh/sshrc(如果 SSH 版本支持)或用户家目录的 .ssh/rc 文件中添加脚本。
下面是一个简化的示例脚本思路,我们可以根据实际情况进行调整和完善:
apply_user_policy.sh (登录时执行):
#!/bin/bash
USER_GROUP=$(id -gn)
SSH_CONNECTION_INFO="$PAM_USER from $PAM_RHOST" # PAM 环境变量,或者从SSH环境变量获取
# 获取 sshuttle 可能使用的虚拟网卡 (这里只是一个示例,实际情况可能更复杂)
# 我们可能需要更可靠的方式来识别 sshuttle 创建的接口
SSHUTTLE_INTERFACE="tun0" # 或者通过 sshuttle 的输出来动态获取
LOG_FILE="/var/log/ssh_user_policy.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
# 清理旧规则 (可选,但推荐,防止规则重复)
# 注意:这里的清理逻辑需要非常小心,避免影响其他用户或服务
# 一个简单的方法是为每个用户或会话使用特定的 iptables 链,然后只清理那个链
log_message "Applying policy for user $PAM_USER (group: $USER_GROUP), connection: $SSH_CONNECTION_INFO"
if [ "$USER_GROUP" == "group_allow_web_only" ]; then
log_message "Applying SSHUTTLE_WEB_ONLY policy for $PAM_USER on $SSHUTTLE_INTERFACE"
# 将来自该用户通过 sshuttle 转发的流量导向特定链
# 这里需要一种方式来识别特定用户的流量,一个简单但不完美的方法是基于 sshuttle 进程的 UID
# 更可靠的方法可能需要结合 conntrack 标记或者网络命名空间
# 简化示例:假设所有 sshuttle 流量都经过 FORWARD 链,并且 sshuttle 在特定用户下运行
sudo iptables -I FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j SSHUTTLE_WEB_ONLY
elif [ "$USER_GROUP" == "group_allow_all_internal" ]; then
log_message "Applying SSHUTTLE_INTERNAL_ALL policy for $PAM_USER on $SSHUTTLE_INTERFACE"
sudo iptables -I FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j SSHUTTLE_INTERNAL_ALL
else
log_message "No specific policy for user $PAM_USER (group: $USER_GROUP), applying default deny (if configured)."
# 我们可以设置一个默认的严格拒绝策略
# sudo iptables -I FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j DROP
fi
# 启动用户的 shell (如果是通过 ForceCommand)
# exec $SSH_ORIGINAL_COMMAND
remove_user_policy.sh (退出时执行):
#!/bin/bash
USER_GROUP=$(id -gn)
SSH_CONNECTION_INFO="$PAM_USER from $PAM_RHOST" # PAM 环境变量
SSHUTTLE_INTERFACE="tun0"
LOG_FILE="/var/log/ssh_user_policy.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
log_message "Removing policy for user $PAM_USER (group: $USER_GROUP), connection: $SSH_CONNECTION_INFO"
# 清除之前添加的规则
# 注意:这里的清除逻辑需要和添加逻辑严格对应,避免误删规则
# 使用 -D 选项删除规则,确保参数与添加时一致
if [ "$USER_GROUP" == "group_allow_web_only" ]; then
sudo iptables -D FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j SSHUTTLE_WEB_ONLY
elif [ "$USER_GROUP" == "group_allow_all_internal" ]; then
sudo iptables -D FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j SSHUTTLE_INTERNAL_ALL
else
# sudo iptables -D FORWARD -i "$SSHUTTLE_INTERFACE" -m owner --uid-owner "$(id -u "$PAM_USER")" -j DROP
: # No specific rule to remove for default case if no rule was added
fi
如何触发这些脚本?
-
使用
pam_exec.so: 编辑/etc/pam.d/sshd文件,在session部分添加如下行: session optional pam_exec.so seteuid /path/to/apply_user_policy.sh session optional pam_exec.so close_session /path/to/remove_user_policy.sh 确保脚本有执行权限,并且pam_exec.so能够正确调用它们。这种方式下,脚本会以 root 权限执行(因为seteuid),这对于修改iptables是必要的。 -
SSH
ForceCommand: 在/etc/ssh/sshd_config中,我们可以为特定用户或用户组配置ForceCommand。 Match Group group_allow_web_only ForceCommand /usr/local/bin/user_session_wrapper.sh Match Group group_allow_all_internal ForceCommand /usr/local/bin/user_session_wrapper.shuser_session_wrapper.sh脚本会先执行apply_user_policy.sh,然后通过exec $SSH_ORIGINAL_COMMAND执行用户原本想执行的命令。但这种方式处理退出脚本会比较麻烦,通常需要用户 shell 的退出钩子(如.bash_logout)。 -
用户 Shell 启动和退出脚本: 可以在用户的
.bash_profile或.bashrc中调用apply_user_policy.sh,在.bash_logout中调用remove_user_policy.sh。但这种方法依赖于用户使用的 shell,并且如果用户直接 kill SSH 连接可能不会执行退出脚本。同时,这些脚本通常以用户权限运行,需要sudo来修改iptables,这需要配置sudoers文件,增加了安全风险。
推荐使用 pam_exec.so,因为它与用户的登录和会话结束事件紧密集成,并且可以方便地以 root 权限执行策略脚本。
4. sudoers 配置 (如果脚本中需要 sudo)
如果我们的脚本中某些命令需要 sudo (例如 iptables),并且我们没有使用 pam_exec.so 的 seteuid 功能,我们需要配置 sudoers 文件允许这些脚本无密码执行特定的 iptables 命令。这是一个潜在的安全风险,请务必谨慎配置,只授予最小必要权限。
例如,在 /etc/sudoers.d/ 下创建一个文件:
# Allow specific users or groups to run specific iptables commands for policy management
%group_allow_web_only ALL=(ALL) NOPASSWD: /sbin/iptables -I FORWARD ..., /sbin/iptables -D FORWARD ...
%group_allow_all_internal ALL=(ALL) NOPASSWD: /sbin/iptables -I FORWARD ..., /sbin/iptables -D FORWARD ...
强烈建议尽可能避免这种方式,优先考虑 pam_exec.so 的 seteuid。
5. 测试和调试
配置完成后,使用不同用户组的账户登录 SSH,并启动 sshuttle,然后测试其网络访问是否符合预期。
- 查看
iptables规则是否按预期添加和删除:sudo iptables -L FORWARD -v -n - 查看日志文件
/var/log/ssh_user_policy.log的输出。 - 在客户端测试网络连通性,例如使用
ping、curl等命令。
进阶思考与替代方案
- 网络命名空间 (Network Namespaces):对于更严格的隔离,可以考虑为每个 SSH 用户或用户组创建独立的网络命名空间。sshuttle 可以在该命名空间内运行,然后我们可以在主命名空间和用户命名空间之间设置 veth pair,并对这个 veth pair 应用防火墙规则。这种方式隔离性更好,但配置也更复杂。
nftables的ct helper或meta表达式:nftables提供了更灵活的匹配条件,例如可以根据进程的 UID/GID (meta skuid,meta skgid) 来匹配流量,这可能比iptables的owner模块更通用。- SELinux/AppArmor:虽然配置复杂,但这些强制访问控制系统可以提供非常细粒度的权限控制,包括网络访问。
- 使用更专业的 VPN 解决方案:如果我们的需求非常复杂,或者对审计和管理有更高要求,可以考虑使用 OpenVPN、WireGuard 等更专业的 VPN 解决方案,它们通常内置了更完善的用户管理和访问控制功能。
安全注意事项
- 最小权限原则:确保我们的策略脚本和
sudoers配置只授予必要的最小权限。 - 脚本安全性:仔细审查我们的策略脚本,防止潜在的命令注入等安全漏洞。
- 日志和监控:记录关键操作和策略应用情况,方便排查问题和审计。
- sshuttle 的运行用户:sshuttle 客户端连接后,它在服务器端创建的转发进程是以哪个用户身份运行的,这会影响基于
owner模块的iptables规则。通常,sshuttle 服务器端的转发部分(如果它在服务器上创建了类似sshd -D的子进程)可能是以连接用户的身份运行的。我们需要确认这一点以确保iptables规则能正确匹配。
总结
通过结合 Linux 的用户组、防火墙工具(如 iptables 或 nftables)以及登录/退出钩子脚本,我们可以有效地在 Ubuntu 24.04 上为 sshuttle 实现基于用户的网络访问策略。这种方法虽然需要一些手动配置,但提供了高度的灵活性和控制力。
记住,安全是一个持续的过程,务必根据我们的具体需求和环境进行调整和优化。