我们如何构建对Kubernetes的SELinux支持

2020-05-28 19:23:06

作为重力团队的一名工程师,我的任务是将SELinux支持添加到三月份发布的Gravite7.0中。这项工作的结果是一个基本的Kubernetes集群策略,它限制服务(特定于重力和Kubernetes)和用户工作负载。

在这篇文章中,我将解释我是如何构建它的,我遇到了哪些问题,以及我想要分享的一些有用的提示。具体地说,我们将查看策略常见方面的属性使用情况。我们将了解如何搜索和选择适当的内置接口以提高策略的可读性,以及在使用文件标签时应如何考虑文件等价性规则。

之后,我将向您展示我们如何在集群中限制Kubernetes服务和工作负载。

我们遇到的问题是,Grarain从来没有被设计成在启用了SELinux的主机上运行,我们的客户在尝试运行启用了SELinux的重力时,总是随机破坏他们的安装。

在开始采用SELinux之前,我对这项技术几乎一无所知。在花了三个月的时间研究它之后,我仍然不能自称是专家。不过,我将介绍我学到的一些内容,以及使复杂的应用程序适应SELinux的一些公开问题。

所有进程和文件都有一个关联的SELinux安全上下文。SELinux提供了一种白名单方法来限制正在运行的应用程序对内核提供的资源的访问。这种对所有访问的隐式拒绝给应用程序开发人员带来了巨大的挑战,他们要在每种可能的情况下为复杂应用程序提供详尽的访问权限列表。

拥有使用SELinux的经验和完全运行应用程序的能力对于开发随附的SELinux策略至关重要。

为了设置场景,让我们从关于我们软件的基本信息开始。它是CNCF认证的Kubernetes发行版,包含大量电池。从SELinux的角度来看,该软件分为两组:

在开始使用一项新技术时,普遍推荐的第一件事是探索现有的工具。在SELinux中,我很快发现了sepolgen,它可以帮助生成应用程序的初始存根。Dan Walsch写了一篇关于这个工具的帖子,这绝对是一个很好的开始。

$sepolicy生成--init/path/to/gravityLoaded Plugin:fastestMirror创建了以下文件:/home/centos/selinux/gravity.te#类型强制文件/home/centos/selinux/gravity.if#interface file/home/centos/selinux/gravity.fc#文件上下文文件/home/centos/selinux/graance_selinux.spec#规范文件/home/centos/selinux。

该命令在当前目录中生成了5个文件,该目录使用重力作为新受限域(Grasion_T)的前缀:

我只讨论其中的三个文件-特别是类型强制文件(gravity.te)、接口文件(gravity.if)和上下文文件(gravity.fc)。

te文件是带有类型强制规则的主要源代码。这就是我创建进程和文件类型并显式启用权限的地方。接口文件类似于C头文件,其目的是在有人想要使用您的策略时提供扩展点。用户不是直接使用类型,而是使用接口来挂钩到策略。gravity.fc文件管理文件资源(常规文件、套接字、目录等)的SELinux安全上下文。

尽管有两种语法风格(或者更确切地说是两种语言)来开发策略模块,但我使用内核策略语言进行开发,然后将其编译为通用中间语言(Common Intermediate Language,CIL)。你可以在这里阅读更多关于CIL的信息。

接下来,我将重点介绍我在制定政策时发现有帮助的内容。

属性类似于其他编程语言中的基类型-它有助于为策略的常见方面创建抽象。

毫无疑问,重力二进制文件的一个重要操作方面是安装Kubernetes集群的能力。但是一旦安装,集群就需要维护,之后维护任务使用相同的二进制文件。虽然安装程序方面需要访问用户的主目录(通常从其中提取安装程序tarball),并且希望能够加载策略模块以实现自动策略配置,但维护方面不需要这些权限。

为了捕获策略模块中描述的语义,我们引入了一个属性,该属性将用于表示一般情况下的二进制文件,并且在需要引入特定于类型的语义时(即,当我们需要区分安装和维护时)将使用特定类型。

我们将此属性称为GRANGATION_DOMAIN,它适用于二进制文件的所有操作方面:

现在,我们为我上面提到的两个用例介绍两个具体领域:

我们通过直接引用策略文件中的GRIONAL_DOMAIN属性来定义公共权限。无论哪个用例正在运行,二进制文件都将能够执行名称解析、连接到域套接字并创建出站连接:

现在,我们可以使用具体类型,例如我们的安装程序类型graight_installer_t来授予对系统上主目录的访问权限,其中sysadmin通常会复制用于安装的资产:

不过,对于属性有一个警告-如果宏使用对类型进行操作的构造,则不能使用属性作为输入。例如,在属性上使用typeattribute时不起作用,如下例所示。

接口文件不仅限于外部使用-它们可以用来捕获您自己代码中的常见模式。

使用前面的两个流程域,我们将使用一个名为GRANGATION_DOMAIN_TEMPLATE的模板来帮助我们创建实际的域类型,并为它们添加一组公共权限:

模板(`GRAILITY_DOMAIN_TEMPLATE';,`GEN_REQUIRED(`属性重力_DOMAIN;属性重力_可执行_域;';)类型$1_t,重力_可执行域;类型$1_EXEC_t,重力_可执行_域;角色SYSTEM_R类型$1_t;应用程序_域($1_t,$1_EXEC_t)//.';)。

这将使用所提供的前缀创建一个类型-对于grasion,它将是grasion_t过程域,并为其分配grasion_domain属性。此外,grasion_exec_t将是可执行文件类型,并为其分配了grasion_ecutable_domain属性。

这一点很重要,因为我们有几个systemd服务,而且由于system_r是所有服务的默认域,因此它需要访问该类型才能运行它。

在这里,我们列出了常见的权限(即,我们将grasion_t声明为应用程序域,并指定grasion_exec_t标签是grasion_t进程域的入口点)。

对于grasion_installer前缀也会执行同样的操作,从而分别生成grasion_installer_t和graight_installer_exec_t类型。

在编写策略时,很好地利用基本策略及其接口是经验的标志,因为接口往往会使策略比原始的允许规则和dontaudit规则列表更具可读性。有时,接口“知道”得更多-它们为一组所需的权限提供了更完整、更正确的解决方案。不过,浏览现有界面可能是一项挑战。这篇技巧来自Sven Vermeulen的一本名为SELinux Cookbook的书,它是一组bash函数,用于搜索和显示接口和定义的内容。您可以从这里获取函数的副本。

使用这些函数很简单-您只需将它们添加到bashrc文件即可。

$sefindif';内核。*消息';…。kernel/kernel.if:##允许调用者读取内核消息kernel/kernel.if:interface(`kernel_read_messages';,`kernel/kernel.if:attribute can_Receive_kernel_message;kernel/kernel.if:typeattribute$1 can_Receive_kernel_message;kernel/kernel.if:##允许调用者挂载内核消息文件kernel/kernel.if:interface(`kernel_mount_message'。

然后显示接口的内容以了解它是如何实现的:

$seshowif kernel_read_messagesinterface(`kernel_read_messages';,`gen_Required(`Attribute Can_Receive_Kernel_Messages;type proc_kmsg_t;';)Read_Files_Pattern($1,proc_t,proc_kmsg_t)type$1 CAN_Receive_Kernel_Messages;';)。

$sefinddef';domtrans*';define(`spec_domtrans_pattern';,`DEFINE(`domtransPattern';,`$seshowdef domtrans_patterndefine(`domtrans_pattern';,`DOMAIN_AUTO_TRANSITION_PRODUDE($1,$2,$3)允许$3$1:FD使用;允许$3$1:FIFO_FILE RW_INHERTED_FIFO_FILE_PERMS;允许$3$1:进程签名;';)。

在寻找最合适的接口时,我遵循与此类似的算法:

例如,前面介绍的…_t域没有运行ping重力的权限。

$runcon-t重力_t/usr/bin/pingruncon:/usr/bin/ping:权限被拒绝$osearch--start recent--SUCCESS NO|audit2allow#=。

$sefindif';ping_exec';dmin/netutils.if:interface(`netutils_domtrans_ping';,`admin/netutils.if:键入ping_t,ping_exec_t;admin/netutils.if:domtranspattern($1,ping_exec_t,ping_t)admin/netutils.if:允许$1 ping_exec_t:文件映射;admin/netutils.if:允许$1 ping_exec_t:文件映射;admin/netutils.if:允许$1 ping_exec_t:文件映射;admin/netutils.if。admin/netutils.if:CAN_EXEC($1,PING_EXEC_t)…

…。这也是错误的,因为它不允许mydomain搜索主机的bin目录,

文件上下文等效性允许我们基于来自另一个目录的标签将文件系统标签应用到目录,而不是为系统上的每个路径指定标签。例如,我们可以定义/run和/var/run是等价的,因此应该应用相同的标签。

可以用语义来查看有效的等价规则(它们将一直位于底部):

再举一个例子,假设我们有一个套接字文件(通常存储为/var/run/planet.sock)。还要注意,/var/run通常是指向/run的符号链接。我们在fcontext文件中写入以下内容:

由于某些原因,查找返回文件的基本类型(即var_run_t而不是预期的容器_var_run_t)。现在,如果我们使用/var/run/planet.sock(等价目标),我们将获得预期的结果:

正如我在前面提到的,在重力集群上,Kubernetes和特定于系统的工作负载被封装在Linux容器中,我们使用该容器来实现一致的Linux分发,而不管主机分发是什么。

因为容器实际上是一个Linux发行版,所以我最初的想法是尝试在容器环境中重用主机上的SELinux配置。我推断,通过这样做,我将重用现有的过程域和文件标签-所有这些好东西-而不需要额外的配置。事实证明,这是有问题的,原因有几个:

主机和容器可以是不同的分布,因此文件系统布局不同。

流程域规则在主机和容器内部的配置方面可能不同,从而导致行为不一致。

重力二进制文件负责解压和编辑系统容器的rootfs目录。通过在容器rootfs中使用相同的文件系统标签,二进制文件将获得编辑主机文件系统上所有系统目录的权限,从而违背SELinux隔离的目的。

接下来,我查看了容器SELinux策略。该策略仅限于单个SELinux文件上下文和单个开箱即用的进程域。由于我们使用的是容器技术,并且在逻辑上像VM一样发布整个Linux发行版,因此将其限制为单一类型不利于此部署模型,需要扩展。幸运的是,延长政策很容易。

SELinux容器配置驻留在主机上位于/etc/selinux/target/context/lxc_context ts(其中target是基本SELinux策略的名称)的文件中。

使用标准容器运行时实现(如Docker),整个容器的rootfs将使用该文件中的文件上下文重新标记。因此,每个进程都使用配置的进程上下文执行-除了超级特权容器进程,它们在不同的进程标签下运行,或者在命令行上显式覆盖时运行。

相反,我希望容器运行时能够按照策略指定的方式在其自己的域中执行流程。因此,例如,它将运行一个Kubernetes服务,该服务被限制为grasion_kubernetes_t,但是运行一个特定于重力的服务,其名称为grasion_service_t。

为此,我们嵌入了一组来自任意进程域的类型转换,并要求SELinux在需要时计算类型转换。我选择使用在重力容器内运行的systemd init进程的域作为计算转换的源类型,并以以下一组规则结束:

它们中的每一个都描述了从init进程(作为gravertainerinit_t运行)到我们感兴趣的其他进程域的固定集合的类型转换。

我们可以要求SELinux使用ComputeCreateContext计算到任何域的转换,指定初始化进程的文件路径和源域:

为了演示这是如何工作的,让我们假设我们想要启动kubelet,限制在它自己的域中(Graight_Kubernetes_T)。

我们可以看到,kubelet的入口点是grasion_kubernetes_exec_t,这意味着根据我们的策略,进程应该在grasion_kubernetes_t域中运行。

为了计算正确的域,我们调用selinuxexeccon,它使用与上述相同的API:

写一份政策不是一件微不足道的事,但却是一种极其有益和宝贵的经验。但我们还没有完成-还有很多工作要做。

悬而未决的问题之一是对特定于用户的SELinux配置和策略的支持。目前,所有用户工作负载都被同等对待,并使用相同的通用容器流程域。持久性存储也是如此-文件系统资源都使用相同的容器文件标签进行标记。

作为简化容器的自定义SELinux支持的实现的一种方式,UIDICA对此很感兴趣。

SELinux-Operator是另一个有趣的项目,用于将Kubernetes集群中的SELinux策略作为自定义资源进行管理。

解决根源于SELinux的问题需要丰富的经验。我们正在努力改进我们的工具以简化这一过程。

要使政策保持最新,尤其是在快速发展的Kubernetes生态系统中,我们需要投资于自动化和附加工具,以跟上其发展的步伐。