个人服务器组网与防护实践(三):Docker 发布端口的访问控制与持久化

原创 临窗旋墨 2026-11-05 阅读:1 分类: Linux运维 专题: Linux小主机实践 标签: 网络,linux,安全

# 个人服务器组网与防护实践(三):Docker 发布端口的访问控制与持久化

家庭小主机需要保留“二级域名 + 端口”的公网访问方式,同时让 Redis 等内部服务只供局域网和 WireGuard 使用。这里保留 Docker 自动生成的规则,在 DOCKER-USER 中维护允许访问的来源和公网 TCP 端口。

第一篇记录组网配置,
第二篇解释报文路径。
本篇记录最终采用的家庭防护脚本,以及云主机的安全组分工。

1. 防护范围

1.1 家庭小主机

  • 物理网卡:enp1s0;局域网:192.168.0.0/24。
  • WireGuard:wg0,网段 10.10.0.0/24。
  • 宿主机服务:由 UFW 管理。
  • Docker 发布端口:局域网、WireGuard 可访问;其他从物理网卡进入的转发流量只允许列表中的 TCP 端口及已有连接回包。
  • 公网入口:家庭路由器按需转发端口,无需新增 Nginx。

本方案使用 Docker bridge 与 iptables 后端,仅配置 IPv4。物理网卡上的 DROP 会影响其他转发流量,不应直接用于还承担其他路由任务的网关主机。

1.2 当前公网端口

  1. 10088:springboot附件服务。
  2. 10081:springboot CAS SSO。
  3. 10085:springboot lite-task服务。
  4. 10085:springboot console服务。
  5. 61208:Glances。
  6. 40002:FileBrowser,对应容器端口 80。
  7. 9000:Portainer。

以上是当前明确需要开放的列表。管理页面仍需启用各自的认证;Redis 不加入列表,路由器也不转发 Redis。

2. 防火墙脚本

文件:/usr/local/sbin/docker-user-firewall.sh。网卡、网段和端口数组按实际环境修改。

bash 复制代码
#!/usr/bin/env bash
set -e

# 对公网开放的宿主机 TCP 发布端口。
# FileBrowser 的映射为 40002:80,这里填写 40002。
PUBLIC_TCP_PORTS=(10088 10081 10085 10087 61208 40002 9000)

# 检查 Docker 已创建链;-w 等待 iptables 锁释放。
iptables -w -nL DOCKER-USER >/dev/null

# 允许 WireGuard 与局域网来源继续进入 Docker 后续规则。
iptables -w -C DOCKER-USER -i wg0 -s 10.10.0.0/24 -j RETURN 2>/dev/null \
    || iptables -w -I DOCKER-USER 1 -i wg0 -s 10.10.0.0/24 -j RETURN

iptables -w -C DOCKER-USER -i enp1s0 -s 192.168.0.0/24 -j RETURN 2>/dev/null \
    || iptables -w -I DOCKER-USER 1 -i enp1s0 -s 192.168.0.0/24 -j RETURN

# 拒绝其他从物理网卡进入的转发流量,插在白名单之后。
iptables -w -C DOCKER-USER -i enp1s0 -j DROP 2>/dev/null \
    || iptables -w -I DOCKER-USER 3 -i enp1s0 -j DROP

# 以下规则插入链首,保证排在 DROP 前面。
# 允许容器访问外部服务的响应流量,以及已有连接。
iptables -w -C DOCKER-USER \
    -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN 2>/dev/null \
    || iptables -w -I DOCKER-USER 1 \
        -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN

# 放行公网端口列表。
# 此处已经发生 DNAT,因此用 --ctorigdstport 匹配原始宿主机端口。
for port in "${PUBLIC_TCP_PORTS[@]}"; do
    iptables -w -C DOCKER-USER -i enp1s0 -p tcp \
        -m conntrack --ctdir ORIGINAL --ctorigdstport "$port" \
        -j RETURN 2>/dev/null \
        || iptables -w -I DOCKER-USER 1 -i enp1s0 -p tcp \
            -m conntrack --ctdir ORIGINAL --ctorigdstport "$port" \
            -j RETURN
done

2.1 规则作用与顺序

  • -C 避免重复添加相同规则,-I ... 1 插入链首。
  • RETURN 返回调用链,后续仍由 Docker 规则决定是否放行。
  • --ctorigdstport 匹配宿主机发布端口,因此 40002:80 填写 40002。
  • 最终应先匹配公网端口、已有连接及可信来源,再匹配物理网卡 DROP;公网端口之间的先后顺序不影响本方案。
  • 脚本不会自动修复已有规则的错误顺序,执行后需查看实际规则。

2.2 增删端口

新增端口:加入 PUBLIC_TCP_PORTS,然后重新运行脚本或重启下文的 systemd 服务。

删除端口:从数组移除,另行删除已存在的规则。例如关闭 9000:

bash 复制代码
sudo iptables -w -D DOCKER-USER -i enp1s0 -p tcp \
  -m conntrack --ctdir ORIGINAL --ctorigdstport 9000 -j RETURN
  • 数组不包含某端口,不会自动删除已经加载的旧规则。
  • 已建立连接仍受回包规则放行,不会因为移除端口立即断开。
  • 路由器映射也同步调整;若公网端口与小主机端口不同,数组填写小主机收到的目标端口。

3. systemd 开机加载

文件:/etc/systemd/system/docker-user-firewall.service。

ini 复制代码
[Unit]
Description=Docker User Firewall Rules
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/docker-user-firewall.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

首次安装:

bash 复制代码
sudo chmod +x /usr/local/sbin/docker-user-firewall.sh

# 只检查 Bash 语法,不执行防火墙规则。
sudo bash -n /usr/local/sbin/docker-user-firewall.sh

sudo systemctl daemon-reload
sudo systemctl enable --now docker-user-firewall.service

只更新脚本:

bash 复制代码
sudo bash -n /usr/local/sbin/docker-user-firewall.sh
sudo systemctl restart docker-user-firewall.service
  • 服务在开机时等待 Docker 启动,再执行规则脚本;不用保存、恢复整套 Docker 动态规则。
  • RemainAfterExit=yes 让一次性任务保留已完成状态,不能用再次 start 代替重新执行,应使用 restart。
  • 此单元不会在每次 Docker 单独重启时自动重跑。重启 Docker 后检查规则,缺失时手动重启本服务。

4. 检查与验证

bash 复制代码
sudo systemctl status docker-user-firewall.service

# 查看顺序与命中计数,确认允许规则位于物理网卡 DROP 之前。
sudo iptables -L DOCKER-USER -n -v --line-numbers

docker ps --format 'table {{.Names}}\t{{.Ports}}'

按来源验证:

  1. 局域网:访问所需 Docker 服务。
  2. WireGuard:通过 10.10.0.2 访问所需服务。
  3. 真正外网:验证公网列表内的端口;内部端口应不可访问。
  4. 容器内部:验证访问外部服务正常,避免回包被 DROP。
  5. 服务器或 Docker 重启后:检查规则和实际访问结果。

验证时注意:

  • 路由器未转发某端口导致访问失败,不能单独证明小主机的 DROP 命中,应结合计数判断。
  • NAT 回环或路由器 SNAT 可能让请求显示为可信 LAN 来源,不能用家庭 Wi-Fi 代替真正外网测试。
  • 当前脚本不控制 IPv6。若容器显示 :::端口,需另行配置 IPv6 防护,或明确关闭不使用的 IPv6 发布及入口。

5. 云主机使用安全组控制公网入口

云主机采用阿里云安全组管理 Docker 的公网入口,无需部署家庭机这套脚本。

  1. 开放 22/TCP、80/TCP、443/TCP、51820/UDP,其他确需公开的服务再逐项添加。
  2. 不对公网开放 MySQL 3306、Redis 16379、Meilisearch 7700;通过 WireGuard 访问。
  3. 云端由 Nginx 代理的应用,不再公开后端 10086、10088。
  4. UFW 保留宿主机入站与 WireGuard 规则,具体命令见第一篇第 3、4 节。
  5. 检查全部关联安全组及 IPv6 规则,避免另一条宽泛允许规则重新开放端口。
  6. 对内部服务分别验证:隧道访问成功,公网访问失败。

Docker 在主机内添加转发规则,并不能绕过云平台侧的安全组。WireGuard 解密后的内层服务访问,则继续由主机上的监听地址、防火墙和应用认证决定。

家庭机维护来源和端口列表,云机维护安全组;两边都保留服务认证,并在端口或网络变更后检查实际入口。

评论区

avatar
未登录

请围绕文章内容交流,勿发布广告、违法信息或他人隐私。

暂无评论,来发表第一条评论吧~