Portal:SELinux/WritingPolicy

跳转到:导航搜索

编写策略

任何人都可以编写 SELinux 策略 - 甚至你! 有许多工具可以协助此过程。

为了确保你已准备好为你的应用程序编写 selinux 策略,请安装 `policycoreutils-devel`。

设置

假设我们有一个名为“mydaemon”的守护进程,我们想为其编写策略。

首先切换到你的工作目录,你可以在其中创建文件,并创建模板策略文件。

   cd ~/policy-devel
   sepolicy generate --init /path/to/binary/mydaemon

这将为你创建多个文件。 后缀是

  • fc - filecontexts,列出应用于命名文件路径的类型
  • if - interfaces,可导出的 selinux 策略接口,供其他模块使用。
  • te - type enforcement,定义类型交互规则的规则。

检查生成的文件 fc,你应该看到类似以下的上下文

   /usr/sbin/mydaemon		--	gen_context(system_u:object_r:mydaemon_exec_t,s0)

按照惯例,名为 X 的守护进程的文件类型是将其命名为 X_exec_t。

这里另一个重要的文件是 te 文件。 它的内容将如下所示

   policy_module(mydaemon, 1.0.0)
   ########################################
   #
   # Declarations
   #
   type mydaemon_t;
   type mydaemon_exec_t;
   init_daemon_domain(mydaemon_t, mydaemon_exec_t)
   permissive mydaemon_t;
   ########################################
   #
   # mydaemon local policy
   #
   allow mydaemon_t self:fifo_file rw_fifo_file_perms;
   allow mydaemon_t self:unix_stream_socket create_stream_socket_perms;
   domain_use_interactive_fds(mydaemon_t)
   files_read_etc_files(mydaemon_t)
   miscfiles_read_localization(mydaemon_t)

这定义了

  • 用于新标签的类型名称。
  • 一个规则,允许 init 守护进程从类型 mydaemon_exec_t 转换到一个新的进程类型 mydaemon_t
  • mydaemon_t 的宽松规则,允许守护进程在开发策略时正常工作
  • 一些默认规则,可以广泛允许你的进程继续运行。

你可以使用以下命令重建和安装此策略

   make -f /usr/share/selinux/devel/Makefile mydaemon.pp
   /usr/sbin/semodule -i mydaemon.pp

这将构建并加载策略模块。 现在你可以使用以下命令重新标记从你的 filecontexts 更改的文件

   restorecon -v /path/to/binary/mydaemon

如果你想重置目录中的文件

   restorecon -v -r /path/to/dir

重要的是要重新标记文件,以便正确启用下一阶段的交互! 如有疑问,请运行

   restorecon -v -r /

此时,你可以重新启动你的服务,你将能够看到它在其新的 selinux 类型中运行。

   systemctl restart mydaemon
   # ps auxZ | grep mydaemon
   ...

如果进程没有作为 mydaemon_t 运行,那么你可以使用以下命令进行调查

   # Can init_t transition to mydaemon_t?
   sepolicy transition -s init_t -t mydaemon_t
   # Is there a denial during the transition?
   ausearch -ts recent -m avc -u system_u:system_r:init_t:s0
   

调查拒绝

如果进程正在作为 mydaemon_t 运行,或者没有,我们需要开始调查并添加规则以授权我们的守护进程执行其所需的有限操作。 使用 `ausearch -ts recent -m avc` 查看最近的拒绝。 一个例子是

   type=AVC msg=audit(1687768006.183:92): avc:  denied  { nnp_transition } for  pid=1206 comm="(dm_unixd)" scontext=system_u:system_r:init_t:s0 tcontext=system_u:system_r:kanidm_unixd_t:s0 tclass=process2 permissive=0

这显示了 init_t 转换为 kanidm_unix_t 的失败,原因是 nnp_transition 的拒绝。 这导致 kanidm_unixd 作为 init_t 运行,而不是 kanidm_unix_t。

有两种方法可以解决这个问题。

首先,我们可以尝试使用 audit2allow 自动生成规则。

   # ausearch -ts recent -m avc | audit2allow
   allow init_t kanidm_unixd_t:process2 nnp_transition;

虽然这可行,但有时 audit2allow 可能会“允许过多”,并且它生成的规则相当粗略。 但是,它将创建一个可用的策略。 你可以将你满意的规则添加到你的 .te 文件中,然后重建并安装策略。

编写策略的首选方法是使用现有策略中的接口。 这些接口存在于 `/usr/share/selinux/devel` 中,可以使用 grep 或类似工具进行搜索。 因此,我们可以使用关键字 `nnp_transition` 来查找相关策略。

   # grep -i -r -n -e 'nnp_transition' /usr/share/selinux/devel
   /usr/share/selinux/devel/include/contrib/kpatch.if:38:	allow $1 kpatch_t:process2 { nnp_transition nosuid_transition };
   /usr/share/selinux/devel/include/contrib/mozilla.if:289:	allow $1 mozilla_plugin_t:process2 nnp_transition;
   /usr/share/selinux/devel/include/system/init.if:128:	allow init_t $1:process2 { nnp_transition nosuid_transition };
   /usr/share/selinux/devel/include/system/init.if:155:    allow init_t $1:process2 { nnp_transition nosuid_transition };

我们可以看到,这里 kpatch 和 mozilla 的策略都引用了 nnp_transition,但是它们似乎与 kpatch 和 mozilla 转换到其他类型有关,因为允许规则是 `allow source dest`。 但是,在 init.if 中,我们看到一些可能匹配我们的关键字并允许 init_t 转换为变量的规则。 查看这些文件,我们看到一个名为 `init_daemon` 的接口,以及另一个名为 `init_nnp_daemon_domain` 的接口

最终你需要审查这些接口并确定你想在你的规则中使用哪个接口。 在这个例子中,我选择使用 `init_nnp_daemon_domain`,它更新了我们的策略为


   policy_module(mydaemon, 1.0.0)
   ########################################
   #
   # Declarations
   #
   type mydaemon_t;
   type mydaemon_exec_t;
   init_daemon_domain(mydaemon_t, mydaemon_exec_t)
   init_nnp_daemon_domain(mydaemon_t, mydaemon_exec_t)
   permissive mydaemon_t;
   ########################################
   #
   # mydaemon local policy
   #
   allow mydaemon_t self:fifo_file rw_fifo_file_perms;
   allow mydaemon_t self:unix_stream_socket create_stream_socket_perms;
   domain_use_interactive_fds(mydaemon_t)
   files_read_etc_files(mydaemon_t)
   miscfiles_read_localization(mydaemon_t)


完善策略

虽然 audit2allow 会起作用,并且上面的策略将为我们提供一个最小的工作受限二进制文件,但我们希望拥有更严格的规则和类型,以便我们的守护进程只能访问其资源 - 并且其他进程无法访问我们的守护进程的资源。

为了实现这一点,我们需要标记我们的文件系统资源,以便我们看到的拒绝特定于这些资源。

首先,将我们需要的新类型添加到我们的 .te 文件中。 有许多“约定”规定了这些类型名称应该如何存在。 如有疑问,ls -lZ 其他文件并查看其他人做了什么!

   type mydaemon_conf_t;
   files_config_file(mydaemon_conf_t)
   type mydaemon_var_run_t;
   files_type(mydaemon_var_run_t)
   files_pid_filetrans(mydaemon_t, mydaemon_var_run_t, { dir file sock_file })
   type mydaemon_var_cache_t;
   files_type(mydaemon_var_cache_t)

一旦这些类型存在,我们需要将文件上下文添加到我们的 fc 文件中。 这些规则是正则表达式,因此请小心!

   /etc/mydaemon(/.*)?                 gen_context(system_u:object_r:mydaemon_conf_t,s0)
   /var/run/mydaemon(/.*)?             gen_context(system_u:object_r:mydaemon_var_run_t,s0)
   /var/cache/mydaemon(/.*)?           gen_context(system_u:object_r:mydaemon_var_cache_t,s0)
   /usr/sbin/mydaemon		    --	    gen_context(system_u:object_r:mydaemon_exec_t,s0)

提示 请注意,应用于目录的规则中,我们删除了 --? 这允许上下文更改其他文件类型,例如目录。

现在,我们重建并重新安装我们的策略。 我们还需要在更新的文件路径上恢复con。

   restorecon -r -v /etc/mydaemon /var/run/mydaemon /var/cache/mydaemon

现在你可以重新启动你的守护进程,并使用这些新类型完善你的策略。

从这里,你迭代地使用 audit2allow 添加规则,或者手动添加从搜索策略标头中找到的接口。

你还需要练习你的守护进程及其功能,以便找到所有可能发生拒绝的地方以及你的守护进程如何交互。

将你的 ausearch 查询限制在你的守护进程可能会很有用

   ausearch -ts timestamp -su system_u:system_r:mydaemon_t:s0

调查非拒绝行为

有时你的策略看起来是正确的,但行为可能不如你预期的那样。 例如,文件可能没有正确重新标记,或者类型没有被用于进程转换。

SELinux 中有时存在许多微妙的边缘情况,这些情况并不总是显而易见的。

查看其他现有策略以了解它们如何处理相同情况可能是一个有用的参考。 这对于位于 `/var/run` 或 `/etc` 等位置的文件尤其如此。

最终确定策略

一旦你对策略感到满意,并且在一段时间内没有拒绝,你应该删除以下行

   permissive mydaemon_t

然后重建并重新安装你的策略。 如果你遇到拒绝,你可以暂时将域设置为宽松模式,方法是

   semanage permissive -a mydaemon_t

你可以将类型重置为强制模式,方法是

   semanage permissive -d mydaemon_t