受 SELinux 保护的容器
我们将在容器上使用的 SELinux 策略包含一个 策略模块;这个模块已经发布到 refpolicy -- SELinux Reference Policy 开发邮件列表。将这个策略分别下载到 /root/vs 目录下的 vs.if、vs.fc 和 vs.te 文件中。像下面这样编译和安装新的模块:
cp vm.img selinux.img
cp vm.img smack.img
然后使用 lxc-debian 创建 /vs1 and /vs2 容器,并且使用
mkdir /vs1; cd /vs1
lxc-debian create
container name: vs1
hostname: vs1
address: 10.0.2.21
gateway: 10.0.2.2
arch: 2 (i386)
mkdir /vs2; cd /vs2
lxc-debian create
container name: vs2
hostname: vs2
address: 10.0.2.22
gateway: 10.0.2.2
arch: 2 (i386)
fixfiles relabel /vs1
fixfiles relabel /vs2
重新标记它们的文件系统。
在启动容器时(例如通过使用命令 lxc-start -n vs1),很可能会收到一些关于 SELinux 访问拒绝的审计消息。但不要担心 —— 容器将正常启动,并且会启用网络服务并 隔离容器。如果在启动容器之前使用 mount --bind / /vs1/rootfs.vs1/mnt 帮助容器 vs1 进行伪装,那么您将会发现,即使是根用户,也会重用 ls /mnt/root。
为了了解其中的原理,我们看看 vs.if 接口文件。这个文件定义一个称为 container 的接口,它带有一个参数(即容器将要定义的基本名称)。vs.te 文件使用容器名 vs1 和 vs2 两次调用这个函数。在这个接口中,$1 被扩展到这个参数,因此当我们调用 container(vs1) 时,$1_t 就会变成 vs1_t(从这里开始,假设我们定义的是 vs1)。
包含 vs1_exec_t 内容的行是最重要的。这个容器以 vs1_t 类型运行。当 unconfined_t 执行容器的 /sbin/init(类型为 vs1_exec_t)时,它将进入这种类型。
剩余的策略主要是授与容器充分的特权,以访问系统的各个部分:网络端口、设备和控制台等。该接口很长,这是由现有 SELinux 引用策略的细粒度特性决定的。正如我们将要看到的一样,受 Smack 保护的容器具有更加简单的策略;但是它在系统服务行为失误时提供的灵活保护要少得多。
还有一件事情需要做。需要注意的是,虽然容器不能够重写它的 $1_exec_t(即 /sbin/init),但它能够执行
mv /sbin /sbin.bak
mkdir /sbin
touch /sbin/init
生成的 /sbin/init 的类型为 vs1_file_t。容器管理员为什么需要这样做呢?因为它会在 unconfined_t 域中启动容器,包括 ssh daemon,这使他能够获得有特权的 shell,并且能够绕过我们将要实施的 SELinux 限制。
为了避免这样做,需要通过定制脚本实际启动容器,并在启动容器前将 sbin/init 重新标记为 vs1_exec_t。事实上,如果容器管理员不介意的话,可以将一个 init 原始副本复制回容器中并重新标记它。但我们仅重新标记现有的 init:
cat >> /vs1/vs1.sh << EOF
#!/bin/sh
chcon -t vs1_exec_t /vs1/rootfs.vs1/sbin/init
lxc-start -n vs1
EOF
chmod u+x /vs1/vs1.sh
现在需要使用 /vs1/vs1.sh 启动容器,而不是使用 lxc-start 手工启动。