16.6 Jail 的应用

16.6.1 服务 Jail

原作 Daniel Gerzo.

  这一节主要基于 Simon L. Nielsen http://simon.nitro.dk/service-jails.html 中的思路, 以及由 Ken Tom 更新的文档。 这一节中描述了如何配置 FreeBSD 系统的 jail(8) 功能为其增加一个安全层次。 这部分假定您运行 RELENG_6_0 或更新版本, 并理解本章之前部分的内容。

16.6.1.1 设计

  jail 的一个主要问题是如何对它们进行升级和管理。 由于每个 jail 都是从头联编的, 对于单个 jail 而言升级也许还不是个很严重的问题, 因为升级不会太过麻烦, 而对于多个 jail 而言, 升级不仅会耗费大量时间, 并且是十分乏味的过程。

警告: 这个配置过程需要您对 FreeBSD 有较多的配置和使用经验。 如果这些过程显得太过复杂, 您应考虑使用较简单的系统, 例如 sysutils/ezjail, 它提供了更简单的管理 FreeBSD jail 的方法。

  基本的想法是, 在不同的 jail 中尽可能多地以安全的方式使用共享的资源 ── 使用只读的 mount_nullfs(8) 挂接, 这会让升级简单许多, 从而使为每个服务建立不同的 jail 这种方案变得更加可行。 另外, 它也为增加、删除以及升级 jail 提供了更为便捷的方法。

注意: 在这里服务的常见例子包括: HTTP 服务、 DNS 服务、 SMTP 服务等等, 诸如此类。

  这节介绍的配置的目的包括:

  • 建立简单并易于理解的 jail 结构。 也就是说 不必 为每个 jail 执行完整的 installworld 操作。

  • 使增删 jail 更容易。

  • 使更新或升级 jail 更容易。

  • 使运行自订的 FreeBSD 分支成为可能。

  • 对安全的更偏执的追求, 尽可能减少被攻陷的可能。

  • 尽可能节省空间和 inode。

  如前面提到的那样, 这个设计极大程度上依赖于将一份只读的主模板 (known as nullfs) 挂接到每一个 jail 中, 并为每个 jail 配置一个可读写的设备。 这种设备可以是物理磁盘、 分区, 或以 vnode 为后端的 md(4) 设备。 在这个例子中, 我们将使用可读写的 nullfs 挂接。

  下面的表中描述了文件系统格局:

  • 每个 jail 挂接到 /home/j 目录下的一个目录。

  • /home/j/mroot 是每个 jail 共用的模板, 对于所有的 jail 而言都是只读的。

  • /home/j 目录中, 每个 jail 有一个对应的空目录。

  • 每个 jail 中都有一个 /s 目录, 这个目录将连接到系统中的可读写部分。

  • 每个 jail 应基于 /home/j/skel 建立其可读写空间。

  • 每个 jailspace (jail 中的可读写部分) 应创建到 /home/js

注意: 这假定所有的 jail 都放置于 /home 分区中。 当然, 您可以根据需要将这个配置改为需要的任何样子, 但在接下来的例子中, 也应相应地加以变动。

16.6.1.2 建立模板

  这一节将介绍创建 jail 所需的只读主模板所需的步骤。

  一般来说, 您应将系统升级到最新的 FreeBSD -RELEASE 分支, 具体做法请参见本手册的相关 章节。 当更新不可行时, 则需要完成 buildworld 过程, 另外, 您还需要 sysutils/cpdup 软件包。 我们将使用 portsnap(8) 工具来下载 FreeBSD Ports 套件。 在使用手册的 Portsnap 章节 中, 提供了针对初学者的介绍。

  1. 首先, 需要为将要存放只读的 FreeBSD 执行文件的文件系统建立一个目录, 接着进入 FreeBSD 源代码的目录, 并在其中安装 jail 模板:

    # mkdir /home/j /home/j/mroot
    # cd /usr/src
    # make installworld DESTDIR=/home/j/mroot
    
  2. 接着, 准备一份 FreeBSD Ports 套件, 以及用于执行 mergemaster 的 FreeBSD 源代码:

    # cd /home/j/mroot
    # mkdir usr/ports
    # portsnap -p /home/j/mroot/usr/ports fetch extract
    # cpdup /usr/src /home/j/mroot/usr/src
    
  3. 创建系统中可读写部分的骨架:

    # mkdir /home/j/skel /home/j/skel/home /home/j/skel/usr-X11R6 /home/j/skel/distfiles
    # mv etc /home/j/skel
    # mv usr/local /home/j/skel/usr-local
    # mv tmp /home/j/skel
    # mv var /home/j/skel
    # mv root /home/j/skel
    
  4. 使用 mergemaster 安装缺失的配置文件。 接下来, 删除 mergemaster 创建的多余目录:

    # mergemaster -t /home/j/skel/var/tmp/temproot -D /home/j/skel -i
    # cd /home/j/skel
    # rm -R bin boot lib libexec mnt proc rescue sbin sys usr dev
    
  5. 现在, 将可读写文件系统连接到只读文件系统中。 请确保您在 s/ 目录中建立了适当的符号连接。 如果没有建立目录或建立的位置不正确, 可能会导致安装失败。

    # cd /home/j/mroot
    # mkdir s
    # ln -s s/etc etc
    # ln -s s/home home
    # ln -s s/root root
    # ln -s ../s/usr-local usr/local
    # ln -s ../s/usr-X11R6 usr/X11R6
    # ln -s ../../s/distfiles usr/ports/distfiles
    # ln -s s/tmp tmp
    # ln -s s/var var
    
  6. 最后, 创建一个默认的包含下列配置的 /home/j/skel/etc/make.conf

    WRKDIRPREFIX?=  /s/portbuild
    

    配置 WRKDIRPREFIX 使得在每个 jail 中分别编译 FreeBSD 成为可能。 请注意 ports 目录是只读系统的一部分。 而自订的 WRKDIRPREFIX 则使得联编过程得以在 jail 中的可读写部分完成。

16.6.1.3 建立 Jail

  现在我们已经有了完整的 FreeBSD jail 模板, 可以在 /etc/rc.conf 中安装并配置它们了。 这个例子中演示了建立 3 个 jail: “NS”、 “MAIL” 和 “WWW”。

  1. /etc/fstab 文件中加入下列配置, 以便让系统自动挂接 jail 的只读模板和读写空间:

    /home/j/mroot   /home/j/ns     nullfs  ro  0   0
    /home/j/mroot   /home/j/mail   nullfs  ro  0   0
    /home/j/mroot   /home/j/www    nullfs  ro  0   0
    /home/js/ns     /home/j/ns/s   nullfs  rw  0   0
    /home/js/mail   /home/j/mail/s nullfs  rw  0   0
    /home/js/www    /home/j/www/s  nullfs  rw  0   0
    

    注意: 扫描批次号 (pass number) 为 0 的分区不会在启动时使用 fsck(8) 进行检查, 而转存批次号 (dump number) 为 0 的分区则不会在 dump(8) 时备份。 我们不希望 fsck 检查 nullfs 挂接, 或让 dump 备份 jail 中的只读 nullfs 挂接。 这就是为什么在每个 fstab 条目的最后两列是 “0 0” 的原因。

  2. /etc/rc.conf 中配置 jail:

    jail_enable="YES"
    jail_set_hostname_allow="NO"
    jail_list="ns mail www"
    jail_ns_hostname="ns.example.org"
    jail_ns_ip="192.168.3.17"
    jail_ns_rootdir="/usr/home/j/ns"
    jail_ns_devfs_enable="YES"
    jail_mail_hostname="mail.example.org"
    jail_mail_ip="192.168.3.18"
    jail_mail_rootdir="/usr/home/j/mail"
    jail_mail_devfs_enable="YES"
    jail_www_hostname="www.example.org"
    jail_www_ip="62.123.43.14"
    jail_www_rootdir="/usr/home/j/www"
    jail_www_devfs_enable="YES"
    

    警告: 应把 jail_name_rootdir 变量设置成 /usr/home 而不是 /home 的原因是 /home 目录在默认安装的 FreeBSD 上是指向 /usr/home 的一个符号连接。 而 jail_name_rootdir 变量必须是一个 包含符号连接的路径, 否则 jail 将拒绝启动。 可以使用 realpath(1) 工具来决定这一变量应被赋予一个什么样的值。 更详细的信息请参阅安全公告 FreeBSD-SA-07:01.jail

  3. 为每个 jail 创建所需的只读文件系统挂接点:

    # mkdir /home/j/ns /home/j/mail /home/j/www
    
  4. 在 jail 中安装可读写的模板。 注意您需要使用 sysutils/cpdup, 它能够帮助您确保每个目录都是正确地复制的:

    # mkdir /home/js
    # cpdup /home/j/skel /home/js/ns
    # cpdup /home/j/skel /home/js/mail
    # cpdup /home/j/skel /home/js/www
    
  5. 这样, 就完成了 jail 的制作, 可以运行了。 首先为 jail 挂接文件系统, 然后使用 /etc/rc.d/jail 脚本来启动它们:

    # mount -a
    # /etc/rc.d/jail start
    

  现在 jail 应该就启动起来了。 要检查它们是否运行正常, 可以使用 jls(8) 命令。 它的输出应该类似这样:

# jls
   JID  IP Address      Hostname                      Path
     3  192.168.3.17    ns.example.org                /home/j/ns
     2  192.168.3.18    mail.example.org              /home/j/mail
     1  62.123.43.14    www.example.org               /home/j/www

  这时, 就可以登入 jail 并增加用户和配置服务了。 JID 列给出了正在运行的 jail 的标识编号。 您可以使用下面的命令来在 JID 编号为 3 的 jail 中执行管理任务:

# jexec 3 tcsh

16.6.1.4 升级

  有时, 由于安全问题, 或新增功能有用, 会希望将系统升级到一个新版本的 FreeBSD。 这种安装方式的设计使得升级现有 jail 变得很容易。 另外, 它也能最大限度地减小停机时间, 因为 jail 只在最后时刻才需要关闭。 另外, 它也提供了简单的回退到先前版本的方法。

  1. 第一步是按通常的方法升级主机的系统。 接着, 在 /home/j/mroot2 中建立一个新的临时模板:

    # mkdir /home/j/mroot2
    # cd /usr/src
    # make installworld DESTDIR=/home/j/mroot2
    # cd /home/j/mroot2
    # cpdup /usr/src usr/src
    # mkdir s
    

    在运行 installworld 时会创建一些不需要的目录, 应将它们删除:

    # chflags -R 0 var
    # rm -R etc var root usr/local tmp
    
  2. 重建到主系统中的可读写符号连接:

    # ln -s s/etc etc
    # ln -s s/root root
    # ln -s s/home home
    # ln -s ../s/usr-local usr/local
    # ln -s ../s/usr-X11R6 usr/X11R6
    # ln -s s/tmp tmp
    # ln -s s/var var
    
  3. 现在是时候关闭 jail 了:

    # /etc/rc.d/jail stop
    
  4. 卸下原先的文件系统:

    # umount /home/j/ns/s
    # umount /home/j/ns
    # umount /home/j/mail/s
    # umount /home/j/mail
    # umount /home/j/www/s
    # umount /home/j/www
    

    注意: 可读写的文件系统 (/s) 会在只读系统之后挂接, 因此应首先卸载。

  5. 将先前的只读文件系统挪走, 换成新的系统。 这样做也同时保留了先前系统的备份, 从而可以在出现问题时从中恢复。 这里我们根据新系统的创建时间来命名。 此外我们把先前的 FreeBSD Ports 套件直接移动到新的文件系统中, 以节省磁盘空间和 inode:

    # cd /home/j
    # mv mroot mroot.20060601
    # mv mroot2 mroot
    # mv mroot.20060601/usr/ports mroot/usr
    
  6. 现在新的只读模板就可以用了, 剩下的事情是重新挂接文件系统并启动 jails:

    # mount -a
    # /etc/rc.d/jail start
    

  最后用 jls(8) 检查 jail 启动是否正常。 不要忘记在 jail 中运行 mergemaster。 配置文件和 rc.d 脚本在升级时应进行更新。

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.