资讯动态

Moby Docker Engine iptables 规则剖析:容器端口发布到回环地址的安全隔离机制

发布时间:2026/9/5 17:50:53 来源:尧图企业网站定制
Moby Docker Engine iptables 规则剖析容器端口发布到回环地址的安全隔离机制【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby本文以 Moby 仓库中TestBridgeIptablesDoc集成测试自动生成的文档usernet-portmap-lo.md为主体完整解读“用户自定义 bridge 网络上、容器端口发布到 127.0.0.1 回环地址”这一场景下 Docker Engine 生成的 filter、nat、raw 三张 iptables 表并结合 iptabler/port.go 与 nftabler/port.go 的源码说明filterPortMappedOnLoopback是如何在 raw-PREROUTING 链中拦截远程流量确保“只能从本机访问”的承诺在内核层面真正落地。场景定义端口只发布到回环地址该文档描述的场景等价于以下两条命令创建一个名为bridge1的用户自定义 bridge 网络子网 192.0.2.0/24、网关 192.0.2.1再在该网络上运行容器c1并把容器 80 端口发布到主机的127.0.0.1:8080docker network create \ -o com.docker.network.bridge.namebridge1 \ --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 docker run --network bridge1 -p 127.0.0.1:8080:80 --name c1 busybox与普通-p 8080:80绑定 0.0.0.0对全网卡开放不同127.0.0.1:8080:80的语义是“仅本机可访问”。但 iptables 的 DNAT 规则本身只是把127.0.0.1:8080的流量重写到容器 IP本例中为 192.0.2.2:80并不会主动阻止从外部网卡到达的、目的地址恰好是 127.0.0.1 的报文——如果远程主机构造这类报文在缺少额外防护时可能绕过“仅本机”的约定。Moby 的解法就是在raw 表 PREROUTING 链中追加 DROP 规则在连接跟踪之前就丢弃这类远程报文。这正是本文档相对 普通 NAT 模式文档 多出来的核心差异。filter 表与普通 NAT 模式完全一致原始文档指出本场景的 filter 表和 nat 表与 nat mode 场景完全相同。以下是完整快照容器启动后、无实际流量时的状态Chain INPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER-USER all -- any any anywhere anywhere 2 0 0 DOCKER-FORWARD all -- any any anywhere anywhere Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain DOCKER (2 references) num pkts bytes target prot opt in out source destination 1 0 0 ACCEPT tcp -- !bridge1 bridge1 anywhere 192.0.2.2 tcp dpt:http 2 0 0 DROP all -- !docker0 docker0 anywhere anywhere 3 0 0 DROP all -- !bridge1 bridge1 anywhere anywhere Chain DOCKER-BRIDGE (1 references) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any docker0 anywhere anywhere 2 0 0 DOCKER all -- any bridge1 anywhere anywhere Chain DOCKER-CT (1 references) num pkts bytes target prot opt in out source destination 1 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED 2 0 0 ACCEPT all -- any bridge1 anywhere anywhere ctstate RELATED,ESTABLISHED Chain DOCKER-FORWARD (1 references) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER-CT all -- any any anywhere anywhere 2 0 0 DOCKER-INTERNAL all -- any any anywhere anywhere 3 0 0 DOCKER-BRIDGE all -- any any anywhere anywhere 4 0 0 ACCEPT all -- docker0 any anywhere anywhere 5 0 0 ACCEPT all -- bridge1 any anywhere anywhere Chain DOCKER-INTERNAL (1 references) num pkts bytes target prot opt in out source destination Chain DOCKER-USER (1 references) num pkts bytes target prot opt in out source destination -P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT -N DOCKER -N DOCKER-BRIDGE -N DOCKER-CT -N DOCKER-FORWARD -N DOCKER-INTERNAL -N DOCKER-USER -A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-FORWARD -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT -A DOCKER ! -i docker0 -o docker0 -j DROP -A DOCKER ! -i bridge1 -o bridge1 -j DROP -A DOCKER-BRIDGE -o docker0 -j DOCKER -A DOCKER-BRIDGE -o bridge1 -j DOCKER -A DOCKER-CT -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-CT -o bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-FORWARD -j DOCKER-CT -A DOCKER-FORWARD -j DOCKER-INTERNAL -A DOCKER-FORWARD -j DOCKER-BRIDGE -A DOCKER-FORWARD -i docker0 -j ACCEPT -A DOCKER-FORWARD -i bridge1 -j ACCEPT关键规则逐条解读DOCKER链第 1 条-d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp --dport 80 -j ACCEPT即“转发给容器 192.0.2.2:80、但不从 bridge1 进入也就是经 DNAT 改写的流量”的报文放行。这条 per-port 放行规则由 iptabler/port.go 中的setPerPortForwarding插入注释中明确说明每端口 ACCEPT 规则必须位于该网络创建时追加的 per-network DROP 规则之前。DOCKER链第 2、3 条对docker0和bridge1各有一条“默认 DROP”规则! -i xxx -o xxx -j DROP配合上面的 per-port ACCEPT实现“未发布端口一律禁止从主机侧直达容器”的最小化暴露。DOCKER-CT链放行与容器网桥相关的RELATED,ESTABLISHED回包DOCKER-USER空链保留给用户自定义规则。与回环地址相关的部分在 filter 表中没有任何特殊规则——回环限制完全由后面的 raw 表承担。nat 表DNAT 到容器MASQUERADE 出网Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any any anywhere !loopback/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 MASQUERADE all -- any !bridge1 192.0.2.0/24 anywhere 2 0 0 MASQUERADE all -- any !docker0 172.17.0.0/16 anywhere Chain DOCKER (2 references) num pkts bytes target prot opt in out source destination 1 0 0 DNAT tcp -- !bridge1 any anywhere localhost tcp dpt:http-alt to:192.0.2.2:80 -P PREROUTING ACCEPT -P INPUT ACCEPT -P OUTPUT ACCEPT -P POSTROUTING ACCEPT -N DOCKER -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A DOCKER -d 127.0.0.1/32 ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80回环场景在 nat 表中的体现是DOCKER链中这条 DNAT 规则-A DOCKER -d 127.0.0.1/32 ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80对应源码位于 iptabler/port.go 的setPerPortNAT当HostIP不是通配地址本例为 127.0.0.1时-d参数取b.HostIP.String()精确匹配该地址! -i bridge1条件则在未开启 hairpin 时排除从本桥进入的流量n.ipt.config.Hairpin为 false 时追加该匹配见源码第 98–100 行。POSTROUTING 中两条 MASQUERADE 负责容器子网出网伪装属于所有 bridge 网络的公共规则与本场景无特殊关系。raw 表本场景的独有差异文档末尾展示的是 raw 表快照与等价的 iptables 命令Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DROP all -- !bridge1 any anywhere 192.0.2.2 2 0 0 DROP tcp -- !lo any anywhere localhost tcp dpt:http-alt Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination-P PREROUTING ACCEPT -P OUTPUT ACCEPT -A PREROUTING -d 192.0.2.2/32 ! -i bridge1 -j DROP -A PREROUTING -d 127.0.0.1/32 ! -i lo -p tcp -m tcp --dport 8080 -j DROP两条 DROP 规则各管一事-d 192.0.2.2/32 ! -i bridge1 -j DROP凡是目的为容器 IP 192.0.2.2、但没有从容器所在桥bridge1进入的报文即绕过桥、经其他接口直连容器地址的“routed direct access”在 PREROUTING 阶段直接丢弃。从源码结构看iptabler/port.go 的dropLegacyFilterDirectAccess中有一段版本演进说明此类“直连容器 IP 的流量一律在 raw-PREROUTING 丢弃”的规则自 28.2.0 起不再按端口创建而是在端点创建时统一建立本函数只负责清理 28.0.x 时代遗留的旧规则。-d 127.0.0.1/32 ! -i lo -p tcp --dport 8080 -j DROP这就是本文档的主角——凡是目的为 127.0.0.1:8080、但入口不是回环接口lo的报文直接 DROP。由于 raw 表在 netfilter 处理链中位于 conntrack 与 filter/nat 之前远程报文在建立连接跟踪、进入 DNAT 之前就被丢弃“端口仅绑定回环地址”的语义由此得到内核级强制保证。源码实现filterPortMappedOnLoopback原始文档最后一句点明机制来源filterPortMappedOnLoopback会向 raw-PREROUTING 链追加一条 DROP 规则丢弃发往“发布在回环地址上的端口”的远程流量。仓库中该函数有两个后端实现分别对应 iptables 与 nftables 防火墙。iptables 后端iptabler/port.go 中的实现// filterPortMappedOnLoopback adds an iptables rule that drops remote // connections to ports mapped on loopback addresses. // // This is a no-op if the portBinding is for IPv6 (IPv6 loopback address is // non-routable), or over a network with gw_moderouted (PBs in routed mode // dont map ports on the host). func filterPortMappedOnLoopback(ctx context.Context, b types.PortBinding, hostIP net.IP, wsl2Mirrored, enable bool) error { if rawRulesDisabled(ctx) { return nil } if b.HostPort 0 || !hostIP.IsLoopback() || hostIP.To4() nil { return nil } ... }要点触发条件只有HostPort ! 0NAT 生效、HostIP是回环地址且为 IPv4 时才添加规则IPv6 回环不可路由、gw_moderouted网络不在主机上映射端口因此均为 no-op。这与文档注释一致。DROP 规则-p tcp -d hostIP --dport hostPort ! -i lo -j DROP即文档 raw 表第 2 条规则的来源注释标记为LOOPBACK FILTERING - DROP。WSL2 mirrored 特例在wsl2Mirrored模式下会先追加一条-i loopback0 -j ACCEPTLOOPBACK FILTERING - ACCEPT MIRRORED允许 WSL2 镜像网络经loopback0接口进入的回环流量再落到 DROP 规则上——规则顺序保证 ACCEPT 优先。逃生开关rawRulesDisabled检查环境变量DOCKER_INSECURE_NO_IPTABLES_RAW1设置后跳过所有 raw 规则含本条 DROP 与直连过滤变量名中的 “INSECURE” 表明这是不推荐的调试手段。该函数在setPerPortIptables中为每个端口绑定调用第 50 行先于 NAT 与 FORWARD 规则的写入删除端口时以enablefalse走同样的匹配删除逻辑appendOrDelChainRule。nftables 后端nftabler/port.go 中语义完全对应的实现updater(nftables.Rule{ Chain: rawPreroutingChain, Group: rawPreroutingPortsRuleGroup, Rule: []string{ iifname ! lo ip daddr, pb.HostIP.String(), pb.Proto.String(), dport, strconv.Itoa(int(pb.HostPort)), counter drop comment DROP REMOTE LOOPBACK, }, })nftables 版本将规则放入统一的rawPreroutingChain并按组管理counter drop comment DROP REMOTE LOOPBACK与 iptables 版的LOOPBACK FILTERING - DROP一一对应WSL2 特例同样存在ACCEPT WSL2 LOOPBACK。setPerPortRules第 61–77 行中四个规则族的写入顺序为转发放行 → DNAT → hairpin 伪装 → 回环过滤与 iptables 后端的调用次序保持同一逻辑。文档如何生成TestBridgeIptablesDoc集成测试这份“生成文件”首行标注!-- This is a generated file; DO NOT EDIT. --并非手写而是由 integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go 中的TestBridgeIptablesDoc自动生成并与仓库内 golden 文件比对。流程如下场景声明测试文件顶部的index列出所有场景小节。本场景的声明位于第 176–186 行name: usernet-portmap-lo.md容器c1的端口映射为80/tcp→HostIP127.0.0.1, HostPort8080与文档开头的docker run -p 127.0.0.1:8080:80完全对应网络 IPAM 固定使用192.0.2.0/24 / 192.0.2.1docNetworks/docGateways变量第 50–52 行保证快照中的地址可复现。真实运行为每个小节创建独立 L3 网段与网络命名空间在其中启动 dockerd按声明创建 bridge 网络与容器createBridgeNetworks通过 API 指定com.docker.network.bridge.name、子网、网关及--internal、--iccfalse、gw_mode等选项然后runIptables先iptables -Z清零计数器再采集 filter/nat/raw 三张表的-vL --line-numbers与-S输出并用正则把包计数统一替换为 0 以保证可比性。模板渲染templates/usernet-portmap-lo.md 是text/template用{{index . LFilter4}}、{{index . LNat4}}、{{index . LRaw4}}、{{index . SRaw4}}等占位符嵌入各表输出——这也解释了为什么生成文件里 filter/nat/raw 三块内容呈现“列表 -S命令”成对出现的形式。Golden 比对渲染结果先写入 bundles 目录再与 generated/usernet-portmap-lo.md 做 golden 断言任何规则差异都会导致测试失败强制维护者在规则变更时同步更新模板描述。测试包注释说明确认 diff 属于预期变更后用TESTFLAGS-update重跑即可刷新参考文档。运行前提测试在 firewalld 运行、rootless 或 nftables 防火墙后端下会自动跳过第 216–218 行。另外 index.md 明确提示Docker 的 iptables 结构是开发用途、非稳定接口版本之间规则会变化本文所引用的规则快照仅对应当前仓库版本。小结在“端口发布到 127.0.0.1”的场景下filter 表与 nat 表和普通端口发布场景一致DNAT 把127.0.0.1:8080改写到192.0.2.2:80filter 的DOCKER链做 per-port 放行与默认 DROP。真正的差异在raw-PREROUTINGfilterPortMappedOnLoopback追加的-d 127.0.0.1/32 ! -i lo -p tcp --dport 8080 -j DROP规则在连接跟踪建立前就丢弃远程回环流量使“仅本机可访问”从约定变为强制。该机制对 IPv6 回环、gw_moderouted不生效no-op可通过DOCKER_INSECURE_NO_IPTABLES_RAW1整体禁用 raw 规则不推荐。上述全部快照来自TestBridgeIptablesDoc的真实运行采集与 golden 文件比对可通过 integration/network/bridge/iptablesdoc/ 目录下的其余场景文档new-daemon、usernet-portmap、usernet-internal、swarm-portmap 等对照理解 Moby 在各类网络配置下的 iptables 全貌。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价