我有一个运行防火墙的 Debian 机器,设置了网关 NAT/路由器。此设备有两个 NICS;wan
--- 公共接口,分配给external
防火墙区域,使用 DHCP 动态分配 IP 地址 ---,和lan1
--- 私有接口,分配给trusted
防火墙区域,静态分配 IP 地址192.168.0.1/16
。系统还配置为使用防火墙进行 NAT 和端口转发:
(router) $> sudo firewall-cmd --info-zone=external
external (active)
target: DROP
icmp-block-inversion: yes
interfaces: wan
sources:
services: http https ssh
ports: 8443/tcp
protocols:
forward: yes
masquerade: yes
forward-ports:
port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2
port=443:proto=tcp:toport=443:toaddr=192.168.0.2
port=80:proto=tcp:toport=80:toaddr=192.168.0.2
source-ports:
icmp-blocks: echo-reply echo-request fragmentation-needed neighbour-advertisement neighbour-solicitation packet-too-big port-unreachable router-advertisement router-solicitation time-exceeded
rich rules:
(router) $> sudo firewall-cmd --info-zone=trusted
trusted (active)
target: ACCEPT
icmp-block-inversion: no
interfaces: lan1 lo
sources:
services: ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
(router) $> sudo sysctl -n net.ipv4.ip_forward
1
到目前为止一切顺利。内部192.168.0.0/16
网络中的设备可以毫无问题地访问互联网,并且接口上wan
到端口 80、443 和 8443 的传入连接被正确转发到192.168.0.2
反向代理实例正在监听的位置。
当我想从内部设备使用路由器的公开 IP 地址访问 上的反向代理时,就会出现问题192.168.0.2
。为了说明这一点,我有两个与 的公共地址关联的公共 DNS 记录wan
;hq.mydomain.tld
,它通过 Cloudflare 代理,而gateway.mydomain.tld
,它指向直接地到 的公共 IP 地址wan
。如果我hq.mydomain.tld
从内部设备访问,请求将正确路由到192.168.0.2
,正如预期的那样,因为请求首先发送到 Cloudflare,然后代理回我的网关:
$> curl -sS -D - https://hq.mydomain.tld -o /dev/null -L
HTTP/2 200
date: Sat, 16 Dec 2023 16:43:09 GMT
content-type: text/html; charset=utf-8
cache-control: public, max-age=600
...
但是,如果我gateway.mydomain.tld
从内部设备发起请求,则似乎没有应用任何规则,请求直接到达路由器。路由器上的转发端口上没有任何监听,因此请求立即失败。
$> curl -sS -D - https://gateway.mydomain.tld -o /dev/null -L
curl: (7) Failed to connect to gateway.mydomain.tld port 443 after 713 ms: Couldn't connect to server
如果我不使用公共主机名而是使用路由器的公共 IP,也会发生同样的事情。
$> curl -sS -D - https://a.b.c.d -o /dev/null -L
curl: (7) Failed to connect to a.b.c.d port 443 after 18 ms: Couldn't connect to server
curl
如果我从路由器本身使用它自己的公共 IP 地址或以下情况,也会发生这种情况localhost
:
(router) $> curl -sS -D - https://a.b.c.d. -o /dev/null -L
curl: (7) Failed to connect to a.b.c.d port 443 after 0 ms: Couldn't connect to server
(router) $> curl -sS -D - https://localhost -o /dev/null -L
curl: (7) Failed to connect to localhost port 443 after 0 ms: Couldn't connect to server
我的问题是:如何配置firewalld(或其后端,nftables)以将公共端口转发规则应用于来自内部网络或路由器本身的流量?
请注意,我不能使用直接使用公共 IP 的规则wan
,因为这是 DHCP 分配的 IP 地址,并且可能随时发生变化,从而导致规则无效。
我尝试了很多不同的方法,但都没有成功:
在区域上设置相同的端口转发规则
trusted
。在环回接口上使用直接的firewalld端口转发规则。
最后,我尝试创建一个防火墙策略,使用
trusted
(内部区域)作为入口区域和HOST
出口区域:(router) $> sudo firewall-cmd --info-policy=internal_fwd internal_fwd (active) priority: -1 target: CONTINUE ingress-zones: trusted egress-zones: HOST services: ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:
我确实认为这会有效,因为这样的策略适用于来自内部网络并终止于路由器的任何流量。但是,似乎您不能使用带有将
egress-zones: HOST
端口转发到另一台设备的策略:(router) $> sudo firewall-cmd --permanent --policy=internal_fwd --add-forward-port=port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2 Error: INVALID_FORWARD: Policy 'internal_fwd': A 'forward-port' with 'to-addr' is invalid for egress zone 'HOST'
答案1
最常见的做法是从外部到内部进行 NAT。首先为该区域启用伪装(我在此示例中使用公共)
firewall-cmd --zone=public --add-masquerade
firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toaddr=192.168.x.x:toport=80
哦,是的,你还需要启用net.ipv4.ip_forward = 1
答案2
这是一个NAT 发夹结构:此处最终目标与原始源位于同一 LAN 中,因此它将尝试直接回复它,绕过回复必须进行的 NAT 逆转换。由于源不知道最终目标地址(它不知道 192.168.0.2,因此来自此源的 TCP 回复将被丢弃并带有 TCP RST)。
为了处理 NAT 发夹式连接,回复还必须经过 NAT 路由器,以便进行逆 NAT 转换。一种简单的方法是除了目标地址外,还对源地址执行 NAT。任何可以让反向代理服务器通过 NAT 路由器回复的地址都是可以接受的:其 LAN 地址、其公共地址或任何其他地址(包括虚构地址),只要 NAT 路由器作为反向代理服务器的网关即可。这里,为简单起见,将使用 LAN 地址(通过选择性使用masquerade
)。
纯净nftables这可以通过 nat/postrouting 中的以下规则来处理:
iif lan1 oif lan1 ct status dnat masquerade
但这无法在防火墙. 而是使用源地址沿输出接口富人统治可以达到同样的效果。富人的统治将在值得信赖源位于 192.168.0.0/16 内时,才会进入区域。仅当数据包从 192.168.0.0/16 路由回 192.168.0.0/16(即路由到局域网1即值得信赖),这意味着执行了 NAT,否则这种情况不会发生(除了在值得信赖区域将使用网关来访问其他 192.168.0.0/16 地址(而不是直接访问):
firewall-cmd --zone=trusted --add-rich-rule='rule family="ipv4" source address="192.168.0.0/16" masquerade'
这导致了nftables后端规则集:
--- /tmp/ruleset1 2023-12-20 10:22:31.937292389 +0000
+++ /tmp/ruleset2 2023-12-20 10:26:31.155214737 +0000
@@ -519,6 +519,7 @@
}
chain nat_POST_trusted_allow {
+ ip saddr 192.168.0.0/16 oifname != "lo" masquerade
}
chain nat_POST_trusted_post {
这正是预期的结果。
唉,这还不够。因为端口转发规则被添加到外部的区域,实际上没有发生 DNAT,因为唯一被遍历的区域是值得信赖区域(绑定到lan1
接口),但仅当数据包穿越外部的区域(与接口绑定wan
),以及防火墙似乎没有后备方案(相关部分在nftables后端使用goto
语句而不是jump
语句,从而阻止了进一步的链的遍历)。端口转发规则必须在值得信赖区域,因此它们也针对这种情况执行:
firewall-cmd --zone=trusted --add-forward-port=port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2
firewall-cmd --zone=trusted --add-forward-port=port=443:proto=tcp:toport=443:toaddr=192.168.0.2
firewall-cmd --zone=trusted --add-forward-port=port=80:proto=tcp:toport=80:toaddr=192.168.0.2
--permanent
一旦验证了正确的行为,则在所有先前的命令上使用。
注意:反向代理的日志将全部显示 NAT 路由器的 192.168.0.1 作为所有客户端的源lan1
。在富规则语言的限制下,这似乎是不可避免的。直接使用nftables,可以在 nat/postrouting 中执行前缀更改,例如:
snat ip prefix to ip saddr map { 192.168.0.0/16 : 10.168.0.0/16 }
仍然能够区分单个客户和值得信赖代理日志中的区域与反向映射一致(例如:10.168.1.2 表示客户端实际上是 192.168.1.2)。我不知道如何在防火墙。
更新:路由器 NAT 的情况从自身到自身可以使用以下方法处理防火墙 政策,但即使在这里也需要解决方法。
积极政策
仅当以下所有条件均满足时,策略才会生效。
入口区域列表包含最后一个常规区域或单个符号区域。
出口区域列表包含最后一个常规区域或单个符号区域。
对于非符号区域,区域必须处于活动状态也就是说,它必须有分配给它的接口或源。
如果该政策未生效,则该政策无效。
象征性区域:
HOST
此符号区域用于流向或流出运行firewalld的主机的流量。这对应于netfilter(iptables / nftables)链INPUT和OUTPUT。
如果在出口区域列表中使用,它将适用于 INPUT 链上的流量。
如果用于入口区域列表,它将应用于 输出链。
需要解决方法:入口和出口都是必需的,HOST 只能使用一次,并且必须在入口将规则添加到 nat/output,并且端口转发规则要求出口区域没有添加接口,否则会出现如下错误:
Error: INVALID_ZONE: Policy 'nat-hairpin-host': 'forward-port' cannot be used because egress zone 'trusted' has assigned interfaces
会发生。
因此使用 HOST入口并回收未使用的区域:内部的用作出口区域,而不是lo
绑定到它的接口,而是接口上的主机地址wan
,以满足激活策略的所有先决条件(并且仅在到达 WAN 地址时)。可以使用其他未使用的区域,甚至可以通过添加 XML 文件来定义额外的自定义区域(例如:按照这) 代替内部的。与所选的额外区域不会有太多交互,尤其是考虑到过滤器/输入和输出规则始终明确授予对接口的访问权限lo
。
关于使用策略的文档没有提供太多来自 CLI 的示例,并且描述了一个 XML 文件。仍然RHEL 的文档提供了示例(与本问题无关)。策略只能在 中使用--permanent
(因此需要在 末尾--reload
)。
下面的说明将认为 192.0.2.2/24 是用于wan
访问服务的地址。因此最后:
firewall-cmd --permanent --new-policy nat-hairpin-host
firewall-cmd --permanent --policy=nat-hairpin-host --add-ingress-zone=HOST
firewall-cmd --permanent --policy=nat-hairpin-host --add-egress-zone=internal
firewall-cmd --permanent --policy=nat-hairpin-host --add-forward-port=port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2
firewall-cmd --permanent --policy=nat-hairpin-host --add-forward-port=port=443:proto=tcp:toport=443:toaddr=192.168.0.2
firewall-cmd --permanent --policy=nat-hairpin-host --add-forward-port=port=80:proto=tcp:toport=80:toaddr=192.168.0.2
firewall-cmd --permanent --zone=internal --add-source=192.0.2.2
firewall-cmd --reload
最终将创建管道规则和等效过滤规则,类似于以下内容:
[...]
chain nat_OUTPUT_POLICIES_pre {
ip daddr 192.0.2.2 jump nat_OUT_policy_nat-hairpin-host
}
[...]
chain nat_OUT_policy_nat-hairpin-host_allow {
meta nfproto ipv4 tcp dport 443 dnat ip to 192.168.0.2:443
meta nfproto ipv4 tcp dport 80 dnat ip to 192.168.0.2:80
meta nfproto ipv4 tcp dport 8443 dnat ip to 192.168.0.2:8443
}
反向代理将看到源是 WAN 地址(如果设置了多个地址,则为 WAN IP LAN 的主地址),因为此源未更改(并且不必更改),并且这是 NAT 路由器在 DNAT 将其重新路由到其他地方之前到达自身时选择的源。
# ip route get 192.0.2.2
local 192.0.2.2 dev lo src 192.0.2.2 uid 0
cache <local>