跳到主要内容

除了Docker + ufw, 你还有这个选择限制ip访问

背景

在服务器上用 Docker 部署 PostgreSQL 后,发现日志里经常有陌生 IP 尝试登录。想用 ufw 限制访问,却发现 Docker 使用 -p 参数映射端口时会直接操作 iptables,绕过 ufw 规则。

解决方案对比

方案优点缺点
绑定 127.0.0.1 + SSH 隧道最安全,端口不暴露公网每次连接需要开隧道
pg_hba.conf 白名单数据库层面控制IP 变化时需要改配置并重启
禁用 Docker iptablesufw 可以正常工作需要手动管理容器网络
DOCKER-USER 链不影响现有部署,灵活需要了解 iptables
Tailscale/WireGuard零配置 VPN,IP 不变所有节点都要安装

本文采用 DOCKER-USER 链 方案,适合已有部署且服务器 IP 固定的场景。

操作步骤(Debian/Ubuntu)

1. 确认 DOCKER-USER 链存在

iptables -L DOCKER-USER -n -v

正常输出会显示一条 RETURN 规则,说明 Docker 已创建该链。

提示

如果看到 iptables-legacy tables present 警告,执行 iptables-legacy -L DOCKER-USER -n -v 检查。如果 legacy 里没有 DOCKER-USER 链,说明 Docker 用的是 nftables 后端,继续用 iptables 命令即可。

2. 添加白名单规则

假设需要允许以下 IP 访问 PostgreSQL(5432 端口):

  • k3s 节点:203.0.113.10203.0.113.11
  • 其他服务器:198.51.100.20198.51.100.21
  • 家庭网络:192.0.2.100
  • VPN 出口:192.0.2.200

逐条添加 ACCEPT 规则:

iptables -I DOCKER-USER -p tcp --dport 5432 -s 203.0.113.10 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 203.0.113.11 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 198.51.100.20 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 198.51.100.21 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 192.0.2.100 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 192.0.2.200 -j ACCEPT

3. 添加 DROP 规则

iptables -A DOCKER-USER -p tcp --dport 5432 -j DROP
注意顺序
  • ACCEPT 规则用 -I(插入到开头)
  • DROP 规则用 -A(追加到末尾)

这样确保 ACCEPT 在前,DROP 在后。

4. 验证规则顺序

iptables -L DOCKER-USER -n -v --line-numbers

正确的输出应该是:

num   pkts bytes target     prot opt in     out     source               destination
1 0 0 ACCEPT tcp -- * * 192.0.2.200 0.0.0.0/0 tcp dpt:5432
2 0 0 ACCEPT tcp -- * * 192.0.2.100 0.0.0.0/0 tcp dpt:5432
3 0 0 ACCEPT tcp -- * * 198.51.100.21 0.0.0.0/0 tcp dpt:5432
4 0 0 ACCEPT tcp -- * * 198.51.100.20 0.0.0.0/0 tcp dpt:5432
5 0 0 ACCEPT tcp -- * * 203.0.113.11 0.0.0.0/0 tcp dpt:5432
6 0 0 ACCEPT tcp -- * * 203.0.113.10 0.0.0.0/0 tcp dpt:5432
7 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5432
8 xxx xxxxx RETURN all -- * * 0.0.0.0/0 0.0.0.0/0

关键点:DROP 必须在 RETURN 之前,否则不会生效。

如果 DROP 在 RETURN 后面,需要调整:

# 删除错误位置的 DROP
iptables -D DOCKER-USER -p tcp --dport 5432 -j DROP

# 插入到 RETURN 前面(假设 RETURN 在第 7 位)
iptables -I DOCKER-USER 7 -p tcp --dport 5432 -j DROP

5. 持久化规则

apt install iptables-persistent -y

安装时会提示是否保存当前规则,选择 Yes

手动保存:

netfilter-persistent save

规则保存在 /etc/iptables/rules.v4

6. 验证持久化

grep -A 10 "DOCKER-USER" /etc/iptables/rules.v4

应该能看到你添加的规则。

日常维护

添加新 IP

# 插入到 DROP 规则之前(查看 DROP 的行号后 -1)
iptables -I DOCKER-USER 7 -p tcp --dport 5432 -s 新IP -j ACCEPT
netfilter-persistent save

删除某个 IP

# 先查看行号
iptables -L DOCKER-USER -n --line-numbers

# 删除指定行
iptables -D DOCKER-USER 行号
netfilter-persistent save

查看被拦截的连接

iptables -L DOCKER-USER -n -v

DROP 规则的 pktsbytes 列会显示被拦截的数据包数量。

动态 IP 怎么办?

如果你的家庭网络 IP 经常变化,可以用 SSH 隧道连接:

# 本地执行
ssh -L 5432:127.0.0.1:5432 user@服务器IP

# 然后用客户端连接本地 127.0.0.1:5432

或者在 ~/.ssh/config 配置:

Host mydb
HostName 服务器IP
User 你的用户名
LocalForward 5432 127.0.0.1:5432

之后 ssh mydb 就自动建立隧道。

其他端口

同样的方法可以用于其他 Docker 暴露的端口,只需修改 --dport 参数:

# 例如限制 Redis 6379 端口
iptables -I DOCKER-USER -p tcp --dport 6379 -s 允许的IP -j ACCEPT
iptables -A DOCKER-USER -p tcp --dport 6379 -j DROP