偏执的企鹅 - 在用户模式 Linux 下运行网络服务,第三部分
在前两篇“偏执的企鹅”专栏文章中,我向您介绍了使用用户模式 Linux 构建虚拟网络服务器的过程。我们构建了主机和客户机内核,获取了预构建的根文件系统镜像,配置了主机上的网络,并且在上个月结束时,我们最终启动了客户机内核,并使用桥接网络,准备进行配置、打补丁和服务器软件安装。
本月,我将解决我们示例客户机系统启动和配置中的一些遗留问题,向您展示 uml_moo 命令,演示如何在您的 UML 主机系统上编写防火墙规则,提供一些其他安全提示,并提供有关创建您自己的根文件系统镜像的指导。而且,您能相信即使在三篇文章之后,我们也仅仅触及了用户模式 Linux 的皮毛吗?希望我们已经触及得足够深入,让您能够良好地开始!
您可能还记得,上次我们在主机上设置了桥接网络,创建了一个名为 uml-conn0 的本地隧道接口,我们将其桥接到主机系统的“真实” eth0 接口。如果您没有上个月的专栏文章,我的步骤是基于 David Cannings 的步骤(请参阅在线资源)。当我们随后启动主机(用户模式)内核时,我们通过内核参数将客户机上的虚拟 eth0 映射到 uml-conn0,如下所示
umluser@host$ ./debkern ubd0=debcow,debroot ↪root=/dev/ubda eth0=tuntap,uml-conn0
最后一个参数显然包含网络魔法:eth0=tuntap,uml-conn0。它可以翻译为“客户机内核的 eth0 接口是主机系统的隧道/tap 接口 uml-conn0”。理解这一点很重要;对于主机(真实)系统,客户机的以太网接口称为 uml-conn0,但对于客户机系统本身,其以太网接口是普通的 eth0。
因此,如果您在主机或客户机上运行 iptables(防火墙)规则集(我强烈建议您至少在主机上这样做),则任何使用接口名称作为源或目标的规则都必须考虑到这种命名差异。我们稍后将讨论一些示例主机防火墙规则,但我们尚未完全完成客户机内核启动参数。
回到启动行,我们已经定义了虚拟硬盘驱动器(ubd0,与 ubda 同义)、虚拟根路径,当然还有虚拟以太网接口。但是内存呢?
在我的 OpenSUSE 10.1 主机系统上,使用上述启动行运行 UML Debian 客户机,默认内存大小约为 29MB——按照现代标准,这非常小,特别是如果我希望该客户机系统运行真实世界的、面向互联网的网络服务。此外,我的主机系统上有一个完整的千兆字节物理 RAM 可供分配;我可以轻松地为我的客户机系统腾出 256MB 的 RAM。
要做到这一点,我所要做的就是将参数 mem=256M 传递给客户机内核,如下所示
umluser@host$ ./debkern mem=256M ubd0=debcow,debroot ↪root=/dev/ubda eth0=tuntap,uml-conn0
显然,您可以根据需要指定更多或更少,并且您可以为在单个主机上运行的多个客户机分配不同数量的 RAM(例如,为您的虚拟 DNS 服务器分配 128M,但为您的虚拟 Web 服务器分配 512M)。只需确保为您的主机系统留下足够的非客户机分配的 RAM,以执行 它 需要执行的操作。
说到这一点,通过不运行 X Window System,您将在主机系统上节省大量 RAM,我一直不建议在加固的服务器上运行 X Window System。我的测试主机上的 X 服务器占用大约 100MB,而实际的桌面管理器需要更多。最重要的是,X Window System 有各种安全漏洞的历史,远程攻击者可以利用这些漏洞(请记住,一旦任何非本地用户启动 shell,“本地”漏洞就不再是本地漏洞了)。
如果像我上个月建议的那样,您使用写入时复制 (COW) 文件运行 UML 客户机,您可能想知道您的 UML 客户机内核启动行是否是管理 COW 文件的唯一位置。(当您在 ubd0=... 参数中为 COW 文件指定文件名时,会自动创建 COW 文件。)
实际上,uml-utilities 包包含两个用于管理 COW 文件的独立命令:uml_moo 和 uml_mkcow。在两者中,uml_moo 最有可能对您有用。您可以使用 uml_moo 将 COW 文件中包含的所有文件系统更改合并到其父根文件系统镜像中。
例如,如果我运行前面描述的示例 UML 客户机内核启动命令,并在该 UML 客户机会话中配置网络、应用所有最新的安全补丁、安装 BIND v9 并配置它,并最终达到“生产就绪”状态,我可能会决定是时候通过将所有这些更改(到目前为止,仅写入文件 debcow)合并到实际文件系统镜像(debroot)中来拍摄 UML 客户机的快照了。为此,我将使用以下命令
umluser@host$ uml_moo ./debcow newdebroot
您为 uml_moo 指定的第一个参数是您要合并的 COW 文件。由于 COW 文件包含与其对应的文件系统镜像的名称,因此您不必指定此名称。但是,通常,您应该指定要创建的新文件系统镜像的名称。
因此,我的示例 uml_moo 命令将保持旧的根文件系统镜像 debroot 完好无损(也许它也被其他 UML 客户机使用,或者我只是想保留一个干净的镜像),创建一个名为 newdebroot 的新文件系统,其中包含我完全配置和更新的根文件系统。
但是,如果我想执行硬合并,即将旧文件系统镜像替换为合并后的镜像(文件名与之前相同),可能是因为我的硬盘驱动器太满了,无法容纳额外的镜像文件,我将改为使用uml_moo -d ./debcow(-d 代表破坏性合并)。
是否 chroot 您的用户模式客户机,以及是否使用 SELinux,取决于您希望安全层有多深以及您能够投入多少时间和精力。但是,我强烈建议在任何面向互联网的桥接用户模式 Linux 系统上,您都使用 UML 主机上的 iptables 来限制客户机系统的网络行为。
一方面,如果您的 UML 系统已经位于 DMZ 网络中的防火墙之外(任何互联网服务器都应该如此),您已经保护了您的内部网络免受网络服务器泄露的可能性。但是,真的没有理由不利用 UML 主机 iptables 规则的机会来降低攻击者使用一个受损的 UML 客户机攻击其他 UML 客户机、UML 主机本身或 DMZ 网络中其他系统的能力。
我强烈建议您考虑两种类型的规则。首先,反 IP 欺骗规则可以帮助确保每个客户机发送的每个数据包都带有您实际分配给该客户机的源 IP 地址,而不是伪造(欺骗)的源 IP。这些是低维护规则,您只需在设置时考虑,除非出于某种原因您更改了客户机系统的 IP 地址。
假设您的 UML 系统的 IP 地址为 10.1.1.10,并且其 tun/tap 接口(从主机的角度来看)是 uml-conn0。因此,您在 UML 主机上安装的反欺骗规则可能如下所示,如清单 1 所示。
清单 1. 反 IP 欺骗规则
iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-s ! 10.1.1.10 -j LOG --log-prefix "Spoof from uml-conn0" iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-s ! 10.1.1.10 -j DROP
第一个规则记录欺骗的数据包;第二个规则实际上丢弃它们。您可能知道,LOG 目标不会导致数据包停止针对后续 iptables 规则进行评估,但 DROP 目标会,因此 LOG 规则必须在 DROP 规则之前。
由于空间限制,我无法详细介绍如何编写 iptables 规则或如何在您选择的 Linux 发行版上管理它们。但是,我可以谈谈清单 1 中的桥接特定魔法:physdev iptables 模块和 --physdev-in 参数。
通常,我们使用 iptables 的 -i 和 -o 标志来表示数据包分别从哪个网络接口接收和发送。但是,在桥接网络系统上编写 iptables 规则时,我们需要更精确一些,特别是当我们还使用 tun/tap 接口时,因为 eth0 此时承担的角色与正常的第 3 层(路由)网络不同。
因此,在我们通常可能使用-i uml-conn0在规则中,在桥接主机上,我们应该改为使用-m physdev --physdev-in uml-conn0。同样,代替-o uml-conn0,我们将使用-m physdev --physdev-out uml-conn0。与其他模块调用一样,如果给定的 iptables 规则同时使用 --physdev-in 和 --physdev-out 规则,则您只需要一个-m physdev的实例。
在设置一对反 IP 欺骗规则后,您还应该创建一组“特定于服务”的规则,这些规则实际上控制您的客户机系统如何与世界其他地方(包括其他客户机系统和主机本身)交互。
请记住,在我们的示例场景中,客户机系统是 DNS 服务器。因此,我将强制执行以下逻辑防火墙策略
UML 客户机可以接受 DNS 查询(TCP 和 UDP)。
UML 客户机可以针对上游(外部)服务器递归 DNS 查询。
UML 客户机可以将其日志消息发送到日志服务器(称为 logserver)。
UML 主机可以在 UML 客户机上发起 SSH 会话。
清单 2 显示了可以强制执行此策略的 iptables 命令。
清单 2. UML 客户机的服务规则
iptablee -A FORWARD -m state --state ↪RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -m physdev --physdev-out uml-conn0 ↪-p udp --dport 53 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-out uml-conn0 ↪-p tcp --dport 53 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p udp --dport 53 -d ! 10.1.1.0/24 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p tcp --dport 53 -d ! 10.1.1.0/24 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p udp --dport 514 -d logserver -m state --state NEW -j ACCEPT iptables -A FORWARD -j LOG --log-prefix ↪"Forward Dropped by default" iptables -A FORWARD -j DROP iptables -A OUTPUT -d 10.1.1.10 -p tcp --dport 22 -m ↪state --state NEW -j ACCEPT
清单 2 有两个部分:完整的 FORWARD 规则集和单个 OUTPUT 规则。因为从逻辑上讲,UML 客户机系统“外部于”UML 主机的内核,所以 UML 客户机之间以及 UML 客户机与世界其他地方之间的交互通过 FORWARD 规则处理。但是,UML 客户机与底层主机系统之间的交互由 INPUT 和 OUTPUT 规则处理(就像外部系统与主机系统之间的任何其他交互一样)。
由于我的所有逻辑规则(规则 #4 除外)都由 iptables FORWARD 规则强制执行,因此清单 2 显示了我的 UML 主机的完整 FORWARD 表,包括允许与已批准的会话关联的数据包的初始规则,以及最后一对“默认日志和丢弃”规则。请注意我对 physdev 模块的使用;我喜欢尽可能使用特定于接口而不是特定于 IP 的规则,因为这往往使攻击者更难使用 IP 标头玩游戏。
清单 2 中的最后一个规则实际上应该出现在类似的 OUTPUT 规则块的中间(以 allow-established 规则开头,以默认日志/丢弃规则对结尾),但我想说明的是,如果规则的源或目标涉及 UML 主机系统,您可以编写普通的 OUTPUT 或 INPUT 规则(分别)而不是 FORWARD 规则。
由于您的 UML 主机充当以太网桥,因此您可以编写更精细和低级别的防火墙规则——甚至可以通过 MAC 地址、ARP 协议等进行过滤。但是对于该级别的过滤,您需要安装 ebtables 命令。但是,我刚刚描述的 iptables 类型的规则应该足以满足大多数堡垒主机情况。
如果您使用 SKAS 补丁修补了 UML 主机的内核,那么您已经有相当好的保证,即攻击者入侵 UML 客户机后,将无法在主机系统上执行太多操作,甚至无法执行任何操作。但是,我不会反对偏执,因此我也建议您 chroot 您的 UML 客户机系统。UML Wiki 上详细描述了这一点(请参阅资源)。那么,对 UML 客户机的 shell 访问呢?有多种方法可以访问“本地控制台”。当您从 UML 主机 shell 手动启动 UML 客户机时,您会自动获得一个控制台——在 UML 内核加载后,您将看到登录提示。
但是,如果您从脚本自动启动 UML 客户机,这并没有多大用处。“设备输入”页面(在用户模式 Linux 主页上,请参阅资源)描述了如何将 UML 客户机虚拟串行线路映射到 UML 主机控制台。但是,对我来说,最简单的方法是在我的 UML 客户机系统上安装 SSH,配置并启动其 SSH 守护程序,并创建一个防火墙规则,该规则仅允许从我的 UML 主机连接到它。
一般来说,您希望在 UML 客户机上使用与任何其他堡垒服务器上相同的安全控制和工具(tripwire、chrooted 应用程序、SELinux、tcpwrappers 等)。
详细描述从头开始构建您自己的根文件系统镜像的过程将需要一篇单独的文章(我可能还会写一篇)。可以说,该过程与创建您自己的可启动 Linux CD 或 DVD 的过程几乎相同,只是没有将镜像文件刻录到某些便携式介质的最后一步。主要有三个步骤
使用以下命令创建一个空文件系统镜像文件dd.
格式化镜像文件。
通过环回将其挂载到目录。
将 Linux 安装到其中。
前三个步骤最容易。要创建 1GB ext3 镜像文件,我将以 root 用户身份运行清单 3 中显示的命令。
清单 3. 制作和挂载空文件系统镜像
dd if=/dev/zero of=./mydebroot bs=1024K count=1000 mkfs.ext3 ./mydebroot mkdir /mnt/debian mount -o loop ./mydebroot /mnt/debian
将 Linux 安装到此目录中会更复杂一些,但是如果您有 SUSE 主机系统,YaST 中的“软件”模块包含一个名为“安装到目录”的向导。像其他 YaST 模块一样,这是一个易于使用的 GUI。
同样,如果您运行 Debian,则可以使用命令debootstrap。有关使用 debootstrap 填充根文件系统镜像的详细说明,请参阅 Michael McCabe 和 Demetrios Dimatos 的实用文章“安装用户模式 Linux”。
有关其他发行版中类似实用程序的指针,请参阅 UML Wiki。Linux Bootdisk HOWTO(请参阅资源)虽然并非特定于 UML,但也很有用。
我希望您在构建自己的用户模式 Linux 虚拟网络服务器方面进展顺利!UML 信息的两个最重要来源是 UML 主页和 UML Wiki(请参阅资源)。这些网站和本文中提到的其他网站应该可以帮助您比我在这类入门系列文章中走得更远地使用用户模式 Linux。玩得开心,注意安全!
本文的资源: /article/9457。
Mick Bauer (darth.elmo@wiremonkeys.org) 是美国最大的银行之一的网络安全架构师。他是 O'Reilly 图书 Linux 服务器安全 第二版(以前称为 使用 Linux 构建安全服务器)的作者,偶尔在信息安全会议上发表演讲,也是“网络工程波尔卡”的作曲家。