介绍
实施防火墙是保护服务器的重要一步。 其中很大一部分是决定将对您的网络实施流量限制的个别规则和策略。 iptables
之类的防火墙还允许您对应用规则的结构框架有发言权。
在本指南中,您将学习如何构建可以作为更复杂规则集的基础的防火墙。 该防火墙将主要专注于提供合理的默认值并建立一个鼓励可扩展性的框架。
先决条件
要完成本教程,您需要使用配置有 sudo
权限的非 root 用户访问 Ubuntu 20.04 服务器。 您可以按照我们的 Ubuntu 20.04 初始服务器设置指南 中列出的所有步骤来完成此操作,除了 Step 4,因为我们将在本教程中设置防火墙。
此外,我们建议您查看您希望实施的防火墙策略。 您可以按照 本指南 更好地了解要考虑的事项。
安装持久防火墙服务
首先更新本地包缓存:
sudo apt update
现在安装 iptables-persistent
包。 这允许您保存规则集并在启动时自动应用它们:
sudo apt install iptables-persistent
在安装过程中,系统会询问您是否要保存当前规则,选择' . 请注意,您将运行 netfilter-persistent
命令来执行 iptables
持久防火墙服务。 接下来,您将编辑生成的规则文件。
本指南中有关 IPv6 的说明
在开始之前,我们将简要讨论 IPv4 与 IPv6。 iptables
命令只处理 IPv4 流量。 对于 IPv6 流量,使用名为 ip6tables
的单独配套工具。 规则存储在单独的表和链中。 对于 netfilter-persistent
命令,在 /etc/iptables/rules.v4
中写入和读取 IPv4 规则,而 IPv6 规则存储在 /etc/iptables/rules.v6
中。
本指南假定您 不是 在您的服务器上积极使用 IPv6。 如果您的服务不利用 IPv6,则完全阻止访问会更安全,如本指南中所示。
实施基本防火墙策略(快速方法)
为了尽快启动和运行,我们将向您展示如何直接编辑规则文件并复制和粘贴完成的防火墙策略。 之后,我们将解释一般策略以及如何使用 iptables
命令而不是修改文件来实现这些规则。
要实施防火墙策略和框架,您将编辑 /etc/iptables/rules.v4
和 /etc/iptables/rules.v6
文件。 在您喜欢的文本编辑器中打开 rules.v4
文件。 在这里,我们将使用 nano
:
sudo nano /etc/iptables/rules.v4
在内部,该文件将包含以下内容:
/etc/iptables/rules.v4
# Generated by iptables-save v1.8.4 on Tue Mar 1 19:03:10 2022 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT # Completed on Tue Mar 1 19:03:10 2022
删除这些内容并将其替换为以下内容:
/etc/iptables/rules.v4
*filter # Allow all outgoing, but drop incoming and forwarding packets by default :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Custom per-protocol chains :UDP - [0:0] :TCP - [0:0] :ICMP - [0:0] # Acceptable UDP traffic # Acceptable TCP traffic -A TCP -p tcp --dport 22 -j ACCEPT # Acceptable ICMP traffic # Boilerplate acceptance policy -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -A INPUT -i lo -j ACCEPT # Drop invalid packets -A INPUT -m conntrack --ctstate INVALID -j DROP # Pass traffic to protocol-specific chains ## Only allow new connections (established and related should already be handled) ## For TCP, additionally only allow new SYN packets since that is the only valid ## method for establishing a new TCP connection -A INPUT -p udp -m conntrack --ctstate NEW -j UDP -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP # Reject anything that's fallen through to this point ## Try to be protocol-specific w/ rejection message -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable -A INPUT -p tcp -j REJECT --reject-with tcp-reset -A INPUT -j REJECT --reject-with icmp-proto-unreachable # Commit the changes COMMIT *raw :PREROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *nat :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] COMMIT *security :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *mangle :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] COMMIT
保存并关闭文件。 如果您正在使用 nano
,您可以按 CTRL + X
,然后按 Y
和 ENTER
来执行此操作。
您可以通过运行以下命令来测试文件的语法错误。 如果您收到以下任何错误,请确保修复语法错误:
sudo iptables-restore -t /etc/iptables/rules.v4
接下来打开/etc/iptables/rules.v6
文件修改IPv6规则:
sudo nano /etc/iptables/rules.v6
该文件将包含以下内容:
/etc/iptables/rules.v6
# Generated by ip6tables-save v1.8.4 on Tue Mar 1 19:03:10 2022 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT # Completed on Tue Mar 1 19:03:10 2022
您可以通过将文件内容替换为以下配置来阻止所有 IPv6 流量:
/etc/iptables/rules.v6
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] COMMIT *raw :PREROUTING DROP [0:0] :OUTPUT DROP [0:0] COMMIT *nat :PREROUTING DROP [0:0] :INPUT DROP [0:0] :OUTPUT DROP [0:0] :POSTROUTING DROP [0:0] COMMIT *security :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] COMMIT *mangle :PREROUTING DROP [0:0] :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] :POSTROUTING DROP [0:0] COMMIT
保存并关闭文件。
要测试此文件的语法错误,请使用带有 -t
选项的 ip6tables-restore
命令:
sudo ip6tables-restore -t /etc/iptables/rules.v6
当两个规则文件都没有报告语法错误时,您可以通过运行应用您设置的规则:
sudo service netfilter-persistent reload
Output * Loading netfilter rules... run-parts: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables start run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables start [ OK ]
这将立即实施您文件中列出的政策。 您可以通过列出当前使用的 iptables
规则来验证这一点。 首先检查 IPv4:
sudo iptables -S
Output-P INPUT DROP -P FORWARD DROP -P OUTPUT ACCEPT -N ICMP -N TCP -N UDP -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate INVALID -j DROP -A INPUT -p udp -m conntrack --ctstate NEW -j UDP -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable -A INPUT -p tcp -j REJECT --reject-with tcp-reset -A INPUT -j REJECT --reject-with icmp-proto-unreachable -A TCP -p tcp -m tcp --dport 22 -j ACCEPT
然后检查当前的 IPv6 规则:
sudo ip6tables -S
Output-P INPUT DROP -P FORWARD DROP -P OUTPUT DROP
这些防火墙规则将在每次启动时重新应用。 对其进行测试以确保您仍然可以登录并且所有其他访问都被阻止。
我们的一般防火墙策略的解释
在使用上一节中的规则构建的基本防火墙中,我们创建了一个可扩展的框架,可以对其进行调整以添加或删除规则。 对于 IPv4 流量,我们主要关注 filter
表中的 INPUT
链。 该链将处理所有发往我们服务器的数据包。 我们还允许所有传出流量并拒绝所有数据包转发,这仅适用于该服务器充当其他主机的路由器时。 我们接受所有其他表中的数据包,因为我们只想在本指南中过滤数据包。
一般来说,我们的规则设置了一个防火墙,默认情况下会拒绝传入流量。 然后,我们为希望从该策略中排除的服务和流量类型创建例外。
在主 INPUT
链中,我们为流量添加了一些通用规则,我们相信这些规则将始终以相同的方式处理。 例如,我们总是希望拒绝被认为是“无效”的数据包,并且我们总是希望允许本地环回接口上的流量和与已建立连接相关的数据。
之后,我们根据流量使用的协议匹配流量,并将其打乱到特定协议的链中。 这些特定于协议的链旨在保存匹配并允许特定服务流量的规则。 在此示例中,我们允许的唯一服务是 TCP
链中的 SSH。 如果我们提供其他服务,例如 HTTP(S) 服务器,我们也可以在此处添加例外。 这些链将成为您大部分定制的重点。
任何与协议特定链中的通用规则或服务规则不匹配的流量都由 INPUT
链中的最后几个规则处理。 我们已将防火墙的默认策略设置为 DROP
,这将拒绝不符合我们规则的数据包。 但是,INPUT
链末尾的规则拒绝数据包并向客户端发送一条消息,该消息模拟如果该端口上没有运行服务,服务器将如何响应。
对于 IPv6 流量,我们丢弃所有流量。 我们的服务器没有使用此协议,因此完全不参与流量是最安全的。
使用 iptables
命令实现防火墙
现在您已经了解了我们构建的策略背后的总体思路,我们将讨论如何使用 iptables
命令创建这些规则。 我们最终将得到与上面指定的相同规则,但我们将通过迭代添加规则来创建我们的策略。 因为 iptables
立即应用每个规则,所以规则顺序非常重要(例如,我们将拒绝数据包的规则留到最后)。
重置防火墙
首先重置您的防火墙规则,以便您可以查看如何从命令行构建策略。 通过运行以下命令刷新所有规则:
sudo service netfilter-persistent flush
现在验证您的规则是否已重置:
sudo iptables -S
您应该有输出显示 filter
表中的规则已消失,并且所有链上的默认策略都设置为 ACCEPT
:
Output-P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT
创建特定于协议的链
接下来,您将创建所有特定于协议的链。 这些将用于保存为要公开的服务的拒绝策略创建例外的规则。 您将为 UDP
流量创建一个:
sudo iptables -N UDP
然后是 TCP
的另一个:
sudo iptables -N TCP
ICMP
还有一个:
sudo iptables -N ICMP
接下来,为 SSH 流量添加例外。 SSH 使用 TCP,因此您将添加一条规则以接受以端口 22
为目标的 TCP
流量到 TCP 链:
sudo iptables -A TCP -p tcp --dport 22 -j ACCEPT
如果你想添加额外的 TCP 服务,你现在可以通过重复命令替换端口号来实现。
创建通用接受和拒绝规则
在所有传入流量开始过滤的 INPUT
链中,我们需要添加通用规则。 这些是一些常识性规则,它们通过接受低风险的流量(本地流量和与我们已经检查过的连接相关的流量)并丢弃明显无用的流量(无效数据包)来为我们的防火墙设置基线。
首先,创建一个例外以接受属于已建立连接的一部分或与已建立连接相关的所有流量:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
此规则使用 conntrack
扩展,它提供内部跟踪,以便 iptables
具有评估数据包所需的上下文,将其作为较大连接的一部分,而不是作为离散的、不相关的数据包流。 TCP 是基于连接的协议,因此已建立的连接是相当明确的。 对于 UDP 和其他无连接协议,已建立的连接是指已经看到响应的流量(原始数据包的源将是响应数据包的目标,反之亦然)。 相关连接是指与现有连接相关联的新连接。 这里的经典例子是一个FTP数据传输连接,它与已经建立的FTP控制连接有关。
您还需要允许源自本地环回接口的所有流量。 这是由服务器生成并以服务器为目的地的流量。 主机上的服务使用它来相互通信:
sudo iptables -A INPUT -i lo -j ACCEPT
最后,拒绝所有无效数据包。 由于多种原因,数据包可能无效。 它们可能指的是不存在的连接,它们的目的地可能是不存在的接口、地址或端口,或者它们可能格式不正确。 在任何情况下,您都会丢弃所有无效数据包,因为没有适当的方法来处理它们,并且它们可能代表恶意活动:
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
创建到特定协议链的跳转规则
到目前为止,我们已经在 INPUT
链中创建了一些通用规则,并在我们的协议特定链中为特定可接受的服务创建了一些规则。 但是,现在,流量进入 INPUT
链,无法到达我们的协议特定链。
现在您需要将 INPUT
链中的流量引导到适当的特定于协议的链中。 您可以匹配协议类型以将其发送到正确的链。 此外,请确保数据包代表一个新连接(任何已建立或相关的连接都应早先处理)。 从 UDP
流量开始:
sudo iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
接下来,对 TCP
流量运行以下命令。 请注意,对于 TCP 数据包,您将添加额外的要求,即数据包是 SYN 数据包,这是启动 TCP 连接的唯一有效类型:
sudo iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
然后为 ICMP
流量运行以下命令:
sudo iptables -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
拒绝所有剩余流量
如果传递到特定协议链的数据包与其中的任何规则都不匹配,则控制将传递回 INPUT
链。 您的防火墙不应允许达到这一点的任何内容。
您将使用 REJECT
目标拒绝流量,该目标向客户端发送响应消息。 这允许您指定出站消息,以便您可以模拟客户端尝试将数据包发送到常规关闭端口时给出的响应。 响应取决于客户端使用的协议。
尝试访问已关闭的 UDP
端口将导致 ICMP
消息指出“端口无法访问”。 您可以通过运行以下命令来模仿它:
sudo iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
尝试在关闭的端口上建立 TCP
连接会导致 TCP RST 响应:
sudo iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
对于所有其他数据包,您可以发送 ICMP
“协议不可达”消息以指示服务器不响应该类型的数据包:
sudo iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable
调整默认策略
您添加的最后三个规则应处理 INPUT
链中的所有剩余流量。 但是,您应该将默认策略设置为 DROP
作为预防措施,如下所示:
sudo iptables -P INPUT DROP
如果此服务器未配置为其他机器的路由器,您还应该在 FORWARD
链中设置此策略:
sudo iptables -P FORWARD DROP
警告: 将您的策略设置为 DROP
,如果您使用 sudo iptables -F
清除 iptables
,您当前的 SSH 连接将被丢弃! 使用 sudo netfilter-persistent flush
刷新是清除规则的更好方法,因为它也会重置默认策略。
要匹配丢弃所有流量的 IPv6 策略,您可以使用以下 ip6tables
命令,以 INPUT
开头:
sudo ip6tables -P INPUT DROP
然后为 FORWARD
运行以下命令:
sudo ip6tables -P FORWARD DROP
通过设置 OUTPUT
的策略来完成:
sudo ip6tables -P OUTPUT DROP
这应该相当接近地复制您的规则集。
保存 iptables
规则
此时,您应该测试您的防火墙规则,并确保它们阻止您想要阻止的流量,同时不妨碍您的正常访问。 一旦您对您的规则行为正确感到满意,您可以保存它们,以便它们在启动时自动应用到您的系统。
通过运行以下命令保存当前规则(IPv4 和 IPv6):
sudo service netfilter-persistent save
这将使用您在命令行上制定的策略覆盖您的 /etc/iptables/rules.v4
和 /etc/iptables/rules.v6
文件。
结论
通过遵循本指南,或者将防火墙规则直接粘贴到配置文件中,或者通过在命令行中手动应用和保存它们,您已经创建了一个良好的启动防火墙配置。 您必须添加单独的规则以允许访问您想要提供的服务。
本指南中建立的框架应允许您进行调整,并有助于澄清您现有的政策。 查看我们的其他一些指南,了解如何使用一些流行的服务构建防火墙策略: