使用 Linux 构建透明防火墙,第五部分
亲爱的读者们,我似乎创造了“偏执企鹅”专栏的记录——一个系列文章耗时六个月。(它由五部分组成,第二篇和第三篇之间间隔了一个月。)但是,我们已经涵盖了很多方面:透明防火墙的概念和设计原则;如何在 Linksys WRT54GL 路由器上安装 OpenWrt;如何编译自定义 OpenWrt 系统镜像;如何在 OpenWrt 上配置网络和 iptables 桥接;当然,还有如何用自定义的 iptables 脚本替换原生的 OpenWrt 防火墙脚本,该脚本在桥接模式下工作。本月,我将通过展示如何使用运行 Ubuntu 10.04 的普通 PC 实现相同的目标来结束本系列。
在本系列的后期阶段,我假设您知道什么是透明防火墙以及您可能希望将其部署在网络中的哪个位置。但是,自从第二部分(在 2010 年 9 月的 LJ 杂志中)以来,我还没有提到关于 PC 硬件的任何内容,因此有必要重复我在当时关于选择网络硬件,特别是笔记本电脑上的网络硬件时提出的几个要点。
如果是在十年前,我可能会谈论台式机/塔式系统的内部 PCI 网络适配器和笔记本电脑的 PCMCIA(PC 卡)接口。如今,您的系统几乎肯定内置了以太网卡,并且只需要再添加一块以充当防火墙(除非您想要第三个“DMZ”网络,但这超出了本系列的范围——我假设您只是在防火墙隔离您网络的一部分)。
如果您有一个带有空闲 PCI 插槽的台式机或塔式系统,那么您有很多适用于 Linux 的以太网卡的好选择。但是,如果您有一台笔记本电脑,或者您的 PCI 插槽都已插满,您将需要一个外部 USB 以太网接口。
这是我之前提到的部分:请务必选择支持 USB 2.0 的 USB 以太网接口,因为 USB 1.1 的运行速度仅为 12Mbps,而 USB 1.0 的运行速度为 1.5Mbps。(USB 2.0 的运行速度为 480Mbps——除非您的 LAN 运行千兆以太网,否则速度足够快。)显然,您还希望接口在 Linux 下得到支持。
作为一般规则,我不喜欢推销特定产品,但在撰写这些文章的过程中,我在 D-Link DUB-E100(USB 2.0,快速以太网 (100Mbps) 接口)方面获得了出色的体验。它在 Linux 下通过 usbnet 和 asix 内核模块得到支持。您的 Linux 系统很可能会自动检测到 DUB-E100 接口并加载这两个模块。我喜欢廉价、简单的设备不仅在 Linux 下“开箱即用”,而且性能良好,您不觉得吗?
您会从前两部分中记得,为了支持桥接模式下的 iptables,您的 Linux 内核需要使用以下配置编译:CONFIG_BRIDGE_NETFILTER=1,并且您的 /etc/sysctl.conf 文件要么不包含以下设置的任何条目,要么将它们设置为“1”
net.bridge.bridge-nf-call-arptables=0 net.bridge.bridge-nf-call-ip6tables=0 net.bridge.bridge-nf-call-iptables=0
好吧,如果您是 Ubuntu 用户,则无需担心。与 OpenWrt 不同,标准的 Ubuntu 内核已经编译了 CONFIG_BRIDGE_NETFILTER 支持,并且其默认的 /etc/sysctl.conf 文件也很好,无需您进行任何编辑。对于 Debian 和其他 Debian 衍生发行版来说,情况很可能也是如此,尽管我还没有机会亲自验证。
但是,您可能需要做的一件事是安装命令brctl通过 Debian/Ubuntu 的 bridge-utils 软件包或您的非 Debian 衍生发行版提供的 brctl 命令的任何软件包。这很少是默认软件包,因此如果输入命令which brctl没有产生 brctl 命令的路径,则需要安装它。
但是,与 OpenWrt 一样,您将不需要ebtables(以太网桥接表)命令,除非您想根据以太网标头信息(例如 MAC(硬件)地址和其他非常底层的标准)过滤网络流量。本系列中描述的任何内容都不需要 ebtables,只需要普通的 iptables 即可。
如果您有两个可用的以太网接口,如果您的内核支持桥接模式下的 iptables,并且您的系统已安装 bridge-utils,那么您就可以设置桥接了!在 Ubuntu Server 和其他 Debian 衍生的非图形系统上,这仅涉及更改一个文件,即 /etc/network/interfaces——除非您的窗口管理器控制网络。有关禁用 GNOME 的网络管理器系统的说明,请参阅边栏“网络技巧:GNOME 与您”。
网络技巧:GNOME 与您
通常,您配置为充当网络设备或服务器的任何计算机都不应运行 X Window 系统,这出于性能和安全方面的考虑。但是,如果出于某种原因(例如测试),您想在 Ubuntu 10.04 Desktop(或任何其他基于 GNOME 的发行版)上设置桥接,则需要注意一些事项。
传统上,Ubuntu 和其他 Debian 衍生发行版将网络接口配置存储在文件 /etc/network/interfaces 中。但是,GNOME 的网络管理器系统会自动配置该文件中未明确描述的任何接口。
从理论上讲,这应该意味着如果您在 /etc/network/interfaces 中指定接口和桥接配置,则无需担心网络管理器覆盖或以其他方式与这些设置冲突。但在实践中,至少在我自己在 Ubuntu 10.04 上的经验中,如果您想在 /etc/network/interfaces 中设置持久桥接设置,最好禁用网络管理器,方法是在“系统”→“首选项”→“启动应用程序”小程序中禁用。
要完全禁用网络管理器,您还需要打开“系统”→“首选项”→“网络连接”控制面板,并删除“有线”选项卡下的所有连接配置文件。即使网络管理器作为启动服务被禁用,Ubuntu 也会读取此控制面板设置的网络配置信息,从而导致与 /etc/network/interfaces 发生奇怪的交互。
在我的测试系统中,即使在禁用网络管理器服务、按照清单 1 所示设置 /etc/network/interfaces 以及停止并重新启动 /etc/init.d/networking 之后,eth2 仍然在我的路由表中显示,并且具有与 br0 相同的 IP 地址,即使 br0 应该是唯一具有 任何 IP 地址(更不用说路由)的接口。清除网络连接中 eth2 的条目并再次重新启动网络解决了问题。
要终止正在运行的网络管理器进程,请首先使用以下命令找到其进程 ID:ps auxw |grep nm-applet然后,执行sudo kill -9 [PID](当然,将 [PID] 替换为进程 ID)以关闭网络管理器。这是通过编辑 /etc/network/interfaces 手动配置网络的好时机(sudo vi /etc/network/interfaces)。最后,通过输入以下命令重新启动网络:sudo /etc/init.d/networking restart.
因此,让我们检查一下桥接的 eth1 和 eth2 接口的网络配置。(对于 Fedora、Red Hat、SUSE 和其他非 Debian 发行版的爱好者,我为最近以 Ubuntu 为中心而道歉。但希望以下内容能让您领会如何在各自发行版的手动网络配置方案中执行操作。)
清单 1 显示了我的 Ubuntu 10.04 防火墙的 /etc/network/interfaces 文件。我的测试系统实际上是一个 Ubuntu 10.04 Desktop 系统,但我已按照边栏中的说明禁用了网络管理器。
清单 1. /etc/network/interfaces
auto lo iface lo inet loopback address 127.0.0.1 netmask 255.0.0.0 auto br0 iface br0 inet static address 10.0.0.253 netmask 255.255.255.0 pre-up ifconfig eth1 down pre-up ifconfig eth2 down pre-up brctl addbr br0 pre-up brctl addif br0 eth1 pre-up brctl addif br0 eth2 pre-up ifconfig eth1 0.0.0.0 pre-up ifconfig eth2 0.0.0.0 post-down ifconfig eth1 down post-down ifconfig eth2 down post-down ifconfig br0 down post-down brctl delif br0 eth1 post-down brctl delif br0 eth2 post-down brctl delbr br0
清单 1 的第一部分显示了 lo 的设置,lo 是本地进程用于相互通信的虚拟网络接口。我已明确为 lo 分配了其惯用的 IP 地址 127.0.0.1 和子网掩码 255.0.0.0。
清单 1 的其余部分给出了 br0(我的逻辑桥接接口)的配置。首先,我将桥接接口的 IP 地址设置为 10.0.0.253,子网掩码为 255.255.255.0,就像我在 OpenWrt 中所做的那样。请注意,当您将物理网络接口与逻辑桥接接口关联时,桥接接口会获得 IP 地址,但物理接口不会。此时,它们只是桥上的端口。
请注意,在我的测试系统中,eth1 和 eth2 是分配给我的两个 USB D-Link DUB-E100 接口的名称。实际上,您更有可能使用机器的内置以太网接口(可能名为 eth0),并且您添加的任何第二个接口都将命名为 eth1。如有疑问,请运行命令tail -f /var/log/messages在连接第二个接口之前,查看系统为其分配的名称。您也可以输入sudo ifconfig -a以获取有关所有存在的网络接口的详细信息,即使是关闭的接口。
继续分析清单 1,在配置桥接 IP 地址和子网掩码后,我实际上关闭了我将要桥接的两个物理接口,然后在调用 brctl 命令创建桥接 (br0) 并将每个接口(eth1 和 eth2)添加到其中之前。启动桥接的最后一步是将保留地址 0.0.0.0 分配给两个物理接口 eth1 和 eth2,这具有允许每个接口接收到达它的任何数据包的效果——也就是说,使接口侦听 IP 地址 0.0.0.0 使该接口处于混杂模式。这是交换机端口的必要行为。这不意味着在一个端口上输入的所有数据包都会自动转发到其他端口;它仅表示内核将读取和处理进入该端口的所有数据包。
清单 1 中的“post-down”语句显然都与在关闭时干净地断开桥接有关。
一旦您使用以下命令重新启动网络:sudo /etc/init.d/networking restart,您的系统应开始在其两个物理接口之间进行桥接。您应该通过将 Linux 桥接/防火墙上的一个接口连接到连接到 Internet 的 LAN,并将另一个接口连接到某个测试系统来测试这一点。测试系统应该可以毫无问题地连接到您的 LAN 并访问 Internet,就好像两者之间没有 Linux 桥接一样——至少现在不应该有。但是,我们将解决这个问题!
现在是时候使用我在 OpenWrt 下实现的相同防火墙策略配置 Linux 桥接了。清单 2 显示了上个月的自定义 iptables 脚本,该脚本已调整为用作 Ubuntu init 脚本。(实际上,我们将从新的“upstart”系统而不是 init 运行它,但稍后会详细介绍。)
清单 2. 自定义 iptables 启动脚本
#! /bin/sh ### BEGIN INIT INFO # Provides: iptables_custom # Required-Start: $networking # Required-Stop: # Default-Start: # Default-Stop: 0 6 # Short-Description: Custom bridged iptables rules ### END INIT INFO PATH=/sbin:/bin IPTABLES=/sbin/iptables LOCALIP=10.0.0.253 LOCALLAN=10.0.0.0/24 WEBPROXY=10.0.0.111 . /lib/lsb/init-functions do_start () { log_action_msg "Loading custom bridged iptables rules" # Flush active rules, custom tables $IPTABLES --flush $IPTABLES --delete-chain # Set default-deny policies for all three default tables $IPTABLES -P INPUT DROP $IPTABLES -P FORWARD DROP $IPTABLES -P OUTPUT DROP # Don't restrict loopback (local process intercommunication) $IPTABLES -A INPUT -i lo -j ACCEPT $IPTABLES -A OUTPUT -o lo -j ACCEPT # Block attempts at spoofed loopback traffic $IPTABLES -A INPUT -s $LOCALIP -j DROP # pass DHCP queries and responses $IPTABLES -A FORWARD -p udp --sport 68 --dport 67 -j ACCEPT $IPTABLES -A FORWARD -p udp --sport 67 --dport 68 -j ACCEPT # Allow SSH to firewall from the local LAN $IPTABLES -A INPUT -p tcp -s $LOCALLAN --dport 22 -j ACCEPT $IPTABLES -A OUTPUT -p tcp --sport 22 -j ACCEPT # pass HTTP and HTTPS traffic only to/from the web proxy $IPTABLES -A FORWARD -p tcp -s $WEBPROXY --dport 80 -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 80 -d $WEBPROXY -j ACCEPT $IPTABLES -A FORWARD -p tcp -s $WEBPROXY --dport 443 -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 443 -d $WEBPROXY -j ACCEPT # pass DNS queries and their replies $IPTABLES -A FORWARD -p udp -s $LOCALLAN --dport 53 -j ACCEPT $IPTABLES -A FORWARD -p tcp -s $LOCALLAN --dport 53 -j ACCEPT $IPTABLES -A FORWARD -p udp --sport 53 -d $LOCALLAN -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 53 -d $LOCALLAN -j ACCEPT # cleanup-rules $IPTABLES -A INPUT -j LOG --log-prefix "Dropped by default ↪(INPUT):" $IPTABLES -A INPUT -j DROP $IPTABLES -A OUTPUT -j LOG --log-prefix "Dropped by default ↪(OUTPUT):" $IPTABLES -A OUTPUT -j DROP $IPTABLES -A FORWARD -j LOG --log-prefix "Dropped by default ↪(FORWARD):" $IPTABLES -A FORWARD -j DROP } do_unload () { $IPTABLES --flush $IPTABLES -P INPUT ACCEPT $IPTABLES -P FORWARD ACCEPT $IPTABLES -P OUTPUT ACCEPT } case "$1" in start) do_start ;; restart|reload|force-reload) echo "Reloading bridging iptables rules" do_unload do_start ;; stop) echo "DANGER: Unloading firewall's Packet Filters!" do_unload ;; *) echo "Usage: $0 start|stop|restart" >&2 exit 3 ;; esac
篇幅有限,无法详细介绍此脚本,但清单 2 的核心是“do_start”例程,它将所有三个默认链(INPUT、FORWARD 和 OUTPUT)设置为默认 DROP 策略,并加载防火墙规则。示例规则集强制执行以下策略:
本地 LAN 上的主机可以通过防火墙发送 DHCP 请求并接收其回复。
本地 LAN 上的主机可以使用安全 Shell 连接到防火墙。
只有本地 Web 代理可以发送 HTTP/HTTPS 请求并接收其回复。
本地 LAN 上的主机可以通过防火墙发送 DNS 请求并接收其回复。
此策略假定网络的 DHCP 和 DNS 服务器位于防火墙的另一侧,而 LAN 客户端位于防火墙的同一侧,但其 Web 代理与这些客户端位于防火墙的同一侧。
您可能还记得,对于 OpenWrt,允许内核按事务状态(而不是一次一个数据包)跟踪 tcp 甚至某些 udp 应用程序的状态跟踪模块会带来显着的性能损失。尽管在具有足够 RAM 和足够快的 CPU 的基于 PC 的防火墙上,这几乎肯定不是什么大问题,但我将把如何将状态跟踪添加到清单 2 中的脚本留给您来解决;这根本不难!
但是,我在“do_start”例程的末尾添加了一些行来记录所有丢弃的数据包。尽管由于路由器上有限的虚拟磁盘容量,OpenWrt 上的日志记录尤其有问题,但这对于适当的基于 PC 的防火墙来说,是一个非常重要的功能,不能省略。在大多数 Linux 系统上,防火墙事件都记录在文件 /var/log/messages 中,但是如果您在那里找不到任何内容,则可能会将其写入 /var/log/kernel 或 /var/log 下的其他文件。
您可能知道,Ubuntu 采用了新的启动脚本系统。旧的 init 系统仍然有效,如果您愿意,可以通过使清单 2 中的脚本可执行并通过运行以下命令创建 rc.d 链接,以旧式方式启用该脚本:
bash-$ sudo update-rc.d iptables_custom start 36 2 3 4 5 . ↪stop 98 0 1 6
但是,我建议您通过跳过 update-rc.d 并改为将以下脚本 iptables_custom.conf(清单 3)添加到 /etc/init(不是 /etc/init.d)来投入到更新的“upstart”系统的世界中。
清单 3. iptables_custom 的 Upstart 配置文件
# iptables_custom description "iptables_custom" author "Mick Bauer <mick@wiremonkeys.org>" start on (starting network-interface or starting network-manager or starting networking) stop on runlevel [!023456] console output pre-start exec /etc/init.d/iptables_custom start post-stop exec /etc/init.d/iptables_custom stop
upstart 不要求您弄清楚为“rc.”链接分配哪个启动/停止编号,而是让您只需指定需要事先启动的内容(在本例中为:network-interface、network-manager 或 networking)。如您所见,此 iptables_custom.conf 文件然后调用 /etc/init.d/iptables_custom(如清单 2 所示)来执行加载或卸载规则的实际工作。因此,无论您将其用作 init 脚本还是 upstart 作业,/etc/init.d/iptables_custom 都必须是可执行的。
保存 /etc/init/iptables_custom.conf 文件后,您必须使用以下命令启用它:
bash-$ sudo initctl reload-configuration
现在,您可以重新启动,也可以输入以下命令来加载防火墙规则:
bash-$ sudo initctl start iptables_custom
通过一个简单的步骤,即可创建使用 Linux PC 的桥接防火墙!我希望我已经足够清楚地解释了所有这些内容,以便您弄清楚如何使其满足您的特定需求。我也希望您发现前几个月对 OpenWrt 的探索是有价值的。
“偏执企鹅”专栏将在休息几个月后回归。在此期间,继续前进并保护安全!
资源
Peter de Jong 的 upstart init 系统的 iptables 脚本可在 4pdj.nl/2010/01/11/custom-firewall-under-ubuntu-karmic-koala-with-upstart 找到。
另请参阅我的书:Bauer, Michael D. Linux 服务器安全,第二版。 Sebastopol, California: O'Reilly Media, 2005 年。第 3 章详细解释了 iptables,附录 A 包含两个完整的 iptables 脚本。尽管重点是“本地”(“个人”)防火墙,而不是 Internet 或 LAN 防火墙,但这些材料仍然应该对 iptables 初学者有所帮助。
Mick Bauer (darth.elmo@wiremonkeys.org) 是美国最大的银行之一的网络安全架构师。他是 O'Reilly 图书 Linux 服务器安全,第二版(以前称为 使用 Linux 构建安全服务器)的作者,偶尔在信息安全会议上发表演讲,并且是“网络工程波尔卡”的作曲家。