runsisi's

technical notes

编写 librados SELinux 规则

2019-01-19 runsisi#selinux#ceph

需求比较简单,就是编写独立的规则允许 OpenStack 的 qemu 进程能够访问(完全控制) ceph 的客户端 admin socket,之所以需要独立的规则(或者说模块),而不是直接修改 ceph-selinux 中定义的规则,是因为 ceph-selinux 依赖 ceph-base,我们在客户端节点通常来说只会安装 python-rbd 之类的客户端必须的包,以及 ceph-common 等基础包,而 ceph-base 实际上是服务端所需的包,而且它自身又有其它的依赖,因此不适合在客户端进行安装。

在分离部署和融合部署两种情况下,/var/run/ceph 目录的 SELinux 标签(label)是不同的,在分离部署下是 var_run_t,即默认的标签,而在融合部署情况下,ceph-selinux 会执行 fixfiles/restorecon 将 /var/run/ceph 打上新的标签 ceph_var_run_t,因此在写规则时我们要考虑到这两种情况:

~# vi librados.te
policy_module(librados, 0.0.1)

require {
    type virtd_t;
    type var_run_t;
    type ceph_var_run_t;
}

manage_sock_files_pattern(virtd_t, var_run_t, var_run_t)
manage_sock_files_pattern(virtd_t, ceph_var_run_t, ceph_var_run_t)

然后进行编译(注意不要使用 checkmodule 等网上场景的方式(见第一个参考链接),因为 checkmodule 不会使用到预定义的宏和接口,需要完全手写 allow 规则):

~# rpm -qa | grep selinux-policy-devel
selinux-policy-devel-3.13.1-60.el7.noarch
~# rpm -ql selinux-policy-devel-3.13.1-60.el7.noarch | grep Makefile
/usr/share/selinux/devel/Makefile
/usr/share/selinux/devel/include/Makefile

# 注意这里的 .pp 后缀,makefile 文件会在当前目录寻找 librados.te 的 te 文件,并编译成二进制的 pp 文件
# 另外这里的 librados 文件名并不需要与 te 文件中的模块名一致
~# make -f /usr/share/selinux/devel/Makefile librados.pp

然后可以用 semodule 对编译后得到的二进制规则文件进行测试、验证。

但是,上面测试是我在安装了 ceph-selinux 包的测试结果,如果在没有安装 ceph-selinux 包的机器进行测试,会得到如下的错误:

# semodule -i librados.pp
libsepol.print_missing_requirements: librados's global requirements were not met: type/attribute ceph_var_run_t (No such file or directory).
libsemanage.semanage_link_sandbox: Link packages failed (No such file or directory).
semodule:  Failed!

显然,ceph_var_run_t 是在 ceph-selinux 中的 ceph.pp 定义的,我们没有安装,自然 librados.te 中 require 的 ceph_var_run_t 找不到定义。

解决方法就是 SELinux 的 optional 规则:

~# vi librados2.te
policy_module(librados2, 0.0.2)

optional {
    require {
        type virtd_t;
        type var_run_t;
        type ceph_var_run_t;
    }

    manage_sock_files_pattern(virtd_t, var_run_t, var_run_t)
    manage_sock_files_pattern(virtd_t, ceph_var_run_t, ceph_var_run_t)
}

然后同样的可以用 semodule 对编译后得到的二进制规则文件进行测试、验证。

显然上面的情况仍然可能会出现所需的依赖 type 未定义的情况,而像下面的组合就能考虑到各种未定义的情况:

~# vi librados3.te
policy_module(os-librados3, 0.0.3)

optional {
require {
    type virtd_t;
    type var_run_t;
}

manage_sock_files_pattern(virtd_t, var_run_t, var_run_t)
}

optional {
require {
    type virtd_t;
    type ceph_var_run_t;
}

manage_sock_files_pattern(virtd_t, ceph_var_run_t, ceph_var_run_t)
}

optional {
require {
    type glance_api_t;
    type var_run_t;
}

manage_sock_files_pattern(glance_api_t, var_run_t, var_run_t)
}

optional {
require {
    type glance_api_t;
    type ceph_var_run_t;
}

manage_sock_files_pattern(glance_api_t, ceph_var_run_t, ceph_var_run_t)
}

上面使用 optional 规则确实可以解决依赖的问题,而且可以同时解决在分离部署和融合部署两种场景下 /var/run/ceph(/.*)? 存在两种不同标签(var_run_tceph_var_run_t),但是是不是不需要这么复杂呢,我们可以换另外一种思路,为客户端的 .asok 文件独立定义一种 file context 类型,并且根据这种 file context 定义 type enforcement,然后让 librados 或者 ceph-common 包在安装时安装这个规则模块。

定义 file context 类型(注意 .fc 文件中的文件名匹配是标准的正则表达式,而不是普通的 glob 匹配规则):

~# vi os-librados3.fc
/var/run/ceph/.*\.casok     gen_context(system_u:object_r:librados_var_run_t,s0)

定义 type enforcement(为了简化,去掉了 glance_api_t):

~# vi os-librados3.te
policy_module(os-librados3, 0.0.3)

type librados_var_run_t;
files_pid_file(librados_var_run_t)

optional {
require {
    type virtd_t;
}

manage_sock_files_pattern(virtd_t, librados_var_run_t, librados_var_run_t)
}

编译:

~# make -f /usr/share/selinux/devel/Makefile os-librados3.pp
Compiling targeted os-librados3 module
/usr/bin/checkmodule:  loading policy configuration from tmp/os-librados3.tmp
/usr/bin/checkmodule:  policy configuration loaded
/usr/bin/checkmodule:  writing binary representation (version 17) to tmp/os-librados3.mod
Creating targeted os-librados3.pp policy package
rm tmp/os-librados3.mod tmp/os-librados3.mod.fc

安装:

~# semodule -i os-librados3.pp
~# grep librados /etc/selinux/targeted/modules/active/file_contexts
/var/run/ceph/.*\.casok system_u:object_r:librados_var_run_t:s0
~# grep librados /etc/selinux/targeted/contexts/files/file_contexts
/var/run/ceph/.*\.casok system_u:object_r:librados_var_run_t:s0

测试:

# 此时还没有安装 ceph.pp,因此只会更新 *.casok 文件的 label,而 *.asok 文件仍然保持系统默认的 var_run_t label
~# restorecon -FR /var/run/ceph/
~# ll /var/run/ceph/ -Zd
drwxr-xr-x. root root system_u:object_r:var_run_t:s0   /var/run/ceph/
~# ll /var/run/ceph/ -Z
-rw-r--r--. root root system_u:object_r:librados_var_run_t:s0 1.casok
-rw-r--r--. root root system_u:object_r:var_run_t:s0   2.asok

# 安装 ceph.pp,但是并不会自动修改已有文件的 label
~# semodule -i ceph.pp
# restorecon 根据新的 file context 定义更新文件 label
~# restorecon -FR /var/run/ceph/
~# ll /var/run/ceph/ -Zd
drwxr-xr-x. root root system_u:object_r:ceph_var_run_t:s0 /var/run/ceph/
~# ll /var/run/ceph/ -Z
-rw-r--r--. root root system_u:object_r:librados_var_run_t:s0 1.casok
-rw-r--r--. root root system_u:object_r:ceph_var_run_t:s0 2.asok

虽然我们通过前面的 os-librados3.fc 将 /var/run/ceph/*.casok 文件定义成了 librados_var_run_t type,且 restorecon 能正确的更新 label,但是我们忽视了一个非常大的问题,新创建的文件默认继承了父目录的 label,除非显式的使用 filetrans_pattern 接口定义转换规则,但是 filetrans_pattern 接口需要传递父目录的 type,而在我们分离和融合部署两种场景下,父目录的 type 可能为 var_run_t 也可能为 ceph_var_run_t,因此,我们并不能使用上面这种方法,最终我们还是只能使用 os-librados3.te 定义的方法,但是注意到由于 sVirt 的引入,os-librados3.te 需要做如下的修改(还是考虑不支持 glance_api_t,即只支持 virtd_tsvirt_t 两种 process domain):

~# vi librados.te
policy_module(librados, 0.0.4)

optional {
require {
    type virtd_t;
    type var_run_t;
}

manage_sock_files_pattern(virtd_t, var_run_t, var_run_t)
}

optional {
require {
    type virtd_t;
    type ceph_var_run_t;
}

manage_sock_files_pattern(virtd_t, ceph_var_run_t, ceph_var_run_t)
}

optional {
require {
    type svirt_t;
    type var_run_t;
}

manage_sock_files_pattern(svirt_t, var_run_t, var_run_t)
}

optional {
require {
    type svirt_t;
    type ceph_var_run_t;
}

manage_sock_files_pattern(svirt_t, ceph_var_run_t, ceph_var_run_t)
}

参考资料

[1] How to compile a SELinux policy package

https://relativkreativ.at/articles/how-to-compile-a-selinux-policy-package

[2] Policy development: optional policy

http://selinux-mac.blogspot.com/2009/07/policy-development-optional-policy.html

[3] SELinux - canonical way of automatically applying a context on file creation

https://serverfault.com/questions/531431/selinux-canonical-way-of-automatically-applying-a-context-on-file-creation

[4] SELinuxFileNameTransition

https://fedoraproject.org/wiki/Features/SELinuxFileNameTransition

[5] What is sVirt?

https://rwmj.wordpress.com/2011/05/24/what-is-svirt/