第17章 监狱和容器

17.1. 概要

由于系统管理是一项困难的任务,因此开发了许多工具来简化管理员的工作。这些工具通常增强了系统安装、配置和维护的方式。可用于增强 FreeBSD 系统安全性的工具之一是监狱 (jails)。监狱自 FreeBSD 4.X 版本起就已可用,并且其在实用性、性能、可靠性和安全性方面不断得到增强。

监狱建立在chroot(2)概念的基础上,该概念用于更改一组进程的根目录。这创建了一个安全的环境,与系统其余部分隔离。在 chroot 环境中创建的进程无法访问其外部的文件或资源。因此,入侵在 chroot 环境中运行的服务不应允许攻击者入侵整个系统。

但是,chroot 有一些限制。它适用于不需要太多灵活性和复杂高级功能的简单任务。随着时间的推移,人们发现了许多逃离 chroot 环境的方法,使其成为保护服务的不太理想的解决方案。

监狱通过多种方式改进了传统 chroot 环境的概念。

在传统的 chroot 环境中,进程仅在其可以访问的文件系统部分受到限制。系统资源、系统用户、正在运行的进程和网络子系统由 chroot 进程和主机系统的进程共享。监狱通过虚拟化对文件系统、用户集和网络子系统的访问来扩展此模型。可以获得更细粒度的控制来调整监狱环境的访问权限。监狱可以被认为是一种操作系统级虚拟化。

本章涵盖以下内容:

  • 什么是监狱以及它在 FreeBSD 安装中可能起到的作用。

  • 不同类型的监狱。

  • 为监狱配置网络的不同方法。

  • 监狱配置文件。

  • 如何创建不同类型的监狱。

  • 如何启动、停止和重新启动监狱。

  • 监狱管理的基础知识,包括从监狱内部和外部进行管理。

  • 如何升级不同类型的监狱。

  • 不同 FreeBSD 监狱管理器的非详尽列表。

17.2. 监狱类型

一些管理员将监狱划分为不同的类型,尽管底层技术相同。每个管理员都必须根据需要解决的问题来评估在每种情况下创建哪种类型的监狱。

下面列出了不同类型、其特征以及使用注意事项。

17.2.1. 厚监狱

厚监狱是 FreeBSD 监狱的传统形式。在厚监狱中,基础系统的完整副本会复制到监狱的环境中。这意味着监狱拥有自己独立的 FreeBSD 基础系统实例,包括库、可执行文件和配置文件。可以将监狱视为几乎完整的独立 FreeBSD 安装,但运行在主机系统的限制范围内。这种隔离确保监狱中的进程与主机和其他监狱上的进程保持分离。

厚监狱的优点

  • 高度隔离:监狱中的进程与主机系统和其他监狱隔离。

  • 独立性:厚监狱可以具有与主机系统或其他监狱不同的库、配置和软件版本。

  • 安全性:由于监狱包含自己的基础系统,因此影响监狱环境的漏洞或问题不会直接影响主机或其他监狱。

厚监狱的缺点

  • 资源开销:因为每个监狱都维护自己的独立基础系统,所以与瘦监狱相比,厚监狱消耗更多的资源。

  • 维护:每个监狱都需要对其基础系统组件进行单独的维护和更新。

17.2.2. 瘦监狱

瘦监狱使用 OpenZFS 快照或来自模板的 NullFS 挂载共享基础系统。每个瘦监狱仅复制基础系统的一小部分,与厚监狱相比,这导致资源消耗更少。但是,这也意味着与厚监狱相比,瘦监狱的隔离性和独立性较差。共享组件中的更改可能会同时影响多个瘦监狱。

总而言之,FreeBSD 瘦监狱是一种 FreeBSD 监狱,它在隔离的环境中复制了大部分(但不是全部)基础系统。

瘦监狱的优点

  • 资源效率:与厚监狱相比,瘦监狱的资源效率更高。由于它们共享大部分基础系统,因此它们消耗更少的磁盘空间和内存。这使得可以在相同的硬件上运行更多监狱,而不会消耗过多的资源。

  • 更快的部署:与厚监狱相比,创建和启动瘦监狱通常更快。当您需要快速部署多个实例时,这可能特别有利。

  • 统一维护:由于瘦监狱与其主机系统共享大部分基础系统,因此通用基础系统组件(如库和二进制文件)的更新和维护只需在主机上进行一次即可。与为每个厚监狱维护一个单独的基础系统相比,这简化了维护过程。

  • 共享资源:瘦监狱可以更轻松地与主机系统共享通用资源,如库和二进制文件。这可能导致更有效的磁盘缓存和监狱内应用程序的性能提升。

瘦监狱的缺点

  • 隔离性降低:瘦监狱的主要缺点是与厚监狱相比,它们的隔离性较差。由于它们共享模板基础系统的大部分内容,因此影响共享组件的漏洞或问题可能会同时影响多个监狱。

  • 安全问题:瘦监狱中隔离性降低可能会带来安全风险,因为一个监狱中的入侵可能更容易影响其他监狱或主机系统。

  • 依赖冲突:如果多个瘦监狱需要相同库或软件的不同版本,则管理依赖项可能会变得复杂。在某些情况下,这可能需要额外的努力来确保兼容性。

  • 兼容性挑战:如果瘦监狱中的应用程序假设某个与模板提供的共享组件不同的基础系统环境,则可能会遇到兼容性问题。

17.2.3. 服务监狱

服务监狱直接与主机共享完整的文件系统树(监狱根路径为/),因此可以访问和修改主机上的任何文件,并与主机共享相同的用户帐户。默认情况下,它无法访问网络或监狱中受限的其他资源,但可以将其配置为重用主机的网络并删除某些监狱限制。服务监狱的用例是将服务/守护进程自动限制在监狱内,配置最少,并且无需了解此类服务/守护进程所需的文件。服务监狱自 FreeBSD 15 起存在。

服务监狱的优点

  • 零管理:一个服务监狱就绪的服务只需要在/etc/rc.conf中配置一行,一个非服务监狱就绪的服务需要配置两行。

  • 资源效率:服务监狱比瘦监狱更节省资源,因为它们不需要任何额外的磁盘空间或网络资源。

  • 更快的部署:如果仅需要将不同的服务/守护进程放入监狱,并且不需要同一服务/守护进程的并行实例,则创建和启动服务监狱通常比瘦监狱更快。

  • 共享资源:服务监狱与主机系统共享所有资源,如库和二进制文件。这可能导致更有效的磁盘缓存和监狱内应用程序的性能提升。

  • 进程隔离:服务监狱隔离特定服务,它看不到不是服务监狱子进程的进程,即使它们在同一用户帐户中运行。

服务监狱的缺点

  • 隔离性降低:服务监狱的主要缺点是与厚监狱或瘦监狱相比,它们没有提供文件系统隔离。

  • 安全问题:服务监狱中隔离性降低可能会带来安全风险,因为一个监狱中的入侵可能更容易影响主机系统上的所有内容。

下面讨论的大多数监狱配置对于服务监狱来说是不需要的。要了解监狱的工作原理,建议了解这些配置选项。有关配置服务监狱所需内容的详细信息,请参阅配置服务监狱

17.2.4. VNET 监狱

FreeBSD VNET 监狱是一个虚拟化环境,允许隔离和控制在其内运行的进程的网络资源。它通过为监狱内的进程创建单独的网络堆栈来提供高级别的网络分段和安全性,确保监狱内的网络流量与主机系统和其他监狱隔离。

从本质上讲,FreeBSD VNET 监狱添加了一种网络配置机制。这意味着 VNET 监狱可以作为厚监狱或瘦监狱创建。

17.2.5. Linux 监狱

FreeBSD Linux 虚拟机是 FreeBSD 操作系统中的一项功能,它允许在 FreeBSD 虚拟机中使用 Linux 二进制文件和应用程序。此功能是通过集成一个兼容性层实现的,该层允许将某些 Linux 系统调用和库转换为 FreeBSD 内核并执行。Linux 虚拟机的目的是促进在 FreeBSD 系统上执行 Linux 软件,而无需单独的 Linux 虚拟机或环境。

17.3. 主机配置

在主机系统上创建任何虚拟机之前,有必要执行某些配置并从主机系统获取一些信息。

需要配置 jail(8) 实用程序,创建配置和安装虚拟机的必要目录,从主机的网络获取信息,并检查主机是否使用 OpenZFS 或 UFS 作为其文件系统。

虚拟机中运行的 FreeBSD 版本不能高于主机中运行的版本。

17.3.1. 虚拟机实用程序

jail(8) 实用程序管理虚拟机。

要在系统启动时启动虚拟机,请运行以下命令

# sysrc jail_enable="YES"
# sysrc jail_parallel_start="YES"

使用 jail_parallel_start,所有已配置的虚拟机都将在后台启动。

17.3.2. 网络

FreeBSD 虚拟机的网络可以通过多种不同的方式进行配置

主机网络模式(IP 共享)

在主机网络模式下,虚拟机与主机系统共享相同的网络堆栈。当在主机网络模式下创建虚拟机时,它使用相同的网络接口和 IP 地址。这意味着虚拟机没有单独的 IP 地址,并且其网络流量与主机的 IP 相关联。

虚拟网络 (VNET)

虚拟网络是 FreeBSD 虚拟机的一项功能,它提供了比主机网络等基本网络模式更高级和灵活的网络解决方案。VNET 允许为每个虚拟机创建隔离的网络堆栈,为它们提供自己的独立 IP 地址、路由表和网络接口。这提供了更高水平的网络隔离,并允许虚拟机像在单独的虚拟机上运行一样工作。

netgraph 系统

netgraph(4) 是一个用于创建自定义网络配置的多功能内核框架。它可用于定义网络流量如何在虚拟机和主机系统之间以及不同虚拟机之间流动。

17.3.3. 设置虚拟机目录树

没有特定位置用于放置虚拟机的文件。

一些管理员使用 /jail,另一些使用 /usr/jail,还有一些使用 /usr/local/jails。在本章中将使用 /usr/local/jails

除了 /usr/local/jails 之外,还将创建其他目录

  • media 将包含下载的用户空间的压缩文件。

  • templates 将在使用瘦虚拟机时包含模板。

  • containers 将包含虚拟机。

使用 OpenZFS 时,请执行以下命令以创建这些目录的数据集

# zfs create -o mountpoint=/usr/local/jails zroot/jails
# zfs create zroot/jails/media
# zfs create zroot/jails/templates
# zfs create zroot/jails/containers

在本例中,zroot 用于父数据集,但也可以使用其他数据集。

使用 UFS 时,请执行以下命令以创建目录

# mkdir /usr/local/jails/
# mkdir /usr/local/jails/media
# mkdir /usr/local/jails/templates
# mkdir /usr/local/jails/containers

17.3.4. 虚拟机配置文件

有两种方法可以配置虚拟机。

第一种是为每个虚拟机在 /etc/jail.conf 文件中添加一个条目。另一种选择是为 /etc/jail.conf.d/ 目录中的每个虚拟机创建一个文件。

如果主机系统只有少量虚拟机,则可以在 /etc/jail.conf 文件中为每个虚拟机添加一个条目。如果主机系统有许多虚拟机,则最好在 /etc/jail.conf.d/ 目录中为每个虚拟机创建一个配置文件。

/etc/jail.conf.d/ 中的文件必须以 .conf 作为扩展名,并且必须包含在 /etc/jail.conf

.include "/etc/jail.conf.d/*.conf";

典型的虚拟机条目如下所示

jailname { (1)
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc"; (2)
  exec.stop = "/bin/sh /etc/rc.shutdown"; (3)
  exec.consolelog = "/var/log/jail_console_${name}.log"; (4)

  # PERMISSIONS
  allow.raw_sockets; (5)
  exec.clean; (6)
  mount.devfs; (7)

  # HOSTNAME/PATH
  host.hostname = "${name}"; (8)
  path = "/usr/local/jails/containers/${name}"; (9)

  # NETWORK
  ip4.addr = 192.168.1.151; (10)
  ip6.addr = ::ffff:c0a8:197 (11)
  interface = em0; (12)
}
1jailname - 虚拟机的名称。
2exec.start - 在创建虚拟机时在虚拟机环境中运行的命令。一个典型的运行命令是“/bin/sh /etc/rc”。
3exec.stop - 在删除虚拟机之前在虚拟机环境中运行的命令。一个典型的运行命令是“/bin/sh /etc/rc.shutdown”。
4exec.consolelog - 将命令输出(标准输出和标准错误)定向到的文件。
5allow.raw_sockets - 允许在虚拟机内部创建原始套接字。设置此参数允许 ping(8)traceroute(8) 等实用程序在虚拟机内部运行。
6exec.clean - 在干净的环境中运行命令。
7mount.devfs - 在 chrooted /dev 目录上挂载 devfs(5) 文件系统,并在 devfs_ruleset 参数中应用规则集以限制虚拟机内部可见的设备。
8host.hostname - 虚拟机的主机名。
9path - 将作为虚拟机根目录的目录。在虚拟机内部运行的任何命令,无论是通过 jail 还是 jexec(8) 运行,都将从此目录运行。
10ip4.addr - IPv4 地址。IPv4 有两种配置可能性。第一种是在示例中所做的那样,建立一个 IP 或 IP 列表。另一种是使用 ip4 并将 inherit 值设置为继承主机的 IP 地址。
11ip6.addr - IPv6 地址。IPv6 有两种配置可能性。第一种是在示例中所做的那样,建立一个 IP 或 IP 列表。另一种是使用 ip6 并将 inherit 值设置为继承主机的 IP 地址。
12interface - 要添加虚拟机 IP 地址的网络接口。通常是主机接口。

有关配置变量的更多信息,请参阅 jail(8)jail.conf(5)

17.4. 经典虚拟机(厚虚拟机)

这些虚拟机类似于真实的 FreeBSD 系统。它们可以或多或少像普通主机系统一样进行管理,并独立更新。

17.4.1. 创建经典虚拟机

原则上,虚拟机只需要主机名、根目录、IP 地址和用户空间。

虚拟机的用户空间可以从官方 FreeBSD 下载服务器获取。

执行以下命令下载用户空间

# fetch https://download.freebsd.org/ftp/releases/amd64/amd64/13.2-RELEASE/base.txz -o /usr/local/jails/media/13.2-RELEASE-base.txz

下载完成后,需要将内容提取到虚拟机目录中。

执行以下命令将用户空间提取到虚拟机的目录中

# mkdir -p /usr/local/jails/containers/classic
# tar -xf /usr/local/jails/media/13.2-RELEASE-base.txz -C /usr/local/jails/containers/classic --unlink

在用户空间提取到虚拟机目录中后,需要复制时区和 DNS 服务器文件

# cp /etc/resolv.conf /usr/local/jails/containers/classic/etc/resolv.conf
# cp /etc/localtime /usr/local/jails/containers/classic/etc/localtime

复制完文件后,接下来需要通过执行以下命令更新到最新的补丁级别

# freebsd-update -b /usr/local/jails/containers/classic/ fetch install

最后一步是配置虚拟机。需要在配置文件 /etc/jail.confjail.conf.d 中添加虚拟机的参数条目。

示例如下

classic {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # NETWORK
  ip4.addr = 192.168.1.151;
  interface = em0;
}

执行以下命令启动虚拟机

# service jail start classic

有关如何管理虚拟机的更多信息,请参阅 虚拟机管理 部分。

17.5. 瘦虚拟机

尽管瘦虚拟机使用与厚虚拟机相同的技术,但创建过程不同。瘦虚拟机可以使用 OpenZFS 快照或使用模板和 NullFS 创建。使用 OpenZFS 快照和使用 NullFS 的模板与经典虚拟机相比具有一定的优势,例如能够更快地从快照创建它们,或者能够使用 NullFS 更新多个虚拟机。

17.5.1. 使用 OpenZFS 快照创建瘦虚拟机

由于 FreeBSD 和 OpenZFS 之间的良好集成,使用 OpenZFS 快照创建新的瘦虚拟机非常容易。

要使用 OpenZFS 快照创建瘦虚拟机,第一步是创建一个模板。

模板仅用于创建新的虚拟机。因此,它们是在“只读”模式下创建的,以便虚拟机使用不可变的基础创建。

要创建模板的数据集,请执行以下命令

# zfs create -p zroot/jails/templates/13.2-RELEASE

然后执行以下命令下载用户空间

# fetch https://download.freebsd.org/ftp/releases/amd64/amd64/13.2-RELEASE/base.txz -o /usr/local/jails/media/13.2-RELEASE-base.txz

下载完成后,需要通过执行以下命令将内容提取到模板目录中

# tar -xf /usr/local/jails/media/13.2-RELEASE-base.txz -C /usr/local/jails/templates/13.2-RELEASE --unlink

在用户空间提取到模板目录中后,需要通过执行以下命令将时区和 DNS 服务器文件复制到模板目录中

# cp /etc/resolv.conf /usr/local/jails/templates/13.2-RELEASE/etc/resolv.conf
# cp /etc/localtime /usr/local/jails/templates/13.2-RELEASE/etc/localtime

接下来需要通过执行以下命令更新到最新的补丁级别

# freebsd-update -b /usr/local/jails/templates/13.2-RELEASE/ fetch install

更新完成后,模板就准备好了。

要从模板创建 OpenZFS 快照,请执行以下命令

# zfs snapshot zroot/jails/templates/13.2-RELEASE@base

创建 OpenZFS 快照后,可以使用 OpenZFS 克隆功能创建无限的虚拟机。

要创建名为 thinjail 的瘦虚拟机,请执行以下命令

# zfs clone zroot/jails/templates/13.2-RELEASE@base zroot/jails/containers/thinjail

最后一步是配置虚拟机。需要在配置文件 /etc/jail.confjail.conf.d 中添加虚拟机的参数条目。

示例如下

thinjail {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # NETWORK
  ip4 = inherit;
  interface = em0;
}

执行以下命令启动虚拟机

# service jail start thinjail

有关如何管理虚拟机的更多信息,请参阅 虚拟机管理 部分。

17.5.2. 使用 NullFS 创建瘦虚拟机

通过使用瘦虚拟机技术并使用 NullFS 选择性地共享主机系统中的特定目录到虚拟机中,可以创建系统文件重复性较低的虚拟机。

第一步是创建数据集以保存模板,如果使用 OpenZFS,请执行以下命令

# zfs create -p zroot/jails/templates/13.2-RELEASE-base

或者如果使用 UFS,则使用此命令。

# mkdir /usr/local/jails/templates/13.2-RELEASE-base

然后执行以下命令下载用户空间

# fetch https://download.freebsd.org/ftp/releases/amd64/amd64/13.2-RELEASE/base.txz -o /usr/local/jails/media/13.2-RELEASE-base.txz

下载完成后,需要通过执行以下命令将内容提取到模板目录中

# tar -xf /usr/local/jails/media/13.2-RELEASE-base.txz -C /usr/local/jails/templates/13.2-RELEASE-base --unlink

一旦用户空间提取到 templates 目录中,就需要通过执行以下命令将时区和 DNS 服务器文件复制到模板目录。

# cp /etc/resolv.conf /usr/local/jails/templates/13.2-RELEASE-base/etc/resolv.conf
# cp /etc/localtime /usr/local/jails/templates/13.2-RELEASE-base/etc/localtime

将文件移动到模板后,接下来需要执行以下命令更新到最新的补丁级别。

# freebsd-update -b /usr/local/jails/templates/13.2-RELEASE-base/ fetch install

除了基本模板之外,还需要创建一个 skeleton 将位于其中的目录。一些目录将从模板复制到 skeleton 中。

如果使用 OpenZFS,请执行以下命令创建 skeleton 的数据集。

# zfs create -p zroot/jails/templates/13.2-RELEASE-skeleton

或者如果使用 UFS,则使用此命令。

# mkdir /usr/local/jails/templates/13.2-RELEASE-skeleton

然后创建 skeleton 目录。skeleton 目录将保存 jail 的本地目录。

执行以下命令创建目录。

# mkdir -p /usr/local/jails/templates/13.2-RELEASE-skeleton/home
# mkdir -p /usr/local/jails/templates/13.2-RELEASE-skeleton/usr
# mv /usr/local/jails/templates/13.2-RELEASE-base/etc /usr/local/jails/templates/13.2-RELEASE-skeleton/etc
# mv /usr/local/jails/templates/13.2-RELEASE-base/usr/local /usr/local/jails/templates/13.2-RELEASE-skeleton/usr/local
# mv /usr/local/jails/templates/13.2-RELEASE-base/tmp /usr/local/jails/templates/13.2-RELEASE-skeleton/tmp
# mv /usr/local/jails/templates/13.2-RELEASE-base/var /usr/local/jails/templates/13.2-RELEASE-skeleton/var
# mv /usr/local/jails/templates/13.2-RELEASE-base/root /usr/local/jails/templates/13.2-RELEASE-skeleton/root

下一步是通过执行以下命令创建到 skeleton 的符号链接。

# cd /usr/local/jails/templates/13.2-RELEASE-base/
# mkdir skeleton
# ln -s skeleton/etc etc
# ln -s skeleton/home home
# ln -s skeleton/root root
# ln -s ../skeleton/usr/local usr/local
# ln -s skeleton/tmp tmp
# ln -s skeleton/var var

准备好 skeleton 后,需要将数据复制到 jail 目录。

如果使用 OpenZFS,则可以使用 OpenZFS 快照通过执行以下命令轻松创建所需的任意数量的 jail。

# zfs snapshot zroot/jails/templates/13.2-RELEASE-skeleton@base
# zfs clone zroot/jails/templates/13.2-RELEASE-skeleton@base zroot/jails/containers/thinjail

如果使用 UFS,则可以通过执行以下命令使用 cp(1) 程序。

# cp -R /usr/local/jails/templates/13.2-RELEASE-skeleton /usr/local/jails/containers/thinjail

然后创建将挂载基本模板和 skeleton 的目录。

# mkdir -p /usr/local/jails/thinjail-nullfs-base

/etc/jail.confjail.conf.d 中添加如下所示的 jail 条目。

thinjail {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/${name}-nullfs-base";

  # NETWORK
  ip4.addr = 192.168.1.153;
  interface = em0;

  # MOUNT
  mount.fstab = "/usr/local/jails/${name}-nullfs-base.fstab";
}

然后创建如下所示的 /usr/local/jails/thinjail-nullfs-base.fstab 文件。

/usr/local/jails/templates/13.2-RELEASE-base  /usr/local/jails/thinjail-nullfs-base/ nullfs   ro          0 0
/usr/local/jails/containers/thinjail     /usr/local/jails/thinjail-nullfs-base/skeleton nullfs  rw  0 0

执行以下命令启动虚拟机

# service jail start thinjail

17.5.3. 创建 VNET Jail

FreeBSD VNET Jail 具有自己独特的网络堆栈,包括接口、IP 地址、路由表和防火墙规则。

创建 VNET Jail 的第一步是通过执行以下命令创建 bridge(4)

# ifconfig bridge create

输出应类似于以下内容。

bridge0

创建 bridge 后,需要通过执行以下命令将其附加到 em0 接口。

# ifconfig bridge0 addm em0

要使此设置在重新引导后保持不变,请将以下行添加到 /etc/rc.conf 中。

defaultrouter="192.168.1.1"
cloned_interfaces="bridge0"
ifconfig_bridge0="inet 192.168.1.150/24 addm em0 up"

下一步是按照上述说明创建 jail。

可以使用 经典 Jail(厚 Jail) 过程和 瘦 Jail 过程。唯一会改变的是 /etc/jail.conf 文件中的配置。

路径 /usr/local/jails/containers/vnet 将用作创建的 jail 的示例。

以下是一个 VNET Jail 的示例配置。

vnet {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop  = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 5;

  # PATH/HOSTNAME
  path = "/usr/local/jails/containers/${name}";
  host.hostname = "${name}";

  # VNET/VIMAGE
  vnet;
  vnet.interface = "${epair}b";

  # NETWORKS/INTERFACES
  $id = "154"; (1)
  $ip = "192.168.1.${id}/24";
  $gateway = "192.168.1.1";
  $bridge = "bridge0"; (2)
  $epair = "epair${id}";

  # ADD TO bridge INTERFACE
  exec.prestart  = "/sbin/ifconfig ${epair} create up";
  exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
  exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
  exec.start    += "/sbin/ifconfig ${epair}b ${ip} up";
  exec.start    += "/sbin/route add default ${gateway}";
  exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
  exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}
1表示 Jail 的 IP,它必须是**唯一的**。
2指的是之前创建的桥接。

17.5.4. 创建 Linux Jail

FreeBSD 可以使用 Linux 二进制兼容性debootstrap(8) 在 jail 中运行 Linux。Jail 没有内核。它们在宿主的内核上运行。因此,需要在主机系统中启用 Linux 二进制兼容性。

要在引导时启用 Linux ABI,请执行以下命令。

# sysrc linux_enable="YES"

启用后,可以通过执行以下命令在不重新引导的情况下启动它。

# service linux start

下一步将按照上述说明创建 jail,例如在 使用 OpenZFS 快照创建瘦 Jail 中,但**不**执行配置。FreeBSD Linux Jail 需要特定的配置,将在下面详细介绍。

按照上述说明创建 jail 后,执行以下命令以执行 jail 的必要配置并启动它。

# jail -cm \
    name=ubuntu \
    host.hostname="ubuntu.example.com" \
    path="/usr/local/jails/ubuntu" \
    interface="em0" \
    ip4.addr="192.168.1.150" \
    exec.start="/bin/sh /etc/rc" \
    exec.stop="/bin/sh /etc/rc.shutdown" \
    mount.devfs \
    devfs_ruleset=4 \
    allow.mount \
    allow.mount.devfs \
    allow.mount.fdescfs \
    allow.mount.procfs \
    allow.mount.linprocfs \
    allow.mount.linsysfs \
    allow.mount.tmpfs \
    enforce_statfs=1

要访问 jail,需要安装 sysutils/debootstrap

执行以下命令以访问 FreeBSD Linux Jail。

# jexec -u root ubuntu

在 jail 内部,执行以下命令以安装 sysutils/debootstrap 并准备 Ubuntu 环境。

# pkg install debootstrap
# debootstrap jammy /compat/ubuntu

当进程完成并在控制台上显示消息 Base system installed successfully 后,需要通过执行以下命令从主机系统停止 jail。

# service jail onestop ubuntu

然后在 /etc/jail.conf 中为 Linux Jail 添加一个条目。

ubuntu {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 4;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # NETWORK
  ip4.addr = 192.168.1.155;
  interface = em0;

  # MOUNT
  mount += "devfs     $path/compat/ubuntu/dev     devfs     rw  0 0";
  mount += "tmpfs     $path/compat/ubuntu/dev/shm tmpfs     rw,size=1g,mode=1777  0 0";
  mount += "fdescfs   $path/compat/ubuntu/dev/fd  fdescfs   rw,linrdlnk 0 0";
  mount += "linprocfs $path/compat/ubuntu/proc    linprocfs rw  0 0";
  mount += "linsysfs  $path/compat/ubuntu/sys     linsysfs  rw  0 0";
  mount += "/tmp      $path/compat/ubuntu/tmp     nullfs    rw  0 0";
  mount += "/home     $path/compat/ubuntu/home    nullfs    rw  0 0";
}

然后可以使用以下命令照常启动 jail。

# service jail start ubuntu

可以使用以下命令访问 Ubuntu 环境。

# jexec ubuntu chroot /compat/ubuntu /bin/bash

更多信息可以在 Linux 二进制兼容性 章节中找到。

17.5.5. 配置服务 Jail

服务 Jail 完全通过 /etc/rc.confsysrc(8) 进行配置。基本系统服务是准备好的服务 Jail。它们包含一个配置行,用于启用网络或解除 Jail 的其他限制。基本系统服务在 Jail 中运行没有意义,即使在 /etc/rc.conf 中启用,也不会配置为作为服务 Jail 启动。此类服务的一些示例是希望在启动或停止方法中挂载或卸载某些内容的服务,或者仅配置某些内容,例如路由、防火墙等。

第三方服务可能已准备好作为服务 Jail,也可能没有准备好。要检查服务是否已准备好作为服务 Jail,可以使用以下命令。

# grep _svcj_options /path/to/rc.d/servicename

如果没有输出,则表示服务没有准备好作为服务 Jail,或者不需要任何其他权限,例如网络访问。

如果服务没有准备好作为服务 Jail,并且需要网络访问,则可以通过向 /etc/rc.conf 添加必要的配置使其准备好。

# sysrc servicename_svcj_options=net_basic

有关所有可能的 _svcj_options,请参阅 rc.conf(5) 手册页。

要为给定服务启用服务 Jail,需要停止该服务并将 servicename_svcj 变量设置为 YES。要将 syslogd(8) 放入服务 Jail 中,请使用以下命令序列。

# service syslogd stop
# sysrc syslogd_svcj=YES
# service syslogd start

如果更改了 servicename_svcj 变量,则需要在更改之前停止服务。如果未停止,则 rc 框架将无法检测服务的正确状态,也无法执行请求的操作。

服务 Jail 仅通过 rc.conf(5)/sysrc(8)service(8) 命令进行管理。Jail 实用程序(如 jls(8),如 Jail 管理 中所述)可用于调查操作,但不应使用 jail(8) 命令来管理它们。

17.6. Jail 管理

创建 Jail 后,可以执行许多操作,例如启动、重启或删除 Jail,在其内部安装软件等。本节将介绍从主机对 Jail 可以执行的不同操作。

17.6.1. 列出正在运行的 Jail

要列出主机系统上正在运行的 Jail,可以使用命令 jls(8)

# jls

输出应类似于以下内容。

   JID  IP Address      Hostname                      Path
     1  192.168.250.70  classic                       /usr/local/jails/containers/classic

jls(8) 支持 --libxo 参数,该参数通过 libxo(3) 库允许显示其他类型的格式,例如 JSONHTML 等。

例如,执行以下命令以获取 JSON 输出。

# jls --libxo=json

输出应类似于以下内容。

{"__version": "2", "jail-information": {"jail": [{"jid":1,"ipv4":"192.168.250.70","hostname":"classic","path":"/usr/local/jails/containers/classic"}]}}

17.6.2. 启动、重启和停止 Jail

service(8) 用于在主机上启动、重启或停止 Jail。

例如,要启动 Jail,请运行以下命令。

# service jail start jailname

start 参数更改为 restartstop 以对 Jail 执行其他操作。

17.6.3. 销毁 Jail

销毁 Jail 不像使用 service(8) 停止 Jail 并删除 Jail 目录和 /etc/jail.conf 条目那样简单。

FreeBSD 非常重视系统安全。出于这个原因,某些文件甚至 root 用户也无法删除。此功能称为文件标志。

第一步是停止所需的 Jail,执行以下命令。

# service jail stop jailname

第二步是使用 chflags(1) 删除这些标志,执行以下命令,其中 classic 是要删除的 Jail 的名称。

# chflags -R 0 /usr/local/jails/containers/classic

第三步是删除 Jail 所在的目录。

# rm -rf /usr/local/jails/containers/classic

最后,需要删除 /etc/jail.confjail.conf.d 中的 Jail 条目。

17.6.4. 处理 Jail 中的软件包

pkg(8) 工具支持 -j 参数,以便处理安装在 Jail 中的软件包。

例如,要在 Jail 中安装 nginx-lite,可以**从主机**执行以下命令。

# pkg -j classic install nginx-lite

有关在 FreeBSD 中使用软件包的更多信息,请参阅 安装应用程序:软件包和端口

17.6.5. 访问 Jail

虽然上面已经说明最好从主机系统管理 Jail,但可以使用 jexec(8) 进入 Jail。

可以通过从主机运行 jexec(8) 进入 Jail。

# jexec -u root jailname

访问 Jail 时,将显示在 motd(5) 中配置的消息。

17.6.6. 在 Jail 中执行命令

要从主机系统中执行监狱内的命令,可以使用 jexec(8)

例如,要停止在监狱中运行的服务,将执行以下命令

# jexec -l jailname service nginx stop

17.7. 监狱升级

升级 FreeBSD 监狱可确保隔离环境保持安全、最新,并与 FreeBSD 生态系统中提供的最新功能和改进保持一致。

17.7.1. 使用 OpenZFS 快照升级经典监狱或精简监狱

必须**从主机**操作系统更新监狱。FreeBSD 中的默认行为是不允许在监狱中使用 chflags(1)。这将阻止某些文件的更新,因此从监狱内部进行更新将失败。

要将监狱更新到其正在运行的 FreeBSD 版本的最新补丁版本,请在主机上执行以下命令

# freebsd-update -j classic fetch install
# service jail restart classic

要将监狱升级到新的主版本或次版本,请首先按照 执行主版本和次版本升级 中的说明升级主机系统。主机升级并重新启动后,即可升级监狱。

如果从一个版本升级到另一个版本,创建新的监狱比完全升级更容易。

例如,要从 13.1-RELEASE 升级到 13.2-RELEASE,请在主机上执行以下命令

# freebsd-update -j classic -r 13.2-RELEASE upgrade
# freebsd-update -j classic install
# service jail restart classic
# freebsd-update -j classic install
# service jail restart classic

需要执行两次 install 步骤。第一个升级内核,第二个升级其余组件。

然后,如果是主版本升级,请重新安装所有已安装的软件包并重新启动监狱。这是必需的,因为在 FreeBSD 的主版本之间升级时,ABI 版本会发生变化。

从主机

# pkg -j jailname upgrade -f
# service jail restart jailname

17.7.2. 使用 NullFS 升级精简监狱

由于使用 NullFS 的精简监狱共享大部分系统目录,因此它们非常易于更新。只需更新模板即可。这允许同时更新多个监狱。

要将模板更新到其正在运行的 FreeBSD 版本的最新补丁版本,请在主机上执行以下命令

# freebsd-update -b /usr/local/jails/templates/13.1-RELEASE-base/ fetch install
# service jail restart

要将模板升级到新的主版本或次版本,请首先按照 执行主版本和次版本升级 中的说明升级主机系统。主机升级并重新启动后,即可升级模板。

例如,要从 13.1-RELEASE 升级到 13.2-RELEASE,请在主机上执行以下命令

# freebsd-update -b /usr/local/jails/templates/13.1-RELEASE-base/ -r 13.2-RELEASE upgrade
# freebsd-update -b /usr/local/jails/templates/13.1-RELEASE-base/ install
# service jail restart
# freebsd-update -b /usr/local/jails/templates/13.1-RELEASE-base/ install
# service jail restart

17.8. 监狱资源限制

控制监狱从主机系统使用的资源是系统管理员需要考虑的任务。

rctl(8) 允许您管理监狱可以从主机系统使用的资源。

必须在 /boot/loader.conf 中启用 kern.racct.enable 可调参数。

限制监狱资源的语法如下

rctl -a jail:<jailname>:resource:action=amount/percentage

例如,要限制监狱可以访问的最大 RAM,请运行以下命令

# rctl -a jail:classic:memoryuse:deny=2G

要使限制在主机系统重新启动后保持持久,需要将规则添加到 /etc/rctl.conf 文件中,如下所示

jail:classic:memoryuse:deny=2G/jail

有关资源限制的更多信息,请参阅 资源限制部分 中的安全章节。

17.9. 监狱管理器和容器

如前所述,每种类型的 FreeBSD 监狱都可以手动创建和配置,但 FreeBSD 也有第三方实用程序来简化配置和管理。

以下是不同 FreeBSD 监狱管理器的非完整列表

表 1. 监狱管理器
名称许可证软件包文档

BastilleBSD

BSD-3

sysutils/bastille

文档

pot

BSD-3

sysutils/pot

文档

cbsd

BSD-2

sysutils/cbsd

文档

AppJail

BSD-3

sysutils/appjail,用于开发 sysutils/appjail-devel

文档

iocage

BSD-2

sysutils/iocage

文档

ezjail

Beer Ware

sysutils/ezjail

文档


最后修改时间:2024 年 9 月 20 日,作者 Fernando Apesteguía