理解多区域配置中的 Firewalld

作者: Nathan Vance

服务器被入侵和数据盗窃的新闻充斥着今天的报纸。对于那些阅读过信息丰富的博客文章的人来说,通过配置错误的服务器访问系统、利用最近暴露的漏洞或使用被盗密码获得控制权并非难事。在典型的 Linux 服务器上发现的许多互联网服务中的任何一个都可能潜藏着漏洞,从而授予对系统的未授权访问。

由于在应用层面上针对每一种可能的威胁来加固系统是一项不可能完成的任务,防火墙通过限制对系统的访问来提供安全保障。防火墙根据传入数据包的源 IP、目标端口和协议来过滤数据包。这样,只有少数 IP/端口/协议组合与系统交互,其余的则不交互。

Linux 防火墙由 netfilter 处理,netfilter 是一个内核级框架。十多年来,iptables 一直为 netfilter 提供用户空间抽象层。iptables 使数据包经受一系列规则的考验,如果规则的 IP/端口/协议组合与数据包匹配,则应用该规则,导致数据包被接受、拒绝或丢弃。

Firewalld 是 netfilter 的一个较新的用户空间抽象层。不幸的是,由于缺乏描述多区域配置的文档,它的强大功能和灵活性被低估了。本文提供了示例来弥补这种情况。

Firewalld 设计目标

firewalld 的设计者意识到,大多数 iptables 使用案例仅涉及少数唯一的 IP 源,对于每个 IP 源,都允许一个服务白名单,其余的都被拒绝。为了利用这种模式,firewalld 将传入流量分类到由源 IP 和/或网络接口定义的区域中。每个区域都有自己的配置,以根据指定的标准接受或拒绝数据包。

相对于 iptables 的另一个改进是简化的语法。Firewalld 通过使用服务名称而不是其端口和协议来更容易地指定服务——例如,samba 而不是 UDP 端口 137 和 138 以及 TCP 端口 139 和 445。它还通过消除对语句顺序的依赖性(iptables 的情况)来进一步简化语法。

最后,firewalld 允许交互式修改 netfilter,从而允许防火墙中的更改独立于存储在 XML 中的永久配置而发生。因此,以下是一个临时修改,将在下次重新加载时被覆盖


# firewall-cmd <some modification>

而以下是一个永久性更改,它将在重启后持续存在


# firewall-cmd --permanent <some modification>
# firewall-cmd --reload

区域

firewalld 中最顶层的组织是区域。如果数据包与该区域关联的网络接口或 IP/掩码源匹配,则该数据包是该区域的一部分。有几个预定义的区域可用


# firewall-cmd --get-zones
block dmz drop external home internal public trusted work

活动区域是任何配置了接口和/或源的区域。要列出活动区域


# firewall-cmd --get-active-zones
public
  interfaces: eno1 eno2

接口是系统中硬件和虚拟网络适配器的名称,您可以在上面的示例中看到。所有活动接口都将分配给区域,要么分配给默认区域,要么分配给用户指定的区域。但是,一个接口不能分配给多个区域。

在其默认配置中,firewalld 将所有接口与公共区域配对,并且不为任何区域设置源。因此,公共区域是唯一的活动区域。

是传入 IP 地址范围,也可以分配给区域。一个源(或重叠的源)不能分配给多个区域。这样做会导致未定义的行为,因为它不清楚应该将哪些规则应用于该源。

由于指定源不是必需的,因此对于每个数据包,都会有一个具有匹配接口的区域,但不必一定有一个具有匹配源的区域。这表明某种形式的优先级,优先级转到更具体的源区域,但稍后会详细介绍。首先,让我们检查一下公共区域是如何配置的


# firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eno1 eno2
  sources:
  services: dhcpv6-client ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
# firewall-cmd --permanent --zone=public --get-target
default

逐行浏览输出

  • public (default, active) 表示公共区域是默认区域(接口在启动时默认使用它),并且它是活动的,因为它至少有一个与之关联的接口或源。

  • interfaces: eno1 eno2 列出了与该区域关联的接口。

  • sources: 列出了该区域的源。现在没有任何源,但如果有,它们将是 xxx.xxx.xxx.xxx/xx 的形式。

  • services: dhcpv6-client ssh 列出了允许通过防火墙的服务。您可以通过执行 firewall-cmd --get-services 来获得 firewalld 定义的服务的详尽列表。

  • ports: 列出了允许通过防火墙的端口目标。如果您需要允许 firewalld 中未定义的服务,这将非常有用。

  • masquerade: no 表示此区域已禁用 IP 伪装。如果启用,这将允许 IP 转发,您的计算机将充当路由器。

  • forward-ports: 列出了转发的端口。

  • icmp-blocks: 被阻止的 icmp 流量的黑名单。

  • rich rules: 高级配置,在一个区域中首先处理。

  • default 是区域的目标,它决定了对与该区域匹配但未被上述设置之一显式处理的数据包采取的操作。

一个简单的单区域示例

假设您只想锁定您的防火墙。只需删除当前公共区域允许的服务并重新加载


# firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --reload

这些命令产生以下防火墙


# firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eno1 eno2
  sources:
  services:
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
# firewall-cmd --permanent --zone=public --get-target
default

本着尽可能保持安全的精神,如果出现需要临时打开防火墙漏洞的情况(可能用于 ssh),您可以仅为当前会话添加服务(省略 --permanent),并指示 firewalld 在指定时间后恢复修改


# firewall-cmd --zone=public --add-service=ssh --timeout=5m

timeout 选项采用秒 (s)、分钟 (m) 或小时 (h) 为单位的时间值。

目标

当区域由于其源或接口而处理数据包时,但没有明确处理该数据包的规则时,区域的目标决定了行为

  • ACCEPT:接受数据包。

  • %%REJECT%%:拒绝数据包,返回拒绝回复。

  • DROP:丢弃数据包,不返回任何回复。

  • default:什么都不做。该区域对问题不闻不问,并将其“向上”踢。

在 firewalld 0.3.9 中存在一个错误(在 0.3.10 中修复),对于目标不是 default 的源区域,无论允许的服务如何,都会应用目标。例如,目标为 DROP 的源区域将丢弃所有数据包,即使它们在白名单中也是如此。不幸的是,此版本的 firewalld 被打包用于 RHEL7 及其衍生版本,导致它成为一个相当常见的错误。本文中的示例避免了会表现出此行为的情况。

优先级

活动区域履行两种不同的角色。具有关联接口的区域充当接口区域,而具有关联源的区域充当源区域(一个区域可以同时履行这两种角色)。Firewalld 按以下顺序处理数据包

  1. 对应的源区域。可能存在零个或一个这样的区域。如果源区域处理数据包,因为数据包满足丰富规则、服务在白名单中,或者目标不是 default,则在此处结束。否则,我们将数据包传递下去。

  2. 对应的接口区域。总是会存在且仅存在一个这样的区域。如果接口区域处理数据包,则在此处结束。否则,我们将数据包传递下去。

  3. firewalld 默认操作。接受 icmp 数据包,拒绝其他所有内容。

最重要的信息是源区域优先于接口区域。因此,多区域 firewalld 配置的通用设计模式是创建一个特权源区域,以允许特定 IP 对系统服务进行提升的访问,并创建一个限制性接口区域,以限制其他所有人的访问。

一个简单的多区域示例

为了演示优先级,让我们将公共区域中的 ssh 交换为 http,并为我们最喜欢的 IP 地址 1.1.1.1 设置默认的内部区域。以下命令完成此任务


# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --permanent --zone=public --add-service=http
# firewall-cmd --permanent --zone=internal --add-source=1.1.1.1
# firewall-cmd --reload

这将导致以下配置


# firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eno1 eno2
  sources:
  services: dhcpv6-client http
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
# firewall-cmd --permanent --zone=public --get-target
default
# firewall-cmd --zone=internal --list-all
internal (active)
  interfaces:
  sources: 1.1.1.1
  services: dhcpv6-client mdns samba-client ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
# firewall-cmd --permanent --zone=internal --get-target
default

使用上述配置,如果有人尝试从 1.1.1.1 ssh 进入,请求将成功,因为源区域(内部)首先应用,并且它允许 ssh 访问。

如果有人尝试从其他地方(例如 2.2.2.2)ssh 进入,则不会有源区域,因为没有区域与该源匹配。因此,请求将直接传递到接口区域(公共),该区域未显式处理 ssh。由于公共区域的目标是 default,因此请求将传递到 firewalld 默认操作,即拒绝它。

如果 1.1.1.1 尝试 http 访问会怎样?源区域(内部)不允许它,但目标是 default,因此请求将传递到接口区域(公共),该区域授予访问权限。

现在假设有人从 3.3.3.3 恶意访问您的网站。要限制该 IP 的访问,只需将其添加到预配置的 drop 区域,该区域被恰当地命名为因为它会丢弃所有连接


# firewall-cmd --permanent --zone=drop --add-source=3.3.3.3
# firewall-cmd --reload

下次 3.3.3.3 尝试访问您的网站时,firewalld 将首先将请求发送到源区域 (drop)。由于目标是 DROP,因此请求将被拒绝,并且不会到达接口区域(公共)而被接受。

一个实用的多区域示例

假设您正在为组织中的服务器设置防火墙。您希望全世界都可以访问 http 和 https,您的组织 (1.1.0.0/16) 和工作组 (1.1.1.0/8) 可以访问 ssh,而您的工作组可以访问 samba。使用 firewalld 中的区域,您可以以直观的方式设置此配置。

鉴于命名,为您的全球用途征用公共区域,为本地使用征用内部区域似乎是合乎逻辑的。首先,将公共区域中的 dhcpv6-client 和 ssh 服务替换为 http 和 https


# firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --permanent --zone=public --add-service=http
# firewall-cmd --permanent --zone=public --add-service=https

然后从内部区域中删除 mdns、samba-client 和 dhcpv6-client(仅留下 ssh),并将您的组织添加为源


# firewall-cmd --permanent --zone=internal --remove-service=mdns
# firewall-cmd --permanent --zone=internal --remove-service=samba-client
# firewall-cmd --permanent --zone=internal --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=internal --add-source=1.1.0.0/16

为了适应您提升的工作组 samba 权限,添加一个丰富规则


# firewall-cmd --permanent --zone=internal --add-rich-rule='rule
 ↪family=ipv4 source address="1.1.1.0/8" service name="samba"
 ↪accept'

最后,重新加载,将更改拉入活动会话


# firewall-cmd --reload

只剩下几个细节。尝试从内部区域之外的 IP ssh 进入您的服务器会导致拒绝消息,这是 firewalld 默认设置。更安全的做法是表现出非活动 IP 的行为,而是丢弃连接。将公共区域的目标更改为 DROP 而不是 default 以完成此操作


# firewall-cmd --permanent --zone=public --set-target=DROP
# firewall-cmd --reload

但是等等,您再也无法 ping 了,即使是从内部区域 ping 也不行!并且 icmp(ping 协议)不在 firewalld 可以列入白名单的服务列表中。这是因为 icmp 是 IP 第 3 层协议,没有端口的概念,这与绑定到端口的服务不同。在将公共区域设置为 DROP 之前,ping 可以通过防火墙,因为您的两个 default 目标都将其传递给 firewalld 默认设置,该设置允许它。现在它被丢弃了。

要恢复内部网络的 ping,请使用丰富规则


# firewall-cmd --permanent --zone=internal --add-rich-rule='rule
 ↪protocol value="icmp" accept'
# firewall-cmd --reload

总而言之,这是两个活动区域的配置


# firewall-cmd --zone=public --list-all
public (default, active)
  interfaces: eno1 eno2
  sources:
  services: http https
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
# firewall-cmd --permanent --zone=public --get-target
DROP
# firewall-cmd --zone=internal --list-all
internal (active)
  interfaces:
  sources: 1.1.0.0/16
  services: ssh
  ports:
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
        rule family=ipv4 source address="1.1.1.0/8"
         ↪service name="samba" accept
        rule protocol value="icmp" accept
# firewall-cmd --permanent --zone=internal --get-target
default

此设置演示了一个三层嵌套防火墙。最外层公共区域是一个接口区域,跨越整个世界。下一层内部区域是一个源区域,跨越您的组织,它是公共区域的子集。最后,一个丰富规则添加了最内层,跨越您的工作组,它是内部区域的子集。

这里最重要的信息是,当一个场景可以分解为嵌套层时,最广泛的层应使用接口区域,下一层应使用源区域,其他层应使用源区域内的丰富规则。

调试

Firewalld 采用直观的范例来设计防火墙,但比其前身 iptables 更容易引起歧义。如果发生意外行为,或者为了更好地理解 firewalld 的工作原理,获得 netfilter 配置为如何运行的 iptables 描述可能会很有用。以下是先前示例的输出,为了简单起见,已修剪了 forward、output 和 logging 行


# iptables -S
-P INPUT ACCEPT
... (forward and output lines) ...
-N INPUT_ZONES
-N INPUT_ZONES_SOURCE
-N INPUT_direct
-N IN_internal
-N IN_internal_allow
-N IN_internal_deny
-N IN_public
-N IN_public_allow
-N IN_public_deny
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -j INPUT_ZONES_SOURCE
-A INPUT -j INPUT_ZONES
-A INPUT -p icmp -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -j REJECT --reject-with icmp-host-prohibited
... (forward and output lines) ...
-A INPUT_ZONES -i eno1 -j IN_public
-A INPUT_ZONES -i eno2 -j IN_public
-A INPUT_ZONES -j IN_public
-A INPUT_ZONES_SOURCE -s 1.1.0.0/16 -g IN_internal
-A IN_internal -j IN_internal_deny
-A IN_internal -j IN_internal_allow
-A IN_internal_allow -p tcp -m tcp --dport 22 -m conntrack
 ↪--ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p udp -m udp --dport 137
 ↪-m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p udp -m udp --dport 138
 ↪-m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p tcp -m tcp --dport 139
 ↪-m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p tcp -m tcp --dport 445
 ↪-m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -p icmp -m conntrack --ctstate NEW
 ↪-j ACCEPT
-A IN_public -j IN_public_deny
-A IN_public -j IN_public_allow
-A IN_public -j DROP
-A IN_public_allow -p tcp -m tcp --dport 80 -m conntrack
 ↪--ctstate NEW -j ACCEPT
-A IN_public_allow -p tcp -m tcp --dport 443 -m conntrack
 ↪--ctstate NEW -j ACCEPT

在上面的 iptables 输出中,首先声明了新的链(以 -N 开头的行)。其余的是附加到 iptables 的规则(以 -A 开头)。已建立的连接和本地流量被接受,传入数据包进入 INPUT_ZONES_SOURCE 链,此时 IP 被发送到相应的区域(如果存在)。之后,流量进入 INPUT_ZONES 链,此时它被路由到接口区域。如果它在那里没有被处理,则 icmp 被接受,无效的被丢弃,其他所有内容都被拒绝。

结论

Firewalld 是一种文档不足的防火墙配置工具,其潜力超出了许多人的认识。凭借其创新的区域范例,firewalld 允许系统管理员将流量分解为类别,每个类别都接受独特的处理,从而简化了配置过程。由于其直观的设计和语法,它对于简单的单区域配置和复杂的多区域配置都很实用。

加载 Disqus 评论