Home systemd 对chroot的支持和替代
Post
Cancel

systemd 对chroot的支持和替代

小结

  • 安全的使用chroot的最好方式是在c代码中本地完成
  • ReadOnlyDirectories=InaccessibleDirectories= 应该是chroot环境的合适替代方案
  • 如果必须要对一个服务chroot,可以使用 RootDirectory= 选项
  • 新的操作系统镜像的制作调试可以使用systemd-nspawn工具代替chroot,它更安全方便,且能与systemd本身更好的适配

change root

关于chroot系统调用,切换进程及其子进程的根目录,从而将进程能看到的文件层次结构限制在某个子目录树中。 主要有两个用途:

  1. 为安全使用,限制可以守护进程能操作的目录,避免守护进程破坏系统文件
  2. 要设置和控制操作系统的调试、测试、构建、安装或恢复映像:为此,将整个客户操作系统层次结构挂载或引导到主机操作系统的子目录中, 然后在其中启动shell(或其他应用程序),该子目录转换为/。对于shell来说,它似乎是在一个与主机操作系统有很大不同的系统中运行的。 例如,它可能运行不同的发行版,甚至不同的体系结构(例如:主机x86_64,客户i386)。它无法看到的主机操作系统的完整层次结构。

在sysV init中,使用chroot相对容易,创建好目录,挂载 /proc,/sys和其他等到目录树中,然后使用chroot(1) 即可,最后在chroot中 通过/sbin/service启动进程。

到了systemd中,则比较麻烦了。因为systemd的一大优点就是,保证所有的守护进程在完全干净和独立的上下文中被调用, 即与用户请求启动服务时的上下文无关。 而在sysV init的系统中,有很大部分的执行环境是从shell中继承下来的,如资源限制, 环境变量等等。systemd中,用户只是通知init守护进程,然后init守护进程将在一个正常的、定义良好的和原始的环境中分离守护进程, 并且不会发生用户上下文参数的继承。这是方式很好,但也打破了传统。

While this is a formidable feature it actually breaks traditional approaches to invoke a service inside a chroot() environment: since the actual daemon is always spawned off PID 1 and thus inherits the chroot() settings from it, it is irrelevant whether the client which asked for the daemon to start is chroot()ed or not. On top of that, since systemd actually places its local communications sockets in /run/systemd a process in a chroot() environment will not even be able to talk to the init system (which however is probably a good thing, and the daring can work around this of course by making use of bind mounts.)

这就引出了如何在systemd环境中正确使用chroot()s的问题。

安全问题示例

先放一个示例:基于安全目的,将守护进程锁定在chroot()环境中。chroot也并非绝对安全,需要配合一些手段才能使它在一定程度上 变得安全。通常chroot用于安全时,在守护程序本身的C代码实现中最好。使用chroot对于开发者来说,至少需要知道哪些文件,目录 需要在chroot中使用,即一个最小边界。

chroot 安全,参考 man 2 chroot 说明。它仅改变路径名解析过程中的一个成分。不是专门为安全目的使用,既不能完全 沙盒化一个进程,也不能限制文件系统调用。有方法可以逃离chroot。

systemd 提供了一种简单的方法来chroot特定的守护程序。这是通过 RootDirectory= 关键字支持的(在service文件中使用)。

1
2
3
4
5
6
7
8
[Unit]
Description=A chroot()ed Service

[Service]
RootDirectory=/srv/chroot/foobar
ExecStartPre=/usr/local/bin/setup-foobar-chroot.sh
ExecStart=/usr/bin/foobard
RootDirectoryStartOnly=yes

RootDirectory=会在调用ExecStart=指定的守护程序之前配置chroot()的位置。需要注意的是,ExecStart=中指定的路径 是指向chroot()后的路径中的二进制文件,不是主机根目录中的原本路径(在本例中,守护程序的主机实际路径为 /srv/chroot/foobar/usr/bin/foobard)。在启动守护进程之前,将调用shell脚本setup-foobar-chroot.sh, 其目的是根据需要设置chroot环境,如将/proc和类似的文件系统挂载都需要chroot的路径中,具体需要挂载哪些目录取决于守护程序的需要。 使用RootDirectoryStartOnly=,确保只有ExecStart=中指定的守护进程被chroot,而ExecStartPre=指定脚本不被chroot生效, 该脚本需要访问完整的操作系统层次结构,以便它可以从那里绑定挂载目录。最后,该服务可以像普通服务一样被systemctl操作。

现代的Linux内核支持文件系统命名空间(file system namespace,属于linux namespace)。它们与chroot()类似,但功能强大得多, 且它们不存在与chroot()相同的安全问题。Systemd可以在单元文件中显示暴露服务需要使用的文件系统命名空间的一个子集。 通常,这是一个比传统的chroot更加有用和简单的替代方法。使用ReadOnlyDirectories=InaccessibleDirectories=两个选项, 就可以为守护进程服务设置一个文件系统名称空间的特定子集(范围和权限)。初始化时,这个文件系统命名空间的子集 是和主机操作系统的 文件系统名称空间相同的。通过上面两个选项的配置,就可以将主机操作系统的某些目录或挂载点标记为只读,甚至对守护检测出拒接访问。示例:

1
2
3
4
5
6
[Unit]
Description=A Service With No Access to /home

[Service]
ExecStart=/usr/bin/foobard
InaccessibleDirectories=/home

这示例中,服务可以访问除了 /home 以外的所有目录。

文件系统命名空间实际上在许多方面都是chroot()的更好替代品。

关于 Linux namespace,可以简单参考
浅谈Linux Namespace机制
搞懂容器技术的基石: namespace

系统构建及调试示例

系统镜像制作、调试、恢复等。常用chroot,进入到新的操作系统中,作为新的根,并进行制作和调试等。

Chroot()环境相对简单:它们只虚拟化文件系统层次结构。通过chroot()进入子目录,进程仍然可以完全访问所有系统调用,可以杀死所有进程, 并与它所运行的主机共享所有其他内容。 因此,在chroot()中运行操作系统(或其部分)是一件危险的事情:主机和客户之间的隔离仅限于文件系统,其他所有内容都可以从chroot()中自由访问。 例如,在chroot()中升级了一个发行版,并且包脚本将SIGTERM发送到PID 1以触发init系统的重新执行,这实际上将在主机操作系统中发生! 在此基础上,SysV共享内存、抽象命名空间套接字和其他IPC原语在主机和客户之间共享。虽然可能没有必要为测试、调试、构建、安装或恢复操作系统 提供完全安全的隔离,但有必要使用基本隔离来避免chroot()环境内部对主机操作系统的意外修改:因为你永远无法确定哪些包脚本可能会干扰主机操作系统。

systemd针对以上的问题提供了几个特性:

为了处理chroot()的设置,systemd提供了几个特性:

(1) systemctl能够检测它是否运行在chroot环境中,如果是,除了 systemctl enable/disable 之外,它的大部分操作都会变成nop空操作,以 确保在chroot环境中的安全,不会影响到运行中的主机系统。

(2)更重要的,systemd提供了一个替代工具systemd-nspawn,它能利用文件系统和PID命名空间启动一个非常简单的轻量级容器,使用时和chroot几乎相同, 并且它对主机操作系统的隔离做得更好,更安全。事实上,systemd-nspawn能够用一个命令在容器中启动一个完整的systemd或sysvinit操作系统。 由于它虚拟化了PID,容器中的init系统可以充当PID 1,从而正常地完成它的工作。与chroot(1)相比,这个工具将隐式地帮你挂载/proc, /sys。

示例:用三个命令在Fedora机器上的nspawn容器中启动Debian操作系统:

1
2
3
# yum install debootstrap
# debootstrap --arch=amd64 unstable debian-tree/
# systemd-nspawn -D debian-tree/

这将引导OS目录树,然后简单地调用其中的shell。如果你想在容器中启动一个完整的系统,使用如下命令:

1
# systemd-nspawn -D debian-tree/ /sbin/init

使用起来确实和chroot几乎相同。 在快速启动之后,在一个完整的操作系统中,应该在容器中启动一个shell。容器将无法看到它外部的任何进程。它将共享网络配置,但无法修改它。 像/sys和/proc/sys这样的目录在容器中是也可用的,但挂载为只读,以避免容器可以修改内核或硬件配置。 但是请注意,这只保护主机操作系统不受其参数的意外更改的影响。容器中的进程可以手动重新挂载可读可写的文件系统,然后更改它想更改的任何内容。

system -nspawn主要优点:

  • 易用。不需要手动将/proc和/sys挂载到chroot()环境。该工具将为您完成该工作,当容器终止时,内核将自动清理它。
  • 隔离更加完整,可以保护主机操作系统不受容器内部意外更改的影响。
  • 更完善,可以在容器中引导一个完整的操作系统,而不仅仅是一个单独的shell。
  • 容量小,可以安装在systemd安装的任何地方。无需复杂的安装或设置。

Systemd本身经过了修改,可以在这样的容器中很好地工作。(适配systemd-nspawn工具)。例如,当systemd关闭并检测到它在容器中运行时, 它只调用exit(),而不是作为最后一步调用reboot()。

注意,systemd-nspawn不是一个完整的容器解决方案。容器还是使用docker或lxc,它们使用相同的底层内核技术,但提供了更多功能,包括网络虚拟化等。 systemd-nspawn 主要是chroot的更好的替代品,在使用systemd的系统上。

让我们把这个做完,这已经够长了。以下是我们可以从这个小博客故事中学到的东西:

Systemd-nspawn由awesome组成。 Chroot()是蹩脚的,文件系统名称空间完全是l33t。 所有这些都可以在您的Fedora 15系统上使用。

This post is licensed under CC BY 4.0 by the author.