SSH VPN 进阶:基于用户策略精细化 sshuttle 网络访问

127 阅读9分钟

SSH VPN 进阶:基于用户策略精细化 sshuttle 网络访问

嘿,各位技术爱好者们!今天我们来聊一个大家在使用 sshuttle 搭建 SSH VPN 时可能会遇到的进阶需求:如何为不同的登录用户设置不同的网络访问权限?这在需要精细化管理用户访问范围的场景下非常有用,比如企业内部根据员工角色限制其可访问的内网资源。

在 Ubuntu 24.04 环境下,虽然 sshuttle 本身并没有直接提供基于用户的网络策略配置选项,但我们可以巧妙地结合 Linux 系统的防火墙工具(如 iptablesnftables)以及一些脚本来实现这一目标。

image.png

核心思路

我们的核心思路是:当用户通过 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> 通常是我们的服务器连接外网的网卡,例如 eth0ens33

使用 nftables 如果我们的 Ubuntu 24.04 默认使用 nftables,相应的配置思路类似,只是语法不同。我们需要创建 nftablestablechainrule

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.sh user_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.soseteuid 功能,我们需要配置 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.soseteuid

5. 测试和调试

配置完成后,使用不同用户组的账户登录 SSH,并启动 sshuttle,然后测试其网络访问是否符合预期。

  • 查看 iptables 规则是否按预期添加和删除:sudo iptables -L FORWARD -v -n
  • 查看日志文件 /var/log/ssh_user_policy.log 的输出。
  • 在客户端测试网络连通性,例如使用 pingcurl 等命令。

进阶思考与替代方案

  • 网络命名空间 (Network Namespaces):对于更严格的隔离,可以考虑为每个 SSH 用户或用户组创建独立的网络命名空间。sshuttle 可以在该命名空间内运行,然后我们可以在主命名空间和用户命名空间之间设置 veth pair,并对这个 veth pair 应用防火墙规则。这种方式隔离性更好,但配置也更复杂。
  • nftablesct helpermeta 表达式nftables 提供了更灵活的匹配条件,例如可以根据进程的 UID/GID (meta skuid, meta skgid) 来匹配流量,这可能比 iptablesowner 模块更通用。
  • SELinux/AppArmor:虽然配置复杂,但这些强制访问控制系统可以提供非常细粒度的权限控制,包括网络访问。
  • 使用更专业的 VPN 解决方案:如果我们的需求非常复杂,或者对审计和管理有更高要求,可以考虑使用 OpenVPN、WireGuard 等更专业的 VPN 解决方案,它们通常内置了更完善的用户管理和访问控制功能。

安全注意事项

  • 最小权限原则:确保我们的策略脚本和 sudoers 配置只授予必要的最小权限。
  • 脚本安全性:仔细审查我们的策略脚本,防止潜在的命令注入等安全漏洞。
  • 日志和监控:记录关键操作和策略应用情况,方便排查问题和审计。
  • sshuttle 的运行用户:sshuttle 客户端连接后,它在服务器端创建的转发进程是以哪个用户身份运行的,这会影响基于 owner 模块的 iptables 规则。通常,sshuttle 服务器端的转发部分(如果它在服务器上创建了类似 sshd -D 的子进程)可能是以连接用户的身份运行的。我们需要确认这一点以确保 iptables 规则能正确匹配。

总结

通过结合 Linux 的用户组、防火墙工具(如 iptablesnftables)以及登录/退出钩子脚本,我们可以有效地在 Ubuntu 24.04 上为 sshuttle 实现基于用户的网络访问策略。这种方法虽然需要一些手动配置,但提供了高度的灵活性和控制力。

记住,安全是一个持续的过程,务必根据我们的具体需求和环境进行调整和优化。