用 DOCKER-USER 链封锁 Nacos 8848/9848 端口,防止公网裸奔

发布时间:2026/9/30 3:40:18
用 DOCKER-USER 链封锁 Nacos 8848/9848 端口,防止公网裸奔 如果你用docker run -p 8848:8848起过 Nacos我说的这个场景你八成见过服务起来一切正常浏览器打开http://服务器IP:8848/nacos/还能直接看到控制台。方便是真方便但风险也很直接。Nacos 默认配置并不强制鉴权网上经常提到的 Nacos namespaces 未授权访问这类隐患绝大多数就是这种裸露端口造成的。要么你在应用侧把账号、Token、IP 白名单全部配齐要么就在主机侧把 8848 端口从网络世界“摘”掉。后者最实际的一招就是 iptables 的 DOCKER-USER 链只放行 localhost 访问进来的流量其余外部来源一律 DROP。这篇文章就围绕这个思路展开包括为什么必须用 DOCKER-USER 而不能简单改 INPUT 链规则具体怎么下如何验证锁死效果以及重启后规则丢失、容器互访受影响这类常见坑。我尽量把实际操作中会碰到的问题都写出来毕竟安全规则最怕的不是配不上而是配完了自己都不知道它到底挡没挡。1. 为什么“端口映射 防火墙规则”会失效DOCKER-USER 链的前世今生1.1 Docker 改规则比你想象的更彻底Docker 守护进程在启动时会为容器网络注入大量 iptables 规则包括 PREROUTING 链、OUTPUT 链里的DOCKER链FORWARD 链里的DOCKER-ISOLATION-STAGE-1/2、DOCKER链以及一个专门留给用户自定义规则的位置就是DOCKER-USER链。换成 nftables 后端的发行版里filter 表中同样能看到这个链。这里的关键点在于Docker 的端口映射功能并不是靠守护进程在用户态转发数据包而是在 IP 层用 DNAT 直接改写目标地址。当外部请求到达宿主机 8848 端口时包先进 PREROUTING被DOCKER链里对应的 DNAT 规则改写目标 IP 为容器地址比如172.17.0.2:8848之后进入 FORWARD 链做过滤。也就是说外部流量根本没机会走 INPUT 链。本地进程访问localhost:8848也类似包从 OUTPUT 链出来后会经过 DNAT最终同样走上 FORWARD 这条转发路径。所以主机上常见的“INPUT 链操作”对 Docker 发布端口几乎形同虚设真正的生效点是 FORWARD 路径而DOCKER-USER恰恰排在 FORWARD 路径最前面这是 Docker 官方专门预留的“用户自定义规则位”。用生活类比理解你给前门装了门禁但 Docker 发布出来的端口相当于一扇侧门访客从侧门进来你只在前门查证件当然拦不住。DOCKER-USER等于直接在侧门门口加了一个保安岗亭。1.2 常规 iptables 的“绕行”路径INPUT 管不到转发流量很多运维第一次排查这个问题时习惯性地敲iptables -A INPUT -p tcp --dport 8848 -j DROP然后发现一点效果都没有。原因在于 INPUT 链只管“目标是本机”的包而 Docker 发布端口后包的目标地址早就被 DNAT 成容器 IP 了包的目的地已经不是宿主机本身自然归 FORWARD 管。更隐蔽的是即便你把宿主机 INPUT 策略改成 DROP外部访问 Docker 端口依然不受影响因为数据包在 PREROUTING 阶段就已经被改写目标地址并进入转发流程。这个现象我见过不少人踩坑有人在云服务器安全组、宿主机 iptables、Docker 端口映射三层同时做了配置结果某个端口仍然访问得到回过头排查时才发现是 FORWARD 路径漏了。因此对容器发布端口做访问控制时不要一上来就写 INPUT 规则应该先确认流量走向。这也是 Docker 官方文档反复强调的一点用户自定义的 iptables 规则应当放在DOCKER-USER链里而不是去改动 Docker 自动生成的DOCKER、DOCKER-ISOLATION-STAGE-1/2等链。1.3 为什么规则必须放在 RETURN 之前默认情况下DOCKER-USER链只有一条RETURN规则。RETURN的意思是既然你这条链里没有别的规则要处理那就回到 FORWARD 链继续执行 Docker 自动生成的后续规则也就是放行。如果你用-A把新规则追加到链尾那规则会排在RETURN之后永远不会被命中。所以正确做法是用-I DOCKER-USER 1把规则插到最前面确保在 RETURN 之前先做匹配。这也是后续所有命令的基础逻辑。我会在第 3 部分给出具体命令现在先别急着敲把基础概念理清楚后面操作才不会乱。2. 开工前先摸底查 DOCKER-USER理清 Nacos 端口2.1 一条命令看到当前状态动手修改之前先看一下当前DOCKER-USER链是什么状态。在宿主机上执行iptables -t filter -L DOCKER-USER -n --line-numbers正常情况下你会看到类似这样的输出Chain DOCKER-USER (1 references) num target prot opt source destination 1 RETURN all -- 0.0.0.0/0 0.0.0.0/0如果 Docker 服务正常运行但这条命令报错说找不到链那通常意味着当前 Docker 版本没创建这个链可以先用nft list chain ip filter DOCKER-USER看看或者在 nftables 后端下确认链的真实名字。少数发行版上Docker 启动后才会生成链确认docker ps正常后一般就能看到。另外提示一下如果宿主机运行的是 iptables-nft 后端iptables命令本身会管理 nft 的表但你脑子里要清楚规则实际落在 netfilter 里不要被“legacy 还是 nft”这个表象迷惑操作逻辑是一样的。2.2 8848、9848、9849、7848 各自是干什么的Nacos 2.x 已经不是一个“单端口”中间件了。除了大家熟知的 8848还有几个相关端口整理如下端口用途是否应该公网暴露8848HTTP 控制台、客户端接口、配置管理、服务注册发现的基础端口不应暴露本文重点9848Nacos 2.x 新增的客户端 gRPC 端口端口号通常是主端口 1000不应暴露需同步处理9849服务端之间的 gRPC 端口用于集群内部同步视集群模式决定不宜粗暴封死7848集群成员发现与通信端口只在内网或集群节点间开放从安全隐患角度8848 和 9848 是最需要盯紧的。很多部署只封了 8848结果 Nacos 客户端升级到 2.x 后依然能通过 9848 建立连接等于白封。所以在下一部分我会把这两哥儿们一起锁掉。2.3 顺带想清楚你到底要不要容器互访这里有个容易被忽略的关键点DOCKER-USER链拦截的不只是外部流量同一宿主机上其它容器访问 Nacos 容器时只要目标端口是 8848同样会经过 FORWARD 路径并命中这条链。如果你部署了微服务订单服务容器需要连接 Nacos 容器那么一个简单的! -s 127.0.0.1 -j DROP规则会把订单服务容器的访问也干掉。这种情况下你要么在网络规划时让微服务通过宿主机 localhost 访问要么在规则里显式放行容器网段要么用 Docker 网络隔离方案。我先把这个坑摆出来后面第 6 部分还会详细排查。3. 锁门的核心命令只放行 localhost 访问 Nacos 88483.1 最小规则集假设你的 Nacos 容器是用默认方式发布的宿主机是 Linux先跑下面两条命令iptables -I DOCKER-USER 1 -p tcp --dport 8848 -s 127.0.0.1 -j ACCEPT iptables -I DOCKER-USER 2 -p tcp --dport 8848 ! -s 127.0.0.1 -j DROP如果你希望把 Nacos 2.x 的 gRPC 端口 9848 也一起锁住把两条命令换成一条 multport 写法iptables -I DOCKER-USER 1 -p tcp -m multiport --dports 8848,9848 -s 127.0.0.1 -j ACCEPT iptables -I DOCKER-USER 1 -p tcp -m multiport --dports 8848,9848 ! -s 127.0.0.1 -j DROP这两组命令的核心思想完全一致遇到 127.0.0.1 来源就直接放行其它来源全部丢弃。第二条命令里的! -s 127.0.0.1表示“源地址不是 127.0.0.1”配合 DROP 就是关闭所有非本机访问。3.2 逐条解读与位置逻辑第一组命令为什么要写两条有人觉得只写DROP就够了因为本机访问并不一定需要 ACCEPT。但显式写出来有两个好处一是规则意图清楚别人接手时一看就明白“本机回环允许、其他阻止”二是避免某些特殊网络路径下回环包被第二条误伤例如 IPv6 场景或者使用其他环回地址时。第二条命令里的位置也不能乱。我用-I DOCKER-USER 1将规则插入链首第二条插入后成为链首。最终链内顺序是1 DROP tcp -- !127.0.0.1 0.0.0.0/0 tcp dpt:8848 2 ACCEPT tcp -- 127.0.0.1 0.0.0.0/0 tcp dpt:8848外部来源的包先匹配第一条 DROP直接被丢弃本机 127.0.0.1 来源的包不匹配第一条继续匹配第二条 ACCEPT被放行。两条规则顺序倒过来问题也不大ACCERT 在前时本机包立即放行外部包继续匹配 DROP最终效果一致。但一定要保证两条规则都在默认 RETURN 之前。为什么用 DROP 而不是 REJECTDROP 会让外部扫描端口的机器一直等不到响应相当于 TCP 层静默超时既不暴露端口存在也给扫描行为增加成本。REJECT 则会直接返回 ICMP 或 RST对方立刻知道端口是活的只是被拒了。锁死端口自然是 DROP 更合适。3.3 顺带堵上 Nacos 2.x 的 gRPC 端口 9848只封 8848 容易漏掉 9848。Nacos 2.x 客户端默认会通过主端口 1000 的偏移量建立 gRPC 连接也就是说如果你的主端口是 8848客户端 gRPC 就会尝试连 9848。暴露 9848 同样能实现恶意注册、配置拉取。把两条命令改成 multiport 版本即可。如果你的 Nacos 部署了集群9849 和 7848 是否要一起禁掉要慎重——这两个端口主要供 Nacos 节点之间内部通信使用如果节点分布在多台主机上且端口映射转出去DOCKER-USER 规则会一并拦截可能导致集群不稳定。通常建议 9849、7848 只通过内网访问而不是用本机防火墙来做一刀切。3.4 如果应用通过宿主机 IP 访问需要额外放行有些应用的 Nacos 地址配置是http://192.168.1.100:8848而不是http://127.0.0.1:8848。这种情况下请求源 IP 是宿主机局域网 IP不是 127.0.0.1会被 DROP。如果这是你的实际场景需要把宿主机 IP 也加进 ACCEPTiptables -I DOCKER-USER 1 -s 192.168.1.100 -p tcp --dport 8848 -j ACCEPT我自己更推荐统一用127.0.0.1作为服务发现地址语义清晰也不容易误伤。但如果历史配置已经用了内网 IP那就按实际来源加上这一条。4. 验证是否真锁死从本地通到外部拒绝4.1 本地回环访问测试规则配完后先在本机验证。用 curl 访问回环地址curl -s -o /dev/null -w %{http_code}\n http://127.0.0.1:8848/nacos/正常情况下会返回 Nacos 后端给的响应码比如 200、302说明本机访问是通的。用-w输出状态码是为了避免控制台返回内容干扰判断。再验证宿主机 IP 访问应该失败curl -s --connect-timeout 3 -o /dev/null -w %{http_code}\n http://192.168.1.100:8848/nacos/由于 DOCKER-USER 已经丢弃非 127.0.0.1 来源的包这条 curl 会一直卡到超时最终返回 000 或者报connection timed out。这里的关键是从宿主机自身访问宿主机 IP 也会被丢弃因为源 IP 是局域网 IP 而不是 127.0.0.1所以这种“本机测自己外网 IP 失败”的现象本身就是规则生效的标志。4.2 外部机器验证与健康检查的坑最稳妥的办法是找另一台机器或者直接用云控制台的端口测试工具。从外部机器执行nc -vz 目标服务器IP 8848预期结果是连接超时或者无响应而不是 Connection refused。如果看到 Connection refused说明 DROP 没生效很可能规则又落到 RETURN 后面去了赶紧回去看链内顺序。这里要额外提一个坑如果服务器前方有云负载均衡器负载均衡器的健康检查也走 8848 端口那 DOCKER-USER 规则会把健康检查包一并丢弃可能出现“服务真活着但 LB 判定后端不可用”的情况。遇到这种场景需要在规则里临时放行 LB 的源 IP或者在健康检查配置里改用其它端口否则会自己把自己搞懵。5. 别让规则一重启就没持久化方案与 Docker 重启应对5.1 用 iptables-persistent 落盘手动敲的命令只存在于当前内核的 netfilter 规则集里重启后自然消失。Debian/Ubuntu 系推荐用iptables-persistent保存apt install -y iptables-persistent netfilter-persistent save这个命令会把当前 IPv4 和 IPv6 规则分别保存到/etc/iptables/rules.v4和/etc/iptables/rules.v6系统重启时会自动恢复。注意规则状态必须是你最终确认过的状态恢复的不是脚本而是“当前快照”。另一个需要习惯的动作是每次调整规则后都手动执行一次netfilter-persistent save否则改了等于白改。5.2 Docker 服务重启后规则可能被清空很多朋友配置完规则后觉得万事大吉直到 docker 服务重启或者服务器重启后发现端口又暴露了才意识到 DOCKER-USER 里的自定义规则可能被重置。Docker 在启动过程中会重建自己的多个 iptables 链DOCKER-USER链虽然会保留但用户追加在链里的规则在部分版本和部分系统上会被清理掉只剩默认 RETURN。因此依赖 iptables-persistent 还不够最好写一个在 docker 服务启动之后重新执行规则的脚本。我的做法是把核心规则放进一个脚本比如/usr/local/sbin/nacos-docker-user-lock.sh#!/bin/bash iptables -N DOCKER-USER 2/dev/null iptables -F DOCKER-USER iptables -I DOCKER-USER 1 -p tcp -m multiport --dports 8848,9848 -s 127.0.0.1 -j ACCEPT iptables -I DOCKER-USER 1 -p tcp -m multiport --dports 8848,9848 ! -s 127.0.0.1 -j DROP然后配一个 systemd oneshot 服务确保它在 docker.service 之后再运行[Unit] DescriptionLock Nacos 8848 to localhost Afterdocker.service [Service] Typeoneshot ExecStart/usr/local/sbin/nacos-docker-user-lock.sh RemainAfterExityes [Install] WantedBymulti-user.target这个方案不算优雅但很实在。脚本里先用-F清空链内规则再重新插入避免重复执行时叠加出一堆相同规则。5.3 回滚或者临时放行需要临时放行某个 IP 时在 DOCKER-USER 链首插入一条 ACCEPT 即可。比如让公司出口 IP 临时访问iptables -I DOCKER-USER 1 -s 203.0.113.10 -p tcp --dport 8848 -j ACCEPT如果后来想完全恢复默认删掉自己添加的规则iptables -D DOCKER-USER -p tcp --dport 8848 ! -s 127.0.0.1 -j DROP iptables -D DOCKER-USER -p tcp --dport 8848 -s 127.0.0.1 -j ACCEPT删除时注意手滑删成 Docker 自动生成的规则真删错了也不用慌重启 docker 服务会让它重建。6. 常见坑与排查思路实录6.1 规则没生效先看 RETURN 尾规则这是频率最高的一个问题。配完规则后外部还是能访问十有八九是规则插入位置不对。用iptables -L DOCKER-USER -n --line-numbers看链内顺序。如果规则排在 RETURN 下面那等于没配。必须确保所有自定义规则在 RETURN 之前最好是链首。这也是我一直用-I DOCKER-USER 1的原因。6.2 其他容器访问 Nacos 失败是预期内的副作用第 2 节提到的容器互访问题这里展开说。假设你有订单服务容器在同一个宿主机上通过 Docker 内网访问 Nacos 容器172.17.0.2:8848而 DOCKER-USER 里有! -s 127.0.0.1 -j DROP那么订单服务容器的请求会因为源 IP 是172.17.0.3而被丢弃。这不是规则写错了而是 DOCKER-USER 链本身的特性所有容器间通过 bridge 的 FORWARD 流量都会经过这里。如果你的微服务容器也需要连 Nacos请重新评估网络模型。合理方案包括微服务应用不为所有人暴露在 Docker 中而 Nacos 通过宿主机 localhost 访问保持源 IP 为 127.0.0.1。规则中放行 Docker 自定义子网比如-s 172.17.0.0/16但这会引入一定风险需结合网络环境判断。用 Docker 网络隔离将能访问 Nacos 的容器放到独立网络里其它容器不挂这个网络。很多同学把规则做好后忽然发现容器间服务不可用就以为规则崩溃直接把 iptables 清了。建议先在头脑中过一遍DOCKER-USER 天然会拦容器间流量遇到这种问题先判断该容器是否需要真正访问 Nacos再调整白名单。6.3 封了 8848 别忘了 9848也别误伤 9849 和 7848Nacos 2.x 的客户端 gRPC 走 9848光锁 8848 确实不够。但如果你把 9849、7848 也一并禁掉尤其是 Nacos 集群跨宿主机部署时集群节点之间的心跳和同步可能全断表现为节点反复上下线、配置同步失效。建议结合集群拓扑只拦需要拦的端口不要图省事一把梭。6.4 云安全组和本机防火墙叠加时容易产生“三重困惑”云服务器通常前面还有一层安全组。假设安全组放行了 8848DOCKER-USER 又 DROP 了 8848效果自然以最严格的为准外部访问不通。这时你从云控制台看安全组规则会觉得很诡异明明放行了为什么不通排查这种问题时先在云控制台安全组把 8848 的入方向暂时去掉然后从外部测试。如果外部依然超时说明是宿主机规则在拦截如果外部通了说明规则状态和预期不一致需要重新审视 DOCKER-USER 里的内容。这样分层判断比到处乱试高效得多。6.5 规则脚本化别在 SSH 会话里手动敲这是我自己的一个惨痛教训。早年间我在生产服务器上手动敲规则敲到一半 SSH 断开重连后已经不确定当时敲到了第几条。后来我把所有端口安全规则全部脚本化并通过 systemd 或 crontab 在需要时统一应用。这样不仅重启后可恢复出问题也能快速对照脚本重新部署。最后补一句实际体会在接触 DOCKER-USER 链之前我一直以为 Docker 暴露端口只要靠云安全组兜底就够了直到亲眼看到未开鉴权的 Nacos 在公网被扫描才意识到默认的“放行”策略有多危险。一条 iptables 规则肯定不能代表完整安全方案但它能把最容易暴露的入口先堵住。做成脚本、加上持久化也别嫌麻烦——等你看到外部扫描包在 netfilter 层面就被丢弃时就知道这个门槛花得值。