远程管理物理主机时,防火墙变更最容易出现的误判是:当前 SSH 会话还能执行命令,于是认为规则已经通过验收。实际情况可能相反,连接跟踪保留了旧会话,新连接却已被阻断。可靠的操作顺序应当是先保存现场、安排恢复,再加载规则,最后通过独立登录确认管理通道。
本文使用 Debian 12、nftables 与 systemd,演示只限制 SSH 来源的增量变更。AMDSSH 机器的连接地址与凭据在付款后到控制台查看;有多个 IPv4 时,SSH 使用第一个地址。示例不假定账户名或端口,也不构建完整的业务端口策略。
一、先明确规则的管理边界
以下操作要求账户具备 sudo 权限,系统已安装 nftables,并且规则由原生 nftables 统一管理。若容器网络、虚拟化网桥或其他防火墙工具正在动态生成规则,不应直接套用整套快照恢复:回滚可能覆盖它们在变更期间新增的内容。此时应先明确规则所有者,改用对应工具的变更流程。
先查看现场:
sudo nft list ruleset
sudo systemctl cat nftables.service
command -v nft
确认现有规则、启动时加载的文件,以及 nft 的实际路径。本文单独使用 inet ops_guard 表;若同名表已经存在,必须核对其用途,不能覆盖其他任务的规则。
回滚计时器只处理本次防火墙规则变更,不能修复路由、SSH 服务或管理来源同时变化造成的故障。一次变更只调整一个变量。
读取实际 SSH 参数
在当前远程登录的 Bash 中执行,不能先切换到丢失连接环境变量的其他会话:
test -n "$SSH_CONNECTION" || exit 1
read -r ADMIN_IP CLIENT_PORT SERVER_IP SSH_PORT <<< "$SSH_CONNECTION"
case "$ADMIN_IP" in
*:*) printf '%s\n' 'IPv4 SSH session required'; exit 1 ;;
esac
printf 'source=%s destination=%s port=%s\n' \
"$ADMIN_IP" "$SERVER_IP" "$SSH_PORT"
这里取得的是服务器看到的连接来源,可能是办公出口或跳板地址,并非工作站的内网地址。若出口会变化,应先建立稳定管理路径。本例要求当前会话使用 IPv4;不要从 IPv6 会话生成规则后强行继续。
二、保存快照并预约自动恢复
快照需要包含清空旧规则的指令,否则恢复时可能与现有对象重复。目录只允许管理员访问,避免规则中的地址规划被普通账户读取。
sudo install -d -m 700 /root/firewall-change
sudo sh -c '
set -e
umask 077
{
printf "flush ruleset\n"
nft list ruleset
} > /root/firewall-change/before.nft
'
sudo nft -c -f /root/firewall-change/before.nft
-c 只检查,不加载。它能发现语法与规则引用问题,不能证明远程登录仍然可用。检查失败时应停止变更,不能绕过错误继续执行。
确认没有同名遗留任务后,预约三分钟后恢复:
sudo systemctl status ops-firewall-rollback.timer
sudo systemd-run \
--unit=ops-firewall-rollback \
--on-active=3m \
"$(command -v nft)" \
-f /root/firewall-change/before.nft
sudo systemctl list-timers ops-firewall-rollback.timer
首次查询提示单元不存在是正常情况;若已有任务,应先查明来源。三分钟是本次操作的回滚等待时间,不是服务交付承诺。必须看到计时器已安排执行,再进入加载步骤。不要在此期间修改其他网络规则,否则整套恢复会一并撤销那些修改。
三、用独立表限制管理来源
首次使用时建立空表。只有确认同名表不存在后,才执行创建命令;已有本次专用表则跳过:
sudo nft add table inet ops_guard
随后生成候选文件:
sudo tee /root/firewall-change/candidate.nft > /dev/null <<EOF
delete table inet ops_guard
table inet ops_guard {
chain input {
type filter hook input priority 10; policy accept;
iifname "lo" accept
ct state established,related accept
ip saddr $ADMIN_IP tcp dport $SSH_PORT accept
tcp dport $SSH_PORT counter drop
}
}
EOF
sudo chmod 600 /root/firewall-change/candidate.nft
sudo nft -c -f /root/firewall-change/candidate.nft
sudo nft -f /root/firewall-change/candidate.nft
sudo nft -a list table inet ops_guard
文件中的删除与重建在一次 nftables 事务内提交,不是先清空、再逐条追加。候选文件每次加载都要求专用表已存在,因此不能直接当作冷启动配置。
规则允许回环及已建立连接,允许当前 IPv4 管理来源访问实际 SSH 端口,然后拒绝其他来源访问该端口。最后一条也匹配 IPv6,因此本例不会允许 IPv6 新建 SSH 连接。需要 IPv6 管理时,应事先增加明确的 ip6 saddr 允许规则,并单独测试。
其他业务端口没有被本表限制。这里的 accept 也不能推翻其他基础链中的 drop:新连接仍可能被已有规则拒绝。不要将此例理解为完整安全基线,更不能据此删除原有业务策略。
四、以独立新连接决定是否提交
保持原会话不退出,从允许来源启动另一条连接。客户端如果启用了连接复用,需要禁用复用,否则打开的新窗口可能继续使用旧 TCP 连接。
下列命令在客户端 Bash 执行,交互输入控制台提供的参数:
read -r -p 'Host: ' HOST
read -r -p 'User: ' LOGIN
read -r -p 'Port: ' PORT
ssh -o ControlMaster=no -o ControlPath=none \
-p "$PORT" "$LOGIN@$HOST"
验收至少包含三项:
- 允许来源能建立独立连接,完成认证并执行命令。
- 从另一个受控、未获允许的来源发起连接,确认不能建立 SSH 会话。
- 查看拒绝规则的计数变化,区分防火墙拦截与账户认证失败。
仅出现密码或密钥认证错误,不能证明来源限制失败;应判断连接究竟在哪一层被拒绝。不要为测试临时放宽规则。
若独立登录失败,保留现场并等待回滚,不要连续追加允许规则。恢复后查看计时器对应服务的日志,再分析来源地址、端口及其他链的影响。确认通过后停止计时器:
sudo systemctl stop ops-firewall-rollback.timer
sudo systemctl is-active ops-firewall-rollback.timer
还应确认恢复服务没有已经启动。若计时器恰好到期,必须重新读取实时规则,不能凭停止命令的成功判断候选配置仍在。
五、持久化与启动验收分开完成
运行态通过后,才处理启动文件。按照前面查到的服务配置确认实际加载路径;Debian 12 常见路径为 /etc/nftables.conf,但自定义服务可能不同。
在规则稳定、没有其他管理器写入的前提下,将完整实时规则导出为带 flush ruleset 的文件,执行 nft -c -f 检查,并备份原启动文件后再替换。不能只保存专用表,否则重启后可能遗漏原有业务规则。
持久化属于第二次变更,应重新安排回滚,加载完整启动文件,再重复独立登录测试。不要把启用服务、替换配置、重启机器连成一条命令。重启验收另行安排,并核对规则、允许来源登录及业务连通性。
最终记录实际来源、SSH 端口、候选文件、快照和验证结果。可提交的证据不是“命令返回成功”,而是新连接通过、拒绝路径符合预期、启动配置能够重现规则,三者同时成立。
常见问题
原有 SSH 会话还在,是否可以取消回滚?
不可以。已建立连接可能被状态规则放行,必须从允许的管理来源建立独立新连接,完成登录后才能取消回滚计时器。
示例会把所有业务端口一起关闭吗?
不会。示例规则只限制实际 SSH 端口的新连接,其他端口继续受已有规则控制;它不是整机默认拒绝的完整防火墙基线。