SELinux-秘籍-全-
SELinux 秘籍(全)
原文:
annas-archive.org/md5/74afeca8946d24065c091b5d4af61c93译者:飞龙
前言
SELinux 被认为是一个难以驯服的庞然大物。对许多人来说,它被视为一个复杂的安全系统,而 Linux 本身就是一个多功能的环境。但是,正如大多数 IT 相关服务一样,正是对这项技术的不熟悉造成了对系统复杂性的误解。
然而,情况并非如此。SELinux 并不像看起来那样难以理解。如果它真有那么难,那么像 Red Hat Enterprise Linux 这样的 Linux 发行版就不会默认启用它了。
为了支持大家在日常操作中使用启用 SELinux 的系统,本书应运而生。书中包含了许多关于 SELinux 处理和策略开发的章节,采用基于实例的方法,使每个人都能迅速深入理解提高系统安全性所带来的细节和挑战。
本书不涉及与管理相关的命令和示例。为此,我编写了另一本更适合的 SELinux 资源书籍,SELinux 系统管理,Packt Publishing,该书涵盖了启用了 SELinux 系统的系统管理任务,例如处理 SELinux 布尔值和文件上下文的变化,以及 SELinux 技术的简介。
本书也不是关于 SELinux 策略语言的完整参考。尽管书中会提到并多次使用最常见的语句,但需要注意的是,SELinux 策略语言及其内部架构的范围要广泛得多。对于良好的语言和组件参考,推荐 The SELinux Notebook – The Foundations,Richard Haines。该资源可以在线访问:www.freetechbooks.com/the-selinux-notebook-the-foundations-t785.html。
本书内容
第一章,SELinux 开发环境,告诉我们如何设置 SELinux 策略开发环境,从而进一步进行策略开发。我们将研究一种结构化、可重用的 SELinux 策略开发方法,并创建我们的第一组 SELinux 策略模块,这些模块与现有的 SELinux 策略完美集成。
第二章,处理文件标签,重点讲解文件标签是如何设置和管理的。我们将学习如何自己配置 SELinux 策略,以及如何使用和声明文件上下文,并为正确的资源类型分配正确的上下文。
第三章,限制 Web 应用程序,介绍了 Web 服务器 SELinux 域的默认限制,并解释了如何增强现有策略以满足我们的需求。还涉及通过 mod_selinux Apache 模块提供的额外 SELinux 支持。
第四章,创建桌面应用策略,是首个编写全新应用领域和策略的章节。我们将研究如何构建策略的结构,以及成功且安全地运行应用程序所需的策略规则。
第五章,创建服务器策略,延续了上一章的思路,但本章重点关注服务器服务。本章主要探讨桌面应用策略与服务器策略之间的差异,并且我们将一起开发一个完整功能的 SELinux 策略模块,以及将该策略集成到更大 SELinux 环境中所需的管理策略接口。
第六章,设置独立角色,探讨了 SELinux 提供的基于角色的访问控制。我们将根据最小权限原则创建自己的角色集。在考虑 SELinux 用户和角色的定义后,我们将练习在更大环境中管理这些角色。
第七章,选择限制级别,审视了策略可以使用的不同限制级别,以及这些级别如何在系统上实现。我们将了解每个限制级别的优缺点,并创建我们自己的策略集,提供不同的限制级别。
第八章,调试 SELinux,详细介绍了可用于调试 SELinux 行为和策略的各种方法。我们将掌握如何利用 Linux 审计子系统生成额外的日志,并对 SELinux 策略执行高级查询。在本章中,我们还会揭示为何某些流行的 Linux 调试工具在启用 SELinux 的系统上无法(正确)工作。
第九章,将 SELinux 与 DAC 对齐,探讨了如何利用 SELinux 增强现有的 Linux DAC 限制。我们将处理各种可用的技术,并讨论如何扩展 SELinux 策略,以确保与这些技术正确配合使用。
第十章,处理 SELinux 感知应用,考虑了 SELinux 感知应用程序及其与系统和 SELinux 的交互(以及调试难题)。我们将学习如何配置这些应用的 SELinux 集成,并且在出现问题时如何调试这些应用。本章还介绍了如何创建我们自己的 SELinux 感知应用。
本书所需的内容
由于本书注重实践经验,因此强烈推荐你拥有一个启用 SELinux 的系统。许多发行版提供可以用于初步调查的 live 环境,但请确保选择一个可以保存对系统所做更改的发行版。
启用 SELinux 的系统应使用最新版本的 SELinux 库和用户空间工具。本书是基于 Gentoo Hardened 编写的,运行的是 2013 年 10 月发布的 SELinux 用户空间库和工具(如 libselinux-2.2.2),以及 2014 年 3 月发布的参考策略。发行版本身并不重要,因为本书内容是与发行版无关的,因此无论是 Fedora 还是 Red Hat Enterprise Linux 都能良好使用,尽管后者在本书编写时(版本 6)仍使用较旧版本的 SELinux 用户空间库和工具。
从经验角度看,你应当精通 Linux 系统管理,因为 SELinux 策略开发和集成需要良好的组件知识,我们即将对这些组件进行限制和保护。本书假设你作为终端用户已经熟悉 Git 版本控制系统。此外,本书还假设你具备基本的 SELinux 系统工作原理知识。
本书适用对象
本书面向 Linux 系统管理员和安全管理员,帮助他们执行以下任务:
-
微调 SELinux 子系统以适应他们的 Linux 系统
-
为应用程序和用户开发 SELinux 策略
-
将 SELinux 紧密集成到他们现有的流程中
规范
本书中有许多不同的文本样式,用来区分不同种类的信息。以下是一些样式示例,并解释它们的含义。
文章中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入以及 Twitter 账号名如下所示:“使用 auditallow 语句,我们可以跟踪 SELinux 策策决策,并帮助开发策略和调试应用行为。”
代码块设置如下:
write_files_pattern(syslogd_t, named_conf_t, named_conf_t)
allow syslogd_t named_conf_t:file setattr_file_perms;
当我们希望你注意到代码块中的特定部分时,相关行或项目会以粗体显示:
policy_module(mysysadm, 0.1)
gen_require(`
type sysadm_t;
')
logging_exec_syslog(sysadm_t)
所有命令行输入或输出格式如下:
~# setsebool cron_userdomain_transition on
~# grep crond_t /etc/selinux/mcs/contexts/users/user_u
system_r:crond_t user_r:user_t
新术语和重要词汇以粗体显示。例如,屏幕上看到的词汇、菜单或对话框中的词汇等,在文本中通常呈现如下形式:“Chris Friedhoff 的 POSIX Capabilities & File POSIX Capabilities 页面详细解释了能力。”
注意
警告或重要提示会以框的形式呈现。
提示
小贴士和技巧以如下方式呈现。
读者反馈
我们始终欢迎读者的反馈。让我们知道你对本书的看法——你喜欢或不喜欢的部分。读者反馈对我们开发能够让你最大化受益的书籍非常重要。
要向我们发送一般反馈,请简单地发送电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。
如果您在某个领域拥有专长,并且有兴趣撰写或参与书籍的创作,请参阅我们的作者指南:www.packtpub.com/authors。
客户支持
现在您已经成为 Packt 书籍的骄傲拥有者,我们有很多内容可以帮助您最大化您的购买价值。
下载示例代码
您可以从您在www.packtpub.com的账户下载所有您购买的 Packt 书籍的示例代码文件。如果您是在其他地方购买的本书,您可以访问www.packtpub.com/support,并注册以便直接通过电子邮件将文件发送给您。
勘误
尽管我们已经尽力确保内容的准确性,但难免会有错误。如果您在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感激您向我们报告。通过这样做,您不仅能为其他读者避免困扰,还能帮助我们改进后续版本的书籍。如果您发现任何勘误,请访问www.packtpub.com/support,选择您的书籍,点击勘误 提交 表单链接,并输入勘误的详细信息。一旦您的勘误被验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该书籍的现有勘误列表中,位于该书籍的勘误部分。
盗版
网络上的版权材料盗版问题是各类媒体面临的持续问题。在 Packt,我们非常重视对我们的版权和许可证的保护。如果您在互联网上发现我们的作品的任何非法复制品,请立即提供该地址或网站名称,以便我们采取措施。
如果您发现任何涉嫌盗版的内容,请通过<copyright@packtpub.com>联系我们,并提供该盗版内容的链接。
感谢您在保护我们作者的权益以及我们为您提供有价值内容的能力方面的帮助。
问题
如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决。
第一章:SELinux 开发环境
本章介绍 SELinux 策略开发环境的设置。我们将在本章讨论以下主题:
-
创建开发环境
-
构建一个简单的 SELinux 模块
-
调用 refpolicy 接口
-
创建我们自己的接口
-
使用 refpolicy 命名约定
-
分发 SELinux 策略模块
介绍
类似于其他任何软件开发,拥有一个良好的开发环境对成功创建和管理 SELinux 策略至关重要。这样的环境不仅应该支持版本控制,还应该能够快速搜索源代码或显示定义。
对于 SELinux,这意味着策略模块源(这些都是可读的文本文件)应以结构化的方式存储,提供 SELinux 策略的上游项目应该容易访问,并且查询和搜索策略所需的功能或脚本应该可用。
有冒险精神的用户可能想看看 Tresys Technology 提供的SELinux Policy IDE(SLIDE)(oss.tresys.com/projects/slide)。在本书中,我们不专注于这个 IDE;相反,我们使用用户想要的任何文件编辑器(如 VIM、Emacs 或 Kate),并通过必要的 shell 功能和命令来增强环境。
在我们讨论开发环境的设置之前,先简单回顾一下 SELinux 是什么。
关于 SELinux
Security Enhanced Linux(SELinux)项目是由美国国家安全局(NSA)发起和支持的项目,诞生于 2000 年 12 月。它实现了一个安全系统架构,采用灵活的、基于策略的配置方法。该架构被称为Flux 高级安全内核(Flask),与其相关的资源仍然是每个与 SELinux 相关的人员需要阅读的重要资料。
大多数论文可以通过 Flask 网站链接访问,网址为www.cs.utah.edu/flux/fluke/html/flask.html。以下是其中一些论文的示例:
-
论文《失败的必然性:现代计算环境中安全性假设的缺陷》仍然是关于为何操作系统需要强制访问控制的非常有话题性的论文。
-
NSA 发布的《将 SELinux 作为 Linux 安全模块实现》一文,详尽讲解了 SELinux 的实现过程,文献可在
www.nsa.gov/research/_files/publications/implementing_selinux.pdf获取。
如今,SELinux 最好被看作是现有 Linux 操作系统之上的一个额外安全层。它是主流 Linux 内核的一部分,并通过 Linux 安全模块 (LSM) 接口与进程(用户空间)和资源之间的交互进行挂钩。它通过一种称为 类型强制(type enforcement)的强大方法,管理各种访问服务(如读取文件、获取目录属性、绑定域套接字、连接 TCP 套接字和获取额外的能力)。
以下图展示了 SELinux 子系统的高层次功能位置。当一个主体(图中为应用程序)想要对一个资源执行某个操作时,该操作首先会经过 Linux 内核提供的自愿访问控制机制的检查。如果该操作通过了 DAC 机制的检查并被允许,LSM 实现(SELinux 在其上注册)会调用 SELinux 子系统提供的钩子。随后,SELinux 会检查策略(通过缓存进行检查,如果访问尚未在缓存中注册,它会检查整个策略),并返回是否允许该访问。

SELinux 是一种强制访问控制系统,其中系统上的活动通过文档化的策略定义。这些策略适用于系统的所有进程,并通过 SELinux 子系统强制执行,而 SELinux 子系统是 Linux 内核的一部分。任何不被政策允许的操作将完全被拒绝——安全性并不依赖于用户的判断或应用程序的正确性。与 Linux 的 DAC(自愿访问控制)限制不同,SELinux 强制执行本身(即 SELinux 代码)与规则(即 SELinux 策略)是分开的。规则文档定义了系统上应当视为可接受行为的内容。任何不符合策略的操作都会被 SELinux 子系统拒绝。
在 SELinux 中,支持一套访问控制机制。最为显著的是其类型强制机制,其中主体(无论是内核还是 Linux 进程)对对象(如文件、设备、系统能力或安全控制)的权限授予,基于该主体当前的安全上下文。这种安全上下文通常通过可读字符串表示,如 staff_u:staff_r:staff_t:s0:c0,c3。该字符串表示 SELinux 用户(staff_u)、SELinux 角色(staff_r)、SELinux 类型/域(staff_t),并且可选地包括 SELinux 敏感度级别或安全许可,这提供了敏感度(s0)以及分配的类别(c0,c3)。
除了类型强制执行外,SELinux 还具有其他几个特性。例如,它通过将有效域(分配给运行进程的 SELinux 类型)分配给角色来提供基于角色的访问控制系统。如果未授予角色特定域,则该角色无法执行与该域相关的任务或应用程序。SELinux 还支持基于用户的访问控制,从而限制信息流和管理 SELinux 用户之间的数据共享。
SELinux 中的另一个支柱是其对敏感性的支持(SELinux 将其显示为整数,但这些整数可以很好地被解释为公开的、内部的、机密的等),以及访问类别。通过 SELinux 可以在其策略中施加的约束,系统可以大致遵守贝尔-拉帕杜拉模型(en.wikipedia.org/wiki/Bell-LaPadula_model)。此模型支持信息流限制,如无读取上行(低敏感性无法从高敏感性读取信息)和无写入下行(高敏感性无法向低敏感性泄漏信息)。
SELinux 策略的角色
SELinux 策略本身是安全管理员(通常被称为系统上允许和不允许的所有者的角色)对可接受的、预期的和规范化行为的表示:
-
可接受:应用程序和用户行为将被认为是可接受的,因为它将在策略允许的系统上被允许。
-
预期:应用程序和用户行为将按预期进行,因为策略通常不包含系统不适用的访问向量(分配给主体对特定对象的权限),即使在环境中其他系统可能会接受这些访问向量也一样。
-
规范化:应用程序和用户行为将被规范化,不是指数据库规范化的意义上,而是指正常性——与进程最常见行为一致的东西。
策略代表这些行为,正确调整和开发策略非常重要。这就是为什么 SELinux Cookbook 将专注于策略开发和策略原则。
过于严格的策略将导致应用程序出现故障,通常以用户发现意料之外的方式。当然,对于安全管理员来说并不奇怪,因为他知道策略规定了允许什么,他应该(至少应该)完全了解策略的内容。
然而,过于宽泛的策略不会导致这种行为。相反,一切都会按预期工作。遗憾的是,当非标准或异常行为被触发时,定义过于宽泛的策略可能仍然允许这种非标准或异常行为的发生。当这种异常行为成为被利用的漏洞时,即便 SELinux 再强大,也无法阻止利用,因为策略本身已经授予了访问权限。这个原则的另一个例子是网络防火墙,其策略可能也过于宽松。
通过策略提供的打包方式(SELinux 策略就像可加载的内核模块,但它是针对 SELinux 子系统的),管理员可以将策略推送到一个或多个系统,通常通过包管理系统或首选的集中配置管理系统来进行。与 Linux 的 DAC 控制需要直接应用于文件本身不同,SELinux 策略更容易处理,甚至可以进行版本控制——这是大环境中管理员非常欣赏的特性。
示例
在本书中,我们将经常遇到一些可选的设置,或者这些设置的值依赖于系统管理员所做的选择。为了避免重复记录和解释当某个设置或值是可配置时的内容,我们将使用以下配置设置:
-
本书中的 SELinux 类型将是 MCS。它使用启用了 MLS 的单一敏感性策略定义。这意味着当显示时,所有上下文都会分配一个敏感性级别或安全许可,并且 SELinux 策略配置的位置将始终显示在
/etc/selinux/mcs/中。如果您的环境中的策略存储路径不同,请确保将此路径替换为您自己的路径。例如,Red Hat 或 Fedora 安装的默认路径是/etc/selinux/targeted/。 -
操作将按照通过受限用户的方式记录,这些用户分别命名为
user(代表分配了user_r角色的无特权最终用户)、staff(代表可能执行管理任务并分配了staff_r和sysadm_r角色的用户)和root(映射到sysadm_r角色)。某些发行版可能会将用户与unconfined_r角色相关联。每当某个步骤对于无限制用户是可以跳过的时,我们将明确指出。
创建开发环境
我们现在将创建一个开发环境,在这个环境中将存储 SELinux 策略、上游项目代码以及我们用来轻松查询 SELinux 策略的功能。这个环境将具有以下三个顶级位置:
-
local/:此位置包含本地系统的 SELinux 规则,而非合作开发的代码库的一部分(即其他开发人员工作的位置) -
centralized/:此位置包含开发环境中使用的各种代码库的检出版本 -
bin/:此位置包含我们将用来查询策略源以及排查 SELinux 问题的支持脚本。
在本练习中,我们还将在centralized/位置填充一个检出项:当前系统使用的 SELinux 策略树。
准备工作
寻找一个合适的位置来存储开发环境。这个位置通常是用户主目录中的某个位置,比如 /home/staff/dev/selinux/。每当本书提到开发环境的位置时,它将使用 ${DEVROOT} 变量来指代这个位置。
另一个我们需要的信息是托管当前系统 SELinux 策略的仓库位置。这个位置因发行版而异,因此请参考发行版网站以获取更多信息。在编写本书时,Gentoo Linux 和 Fedora 的策略可以在以下位置找到:
每当使用版本控制时,本书将使用git。当然,还有其他版本控制系统,但这些超出了本书的范围。
如何操作…
为了创建本书中使用的开发环境,请执行以下步骤:
-
创建必要的目录:
~$ cd ${DEVROOT} ~$ mkdir local centralized bin -
创建发行版的 SELinux 策略树的检出(在这个例子中,基于 Gentoo Linux 仓库):
~$ cd ${DEVROOT}/centralized ~$ git clone git://git.overlays.gentoo.org/proj/hardened-refpolicy.git -
为我们将在本书中开发的策略创建一个
git仓库:~$ cd ${DEVROOT}/local ~$ git init -
将
functions.sh脚本(可通过本书的下载包获取)添加到${DEVROOT}/bin/位置。 -
编辑
.profile、.bashrc或其他在我们的 Linux 用户登录系统时加载的 shell 配置文件,并添加以下代码:# Substitute /home/staff/dev/selinux with your DEVROOT DEVROOT=/home/staff/dev/selinux # Substitute the next location with your distributions' policy checkout POLICY_LOCATION=${DEVROOT}/centralized/hardened-refpolicy/ source ${DEVROOT}/bin/functions.sh -
注销并重新登录,然后通过请求
files_read_etc_files接口的定义来验证环境是否正常工作:~$ seshowif files_read_etc_files interface(`files_read_etc_files',` gen_require(` type etc_t; ') allow $1 etc_t:dir list_dir_perms; read_files_pattern($1, etc_t, etc_t) read_lnk_files_pattern($1, etc_t, etc_t) ')
它是如何工作的…
开发环境的设置帮助我们处理进一步配方的策略开发。发行版的 SELinux 策略树的检出用于在开发新策略时查询现有的策略规则。SELinux 不要求系统上必须有策略源(只有编译后的二进制 SELinux 策略模块和部分 SELinux 策略规则可以被其他策略模块使用)。因此,默认安装通常不会在系统上提供策略规则。
通过使用我们的检出版本,我们可以查看现有的 SELinux 策略文件,并高兴地将其中的示例用于自己的需求。此检出的一个主要用户是 functions.sh 脚本,它使用 ${POLICY_LOCATION} 变量来知道检出版本的托管位置。这个脚本提供了我们在本书中将使用的几个函数,它们还将帮助查询源。
通过在登录时加载此 functions.sh 脚本,这些函数便可以在用户的 shell 中立即使用。其中一个函数是 seshowif 函数,它显示特定接口的规则。给出的示例显示了 files_read_etc_files 接口的定义,我们用它来验证我们的设置是否正常工作。
functions.sh 脚本还可以与通过 /usr/share/selinux/devel/(在 Fedora/Red Hat 系统中)提供的接口文件一起使用,这使得如果用户不需要访问完整的策略规则,则可以选择不检出策略库。然后定义的策略位置如下:
export POLICY_LOCATION=/usr/share/selinux/devel/
还有更多...
除了发行版的 SELinux 策略树外,还可以使用参考策略 SELinux 树。这是大多数(如果不是全部)Linux 发行版作为其策略来源所使用的上游项目。然而,必须指出的是,参考策略通常在个别发行版的策略库之后更新,因为它对策略增强有更严格的接受标准。
检查其他发行版的 SELinux 策略库也无妨。通常,Linux 发行版会先在自己的库中更新 SELinux 策略,然后再将更改推送到参考策略(在自由软件开发社区中称为上游)。通过查看其他发行版的库,开发人员可以轻松了解必要的更改是否已经做过(从而更有可能是正确的)。
另见
要了解本食谱中涉及的主题的更多信息,请参阅以下资源:
-
参考策略项目 (
oss.tresys.com/projects/refpolicy/) -
Git 教程 (
git-scm.com/docs/gittutorial)
构建一个简单的 SELinux 模块
现在我们已经有了开发环境,是时候创建我们的第一个 SELinux 策略模块了。由于此时其目的尚不重要,我们将专注于一个默认情况下不被允许(而且是合理的)但容易验证的特权,因为我们希望确保我们的策略开发方法有效。我们将授予的特权是允许系统日志记录器写入标记为 named_conf_t 的日志文件(这是用于 BIND DNS 服务器配置的类型,也称为 named)。
注意
构建 SELinux 策略模块是为了通过更多的规则扩展现有的策略,允许某些访问。不能创建一个减少域所允许权限的策略模块。如果需要这么做,策略模块需要重新创建并替代现有的策略(因此,分发提供的策略将需要从系统中移除)。
准备就绪
在开始之前,我们首先需要确保能够测试更改的结果。一种简单的方法是更改/var/log/messages文件(或系统日志记录器配置使用的另一个常规日志文件)的上下文,并通过logger命令通过系统日志记录器发送消息:
~$ logger "Just a simple log event"
~$ tail /var/log/messages
通过tail命令查看最后几行,验证消息是否已经发送。然后,改变上下文并重试。该事件不应显示,并且审核守护进程应记录拒绝事件:
~# chcon -t named_conf_t /var/log/messages
~$ logger "Another simple log event"
~$ tail /var/log/messages
~# ausearch -m avc -ts recent
完成这些步骤后,我们现在可以创建我们的第一个简单的 SELinux 模块。
如何做到这一点…
构建一个新的 SELinux 策略是经过以下步骤的过程:
-
在
${DEVROOT}/local中创建一个名为mylogging.te的文件,内容如下:policy_module(mylogging, 0.1) gen_require(` type syslogd_t; type named_conf_t; ') # Allow writing to named_conf_t files allow syslogd_t named_conf_t:file { getattr append lock ioctl open write }; -
复制或链接
/usr/share/selinux/devel/或/usr/share/selinux/mcs/include/(具体位置取决于发行版)中的Makefile文件到当前目录:~$ ln –s /usr/share/selinux/devel/Makefile -
通过这个
Makefile构建 SELinux 策略模块。目标是将(目标)策略模块命名为.pp后缀:~$ make mylogging.pp -
切换到 root 用户,如果我们通过非特权 SELinux 域/角色登录,切换到
sysadm_r或secadm_r角色(如果当前用户域已经是sysadm_t或unconfined_t,则不需要此操作):~$ sudo –r sysadm_r –t sysadm_t -s -
现在,加载 SELinux 策略模块(这将立即激活新定义的 SELinux 策略):
~# semodule –i mylogging.pp -
通过生成一个新的日志事件并查看日志文件,验证新定义的 SELinux 策略是否已激活,并确认该事件是否已成功记录。
-
将新创建的文件提交到版本库:
~$ cd ${DEVROOT}/local ~$ git add mylogging.te Makefile ~$ git commit –m 'Adding mylogging.te which allows the system logger to write to the named configuration file type named_conf_t'验证后,使用
restorecon /var/log/messages重置日志文件的上下文,并使用semodule -r mylogging从子系统中删除策略模块。毕竟,我们不希望这个规则一直处于激活状态。
它是如何工作的…
在这个过程里,我们接触到 SELinux 策略开发的三个重要新方面:
-
创建了一个名为
mylogging.te的策略源文件 -
构建了一个名为
mylogging.pp的生成的二进制策略模块 -
二进制策略文件
mylogging.pp已添加到系统的活动策略存储中
最后,我们将文件提交到本地仓库。建议对策略文件使用版本控制,以便跟踪跨时间的变更。一个好的提示是为策略的每个新版本打标签——如果用户报告与策略相关的问题,您可以要求他们提供 SELinux 策略模块的版本(通过 semodule –l),然后使用仓库中的标签轻松查找该特定策略模块的规则。
在本书的其余部分,我们将不再使用 git add/commit,以便专注于 SELinux 配方。
策略源文件
在该配方中,我们创建了一个名为 mylogging.te 的策略源文件,其中包含原始的 SELinux 策略规则。mylogging 这个名称并非随机选择;按照常见的最佳实践,自定义模块通常以 my 开头,后跟我们增强内容的 SELinux 策略模块的名称(在本例中,即提供系统日志相关 SELinux 策略的日志模块)。.te 后缀不仅仅是一个约定(指代 SELinux 的类型强制部分);构建系统要求使用 .te 后缀。
策略模块规则以 policy_module(…) 调用开始,这告诉构建系统,该文件将成为一个可加载的 SELinux 策略模块,并指定其名称和版本。如果我们请求 semodule 命令列出当前加载的所有 SELinux 策略模块,该名称和版本会被显示出来:
~# semodule –l
aide 1.8.0
alsa 1.13.0
…
mylogging 0.1
…
最佳实践是将单一域的所有规则保留在一个策略模块中。如果需要多个不相关域的规则,建议创建多个模块,因为这可以将策略规则隔离开来,并使修改更易于管理。
在这个简单的示例中,我们没有遵循最佳实践(还没有)。相反,我们告诉 SELinux 子系统,策略将通过为 syslogd_t 提供一个访问向量来增强。这里的访问向量是允许该域对标记为 named_conf_t 的文件执行一组权限。
二进制策略模块
当我们调用 Makefile 文件时,底层脚本会构建一个可加载的二进制 SELinux 策略模块。这类文件的后缀为 .pp,并准备好被加载到策略存储中。这个 Makefile 文件可能默认没有安装;某些发行版需要安装特定的开发包(例如 Fedora 中的 selinux-policy-devel)。
如果我们只给定 .pp 文件,获取策略源代码的方式并不理想。当然,有诸如 semodule_unpackage 和 sedismod 这样的命令可以使用,但这些命令仅能提供规则的低级视图,而不是原始的 .te 代码。因此,确保做好备份,并且正如我们在示例中看到的,使用版本控制系统来管理跨时间的变更。
将策略加载到策略存储中
要将新创建的策略加载到策略存储中,我们调用semodule命令。此应用程序负责管理策略存储(即所有 SELinux 策略模块与基础策略模块的集合),并将模块链接或取消链接到最终策略中。
此最终策略(可在/etc/selinux/mcs/policy找到)然后加载到 SELinux 子系统本身并强制执行。
还有更多内容……
在本书中,使用的构建系统基于参考策略构建系统。这是一组 M4 宏和相关脚本,可使 SELinux 策略的开发更加简便。然而,这并非创建 SELinux 策略的唯一方法。
当访问在线资源时,您可能会遇到类似以下结构的 SELinux 策略模块:
module somename 1.0;
require {
type some_type_t;
type another_type_t;
}
allow some_type_t another_type_t:dir { read search };
小贴士
下载示例代码
您可以从您在www.packtpub.com的帐户中下载所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,直接通过电子邮件获取文件。
这些是不使用参考策略构建系统的策略文件。要构建这样的文件,我们首先使用checkmodule创建一个中间模块文件,然后使用semodule_package将模块文件打包成可加载的二进制 SELinux 策略:
~$ checkmodule -M –m –o somename.mod somename.te
~$ semodule_package –m somename.mod –o somename.pp
为了保持简单,我们将坚持参考策略构建系统。
参见
此处涉及了许多主题和领域。更多信息可在以下资源找到:
-
大多数 Linux 发行版都有关于如何集成 SELinux 的特定于发行版的信息。对于 Red Hat,可以通过
access.redhat.com访问这些资源。对于 Fedora,请使用docs.fedoraproject.org。Gentoo 的文档位于wiki.gentoo.org。 -
要了解如何在系统上管理 SELinux 的更多信息,请参阅发行版提供的文档或阅读 Packt Publishing 出版的SELinux 系统管理书籍,网址为
www.packtpub.com/selinux-system-administration/book。 -
SELinux 语言本身的广泛覆盖由在线可获得的 SELinux 笔记本提供,网址为
www.freetechbooks.com/the-selinux-notebook-the-foundations-t785.html。
调用参考策略接口
完全使用 SELinux 提供的标准语言构造编写 SELinux 策略是可行的,但容易出错,且在长期使用中不易管理。为了支持更简单的语言构造,参考策略项目使用了一组 M4 宏,在策略构建时,底层的 SELinux 策略语句会扩展这些宏。
策略开发人员可以在线查阅 API,但大多数系统也会在/usr/share/doc/selinux-*/中提供此信息。然而,找到合适的接口需要一些实践,这就是为什么我们之前安装的其中一个函数(作为开发环境的一部分)简化了这一过程。
在这个步骤中,我们将编辑之前生成的mylogging.te文件,并使用正确的参考策略宏。
如何操作…
要在 SELinux 策略模块中使用参考策略接口,可以采取以下方法:
-
使用
sefinddef函数查找写入文件的权限组或模式:~$ sefinddef 'file.*write' define(`write_files_pattern',` allow $1 $3:file write_file_perms; … define(`write_file_perms',`{ getattr write append lock ioctl open }') … -
使用
seshowdef函数显示write_files_pattern定义的完整性质:~$ seshowdef write_files_pattern define(`write_files_pattern',` allow $1 $2:dir search_dir_perms; allow $1 $3:file write_file_perms; ') -
使用
sefindif函数查找允许写入named_conf_t的接口:~$ sefindif 'write_files_pattern.*named_conf_t' contrib/bind.if: interface(`bind_write_config',` contrib/bind.if: write_files_pattern($1, named_conf_t, named_conf_t) -
现在,更新
mylogging.te文件以使用这个函数。文件现在应该如下所示:policy_module(mylogging, 0.2) gen_require(` type syslogd_t; ') bind_write_config(syslogd_t)注意
注意使用反引号(
`)和单引号(')。每当使用定义时,它们需要以反引号开始并以单引号结束。 -
重新构建并重新加载策略模块,然后重新运行我们之前做的测试,以验证这仍然允许我们写入
named_conf_t标记的文件。
它是如何工作的…
参考策略的构建系统背后的原则之一是,SELinux 策略模块不应直接提及与该模块无关的 SELinux 类型。每当策略模块需要针对由其他模块定义的类型定义规则时,应使用该其他模块定义的接口。
在我们的例子中,我们需要使用 BIND SELinux 策略(它处理 BIND-named 守护进程的策略规则)所使用的接口;该接口允许我们写入 BIND DNS 服务器配置文件类型(named_conf_t)。我们可以查阅在线 API、/usr/share/doc/selinux-*中的 API 文档,或者直接猜测接口名称。然而,为了确保接口做我们需要的事情,我们需要查询接口定义本身。
这时sefinddef、seshowdef、sefindif和seshowif函数就派上用场了。这些函数不是 SELinux 用户空间的一部分,它们是通过我们之前安装的functions.sh文件提供的,并且是针对 SELinux 策略文件的简单awk/grep/sed组合。
使用sefinddef(SELinux 查找定义),我们可以通过支持的宏(与特定的 SELinux 政策模块无关)搜索任何与给定表达式匹配的定义。在这个方案中,我们给出了file.*write作为查找的表达式。seshowdef(SELinux 显示定义)函数会展示给定模式的完整定义。
sefindif(SELinux 查找接口)函数允许我们找到 SELinux 政策提供的接口。在这个方案中,我们用它来搜索允许一个域写入 BIND DNS 服务器配置文件的接口。还有一个seshowif(SELinux 显示接口)函数,能显示出接口的完整定义,如下所示:
~$ seshowif bind_write_config
interface(`bind_write_config',`
gen_require(`
type named_conf_t;
')
write_files_pattern($1, named_conf_t, named_conf_t)
allow $1 named_conf_t:file setattr_file_perms;
')
这个示例接口很好地展示了 SELinux 参考政策构建系统如何处理接口。每当调用此类接口时,都会向接口传递一个或多个参数。在我们的例子中,我们将syslogd_t作为第一个(也是唯一的)参数传递给它。
然后,构建系统会将接口中的每个$1替换为第一个参数,实际扩展调用为以下代码:
write_files_pattern(syslogd_t, named_conf_t, named_conf_t)
allow syslogd_t named_conf_t:file setattr_file_perms;
对write_files_pattern的调用随后会被我们之前看到的定义扩展。
对于政策开发者来说,这一切都由系统透明处理。SELinux 政策文件的源代码保持良好的格式化,并且只调用接口。界面扩展的工作在构建时完成。这允许开发者拥有整洁分隔、模块化的政策定义。
另见
- 参考政策项目可以在线找到,
oss.tresys.com/projects/refpolicy/
创建我们自己的接口
能够调用接口是很好的,但当我们开发 SELinux 政策时,会遇到需要为我们开发的 SELinux 模块创建自己的接口的情况。这是通过一个.if扩展名的文件来实现的。
在这个方案中,我们将通过一个接口扩展mylogging政策,允许其他域执行系统日志守护进程的二进制文件(但不会以系统日志记录器的权限运行该二进制文件;这在 SELinux 中称为域转换)。
如何实现……
-
如果我们当前的上下文是一个无特权用户域(因为未受限的域具有高度特权,并且几乎可以执行任何操作),我们可以尝试直接执行系统日志守护进程(
syslog-ng或rsyslog),并使其失败,如下所示:~$ /usr/sbin/syslog-ng --help bash: /usr/sbin/syslog-ng: Permission denied -
现在,创建
mylogging.if文件(与mylogging.te位于同一位置),其内容如下,授予执行该二进制文件所需的所有权限:## <summary>Local adaptation to the system logging SELinux policy</summary> ########################################## ## <summary> ## Execute the system logging daemon in the caller domain ## </summary> ## <desc> ## <p> ## This does not include a transition. ## </p> ## </desc> ## <param name="domain"> ## <summary> ## Domain allowed access. ## </summary> ## </param> # interface(`logging_exec_syslog',` gen_require(` type syslogd_exec_t; ') can_exec($1, syslogd_exec_t) ') -
为用户域创建一个新的 SELinux 政策模块;该政策应能够直接执行系统日志记录器。例如,对于
sysadm_t域,我们将创建一个mysysadm.te文件,其内容如下:policy_module(mysysadm, 0.1) gen_require(` type sysadm_t; ') logging_exec_syslog(sysadm_t) -
构建
mysysadm策略模块并加载它。然后,测试是否现在可以直接执行守护进程二进制文件:~$ /usr/sbin/syslog-ng --help
它是如何工作的……
首先,我们来看一下构建系统如何知道接口定义的位置。然后,我们将介绍示例中使用的内联注释系统。
接口定义的位置
每当构建一个 SELinux 策略模块时,构建系统会获取它在以下位置找到的所有接口文件:
-
/usr/share/selinux/mcs/include/*或/usr/share/selinux/devel/include/*(具体取决于 Linux 发行版) -
当前工作目录
第一个位置是存储所有 Linux 发行版提供的 SELinux 模块的接口文件的地方。这些文件位于以特定类别命名的子目录中(参考策略将其称为层,但这仅用于在定义中创建一些结构,并无其他用途),例如contrib/、system/和roles/。
对于 SELinux 策略的本地开发,通常该位置不可写。如果我们开发自己的策略模块,那么这意味着本地管理的 SELinux 策略文件不能使用其他本地接口文件的接口。因此,Makefile文件也会获取当前工作目录中找到的所有接口文件。
内联文档
在创建的接口文件中,我们会注意到一些类似 XML 的结构作为注释。这些注释以双井号(##)为前缀,并被参考策略构建系统用于生成 API 文档(可以在/usr/share/doc/selinux-*中找到)。
对于本地策略,不使用这种内联文档,因此也不是强制性的。然而,即使是对于本地策略,编写文档有助于更好地记录规则。此外,如果我们以后想将更改推送到上游,这种内联文档仍然是必需的。
注释系统使用以下结构:
-
在接口定义之前,我们会遇到一个
<summary>元素,它提供了接口的单句描述。 -
额外的信息可以通过
<desc>元素提供,并可以在其中放置 HTML 代码以进一步记录接口。 -
每个接口的参数都通过
<param>实体进行文档化,该实体包含一个<summary>行。
另见
- 参考策略 API 文档可以在线查看,网址是
oss.tresys.com/docs/refpolicy/api/
使用 refpolicy 命名约定
用于简化策略开发的接口名称可以自由选择。然而,参考策略本身使用命名约定,尝试构造所使用的名称,以便 SELinux 策略开发人员能够轻松找到他们需要的接口(如果存在),并为他们想要创建的接口提供一个明确的名称。
参考策略的命名约定可以在 oss.tresys.com/projects/refpolicy/wiki/InterfaceNaming 上找到。
准备工作
在这个方案中,我们将进行一个笔头练习,看看命名约定如何工作。在示例中,我们将为三种情况创建接口名称:
-
读取所有日志文件
-
通过 TCP 连接到 HTTP 端口
-
不审计获取用户主目录的属性
如何操作……
-
首先我们需要弄清楚在这些情况下涉及到的文件类型:
-
通用日志文件是
var_log_t(可以通过查询/var/log/本身的标签看到):~$ ls -dZ /var/log drwxr-xr-x. root root system_u:object_r:var_log_t:s0 /var/log -
当我们处理所有日志文件时,可以安全地假设这是由 SELinux 属性处理的。我们来看一下通用
var_log_t类型的属性:~$ seinfo –tvar_log_t –x var_log_t file_type non_security_file_type mountpoint non_auth_file_type logfile -
logfile属性看起来是一个有趣的命中。我们现在可以通过查询策略源,找出哪些 SELinux 策略模块处理logfile属性,或者使用sefindif(假设有定义接口来处理此属性):~$ sefindif 'attribute logfile' system/logging.if: interface(`logging_log_file',` … -
对于日志文件的例子,我们需要的模块叫做
logging,可以通过sefindif的输出看到。同样,我们会发现对于 HTTP 端口,模块是corenet,而主目录是userdom。
-
-
接下来,我们检查是否有修饰符。前两种情况没有特定的修饰符(所有的动作都是常规动词)。最后一个例子有一个修饰符:do not audit。在 SELinux 策略语言中,这被称为
dontaudit语句。 -
现在,让我们来看看涉及的动词。这主要基于经验,但这些情况表明,动词与最终选择的
refpolicy名称(通常使用 SELinux 权限名称)之间有着很强的关联:-
在第一种情况下,这是
读取 -
第二种情况有
通过 TCP 连接,因此翻译为tcp_connect -
最后一种情况涉及
获取属性,因此翻译为getattr
-
-
最后,我们来看一下被引用的对象:
-
在第一种情况下,这是
所有日志文件,我们将其命名为all_logs -
在第二种情况下,这是
HTTP 端口,因此我们将命名为http_port -
第三种情况涉及
用户主目录,因此我们将命名为user_home_dirs
-
-
结合这些信息,我们得到了以下接口名称:
-
读取所有日志文件:
logging_read_all_logs -
通过 TCP 连接到 HTTP 端口:
corenet_tcp_connect_http_port -
不审计获取用户主目录的属性:
userdom_dontaudit_getattr_user_home_dirs
-
它是如何工作的……
参考策略使用的命名约定并没有在技术上强制要求。就像编程风格一样,命名约定的目的是为了让协作更容易(每个人使用相同的命名约定),并且能够更高效地搜索大量的接口。
使用适当的命名规范是一个练习过程。如果不确定,可以在#selinux频道或者在irc://irc.freenode.net上的参考策略邮件列表中询问。
还有更多...
花一些时间查看位于/usr/share/selinux/devel/include/的接口文件。接下来,对于更标准的基于权限的接口名称,还有用于模板和类型分配的接口名称。
例如,有一个名为apache_content_template的模板。通过它,可以一次性创建附加的 SELinux 类型和权限(用于 Web 应用程序)。类似地,还有一个接口叫做apache_cgi_domain,它将某个特定类型标记为可以通过 Web 服务器的 CGI 支持调用的域。
除了命名规范,参考策略还有一个风格指南,网址为oss.tresys.com/projects/refpolicy/wiki/StyleGuide。和命名规范一样,这纯粹是为了改善协作的一个人为方面——违反编码风格不会带来其他后果,只是可能会导致更改未被上游仓库接受。
分发 SELinux 策略模块
本章的结束部分解释了如何将 SELinux 策略模块分布到多个系统中。
如何操作…
要分发 SELinux 策略,完成以下步骤:
-
考虑需要分发 SELinux 策略的不同系统配置:
-
如果多个系统有不同的 SELinux 策略版本需要激活,那么需要针对每个实现构建 SELinux 策略模块。这是高度依赖发行版的。例如,在 Gentoo 上,这是
sec-policy/selinux-base包的版本。在 Red Hat 及其衍生发行版上,这是selinux-policy包的版本。 -
如果多个 SELinux 策略类型处于激活状态(如
mcs、targeted和strict),并且既有启用 MLS 的策略,也有禁用 MLS 的策略,那么需要同时针对启用和禁用 MLS 的策略构建 SELinux 策略模块。sestatus的输出将告诉我们是否在活动策略上启用了 MLS:~$ sestatus | grep MLS Policy MLS status: enabled
-
-
将生成的
.pp文件打包并分发到不同的系统上。一个常见的最佳实践是将.pp文件放置在/usr/share/selinux/mcs/目录下(这是针对名为mcs的 SELinux 策略存储,你可以根据需要进行调整)。 -
在每个系统上,确保通过
semodule –I policyfile.pp加载.pp文件。
它是如何工作的…
SELinux 策略模块(以.pp结尾的文件)包含了 SELinux 激活策略所需的一切内容。通过将这些文件分发到多个系统中(并通过semodule命令加载它们),这些系统将获得针对其当前 SELinux 策略的所需更新。
一旦加载(并且这只需要发生一次,因为加载的模块即使在系统重启后也会保留),实际上不再需要.pp文件了(加载的模块会被复制到/etc/selinux中)。然而,建议你保留这些策略文件,以便管理员可以根据需要重新加载策略;这可能有助于排查 SELinux 策略和系统权限问题。
但是,仍然有一些需要注意的事项:
-
接口变化
-
内核版本变化
-
启用 MLS 或禁用 MLS 的策略
接口变化
.pp文件包含 SELinux 执行附加策略规则所需的所有规则。这包括(扩展的)规则,它们是模块自身接口定义文件(.if文件)的一部分,以及策略模块引用的接口。
当接口发生更新时,所有可能受该变化影响的 SELinux 策略模块都需要重新构建。由于没有简单的方法可以判断模块是否需要重新构建,因此建议每当至少一个接口发生变化时,都重新构建所有策略模块。
发行版将处理策略的重建和重新分发,但对于自定义策略模块,我们需要自己执行此操作。
内核版本变化
新的内核版本可能会包括对 SELinux 子系统的更新。当这些更新提供附加功能时,策略的二进制表示可能会更新。这将反映在内核支持的策略二进制版本中。
二进制版本是向后兼容的,因此支持最大版本为28的系统(SELinux 的二进制版本是每次更改时递增的整数)也支持加载较低二进制版本的策略模块:
~# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: mcs
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: denied
Max kernel policy version: 28
注意
当 SELinux 策略模块的二进制版本高于最大内核策略版本时,该 SELinux 策略模块将无法在目标系统上加载。较高版本意味着该策略使用了只有在支持该版本的内核中才能使用的功能,因此管理员需要更新这些系统上的内核,以支持较高版本,或更新 SELinux 策略模块,不使用这些功能,从而使重建的二进制 SELinux 策略模块为较低版本。
是否启用 MLS
SELinux 策略模块可能包含与敏感性相关的信息。当构建策略模块时,会添加信息以反映该模块是针对启用了 MLS 的系统构建的,还是未启用 MLS 的系统构建的。
因此,如果我们有多个主机,它们使用了不同的策略(一些策略存储启用了 MLS,而一些则禁用了 MLS),那么 SELinux 策略模块需要同时针对这两种情况进行构建,并分别分发。
通常,这是通过为每种特定的 SELinux 策略类型提供 SELinux 策略模块来实现的(无论是mcs、strict还是targeted)。
第二章:处理文件标签
在本章中,我们将讨论如何设置和管理文件标签,并学习如何自己配置 SELinux 策略以使用和分配正确的文件标签。本章所涵盖的内容如下:
-
通过模式定义文件上下文
-
使用替代定义
-
通过文件转换增强 SELinux 策略
-
设置资源敏感性标签
-
配置敏感性类别
介绍
设置、重置和管理文件标签是管理员在启用 SELinux 的系统上最常执行的任务。策略开发者和 Linux 发行版提供了合理的默认设置,但许多实现可能有不同的服务和文件位置。公司通常会将自定义脚本和日志文件安装在非默认位置,许多守护进程可以配置为在同一系统上支持多个实例——每个实例使用不同的基础目录。
系统管理员将了解如何通过semanage应用程序设置上下文定义,然后使用setfiles或restorecon重置目标文件的上下文:
~# semanage fcontext –a –t httpd_sys_content_t "/srv/web/zone/htdocs(/.*)?"
~# restorecon –RF /srv/web/zone/htdocs
然而,这是一个本地定义,如果需要,必须导出并导入,以便将其传输到其他系统:
~# semanage export -f local_selinux.mods
~# semanage import -f local_selinux.mods
通过将上下文定义移入 SELinux 策略领域,这些定义可以轻松地在多个系统上安装并集中管理,正如我们在 SELinux 策略模块中所看到的那样。
通过模式定义文件上下文
SELinux 策略模块可以通过其.fc文件包含文件上下文定义。在这些文件中,路径表达式用于指向应该匹配特定文件上下文的各种位置,类标识符用于根据文件类别(目录、常规文件、符号链接等)区分文件上下文定义。
在这个教程中,我们将创建一个mylogging SELinux 模块,定义与日志相关的上下文的额外路径规范。我们将使用直接的文件路径以及正则表达式,并查看各种类标识符。
如何操作……
要通过 SELinux 策略模块定义文件上下文,请使用以下方法:
-
使用
matchpathcon,我们可以检查 SELinux 工具将资源重置为的上下文:~# matchpathcon /service/log /service/log system_u:object_r:default_t -
创建
mylogging.te文件,在其中提及将用于定义的类型。最佳实践是通过不同的 SELinux 模块处理 SELinux 模块本身未定义的类型。然而,在此示例中,我们也声明了var_t以保持示例简单:policy_module(mylogging, 0.2) gen_require(` type var_t; type var_log_t; type auditd_log_t; ') -
接下来,创建
mylogging.fc文件,在其中声明路径表达式及其关联的文件上下文:/service(/.*)? gen_context(system_u:object_r:var_t,s0) /service/log(/.*)? gen_context(system_u:object_r:var_log_t,s0) /service/log/audit(/.*)? gen_context(system_u:object_r:auditd_log_t,s0) /lxc/.*/log -d gen_context(system_u:object_r:var_log_t,s0) /var/opt/oracle/listener\.log -- gen_context(system_u:object_r:var_log_t,s0) -
现在,构建策略模块并加载它:
~$ make mylogging.pp ~$ semodule –i mylogging.pp -
使用
matchpathcon,我们现在可以验证 SELinux 工具已知的上下文是否正确:~# matchpathcon /service/log /service/log system_u:object_r:var_log_t
它是如何工作的……
SELinux 策略模块包含 SELinux 正确处理一组策略规则所需的所有内容。包括规则本身(在 .te 文件中声明),以及可选的接口声明(在 .if 文件中),这些接口声明定义了其他策略可以调用的接口,以便生成特定的 SELinux 规则。SELinux 策略模块的第三部分和最后一部分是相关的文件上下文文件——因此是 .fc 文件后缀。
注意
.fc 文件中的上下文声明不会自动强制执行并设置这些上下文。这些仅仅是 SELinux 工具和库在重新标记操作发生时使用的定义。
该上下文文件包含每一行的内容:
-
一个路径表达式,应该匹配到一个绝对文件路径
-
一个可选的类标识符,用于区分上下文(文件、目录、套接字、符号链接等)
-
要分配给此路径的上下文
上下文定义的每一部分都以空格分隔:
<path> [<class identifier>] <context>
这些行可以按照政策开发人员的喜好进行排序。大多数开发人员将路径按字母顺序排列,并根据顶级目录进行分组。
路径表达式
SELinux 工具和库中的正则表达式支持基于 Perl 兼容正则表达式(PCRE)。
在所有可能的表达式中,最简单的表达式是没有通配符的表达式,例如以下代码:
/var/opt/oracle/listener\.log
其中一个重要部分是对句点的转义——如果我们不转义句点,那么 PCRE 支持会将句点视为任何字符,这样不仅匹配到 listener.log 文件,还可能匹配 listener_log 或 listenerslog。
一个非常常见的表达式是匹配特定目录及其所有子目录和文件,这在以下示例中表示:
/service(/.*)?
这确保了每个文件或目录都有一个上下文定义。
处理顺序
给定常规系统中路径表达式的详尽列表,一个文件路径可以匹配多个规则,那么 SELinux 工具将使用哪个规则呢?
基本上,SELinux 工具遵循 最具体优先 的原则。给定 A 行和 B 行,将按以下顺序检查,其中第一个匹配的获胜:
-
如果 A 行中有正则表达式而 B 行没有,那么 B 行更为具体。
-
如果 A 行中第一个正则表达式之前的字符数少于 B 行中第一个正则表达式之前的字符数,则 B 行更为具体。
-
如果 A 行的字符数少于 B 行的字符数,则 B 行更为具体。
-
如果 A 行没有指定 SELinux 类型(因此它的上下文部分是
<<none>>),而 B 行有,那么 B 行更为具体。
SELinux 工具将加载通过 /etc/selinux/mcs/contexts/files/ 中的文件给出的定义,但会优先考虑 file_contexts.local(然后是 file_contexts.homedirs)中的定义,因为这些是由系统管理员本地创建的定义。然而,如果本地定义使用正则表达式而策略提供的定义没有使用,则仍然使用策略提供的定义。这是各上下文文件之间优先规则的唯一例外。
SELinux 工具提供了一个名为 findcon 的工具(属于 setools 或 setools-console 包),可用于分析这个排序,显示单个(!)上下文定义文件中的匹配模式,并按照从最不具体到最具体的顺序排列:
~$ findcon /etc/selinux/mcs/contexts/files/file_contexts -p /var/log/aide
/.* system_u:object_r:default_t:s0
/var/.* system_u:object_r:var_t:s0
/var/log/.* system_u:object_r:var_log_t:s0
/var/log/aide(/.*)? system_u:object_r:aide_log_t:s0
如果只需要实际的上下文定义(而不是 findcon 所显示的完整匹配表达式及其优先顺序),则可以改用 matchpathcon:
~# matchpathcon /var/log/aide
/var/log/aide system_u:object_r:aide_log_t:s0
类标识符
上下文定义的第二部分是可选部分——类标识符。通过类标识符,开发者可以告诉 SELinux 工具,只有当路径表达式匹配特定文件类时,上下文定义才适用。如果省略类标识符,则任何类都会匹配。
如果显示了类标识符,则可以使用以下标识符之一(每行一个):
-
'
--' 标识符用于常规文件 -
'
-d' 标识符用于目录 -
'
-l' 标识符用于符号链接 -
'
-b' 标识符用于块设备 -
'
-c' 标识符用于字符设备 -
'
-p' 标识符用于 FIFO 文件 -
'
-s' 标识符用于套接字
上下文声明
上下文定义的最后部分是要分配给所有匹配资源的上下文。它是通过 gen_context 宏生成的,如下所示:
gen_context(system_u:object_r:var_t,s0)
gen_context 宏用于基于策略特性区分上下文定义。如果目标策略不支持 MLS,则仅使用第一个参数(示例中的 system_u:object_r:var_t)。如果策略支持 MLS,但仅支持单一敏感性(s0),则会在上下文中附加 :s0。否则,会附加第二个参数(示例中恰好也是 s0,前面带冒号)。
通常,上下文只在 SELinux 类型上有所不同。资源的 SELinux 所有者和 SELinux 角色通常分别保持为 system_u 和 object_r。
上下文的特殊值是 <<none>>,如下所示:
/proc -d <<none>>
这告诉 SELinux 工具,它们永远不应尝试设置此资源的上下文。每当管理员触发文件系统重新标记操作时,这些特定位置的标签将不会被更改(无论当前标签是什么)。这并不意味着应该移除现有上下文!
还有更多...
在本教程中,我们详细介绍了如何定义标签。如果进行大量更改,强制对整个系统进行重新标记是有意义的。在 Red Hat 系统中,可以通过创建一个标志文件并重启系统来实现:
~# touch /.autorelabel
~# reboot
在 Gentoo 系统中,可以使用rlpkg命令重新标记整个系统:
~# rlpkg -a -r
在 Red Hat 系统中,用于重新标记系统的命令是fixfiles:
~# fixfiles relabel
如果系统(临时)以没有 SELinux 支持或禁用 SELinux 的状态启动,文件将会被创建为没有文件上下文。当 SELinux 启用的系统再次启动时,它将把这些文件标记为unlabeled_t,这是大多数域无法访问的类型(在 SELinux 方面)。
使用替代定义
有时,应用程序及其资源被安装在与 SELinux 策略预期的位置不同的地方。试图通过为每个子目录定义额外的上下文定义来适应这种情况,容易变得无法管理。
为了帮助管理员,SELinux 工具支持替代条目,这些条目告诉 SELinux:“如果路径以这个开头,那么就像它以那个开头一样标记它”。管理员可以使用semanage设置这样的替代(称为等价类),如下所示:
~# semanage fcontext –a –e / /mnt/chroots/bind
在这个例子中,/mnt/chroots/bind/下的任何位置都会被标记为如果它是从主/目录开始的(所以/mnt/chroots/bind/etc/变成了etc_t,因为/etc/是etc_t)。
chroots的目标位置是一个很好的应用场景。chroot是文件系统中的一个替代位置,它将作为一个或一组应用程序的根文件系统。
对于想要在多个系统上设置替代的管理员,无法将其作为 SELinux 策略模块的一部分。我们需要管理的文件称为file_contexts.subs(还有一个以.subs_dist结尾,由 Linux 发行版管理,我们不会修改它)。话虽如此,我们始终可以找到一种相对合理的方式来更新这个文件。
准备工作
最简单的方法是使用一个中央配置管理工具,如 Puppet、CFEngine、Chef 或 Ansible,因为这些系统允许管理员强制将特定文件的内容设置为特定值。使用配置管理工具本身就可以写成一本书,所以它超出了本书的范围。如果你确实想深入研究,记住file_contexts.subs文件也是由semanage命令管理的。管理员可能希望添加一些中央配置管理工具无法识别的本地定义(因此可能会恢复更改)。
在本教程中,我们将介绍一种通用的方法,但它确实要求能够进行文件传输并执行一条单行命令(以正确的权限执行)。然而,对于大多数系统管理员来说,这不应该是一个挑战。
如何操作…
为了将更改应用到广泛的系统,请按照以下步骤操作:
-
在系统上本地应用更改:
~# semanage fcontext -a -e / /mnt/chroot/bind -
将定义导出到一个文件:
~# semanage export -f local_selinux.mods -
编辑
local_selinux.mods文件,并删除所有与更改无关但需要分发的条目。 -
将生成的文件分发到目标系统。
-
在系统上本地应用更改:
~# semanage import -f local_selinux.mods
它是如何工作的…
semanage fcontext 命令为 /mnt/chroot/bind/ 创建了一个等效类,其中所有子目录和文件都会被标记,就像它们位于根目录(/)下一样。这确保管理员不需要为他们管理的每个 chroot 位置定义大量的文件上下文。
然而,这可能会成为问题,因为 semanage fcontext 只会本地应用更改,在大型基础设施中,可能需要将相同的设置应用于多个系统。为此,可以使用 semanage export 和 semanage import。
semanage export 命令的输出是一组 semanage 指令,完全遵循 semanage 命令的语法。
导出 semanage 定义时,首先存储的命令集是 delete all 语句,如 fcontext -D(删除所有本地创建的 semanage fcontext 设置)。当然,如果我们只需要分发替代定义,则删除所有先前的本地语句是不正确的。因此,需要手动编辑 local_selinux.mods 文件。如果仅需分发等效类定义,则文件可能仅包含以下内容:
fcontext -a -e / /mnt/chroot/bind
然后,可以将导出的文件分发到所有目标系统,并通过 semanage import 命令加载,从而有效地将相同的一组更改应用到系统。
如果定义已经应用于某个系统,则 import 命令将失败:
~# semanage import -f local_selinux.mods
ValueError: Equivalence class for /mnt/chroot/bind already exists
需要注意的是,如果文件中的某个命令未能应用,则文件中的所有命令都不会应用(文件是一次性处理的)。因此,delete all 规则最初被作为导出的命令集的一部分。
如果本地应用的更改也需要保留,那么分布式管理这些设置会变得更加具有挑战性,除非分布式的更改集是单一的(一个导出的指令,允许失败)。
还有更多…
/etc/selinux/mcs/contexts/ 位置中的大多数文件不应通过任何工具进行管理,除非是 Linux 发行版包管理系统(通过安装基础 SELinux 策略)或 semanage。
话虽如此,这个位置内的大多数文件变化不大(除了files/file_contexts文件)。将这些文件与包管理系统连接以更新它们(如果支持)可能是有益的,或者直接接管这些文件的管理,前提是你密切跟踪分发版会进行的更改。
另请参见
以下资源将深入讨论本配方中涉及的主题:
-
要了解更多关于各种配置文件的信息,请查看
selinuxproject.org/page/PolicyConfigurationFiles -
SELinux 与
chroots的交互在第九章中讨论得更详细,对齐 SELinux 与 DAC
通过文件转换增强 SELinux 策略
到目前为止,我们只处理了文件上下文的配置部分:如果我们要求 SELinux 工具重新标记文件,那么我们所做的更改将生效。然而,除非你运行restorecond守护进程来检查所有可能的文件修改(这将非常消耗资源),或者定期手动运行restorecon对所有文件进行检查,否则新定义的上下文将不会应用到文件上。
我们需要做的是修改本地的 SELinux 策略,以便在创建这些文件时,Linux 内核会自动为这些文件分配正确的标签。这通过文件转换来处理,这是类型转换的一个特定情况。
在类型转换中,我们配置策略,使得如果某个域在具有特定标签的目录中创建文件(或其他资源类),则创建的对象应该自动获得一个特定的标签。从策略的角度来看,这是如下所写的:
type_transition <domain> <directory_label>:<resource_class> <specific_label>
SELinux 还增加了对命名文件转换的支持(从 Linux 2.6.39 版本开始,并在 Gentoo、Fedora 16+、Red Hat Enterprise Linux 7+中可用)。在这种情况下,仅当创建的资源与特定的文件名完全匹配时(即没有正则表达式)才会发生这种转换:
type_transition <domain> <directory_label>:<resource_class> <specific_label> <filename>
通过参考策略宏,这可以通过filetrans_pattern定义来支持。
准备工作
为了正确定义文件转换,我们需要知道负责创建资源的源域是什么。例如,/var/run/snort/目录可能由init脚本创建,但如果没有文件转换,那么该目录将以父目录的类型(即var_run_t)创建,而不是正确的类型(snort_var_run_t)。
所以在开始本配方之前,请确保写下所有相关的标签(例如,我们将使用initrc_t作为init脚本的标签,var_run_t作为父目录的标签,snort_var_run_t作为目标目录的标签)。
如何执行…
定义文件转换可以按照以下方式进行:
-
在 SELinux 策略中搜索,看看是否有一个接口能够提供从给定域到
snort_run_t的文件转换:~$ sefindif filetrans.*snort_var_run_t -
假设没有找到相关内容,搜索允许
initrc_t创建的资源转移到给定类型的接口:~$ sefindif filetrans.*initrc_t system/init.if: interface(`init_daemon_pid_file',` system/init.if: files_pid_filetrans(initrc_t, $1, $2, $3) -
成功!现在,让我们通过以下声明创建一个用于增强 snort SELinux 模块的策略(通过
mysnort策略文件):policy_module(mysnort, 0.1) gen_require(` type snort_t; type snort_var_run_t; ') # If initrc_t creates a directory called "snort" in a var_run_t dir, # make sure this one is immediately labeled as snort_var_run_t. init_daemon_pid_file(snort_var_run_t, dir, "snort") -
构建新的策略并加载它。然后使用
sesearch检查是否确实声明了类型转换:~$ sesearch –s initrc_t –t var_run_t –T | grep "snort" type_transition initrc_t var_run_t : dir snort_var_run_t "snort"
它是如何工作的……
支持 SELinux 的 Linux 发行版已经提供了一个在大多数部署中有效的 SELinux 策略。默认策略非常广泛,且大多能够开箱即用。如果需要特定的更改,那么这些特定的 SELinux 规则可能已经被定义(作为策略接口的一部分),只需实例化并加载即可。
策略接口通常存在以下两种类型:
-
主题通过参数传递的接口,其中对象(操作执行的对象)以及可能的目标(在我们的例子中,是转换应发生的目标)是硬编码的
-
主题硬编码且对象、目标或两者是接口参数的接口
在我们的示例中可以使用的第一种接口类型的示例代码如下所示:
interface(`snort_generic_pid_filetrans_pid',`
gen_require(`
type snort_var_run_t;
')
files_pid_filetrans($1, snort_var_run_t, dir, $2)
')
然后我们可以像这样调用这个接口:
snort_generic_pid_filetrans_pid(initrc_t, "snort")
然而,这种接口会增加维护负担。对于系统中每添加一个守护进程支持,init策略就需要通过命名文件转换和新增的守护进程策略规则来进行修改。考虑到系统中可能运行的守护进程数量,init策略实际上会被填充大量的命名文件转换——每个守护进程至少一个。
我们在示例中遇到的接口声明要管理得多得多。该接口旨在由守护进程策略本身调用,并立即确保initrc_t类型可以在通用运行目录(var_run_t)中创建给定类型(snort_var_run_t)的目录。政策的新增不会影响init策略,使得策略的维护变得更加容易。
查找正确的搜索模式
要查找正确的模式,我们使用sefindif接口在可用接口中进行搜索。找到正确的表达式是一项经验性的任务。
如我们所知,我们想要查找文件转换,我们要查找的行将包含filetrans_pattern。然后,其中一个涉及的参数是我们将要转换到的类型(snort_var_run_t)。因此,我们在示例中使用的表达式更改为filetrans.*snort_var_run_t。由于没有结果,接下来的搜索涉及必须进行转换的域(initrc_t),因此表达式改为filetrans.*initrc_t。
然而,假设我们不知道需要搜索filetrans_pattern。类型本身(snort_var_run_t)或域(initrc_t)可能足以进行搜索,如以下的搜索所示:
~$ sefindif snort_var_run_t
~$ sefindif initrc_t
从结果列表中,我们可以看到是否有适合我们需求的接口。
模式
如filetrans_pattern这样的模式是参考策略中的重要支持定义。它们将一组与功能性方法相关的权限(如读取文件,这些通过read_files_pattern来处理)进行捆绑,并且不依赖于特定的类型(与接口不同)。
模式的需求来源于 SELinux 对 Linux 活动的非常细粒度的访问控制。读取文件是一个很好的例子:仅仅允许一个类型执行read操作是不够的:
allow initrc_t snort_var_run_t:file read;
大多数应用程序首先检查文件的属性(getattr)并打开文件,然后才能读取文件。根据目的,它们可能还想锁定文件或通过ioctl对其执行 I/O 操作。所以,不仅仅是前述的访问向量,规则已更改为:
allow initrc_t snort_var_run_t:file { getattr lock open read ioctl }
参考策略为此提供了一个称为read_file_perms的单一权限集,将访问向量转换为以下内容:
allow initrc_t snort_var_run_t:file read_file_perms;
其次,策略开发者通常希望允许一个域在一个标记相似的目录内读取文件。例如,一个snort_var_run_t文件可以位于/var/run/snort/snort.pid,而/var/run/snort/目录也标记为snort_var_run_t。因此,我们还需要授予initrc_t类型在该目录内的搜索权限——这同样是一组权限,可以从search_dir_perms定义中看到:
~$ seshowdef search_dir_perms
define(`search_dir_perms',`{ getattr search open }')
与其为此创建多个规则,不如创建一个模式,叫做read_files_pattern,它如下所示:
~$ seshowdef read_files_pattern
define(`read_files_pattern',`
allow $1 $2:dir search_dir_perms;
allow $1 $3:file read_file_perms;
')
这允许策略开发者使用一个单一的调用:
read_files_pattern(initrc_t, snort_var_run_t, snort_var_run_t)
要查看支持策略开发的各种模式,请使用sefinddef并带上'define.*_pattern'表达式:
~$ sefinddef define.*_pattern
使用模式允许开发者通过函数化方法创建可读的策略规则,而不是对每个单独的访问向量进行总结。
还有更多...
在之前展示的snort_generic_pid_filetrans_pid接口中,我们使用了命名文件转换:只有当传递的最后一个参数中的文件名与创建的文件的文件名匹配时,转换才会发生。
命名文件转换优先于普通文件转换。一个很好的例子是initrc_t域支持的文件转换:
~# semanage –s initrc_t –t var_run_t –T
Found 2 semantic te rules:
type_transition initrc_t var_run_t : file initrc_var_run_t;
type_transition initrc_t var_run_t : dir initrc_var_run_t;
Found 16 named file transition rules:
type_transition initrc_t var_run_t : dir udev_var_run_t "udev";
type_transition initrc_t var_run_t : dir tor_var_run_t "tor";
…
在这种情况下,如果一个init脚本创建了一个名为udev或tor的目录(或示例中未显示的任何其他转换规则),则会发生适当的文件转换。如果文件名不匹配,则会发生转换到initrc_var_run_t类型。
对常规文件和目录的文件转换最为常见,但转换也可以发生在其他各种类别上,如套接字、FIFO 文件、符号链接等。
另见
- 域转换(即为进程分配不同的上下文而非文件)将在第三章,限制 Web 应用程序中详细讨论,并在第四章,创建桌面应用程序策略和第五章,创建服务器策略中使用。
设置资源敏感度标签
当 SELinux 策略启用 MLS 并支持多个敏感度时(而 MCS 则没有,因为 MCS 只有一个敏感度),SELinux 可以根据域的许可级别和资源的敏感度级别管理信息流和资源访问。但是即使只有单一敏感度(如 MCS 的情况),SELinux 也提供了额外的约束支持,确保域无法访问分配了该域没有许可的类别的资源。
敏感度级别由一个敏感度(s0通常用于最低敏感度,而s15——这是一个策略构建时的常量,因此可以配置——是最高敏感度)以及一个类别集(可以是诸如c0,c5,c8.c10之类的列表)组成。
安全许可类似于敏感度级别,但显示的是一个敏感度范围(例如s0-s3),而不是单一的敏感度级别。安全许可可以看作是从最低敏感度级别到域允许的最高敏感度级别的范围。
在为此类系统开发策略时,上下文定义和策略规则可以考虑敏感度。在本教程中,我们将执行两个针对启用 MLS 系统最常见的操作:
-
定义一个更高敏感度的上下文
-
在域转换中按策略设置进程的许可级别
为了实现这一点,我们将以 snort 入侵检测系统为例,强制它始终以s3敏感度和所有可能的类别运行。
本示例还将向我们展示如何替换现有策略,而不是增强它,因为我们将更新一个定义,否则它将与现有定义发生冲突。
如何做到这一点…
要修改现有域以支持特定的敏感度级别,请执行以下步骤:
-
将
snort.te和snort.fc文件从分发策略库复制到本地环境:~$ cp ${POLICY_LOCATION}/policy/modules/contrib/snort.* ${DEVROOT}/local -
将文件重命名为
mysnort(或customsnort),这样我们就知道这是一个定制的策略。不要忘记更新.te文件中的policy_module调用。 -
打开
mysnort.te文件,查找init_daemon_domain调用。将该调用替换为以下内容:init_ranged_daemon_domain(snort_t, snort_exec_t, s3:mcs_allcats) -
在
mysnort.fc中,将 snort 资源标记为s3敏感度。例如,对于 snort 二进制文件,标记如下:/usr/bin/snort -- gen_context(system_u:object_r:snort_exec_t,s3) -
构建
mysnort策略,移除当前加载的 snort SELinux 策略模块,并加载mysnort模块:~# /etc/init.d/snort stop ~# semodule –r snort ~# semodule –i mysnort.pp -
重新标记所有与 snort 相关的文件,然后再次启动 snort。
它是如何工作的……
这个方案有三个重要方面:
-
我们替换整个策略,而不是创建一个增强。
-
我们更新了策略以使用范围守护进程域。
-
我们更新文件上下文,以使用正确的敏感度。
文件上下文的更新显而易见,但完全替换策略的原因可能不太明显。
完整策略替换
在这个示例中,我们复制了现有的 snort SELinux 模块策略,并在副本中进行了更新,而不是通过创建额外的模块来增强策略。
这是必需的,因为我们正在对 SELinux 策略进行更改,这些更改与当前运行的 SELinux 策略是互斥的。例如,文件上下文的更改会让 SELinux 感到困惑,因为它会通过策略模块有两个完全匹配的定义,但每个定义的上下文不同。
在这个示例中,我们仅复制了类型强制声明(snort.te)和文件上下文声明(snort.fc)。如果我们还复制接口定义(snort.if),那么策略构建时会给出警告,提示存在重复的接口定义——毕竟,Linux 发行版提供的接口定义仍然存在于系统中。
范围守护进程域
在 SELinux 策略本身中,我们将 init_daemon_domain(snort_t, snort_exec_t) 条目替换为以下内容:
init_ranged_daemon_domain(snort_t, snort_exec_t, s3:mcs_allcats)
让我们来看看这个接口的内容:
~$ seshowif init_ranged_daemon_domain
interface(`init_ranged_daemon_domain',`
gen_require(`
type initrc_t;
')
init_daemon_domain($1, $2)
ifdef(`enable_mcs',`
range_transition initrc_t $2:process $3;
')
ifdef(`enable_mls',`
range_transition initrc_t $2:process $3;
mls_rangetrans_target($1)
')
')
新调用的接口调用了原始的init_daemon_domain,但增加了 MCS 和 MLS 相关的逻辑。在这两种情况下,它都调用了 range_transition,这样当 snort 的 init 脚本(以 initrc_t 运行)过渡到 snort_t 域时,活动的敏感度范围也会被更改为第三个参数。
在我们的例子中,第三个参数是 s3:mcs_allcats,其中 mcs_allcats 是一个定义,扩展为策略支持的所有类别(例如,如果策略支持 256 类别,则为 c0.c255)。
在 MLS 情况下,它还调用了 mls_rangetrans_target,这是一个接口,将一个属性设置为 snort_t 域,这是策略中启用的 MLS 限制所需要的。
从扩展的代码中,我们可以看到有 ifdef() 语句。这些是 SELinux 策略规则的块,基于构建时的参数启用(或忽略)。如果启用了 MCS 或 MLS 策略,则会设置 enable_mcs 和 enable_mls 参数。其他常用的构建时参数包括发行版选择(例如,如果 SELinux 策略规则针对 Red Hat Enterprise Linux 和 Fedora 系统,则为 distro_redhat)和 enable_ubac(即启用了基于用户的访问控制时)。
限制
大多数(如果不是全部的话)SELinux 策略开发集中在类型强制规则和上下文定义上。SELinux 确实支持各种其他语句,其中之一是constrain语句,用于实现约束。
约束基于一组表达式进一步限制权限,这些表达式不仅涵盖对象或主体的类型,还包括 SELinux 角色和 SELinux 用户。与mlsrangetrans属性(由mls_rangetrans_target接口设置)相关的约束如下所示:
mlsconstrain process transition
(( h1 dom h2 ) and
(( l1 eq l2 ) or ( t1 == mlsprocsetsl ) or
(( t1 == privrangetrans ) and ( t2 == mlsrangetrans ))));
约束告诉我们有关转换的以下信息:
-
只有当主体(域/执行者)的最高敏感度级别支配对象的最高敏感度级别时,转换才会发生。
-
主体的最低敏感度级别与对象的最低敏感度级别相同。
-
如果不是这样,则主体的类型必须设置
mlsprocsetsl属性。 -
如果不是这样,则必须同时满足以下两个条件:
-
主体的类型设置了
privrangetrans属性。 -
对象的类型设置了
mlsrangetrans属性。
-
支配意味着第一个安全级别的敏感度级别等于或高于第二个安全级别的敏感度级别,并且第一个安全级别的类别与第二个安全级别的类别相同或是其超集。
SELinux 策略中的约束是基础策略集的一部分——这意味着我们不能通过可加载的 SELinux 策略添加约束。如果我们想要包括额外的约束,我们需要自己构建整个策略,修补策略库中policy/子目录下的constraints、mls和mcs文件。
了解约束是很重要的,但我们可能永远不需要自己编写约束。
另见
SELinux 项目网站是学习约束及其相关语句的良好起点:
-
可以参考 SELinux 语句
selinuxproject.org/page/NB_MLS
配置敏感度类别
虽然 MCS 策略支持 MLS,但它们配置为仅支持单一敏感度(即s0)。尽管有这个限制,MCS 策略仍然非常有用,例如在系统为多个客户提供服务的情况下。这是因为 MCS 仍然可以根据类别受益于安全许可。
与敏感度不同,类别更像是一个自主访问控制系统。类别旨在由用户(或管理员)用来标记文件和其他资源,使其成为一个或多个类别的成员。对这些资源的访问则基于进程的许可级别和分配给资源的类别。类别也没有层次结构。
一个类别发挥重要作用的使用案例是在多租户部署中:这些系统为多个租户(多个客户)托管一个或多个服务,当然,需要适当的安全隔离,以确保一个租户无法访问另一个租户的资源。
在大多数情况下,管理员会尝试通过运行时用户(和组成员身份)来分隔这些服务。然而,这并非总是可能的。有些情况下,这些独立的进程仍然需要以相同的运行时用户身份运行(尽管通过支持额外的 Linux 安全子系统——如能力——这些情况已经显著减少)。
在本食谱中,我们将配置一个系统,使用多个类别来区分不同客户的资源,用于客户也能访问 shell 的 Web 服务器。通过类别,我们可以为其他客户的资源提供更多保护,以防其中一个客户能够执行提升其权限的漏洞。
准备工作
你需要为多个租户准备系统。例如,我们可以将整个网站内容托管在 /srv/web/<companyname>/ 中,并将 Web 服务器配置放在 /etc/apache/conf/<companyname>/ 中。
在本食谱中,作为示例,我们将为两个公司(CompanyX 和 CompanyY)配置系统。每个公司也有一个普通用户(第一家公司为 userX,第二家公司为 userY)。
如何操作……
要实例化不同的类别,按以下方法进行:
-
确定不同客户的类别命名(和编号),并在
/etc/selinux/mcs/中的setrans.conf文件中进行配置:s0:c100=CompanyX s0-s0:c100=CompanyXClearance s0:c101=CompanyY s0-s0:c101=CompanyYClearance -
重新启动
mcstrans服务,使其能够识别此配置。 -
列出类别,以确保更改能被正确解释:
~$ chcat –L s0 SystemLow s0-s0:c0.c1023 SystemLow-SystemHigh s0:c0.c1023 SystemHigh s0:c100 CompanyX s0-s0:c100 CompanyXClearance s0:c101 CompanyY s0-s0:c101 CompanyYClearance -
创建具有处理正确类别权限的 SELinux 用户:
~# semanage user –a –L s0 –r CompanyXClearance –R "user_r" userX_u ~# semanage user –a –L s0 –r CompanyYClearance –R "user_r" userY_u -
配置 Linux 用户(登录名)以获得正确的安全权限:
~# semanage login –m –s userX_u userX ~# semanage login –m –s userX_u userY -
为公司资源设置正确的类别:
~# chcon –l CompanyX –R /srv/web/www.companyX.com/ /etc/apache/conf/companyX/ ~# chcon –l CompanyY –R /srv/web/www.companyY.com/ /etc/apache/conf/companyY/ -
配置 Apache
init脚本,通过runcon启动 Apache,以正确的安全级别启动。例如,在 Red Hat Enterprise Linux 6 系统中,对于第一家公司的网站服务器,使用以下脚本:LANG=$HTTPD_LANG daemon --pidfile=${pidfile} runcon –t httpd_t –l CompanyX $httpd $OPTIONS -
(重新)启动 Web 服务器,并验证它是否以正确的安全级别运行:
~# ps –efZ | grep httpd
它是如何工作的……
我们从配置系统开始,使得可以命名类别和范围,而不必使用整数表示法。接着,我们为每个公司创建了一个 SELinux 用户,并将每个(普通)Linux 账户分配给正确的 SELinux 用户。在更新所有公司相关文件的上下文后,我们配置了 Apache 以正确的上下文启动。
mcstrans 和 setrans.conf 文件
setrans.conf 文件是一个普通的文本文件,MCS 过渡守护进程(mcstransd)用它来将实际的安全级别(如 s0:c100)替换为人类可读的字符串(如 CompanyX)。
Linux 工具本身(如ls和ps)使用 SELinux 库获取文件和进程的上下文信息。这些库然后与mcstransd进程(通过/var/run/setrans/.setrans-unix套接字)连接,发送实际的安全级别并检索其人类可读的表示。
需要记住的是,这仅仅是一个表示,并非安全级别存储的方式。换句话说,不要在文件上下文定义文件中使用此表示(即 SELinux 策略.fc文件)。
SELinux 用户和 Linux 用户映射
在这个示例中,为每个公司创建了一个 SELinux 用户。这个 SELinux 用户被授予了与该公司类别标记的资源进行工作的许可。然后,实际的 Linux 账户被映射到这个 SELinux 用户。
从示例中,我们看到每个公司都有两个定义:
s0:c100 CompanyX
s0-s0:c100 CompanyXClearance
第一个是安全级别,可以分配给资源以及进程(用户)。第二个是安全许可(一个范围)。在这个特定的示例中,许可告诉我们,高安全级别(可以视为进程被允许访问的内容)是公司的资源(s0:c100),而低安全级别(可以视为进程本身的安全级别)仅为s0。
因此,公司的用户被授权访问分配给其公司类别的文件(和其他资源)。然而,所有由这些用户帐户执行的活动默认不会获得此类别——用户需要使用chcon来设置类别,如下所示:
~$ chcon –l CompanyX public_html/index.html
可以将安全级别本身授予用户,而不是许可。当发生这种情况时,用户创建的任何资源也将获得适当的类别设置。但是,不要将此用作限制资源的方式——用户始终可以从资源中移除类别。
授予安全级别可以在 SELinux 用户级别完成,但也可以通过 SELinux 用户映射来实现,只要传递的范围受到 SELinux 用户级别设置的范围控制。例如,要将CompanyX(s0:c100)设置为安全级别,而不是默认的CompanyXClearance(这是映射到userX_u SELinux 用户的用户的默认设置),可以使用以下命令:
~# semanage login –m –r CompanyX user1
使用正确的上下文运行 Apache
示例中最后的变化是配置系统以在正确的安全级别下启动 Web 服务器。这是通过runcon命令完成的,我们传递敏感性级别(而不是安全许可),以确保通过 Web 服务器创建的每个资源都继承正确的类别以及目标类型。
SELinux 策略知道,如果一个init脚本启动 Apache 二进制文件(httpd),则此应用程序必须在httpd_t域中运行。然而,现在init脚本启动了runcon—SELinux 策略将其视为常规二进制文件—因此应用程序将继续在initrc_t域中运行。因此,我们需要传递目标类型(httpd_t)。在没有非限制域的 SELinux 策略系统上,忘记这一点会阻止 Web 服务器的运行。在具有非限制域的 SELinux 策略系统上,这可能导致 Web 服务器在非限制域(initrc_t)中运行,从而有效地禁用了我们对 Web 服务器所需的 SELinux 保护!
另请参见
关于多租户和 SELinux 交互的更多示例如下:
-
sVirt (
selinuxproject.org/page/SVirt) 使用 SELinux 分类将虚拟客户隔离开来。 -
Linux 容器,例如通过 LXC 项目 (
linuxcontainers.org),利用 SELinux 进一步隔离容器与主系统。 -
Apache 通过
mod_selinux模块支持多租户,在第三章中详细介绍了限制 Web 应用程序。
第三章。限制 Web 应用程序
在本章中,我们将涵盖 Web 服务器域的默认限制,并练习如何调整此策略以适应我们的需求。我们还将研究mod_selinux及其如何进一步限制 Web 应用程序。所有这些将通过以下方法处理:
-
列出条件性策略支持
-
启用用户目录支持
-
分配 Web 内容类型
-
使用不同的 Web 服务器端口
-
使用自定义内容类型
-
创建自定义 CGI 域
-
设置 mod_selinux
-
使用有限清除启动 Apache
-
将 HTTP 用户映射到上下文
-
使用源地址映射来决定上下文
-
使用 mod_selinux 分隔虚拟主机
介绍
Web 应用程序是 SELinux 可以证明其有效性的主要示例。它们经常面对(不受信任的)互联网,并且是攻击的热门目标。尽管保护 Web 服务器和 Web 应用程序只是基本的缓解策略之一,但通过限制 Web 服务器,我们进一步减少了成功利用的结果。
一个良好限制的 Web 服务器只允许向操作系统进行服务的可接受行为。但考虑到可以通过 Web 服务器提供的广泛服务领域,我们必须小心不要开放过多的权限。
策略开发人员已预见到 Web 服务器域可能在其权限上过于广泛,并且已使 Web 服务器域(httpd_t)不仅非常多才多艺,而且非常可配置。在本章中,我们将更详细地研究该域。
列出条件性策略支持
SELinux Web 服务器域策略的第一个可配置方面是其广泛使用 SELinux 布尔值。通过这些布尔值,可以选择性地启用或禁用附加策略规则。在本教程中,我们将查看这些布尔值,并了解如何切换它们。
如何做...
为了列出条件性策略支持,请执行以下步骤:
-
请求所有 SELinux 布尔值的列表,并有选择地显示以
httpd_开头的那些:~# getsebool –a | grep httpd_ -
要获得与布尔值一起的简短描述,我们可以使用
semanage:~# semanage boolean –l | grep httpd_ -
如果布尔值的描述不足够,我们可以请求 SELinux 实用程序显示将在设置布尔值时启用(或禁用)的 SELinux 规则:
~# sesearch –b httpd_enable_ftp_server –AC Found 3 semantic av rules: DT allow httpd_t httpd_t : capability net_bind_service ; [ httpd_enable_ftp_server ] DT allow httpd_t ftp_port_t : tcp_socket { recv_msg send_msg name_bind } ; [ httpd_enable_ftp_server ] DT allow httpd_t ftp_server_packet_t : packet { send recv } ; [ httpd_enable_ftp_server ]
工作原理...
通过 SELinux 布尔值提供条件性 SELinux 策略支持。这些是可配置的参数(具有true/false值),管理员可以使用setsebool或semanage boolean命令启用或禁用。
使用getsebool命令,我们请求所有 SELinux 布尔值的概述。最近的策略分配了几百个布尔值,但幸运的是,大多数布尔值遵循以下两种命名约定之一,这使得过滤更容易:
-
布尔值以
allow_或use_开头 -
布尔值以 SELinux 策略模块前缀开头
以allow_或use_开头的布尔值被认为是全局布尔值,通常会影响多个 SELinux 策略模块。一个很好的例子是allow_execmem,它允许多个域在可写内存中执行代码,而不是在只读内存中执行代码(这是一个有害但有时不可避免的内存权限设置)。
大多数,甚至所有其他布尔值都以它们所应用的 SELinux 策略模块前缀开始。对于 Web 服务器来说,这是httpd_(尽管策略名为 apache,但选择httpd_前缀是因为该策略可以直接应用于各种 Web 服务器,而不仅仅是 Apache HTTPd)。
当我们使用semanage boolean命令时,会提供关于布尔值的简短描述。这个描述来自一个名为policy.xml的 XML 文件,可以在/usr/share/selinux/devel/目录下找到。该 XML 文件是在构建基本 SELinux 策略时生成的。
然而,布尔值的最准确描述是它启用或禁用时会触发的一组规则。这就是sesearch命令发挥作用的地方。
如示例所示,布尔值将触发一个或多个允许规则。sesearch输出的前缀告诉我们,如果布尔值为真(T)或假(F),显示的规则是否处于活动状态,以及该规则是否在策略中启用(E)或禁用(D)。
在使用sesearch查询 SELinux 策略时,一个不错的技巧是同时查询布尔值管理的规则(无论它们当前是否启用或禁用)。这可以通过添加–C选项来实现(该选项是--show_cond的简写)。例如,要查找newrole_t域的转换,可以使用以下命令:
~# sesearch –s newrole_t –c process –p transition –AC
Found 5 semantic av rules:
allow newrole_t newrole_t : process { … };
allow newrole_t chkpwd_t : process transition;
allow newrole_t updpwd_t : process transition;
EF allow newrole_t userdomain : process transition ; [ secure_mode ]
DT allow newrole_t unpriv_userdomain : process transition ; [ secure_mode ]
另请参见
-
httpd_selinux手册页面列出了所有适用于 Apache SELinux 模块的 SELinux 布尔值,并详细解释了它们的用途:~$ man httpd_selinux
启用用户目录支持
让我们看一个如何使用适用于 Web 服务器安装的 SELinux 布尔值的示例。在这个示例中,我们将启用 Apache UserDir 支持(允许 Web 服务器在http://sitename/~username处提供本地用户账户的网页)。
准备工作
配置 Apache Web 服务器以提供用户内容。这里本应包括完整的 Apache 配置教程,但这超出了本书的范围。基本上,这是通过编辑httpd.conf文件并设置UserDir指令来完成的。
如何操作…
要启用用户目录支持,请按照以下步骤操作:
-
确保用户的主目录对于 Apache 运行时账户是可访问的,使用以下命令。如果 Linux DAC 拒绝访问,SELinux 甚至不会处理该请求。
~$ chmod 755 ${HOME}/ ~$ chmod 755 ${HOME}/public_html -
检查访问是否已经被允许,可以通过访问用户页面来确认。如果所有权限都正确,但 SELinux 拒绝访问,则页面应返回 403(禁止访问)错误,并且拒绝访问的操作应记录在审计日志中。Apache 错误日志会显示对该资源的权限拒绝。
-
审计日志可能会显示
httpd_t不允许对home_root_t或user_home_dir_t执行操作。通过查看 SELinux 布尔值,我们发现至少有两个有趣的布尔值(httpd_enable_homedirs和httpd_read_user_content):~# sesearch -s httpd_t -t home_root_t -c dir -p open -AC Found 2 semantic av rules: DT allow httpd_t home_root_t : dir { getattr search open } ; [ httpd_enable_homedirs ] DT allow httpd_t home_root_t : dir { getattr search open } ; [ httpd_read_user_content ] -
首先切换
httpd_read_user_content。这允许 Web 服务器访问所有用户文件,功能上是可以的,但这也立即赋予它对所有文件的访问权限:~# setsebool httpd_read_user_content on -
另一种方法(但这种方法需要用户干预)是将
~/public_html/标记为httpd_user_content_t。完成后,可以关闭httpd_read_user_content并启用httpd_enable_homedirs:~$ chcon –R –t httpd_user_content_t public_html ~# setsebool httpd_read_user_content off ~# setsebool httpd_enable_homedirs on -
当更改工作正常时,我们可以使更改持久化,以便它们在重启后仍然有效:
~# setsebool –P httpd_enable_homedirs on
它是如何工作的……
SELinux 中的默认 Web 服务器策略不允许 Web 服务器访问用户的主目录内容。如果 Web 应用程序或 Apache Web 服务器本身存在漏洞,允许攻击者读取用户内容,SELinux 将阻止这种情况的发生。但有时,确实需要访问用户内容。
通过启用 httpd_read_user_content 布尔值,Web 服务器域(及所有相关域)将拥有对所有用户文件的完全读取权限。如果用户无法(或不知道如何)为其文件设置正确的上下文,则这是唯一合适的选项。
然而,更好的方法是启用 httpd_enable_homedirs 布尔值。这允许 Web 服务器访问主目录(/home/user/,标记为 user_home_dir_t),但不提供对用户内容(标记为 user_home_t)的读取权限。相反,Web 服务器所需的资源被标记为 httpd_user_content_t —— 这是常规用户可以重新标记资源的类型(或从中重新标记资源)。在 httpd_user_content_t 旁边,还可以定义以下内容类型:
-
httpd_user_htaccess_t用于.htaccess文件 -
httpd_user_script_exec_t用于用户提供的 CGI 脚本 -
httpd_user_ra_content_t用于可附加的资源(供 Web 服务器使用) -
httpd_user_rw_content_t用于读/写资源(供 Web 服务器使用)
这些资源可以由最终用户设置,提供对 ~/public_html/ 目录中每个资源如何被 Web 服务器(及 Web 应用)处理的更精细控制。
还有更多……
一些支持 SELinux 的发行版有一个名为 restorecond 的守护进程,可以在文件创建/检测时自动设置文件的上下文,而无需在策略中进行文件转换。这样可以自动将 ~/public_html/ 标记为 httpd_user_content_t。
另见
- 有关每个用户 Web 目录的更多信息,可以参考
httpd.apache.org/docs/2.4/howto/public_html.html
分配 Web 内容类型
对于标准 Web 服务器配置(没有 SELinux),Web 服务器的资源访问权限完全依赖于文件的所有权(以及应用于它的访问掩码)。使用 SELinux 后,资源可以根据其功能意义进行更具体的标记。
Web 应用程序包含应为只读内容和应为读写内容的资源,但也有针对 .htaccess 文件等资源的特定类型。在这个示例中,我们将查看各种 Web 服务器内容类型,并将它们应用于正确的资源。
如何操作……
执行以下步骤,将特定的 Web 内容类型分配给正确的资源:
-
通过请求 SELinux 显示所有具有
httpdcontent属性的类型,来看一下可用于 Web 服务器的内容类型:~$ seinfo –ahttpdcontent –x httpdcontent httpd_sys_content_t httpd_user_ra_content_t httpd_user_rw_content_t httpd_nagios_content_t … -
查询现有策略中的已知上下文分配(因为这些可以为我们提供哪些内容仍然缺失的线索):
~$ semanage fcontext –l | grep httpd_nagios -
现在,将正确的上下文分配给那些尚未正确标记的资源。这里使用的路径是 Nagios 安装的示例:
~# semanage fcontext –a –t httpd_nagios_content_t /var/www/html/nagios(/.*)? ~# semanage fcontext –a –t httpd_nagios_script_exec_t /usr/local/lib/nagios/cgi-bin/.* ~# restorecon –R /var/www/html/nagios /usr/local/lib/nagios
它是如何工作的
Web 服务器策略支持用于 Web 应用程序的功能性内容类型。这些类型用于以下内容类型:
-
Web 应用程序的只读内容
-
Web 应用程序的可写内容(对于可写内容与仅可追加内容(如日志文件)之间的区分)
-
可执行脚本(用于 CGI 脚本和类似内容)
这个优势不在于只读与读写的区分,而在于这种支持是基于每个应用程序的,且具有特定于单个应用程序的类型。在此示例中,我们查看了 Nagios 监控应用程序的内容。
这使得管理员可以向特定的应用程序或用户提供对这些资源的访问权限。即使 /var/www/html/ 中的所有内容都可能归 Apache Linux 用户所有,我们仍然可以向用户(和应用程序)授予对特定于应用程序的资源的访问权限,而无需授予这些用户或应用程序对所有 Apache 资源的完全权限。
对于只读内容,有常规的 Web 应用程序内容类型(httpd_nagios_content_t)和特殊的 .htaccess 内容类型(httpd_nagios_htaccess_t)。这种区分主要是因为对常规内容的访问权限更广泛(并且根据一些 SELinux 布尔值,这也可能成为可写内容),而 .htaccess 内容则保持只读。
为了查询可用的 web 服务器内容,我们使用了 httpdcontent 属性。此属性分配给所有内容,允许管理员创建管理所有 web 内容的策略。httpdcontent 属性赋给了所有这些类型,但也有名为 httpd_rw_content、httpd_ra_content、httpd_htaccess_type 和 httpd_script_exec_type 的属性,用于允许操作这些特定资源。
还有更多…
我们以 Nagios 作为示例 web 应用,它有一组与 web 应用相关的资源。许多其他 web 应用或具有 web 内容的应用已经在策略上被识别。
在所有已知策略默认加载的 Linux 发行版上,通过 seinfo 命令可以看到此概述,如我们前面的示例所示。如果不是这种情况,我们可以通过搜索 SELinux 策略来找出哪些模块调用了 apache_content_template——该接口会自动生成正确的 web 应用内容类型:
~$ grep apache_content_template ${POLICY_LOCATION}/policy/modules/*/*.te
当不同类型的资源变得比有用更麻烦时,可以要求 SELinux 策略将所有这些不同类型视为一个通用的 web 内容类型,处理完成即可。这是通过 httpd_unified 布尔值来支持的。当启用此布尔值时,web 服务器策略将把所有不同的 web 服务器资源类型视为一个统一类型。如果 httpd_enable_cgi 和 httpd_builtin_scripting 布尔值也被启用,那么 web 服务器域就拥有执行该内容的权限。
不用多说,统一 web 服务器资源上下文可能使管理变得更简单;但它也会增加 web 服务器域对各种 web 资源的权限,从而可能降低安全性。
使用不同的 web 服务器端口
默认情况下,web 服务器监听已知的 web 服务器端口(如端口80和443)。通常,管理员可能希望让 web 服务器监听非默认端口。SELinux 策略可能会拒绝这种做法,因为 web 服务器在其他不相关端口上监听并不是标准行为。
在本教程中,我们将告诉 SELinux,一个非默认端口仍应视为 web 服务器端口。
如何操作…
为了将标签分配给不同的端口,请执行以下步骤:
-
若要查看所有匹配
http_port_t的端口,请使用semanage port -l:~# semanage port -l | grep -w http_port_t http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000 -
查询 SELinux 策略以查看特定端口分配了哪个端口类型。例如,对于端口
8881,可以使用以下命令:~$ seinfo --portcon=8881 -
如果端口被标识为
unreserved_port_t,则我们可以将其标记为http_port_t:~# semanage port -a -t http_port_t -p tcp 8881 -
然而,如果端口已分配了特定类型,则需要更新 SELinux 策略,使 web 服务器允许在该特定类型的端口上监听。例如,对于端口
9090(websm_port_t),请执行以下步骤:-
首先,找到允许绑定到
websm_port_t的接口:~$ sefindif websm_port_t.*bind -
创建一个自定义 SELinux 策略(
myhttpd),其内容如下:corenet_sendrecv_websm_server_packets(httpd_t) corenet_tcp_bind_websm_port(httpd_t) -
加载策略以允许 Web 服务器绑定到指定的端口类型。
-
-
最后,编辑 Web 服务器配置文件以监听正确的端口:
Listen *:8881
它是如何工作的...
SELinux 使用标签管理所有资源,包括端口。在本示例中,我们关注的是允许 Web 服务器绑定的 TCP 端口类型。
使用seinfo,我们可以查看端口是否与已知声明匹配。默认情况下,值为 1024 或更高的端口会标记为 unreserved_port_t,而 511 或更低的端口则标记为 reserved_port_t,介于两者之间的端口标记为 hi_reserved_port_t。这些是默认设置,某些端口可能会为特定端口声明更具体的类型。
如果端口尚未分配特定类型,我们可以使用semanage port手动分配。这足以让 Web 服务器绑定到该端口(与文件或目录不同,端口不需要重新标签操作,因为 SELinux 子系统会立即完成这项工作)。
如果端口已分配特定类型,则无法通过其他策略或管理员覆盖。当发生这种情况时,SELinux 策略需要增强,以允许 Web 服务器绑定到该特定类型。
在示例中,我们搜索了允许 Web 服务器绑定到端口的接口,发现 corenet_tcp_bind_websm_port 是可以使用的接口。然而,我们还添加了另一个接口—这是由于 SELinux 中网络控制配置的方式,这可能在某些系统上是必要的,也可能不需要。额外的接口是 corenet_sendrecv_websm_server_packets。此接口用于允许 Web 服务器发送或接收标记为 websm_server_packet_t 的数据包。数据包标记使得应用程序特定的通信流治理成为可能,并通过 SELinux 域感知扩展了 Linux 操作系统的常规防火墙功能(主要集中在网络流量管理上)。
如果需要对数据包进行标记,可以通过在本地系统上使用iptables对数据包进行标记,如以下命令所示:
~# iptables –t mangle –A INPUT –p tcp --dport 9090 -j SECMARK --selctx system_u:object_r:websm_server_packet_t
如果系统没有基于 iptables 的标记(称为 SECMARK 标记),则不需要接口。
还有更多...
最近的 SELinux 用户空间工具提供了另一个命令,用于查询 SELinux 策略,叫做sepolicy。使用sepolicy搜索端口声明的方法如下:
~$ sepolicy network --port 8080
8080: tcp unreserved_port_t 1024-65535
8080: udp unreserved_port_t 1024-65535
8080: tcp http_cache_port_t 8080
此外,在 SELinux 策略规则中,我们会注意到网络通信通常会启用第三个接口。在我们的示例中,第三个接口将被称为corenet_tcp_sendrecv_websm_port。此访问向量将使域能够在websm_port_t TCP 套接字上发送和接收消息。然而,在最近的策略中,出于对 SECMARK 标记的支持,已禁用对该访问向量的支持。
另见
- SECMARK 标签在第九章中进行了探讨,将 SELinux 与 DAC 对齐。
使用自定义内容类型
接下来是为尚未与策略关联的 Web 应用程序创建我们自己的内容类型。我们将以DokuWiki(可以访问 www.dokuwiki.org)为例。
准备工作
通过 Linux 发行版的包管理器或通过从主站点下载发布版本手动安装 DokuWiki。在此示例中,我们假设 DokuWiki 安装在 /srv/web/dokuwiki/ 目录下。
如何操作...
要使用自定义 Web 内容类型,请按照以下步骤操作:
-
创建一个名为
mydokuwiki.te的策略文件,内容如下:apache_content_template(dokuwiki) -
添加一个名为
mydokuwiki.fc的文件上下文定义文件,内容如下:/srv/web/dokuwiki/lib/plugins(/.*)? gen_context(system_u:object_r:httpd_dokuwiki_rw_content_t,s0) /srv/web/dokuwiki/conf(/.*)? gen_context(system_u:object_r:httpd_dokuwiki_rw_content_t,s0) /srv/web/dokuwiki/data(/.*)? gen_context(system_u:object_r:httpd_dokuwiki_rw_content_t,s0) /srv/web/dokuwiki/data/\.htaccess -- gen_context(system_u:object_r:httpd_dokuwiki_htaccess_t,s0) /srv/web/dokuwiki(/.*)? gen_context(system_u:object_r:httpd_dokuwiki_content_t,s0) -
构建并加载策略,然后使用以下命令重新标记所有 DokuWiki 文件:
~# semodule -i mydokuwiki.pp ~# restorecon -RvF /srv/web/dokuwiki
它是如何工作的...
所有与在 SELinux 中创建 Web 应用程序内容相关的魔法都由 apache_content_template 接口处理。使用 seshowif,可以显示所有底层的 SELinux 策略规则,如下所示:
-
创建了各种 SELinux 类型,如
httpd_dokuwiki_content_t等,并为其分配了适当的属性(例如httpdcontent属性)。 -
创建了一个 SELinux 布尔值,它允许管理员启用或禁用 Web 应用程序写入公共文件(标记为
public_content_rw_t)。这是一个用于多个服务共享资源的 SELinux 类型(例如 FTP 服务器、Web 服务器等)。 -
必要的权限授予 Web 服务器域,以便访问和处理新定义的类型,并启用 Web 应用程序的 CGI 域。对于我们的 DokuWiki 示例,通常不需要此操作,因为所有内容都由 Web 服务器本身解析并执行的 PHP 代码处理。
然后,我们根据 DokuWiki 的最佳实践标记了所有 DokuWiki 文件。某些管理员可能希望将 conf/ 子目录标记为不可写资源,并仅在配置过程中(临时)启用此设置。虽然这是一个有效的方法,但使用 Linux DAC 文件访问控制可能足以实现相同的结果。
还有更多内容...
使用 apache_content_template 接口是创建 Web 内容类型的简单方法,但它的缺点是它是一个全有或全无的方法,并且该模块现在高度依赖于 Web 服务器模块(apache)。
经验丰富的用户可能希望选择性地创建内容并为其分配正确的属性,允许 Web 服务器域与资源进行交互,同时仍然对类型和资源保持细粒度的控制。
我们将把这个任务留给你来完成,看看如何实现它。
创建一个自定义 CGI 域
有时,可能不需要创建一整套类型。考虑一下触发的 CGI 脚本,但不需要特定的内容类型集。可以将脚本标记为httpd_sys_script_exec_t(如果它是系统的 CGI 脚本)或httpd_user_script_exec_t(如果它是用户自定义的 CGI 脚本),这样结果脚本将在httpd_sys_script_t或httpd_user_script_t域中运行。
但是,如果这些域没有足够的权限(或者权限过多),则最好创建一个自定义的 CGI 域。
如何操作…
创建自定义 CGI 域时,可以使用以下方法:
-
创建一个自定义的 SELinux 策略模块(
mycgiscript.te),内容如下:policy_module(mycgiscript, 0.1) type cgiscript_t; type cgiscript_exec_t; domain_type(cgiscript_t) domain_entry_file(cgiscript_t, cgiscript_exec_t) apache_cgi_domain(cgiscript_t, cgiscript_exec_t) -
创建适当的文件上下文文件(
mycgiscript.fc),并将可执行文件标记为cgiscript_exec_t:/path/to/script --gen_context(system_u:object_r:cgiscript_exec_t,s0) -
构建并加载模块。
-
重新标记可执行文件并进行测试:
~# restorecon /path/to/script -
由于
cgiscript_t域在权限上较为初级,脚本很可能无法正常工作——然而,不要将 SELinux 设置为宽容模式。审计日志会显示被拒绝的访问尝试。与其使用audit2allow自动授予所有权限,不如使用sefindif功能找到合适的接口。将正确的接口添加到模块中,然后重试,直到脚本正常工作。
它是如何工作的…
策略模块定义了一个域类型(cgiscript_t)和一个可执行类型(cgiscript_exec_t)。通过domain_type接口,cgiscript_t被标记为一个域(并且为处理这个新域创建了适当的 SELinux 规则)。通过domain_entry_type,SELinux 策略被更新,标记cgiscript_exec_t为可以用于过渡到cgiscript_t域的类型。
然后,我们调用apache_cgi_domain,允许 Web 服务器域(httpd_t)执行标记为cgiscript_exec_t的资源,并使结果进程在cgiscript_t域中运行。
然而,最初的策略模块非常初级,权限不足。更新策略需要通过反复试验。例如,假设脚本调用了一个二进制文件;审计日志可能会显示如下内容:
type=AVC msg=audit(1363205612.277:476924): avc: denied { execute } for pid=6855 comm="cgiscript.pl" name="perl" dev=sda3 ino=4325828 scontext=system_u:system_r:cgiscript_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=file
为了找出哪种策略接口允许这样做,我们可以再次使用sefindif:
~$ sefindif exec.*bin_t'
interface(`corecmd_exec_bin',`
can_exec($1, bin_t)
开发自定义策略仍然是一个试错过程,但这是唯一的方法,确保只授予域必要的权限。一些策略开发人员建议开启宽容模式,并查看审计日志中的所有拒绝信息。使用这种方法的问题是,这些拒绝可能无法导致正确的 SELinux 策略规则。
例如,脚本可能需要调用另一个可执行文件(并进行域过渡)。在宽容模式下,过渡不会发生,看起来主域(cgiscript_t)需要所有目标命令所需的权限——即使实际上只需要一个适当的域过渡。
通过专注于强制模式,我们可以逐步增加策略,同时保持最小特权原则,只允许实际需要的权限。
设置 mod_selinux
在接下来的几个教程中,我们使用一个名为mod_selinux的 Apache 模块,使 Apache 能够感知 SELinux 并支持可配置的转换。换句话说,Apache 运行的上下文不再是一个静态定义的上下文,而是可以根据管理员的需求进行更改。
在本教程中,我们将从源代码安装mod_selinux,因为许多 Linux 发行版默认并未提供它,尽管它是 Web 服务器非常强大的一个扩展(这也是为什么mod_selinux的支持常被称为 Apache/SELinux Plus 的原因)。
如何操作……
您可以通过以下步骤设置mod_selinux:
-
确保安装了 Apache 开发头文件(在 Red Hat 或 Fedora 系统中为
httpd-devel)。 -
使用
apxs构建并安装mod_selinux共享库以供 Apache 使用:~# apxs -c -i mod_selinux.c注意
如果构建时出现关于
client_ip的错误,可能是因为此问题。如果是这种情况,编辑错误中显示的行号处的mod_selinux.c文件,并使用remote_ip替换client_ip,然后可以再次运行apxs命令。 -
构建并安装
mod_selinuxSELinux 策略模块,其文件也是下载源代码的一部分:~$ cp mod_selinux.te ${DEVROOT}/local ~$ cp mod_selinux.if ${DEVROOT}/local ~$ cd ${DEVROOT}/local && make mod_selinux.pp ~# semodule -i mod_selinux.pp -
编辑 Web 服务器配置文件(
httpd.conf),并添加适当的LoadModule行:LoadModule selinux_module modules/mod_selinux.so -
重启 Web 服务器。其日志文件应显示 SELinux 策略支持已加载:
[Fri Apr 18 13:11:23 2014] [notice] SELinux policy enabled; httpd running as context unconfined_u:system_r:httpd_t:s0-s0:c0.c1023
它是如何工作的……
mod_selinux.c文件包含 Apache 模块代码,可以使用apxs—Apache 扩展工具进行构建。此工具将执行以下任务:
-
使用正确的编译器参数调用编译器,构建一个可以在运行时由 Apache Web 服务器加载的动态共享对象
-
将生成的模块安装到适当的 Apache
modules/目录中
本教程中提到的构建失败可能取决于所使用的 Apache 版本,其中一个变量的名称不同(client_ip而非remote_ip)。
接下来,我们像设置其他 SELinux 策略模块一样,复制并部署了mod_selinux的 SELinux 策略。
最后,Web 服务器被更新以启用mod_selinux Apache 模块。随着mod_selinux共享库的就位,Apache 现在准备好做出与 SELinux 相关的决策。
如果mod_selinux支持需要分发到多个系统,则只需要分发mod_selinux.so(现在已安装在 Web 服务器modules/目录中,例如/usr/lib64/httpd/modules/)和mod_selinux.pp文件(SELinux 策略模块)。
另见
- 关于
mod_selinux的详细介绍可以在code.google.com/p/sepgsql/wiki/Apache_SELinux_plus找到。
使用有限权限启动 Apache
在上一章节中,我们操作了 /etc/rc.d/init.d/httpd init 脚本,使用 runcon 使 web 服务器以有限权限运行。但借助 mod_selinux,这一点可以变得可配置。
如何操作……
为了使用有限的安全许可启动 Apache,请按照以下步骤操作:
-
编辑 Apache web 服务器配置文件(
httpd.conf)并添加以下代码:<IfModule mod_selinux.c> selinuxServerDomain *:s0-s0:c0.c10 </IfModule> -
撤销在上一章节中对服务脚本所做的更改。
-
重新启动 web 服务器并通过执行以下命令确认它正在以
s0-s0:c0.c10清除权限运行:~# /etc/rc.d/init.d/httpd restart ~# ps -efZ | grep httpd system_u:system_r:httpd_t:s0-s0:c0.c10 root 2838 1 0 13:14 ? 00:00:00 /usr/sbin/httpd system_u:system_r:httpd_t:s0-s0:c0.c10 apache 2840 2838 0 13:14 ? 00:00:00 /usr/sbin/httpd
它是如何工作的……
如前所述,通过 mod_selinux,Apache web 服务器变得支持 SELinux,这意味着它可以根据配置设置和 SELinux 策略规则改变自己的行为并与 SELinux 子系统进行交互。
使用 selinuxServerDomain 配置指令,mod_selinux 会动态更改当前上下文到新上下文,这称为动态域过渡或动态范围过渡(如果类型发生变化则称为域过渡,若敏感度级别或安全许可发生变化则称为范围过渡)。只有应用程序是 SELinux 感知的情况下,这才是可能的。
现在,这样的过渡仍然通过 SELinux 策略进行控制。例如,Apache web 服务器可以过渡的范围必须由 Apache web 服务器最初拥有的范围主导(在我们的示例中为 s0-s0:c0.c1024)。
注意
mod_selinux 模块不支持上下文查询,因此无法使用人类可读的敏感度(如我们之前所见,通过 mcstransd 控制)。
还有更多……
可以定义不同的类型,允许整个 web 服务器运行在自定义域中。为了实现这一点,httpd_t 域必须具备动态过渡到目标类型的权限(即 process 类中的 dyntransition 权限)。然后,selinuxServerDomain 调用可能如下所示:
selinuxServerDomain myhttpd_t:s0-s0:c0.c10
当然,为了访问启动时 httpd_t 域已经可以访问的资源,还需要更多的权限,但 dyntransition 权限特定于希望支持动态域过渡的 SELinux 感知应用程序,而不是在进程执行时进行过渡。
将 HTTP 用户映射到上下文
应用程序通常在静态上下文中运行,这会限制应用程序所需的所有权限。即便是服务(守护进程)通常也会在服务生命周期内保持在自己的上下文中。但是,通过mod_selinux,可以根据已认证的用户将 web 服务器处理程序(负责处理特定请求的进程或线程)的上下文切换到另一个上下文。这允许管理员根据用户授予应用程序某些权限。当一个权限较低的用户利用 web 应用中的漏洞时,web 应用本身的权限限制可能会阻止成功的攻击。
如何操作……
通过以下步骤,我们将把 web 用户映射到一个特定的 SELinux 上下文:
-
创建一个映射文件,其中列出了用户及其目标上下文。例如,将用户 John 的请求处理为敏感度
s0:c0,c2,将用户 Cindy 的请求处理为敏感度s0:c0.c5,c7,所有未认证用户的请求处理为anon_webapp_t:s0,其他已认证用户的请求处理为user_webapp_t:s0:c0:john *:s0:c0,c2 cindy *:s0:c0.c5,c7 __anonymous__ anon_webapp_t:s0 * user_webapp_t:s0:c0 -
将此文件保存到 web 服务器可读取的位置,例如
/etc/httpd/conf/mod_selinux.map。 -
编辑 web 服务器配置文件,并添加以下行:
selinuxDomainMap /etc/httpd/conf/mod_selinux.map -
重启 web 服务器。
它是如何工作的……
mod_selinux模块能够识别已认证用户的值,并且根据映射文件中的设置,它可以将请求处理程序的上下文切换到较低的敏感度范围(如前两个示例中所示),或者完全切换到不同的域。
不过,这里有一个重要的限制。请求处理程序可以过渡的目标上下文必须受主类型(httpd_t)的约束。这意味着授予目标上下文的权限必须是授予httpd_t权限的子集。这是通过typebounds语句来实现的,如下所示:
typebounds httpd_t anon_webapp_t;
这是因为 web 服务器处理程序通常是线程(或轻量级进程),而不是进程。线程共享许多资源,通常是以 SELinux 无法管理的方式共享的。因此,如果某个线程获得的权限超过 web 服务器的权限,那么整个 web 服务器的安全状态可能会受到威胁。此外,不同上下文之间的信息流动将变得困难,甚至几乎不可能进行管理。
使用源地址映射来决定上下文
mod_selinux Apache 模块可以访问除用户名之外的其他信息(对于已认证用户)。它可以访问环境变量(这些变量通过SetEnvIf指令在 Apache web 配置中使用),从而允许在应用程序中对 SELinux 上下文的处理更加灵活。
在本例中,我们将根据客户端的远程 IP 地址来更改请求处理程序的上下文。
如何操作……
除了 Web 用户,我们还可以使用源地址信息来决定上下文。可以通过完成以下步骤来实现:
-
首先,我们在 Web 服务器配置(
httpd.conf)中基于远程 IP 地址定义TARGETDOMAIN环境变量:SetEnvIf Remote_Addr "10\.0\.[0-9]+\.[0-9]+$" TARGETDOMAIN=user_webapp_t:s0 SetEnvIf Remote_Addr "10\.1\.[0-9]+\.[0-9]+$" TARGETDOMAIN=anon_webapp_t:s0 SetEnvIf TARGETDOMAIN ^$ TARGETDOMAIN=*:s0 -
然后,在同一 Web 服务器配置中,我们调用
selinuxDomainEnv指令,这将使处理程序的上下文过渡到TARGETDOMAIN中的值:selinuxDomainEnv TARGETDOMAIN -
重启 Web 服务器以使更改生效。
它是如何工作的...
在第一步中,我们使用了 Apache 的SetEnvIf指令(通过mod_setenvif提供)来检查客户端的远程 IP 地址(Remote_Addr)。如果它与给定的表达式匹配,我们就将TARGETDOMAIN变量设置为给定的上下文。在我们的示例中,我们为每个匹配使用了不同的类型,但也可以仅更改安全权限。最后,我们进行了一个检查,验证TARGETDOMAIN变量是否已被设置。如果没有,则分配一个默认值(*:s0)。
接下来,我们调用了selinuxDomainEnv指令,它会使 Web 服务器的上下文切换到TARGETDOMAIN变量中提供的域。
还有更多...
示例使用了Remote_Addr,但许多其他与请求相关的方面也可以使用:
-
使用
Remote_Host,可以查询客户端的主机名并用来做出决策。 -
使用
Server_Addr,可以使用 Web 服务器本身的地址(请求接收的服务器地址)。这在多宿主系统中非常有用,在这种系统中,Web 服务器绑定到所有可用的 IP 地址。 -
使用
Request_Method,可以使用请求的类型(例如GET或POST)。 -
使用
Request_Protocol,可以使用 HTTP 协议的名称和版本(例如HTTP/1.0或HTTP/1.1)。 -
使用
Request_URI,可以使用请求的 URL 来调整上下文或权限。
另见
- 欲了解有关 Apache 的
mod_setenvif支持的更多信息,请参考该模块文档:httpd.apache.org/docs/2.4/mod/mod_setenvif.html
使用mod_selinux分离虚拟主机
Apache 的一个优势是,它可以根据用于连接服务器的名称来区分站点,而不仅仅是 IP 地址、端口和 URL。这被称为虚拟主机支持,是多租户网站和 Web 应用托管的非常流行的方法。
例如,运行在单一 IP 地址上的 Web 服务器仍然可以托管多个客户的网站,例如www.companyX.com和www.companyY.com。通过mod_selinux,我们可以根据关联的虚拟主机更改 Web 服务器请求处理程序的上下文或安全权限。
如何操作...
以下方法通过mod_selinux区分虚拟主机的限制:
-
决定各个租户的上下文。在前一章中,我们为公司 X 使用了
s0:c100,为公司 Y 使用了s0:c101。 -
在每个虚拟主机中,设置正确的权限。例如,对于公司 X,设置权限如下:
<VirtualHost *:443> ServerName www.companyX.com selinuxDomainVal *:s0-s0:c100 </VirtualHost> -
重启 web 服务器以使更改生效。
它是如何工作的...
与针对整个 web 服务器的 selinuxServerDomain 指令不同,selinuxDomainVal 指令单独设置处理程序(虚拟主机)的上下文。正如我们在上一章中讨论的那样,为多租户系统使用多个类别是一种灵活的方式,可以处理租户之间的信息隔离。
然而,与上一章的一个重要区别是,mod_selinux 模块不使用 mcstransd。以下设置将失败:
selinuxDomainVal *:CompanyXClearance
这样的设置会导致 Apache 返回以下错误信息:
[error] (22)Invalid argument: SELinux: setcon_raw("unconfined_u:system_r:httpd_t:CompanyXClearance") failed
因此,我们需要使用标准的敏感性表示法。
另见
- 你可以在
httpd.apache.org/docs/2.4/vhosts/上找到有关 Apache 虚拟主机支持的更多信息。
第四章:创建桌面应用程序策略
在本章中,我们将涵盖以下主题:
-
研究应用程序的逻辑设计
-
创建骨架策略
-
设置上下文定义
-
定义应用程序角色接口
-
测试和增强策略
-
忽略不需要的权限
-
创建应用程序资源接口
-
添加条件策略规则
-
添加构建时策略决策
引言
到目前为止,我们已经修改并增强了现有的策略,并通过可用的管理命令与 SELinux 子系统进行交互。但是,为了真正从 SELinux 提供的保护措施中受益,我们需要为那些本来会运行时拥有过多权限或根本无法运行的应用程序创建我们自己的策略。
桌面应用程序是一个很好的例子。终端用户域(unconfined_t用于支持未受限域的策略,user_t、staff_t等用于其他策略)已分配了许多权限,以允许通用应用程序在保持在用户域中的同时执行。
这有一个巨大的缺点:桌面应用程序中的漏洞或故障应用程序可能会对用户的文件和资源造成混乱,甚至可能暴露信息给恶意用户。如果所有的终端用户应用程序都在同一个域中运行,那么我们就无法谈论最小权限环境。毕竟,这个单一的用户域必须拥有各种应用程序所需的所有权限的总和。
在本章中,我们将为微软 Skype™创建一个桌面应用程序策略。Skype 是一款流行的文本消息、语音和视频通话应用程序,也可以在 Linux 系统上运行,但由于它是专有的,因此无法审查其代码以确定它可能做什么。限制此应用程序确保它只能执行我们允许它执行的操作。
研究应用程序的逻辑设计
在开始策略开发之前,我们需要查看应用程序的行为和逻辑设计。当我们开始将其建模到 SELinux 策略中时,我们将了解应用程序及其交互。
如何做……
为了为应用程序准备 SELinux 策略,我们首先需要了解应用程序的行为:
-
查看应用程序将要交互的文件和目录,并记录下应用程序所需的权限。尝试根据应用程序的功能来结构化访问。
-
弄清楚应用程序所需的网络资源,应用程序绑定(监听)的端口(如果有的话),以及它需要连接到哪些端口。
-
如果应用程序需要与其他 SELinux 域(进程)进行交互,这种交互是什么样的(或它的目的是什么)?
-
应用程序是否需要特定的硬件访问或其他内核提供的资源?
它是如何工作的……
收集至少以下四种资源的信息(文件、网络、应用程序和硬件/内核)有助于我们开始编写一个骨架策略文件。最终,我们可能会有这些资源的示意图,如下所示:

让我们看一下这个示例是如何实现的。
文件和目录
Skype™ 应用程序需要访问三种主要的文件。
第一个是其特定于用户的配置,存储在 ~/.Skype/ 中。这将包含应用程序的所有设置,包括联系人列表、聊天记录等。在 SELinux 中,特定于用户的配置条目标记为 *_home_t,并标记为用户的主目录内容,允许最终用户仍然管理这些资源。
第二种是通用的用户文件,应用程序需要访问这些文件来上传或下载文件。这可以是任何最终用户文件,尽管一些发行版为此创建了特定的支持(例如通过 ~/Downloads/ 位置)。
第三种包括应用程序可以访问的 Unix 系统的一般资源。这种访问是应用程序加载必要库所必需的。在应用程序策略开发过程中,这通常没有被提及,因为它是默认提供给所有应用程序的访问。
网络资源
应用程序需要通过其消息、语音和视频聊天功能与网络资源进行交互。
一般来说,我们知道应用程序需要连接到中央 Skype™ 基础设施,以支持所有集中管理的服务,如身份验证、目录搜索等。这个连接将通过 TCP 进行。
除了中央基础设施外,应用程序还将连接到其他用户的 Skype™ 实例,以进行直接通信。这个连接将通过 TCP 和 UDP(因为 UDP 更常用于视频和语音)进行。
进程
由于该应用程序是图形应用程序,我们知道它需要与工作站上运行的 X11 服务器交互。正如我们在本章的方案中将看到的那样,这自动需要为应用程序分配一组类型和权限。
除此之外,没有与其他领域的特定交互。
硬件和内核资源
最后,在硬件层面,应用程序将需要访问视频和声音设备(分别用于摄像头和语音通话功能)。
在出现错误时,应用程序还需要使用用户终端(以便显示错误信息)。
创建骨架策略
现在逻辑设置已经到位,我们可以起草一个骨架策略。这个策略将是我们遇到的逻辑设置到 SELinux 策略规则的转换。
整个策略都写在一个 myskype.te 文件中。这一系列方案的最终结果也可以通过本书的下载包作为参考。
如何操作…
我们从一个基本的骨架开始,之后可以进行增强。这个骨架的开发如下:
-
我们从各种类型的声明开始。从设计中,我们可以推导出四种类型:
-
skype_t用作主进程域 -
skype_exec_t用作 Skype 可执行文件的标签 -
skype_home_t用于skype_t域的用户配置文件和目录 -
skype_tmpfs_t用于共享内存和 X11 交互
推导这四种类型的代码如下:
policy_module(myskype, 0.1) attribute_role skype_roles; type skype_t; type skype_exec_t; userdom_user_application_domain(skype_t, skype_exec_t) role skype_roles types skype_t; type skype_home_t; userdom_user_home_content(skype_home_t) type skype_tmpfs_t; userdom_user_tmpfs_file(skype_tmpfs_t); -
-
接下来,我们编写访问各种类型的策略规则,从管理
~/.Skype/上的权限开始:# Allow manage rights on ~/.Skype manage_dirs_pattern(skype_t, skype_home_t, skype_home_t) manage_files_pattern(skype_t, skype_home_t, skype_home_t) userdom_user_home_dir_filetrans(skype_t, skype_home_t, dir, ".Skype") -
我们启用
X11访问和共享内存。这是一组常见的权限,需要分配给启用了 X11 的应用程序:# Shared memory (also needed for X11) manage_files_pattern(skype_t, skype_tmpfs_t, skype_tmpfs_t) manage_lnk_files_pattern(skype_t, skype_tmpfs_t, skype_tmpfs_t) manage_fifo_files_pattern(skype_t, skype_tmpfs_t, skype_tmpfs_t) manage_sock_files_pattern(skype_t, skype_tmpfs_t, skype_tmpfs_t) fs_tmpfs_filetrans(skype_t, skype_tmpfs_t, { file lnk_file fifo_file sock_file }) # Application is an X11 application xserver_user_x_domain_template(skype, skype_t, skype_tmpfs_t) -
接下来,我们编写网络访问规则,如下所示:
# Network access corenet_tcp_bind_generic_node(skype_t) corenet_udp_bind_generic_node(skype_t) # Central skype services corenet_tcp_connect_http_port(skype_t) corenet_tcp_connect_all_unreserved_ports(skype_t) # Listen for incoming communication corenet_tcp_bind_all_unreserved_ports(skype_t) corenet_udp_bind_all_unreserved_ports(skype_t) -
最后,我们处理设备访问:
# Voice and video calls dev_read_sound(skype_t) dev_read_video_dev(skype_t) dev_write_sound(skype_t) dev_write_video_dev(skype_t) # Terminal (tty) output userdom_use_user_terminals(skype_t)
它是如何工作的……
在骨架策略中,我们从我们知道必需的 SELinux 策略规则开始。如果我们对一个或多个规则有些不确定,完全可以先将其注释掉,然后在后续的测试和增强策略阶段启用这些规则。
骨架从类型声明开始,重点是应用程序的资源。然后,我们为这些资源增强应用程序域的适当权限。资源访问之后,我们查看 X11 权限,最后处理应用程序的网络交互。
类型声明
任何策略的第一部分都是类型和角色的声明。我们首先创建一个名为 skype_roles 的角色属性,并将其授予 skype_t SELinux 域。然后将此角色属性分配给允许调用该应用程序的用户。接下来,我们列出策略将提供的各种 SELinux 类型,并为这些类型赋予特定的含义。例如,skype_t 和 skype_exec_t 类型通过 userdom_user_application_domain 模板赋予适当的含义。该模板如下所示:
interface(`userdom_user_application_domain',`
application_domain($1, $2)
ubac_constrained($1)
')
application_domain 模板,从 userdom_user_application_domain 内部调用,具有以下定义:
interface(`application_domain',`
application_type($1)
application_executable_file($2)
domain_entry_file($1, $2)
')
这导致 skype_t 域被标记为应用程序类型(真正的域),而 skype_exec_t 是一个可执行文件,可以作为进入 skype_t 域的入口点。最后,skype_t 被标记为 ubac_constrained,这用于基于用户的访问控制(UBAC),其中资源访问不仅通过类型及其访问向量来控制,还通过 SELinux 用户来控制。换句话说,如果 SELinux 用户 userX_u 以某种方式能够访问另一个 SELinux 用户(userY_u)的进程,那么 skype_t 域将无法访问,因为 UBAC 限制将发挥作用,阻止两者之间的任何交互。
所有userdom_user_*模板都将相关资源标记为 UBAC 受限,以及真实文件类型关联,因此userdom_user_tmpfs_file不仅将文件标记为tmpfs_t文件(用于共享内存的类型),还使其受到 UBAC 约束。
管理文件和目录
接下来,我们提供对文件和资源的访问权限。在示例中,我们仅限制对~/.Skype/的访问,并在它在用户主目录中创建时自动将其标记为skype_home_t(通过userdom_user_home_dir_filetrans),尽管我们确定需要管理用户内容文件。这是因为我们需要在这里做出策略设计决策 —— 我们是否希望应用程序完全访问所有用户资源,还是更愿意限制访问?反之,我们是否希望其他可以访问用户内容的应用程序访问 Skype™用户(配置)数据?
如果我们不希望应用程序访问任何用户内容,那么我们不需要添加任何规则:策略将仅允许通过用户主目录进行搜索权限(以定位~/.Skype/),并拒绝其他所有内容。
如果我们希望授予应用程序对用户内容的访问权限,可以添加以下调用:
userdom_manage_user_home_content_files(skype_t)
userdom_manage_user_home_content_dirs(skype_t)
这将授予skype_t域对用户文件和目录的完全管理权限。
在 Gentoo Linux 策略中,已提供额外的类型,以提供对用户文件更精细的访问控制。这些类型映射到由 Free Desktop 社区提供的XDG 基本目录规范(XDGBDS),包括xdg_downloads_home_t类型。最终用户可以将文件和目录标记为xdg_downloads_home_t,允许应用程序有选择地访问用户文件,而不会冒险使这些应用程序能够访问该用户更私密的文件。
在 Gentoo 中,这意味着可以将以下调用添加到策略中:
xdg_manage_downloads_home(skype_t)
X11 和共享内存
当应用程序需要与 X11 服务器交互(作为客户端应用程序),大部分交互通过共享内存完成。在 Linux 中,共享内存可以被解释为 tmpfs 挂载上的文件(例如/dev/shm/),尽管还可以通过其他共享内存结构进行。
在 SELinux 中,策略开发人员希望确保此共享内存专门为该域标记。因此,他们创建了一个以_tmpfs_t作为后缀的类型。在我们的示例中,这是skype_tmpfs_t。当然,我们需要授予对共享内存的管理权限(对将使用的所有类别)给skype_t域。在 X11 交互的情况下,这些是文件、符号链接、FIFO 和套接字。
除了管理权限之外,我们还包括文件转换:每当skype_t在tmpfs_t标记的位置创建文件、符号链接、FIFO 或套接字时,这些资源应自动标记为skype_tmpfs_t。这通过fs_tmpfs_filetrans调用完成。
最后,我们使用xserver_user_x_domain_template,它包含了 X11 客户端和 X11 服务器之间交互所需的所有 SELinux 权限。此模板使用一个前缀参数(第一个参数,我们提供为skype),该参数将用于创建一个名为skype_input_xevent_t的 X11 资源类型。类似于我们在 Web 服务器中看到的(使用了apache_content_template调用),此模板提供了一种简单的方法,自动构建其他类型并启用 X11 支持。
除了前缀外,域本身(skype_t)和用于共享内存的标签(skype_tmpfs_t)也会传递,因为这些对于 X11 服务器的支持是必需的。
网络访问
对于网络访问,我们首先为skype_t域提供 TCP 套接字和其 IP 地址(由node_t表示)的绑定权限。
接下来,我们允许skype_t域连接到中央 Skype™ 服务,该服务通过 HTTPS 端口443(身份验证)以及多个看似随机的高 TCP 端口(网络节点)提供服务。HTTP 目标端口被识别为http_port_t类型,其他端口用于未保留端口。
最后,我们允许skype_t域监听传入的通信。默认情况下,这通过高 TCP 端口用于消息和状态信息,而语音和视频聊天则通过 UDP 进行。
识别所需类型的一个简单方法是查看netstat输出,因为它向我们显示了进程监听的端口、协议族(TCP 或 UDP)以及它连接的端口:
~$ netstat -naput | grep skype
tcp 0 0 0.0.0.0:34431 0.0.0.0:* LISTEN 8160/skype
tcp 0 0 10.221.44.241:40650 111.221.77.150:40008 ESTABLISHED 8160/skype
…
udp 0 0 0.0.0.0:34302 0.0.0.0:* 8160/skype
还有更多内容……
对音频和视频设备的访问是简单的,但在设计过程中,很可能已经识别出了更多的访问(因为我们的只是一个示例)。随着策略的不断开发,编写骨架策略将变得更加简单。
学习更多关于策略的一个很好的来源是查找与我们正在编写策略的应用程序相似的现有策略,或者是具有与我们应用程序相似功能的应用程序。例如,对于 Skype™,我们可以查看 Gift(一个点对点文件共享应用程序)的策略,它是一个面向终端用户的图形应用程序,具有点对点通信流,支持上传和下载文件。
毕竟,SELinux 策略是域预期行为的书面记录。如果另一个应用程序具有相同或相似的行为,那么它的策略也会非常相似。
在前面的示例中,我们根据功能需求将权限进行了分组。然而,正如参考策略中提到的,SELinux 策略文件的编码风格使用了不同的分组方式,因此如果该策略要上报到上游,确保遵循这种编码风格。
另见
- 有关 XDGBDS 的更多信息,请参见
standards.freedesktop.org/basedir-spec/latest/
设置上下文定义
政策开发的下一步是使用正确的文件上下文标记其资源。这将正确标记应用程序的文件,确保 SELinux 政策做出正确的决策。
如何操作……
要更新文件上下文定义,请按照以下步骤操作:
-
创建
myskype.fc文件,并在其中添加~/.Skype/的定义:HOME_DIR/\.Skype(/.*)? gen_context(system_u:object_r:skype_home_t,s0) -
接下来,添加
skype二进制文件的定义:/opt/skype/skype -- gen_context(system_u:object_r:skype_exec_t,s0) /opt/bin/skype -- gen_context(system_u:object_r:skype_exec_t,s0) /usr/bin/skype -- gen_context(system_u:object_r:skype_exec_t,s0)
它是如何工作的……
二进制文件的定义是标准的基于路径的上下文声明。然而,用户主目录的定义是特别的。
从示例中可以看到,路径以HOME_DIR开始。这是 SELinux 库使用的特殊变量,它会自动映射到所有 Linux 用户的主目录。与其直接创建一个/home/[^/]*/\.Skype(/.*)?上下文,这样会有设计问题,因为其他位置的主目录(例如/export/home/user/)将无法匹配,SELinux 库会检查所有真实用户的主目录(用户 ID 从500开始,尽管这是可以配置的),并且对于每个不同的主目录根目录(/home/是最常用的),它会生成适当的上下文。
该操作的结果存储在/etc/selinux/mcs/contexts/files/目录中的file_contexts.homedirs文件中,并在策略构建期间自动创建(通过genhomedircon命令)。
除了HOME_DIR之外,其他支持的变量包括HOME_ROOT(表示主目录根路径)和ROLE(表示与用户关联的第一个角色)。
定义应用程序角色接口
最后,在测试政策之前,我们需要创建一个角色接口,并将其分配给将用于测试(和运行)应用程序的用户域。如果我们没有创建角色接口并将其分配给用户域,则用户域要么根本没有权限执行该应用程序,要么应用程序将以用户上下文而不是新定义的skype_t域运行。如果用户域没有被解除限制,那么应用程序很可能会失败。
如何操作……
角色接口是政策的门户。它们确保域和 SELinux 用户能够与应用程序交互,并确保特定应用程序的权限集是一致的。
我们在.if文件中创建这样的接口,然后将这个接口分配给一个用户域,以便测试这个接口:
-
创建
myskype.if文件,并在其中加入以下接口:interface(`skype_role',` gen_require(` type skype_t, skype_exec_t, skype_tmp_t, skype_home_t; ') # Allow the skype_t domain for the user role roleattribute $1 skype_roles; # Allow domain transition for user domain to skype_t domtrans_pattern($2, skype_exec_t, skype_t) # Interact with skype process ps_process_pattern($2, skype_t) allow $2 skype_t:process { ptrace signal_perms }; # Manage skype file resources manage_dirs_pattern($2, skype_home_t, skype_home_t) manage_files_pattern($2, skype_home_t, skype_home_t) manage_lnk_files_pattern($2, skype_home_t, skype_home_t) # Allow user to relabel the resources if needed relabel_dirs_pattern($2, skype_home_t, skype_home_t) relabel_files_pattern($2, skype_home_t, skype_home_t) relabel_lnk_files_pattern($2, skype_home_t, skype_home_t) ') -
为用户域创建一个策略(例如,
myunprivuser.te),通过将skype_role调用分配给用户域,从而授予普通用户访问skype_t域的权限:policy_module(myunprivuser, 1.0) gen_require(` type user_t; role user_r; ') optional_policy(` skype_role(user_r, user_t) ') -
构建这两个政策并加载它们。然后,重新标记
skype二进制文件(以及可能已存在的~/.Skype/位置):~# restorecon /opt/skype/bin/skype /opt/bin/skype /usr/bin/skype ~# restorecon -RF /home/user/.Skype
它是如何工作的…
虽然我们已经定义了认为需要的所有规则(在下一个示例中,策略将扩展直到真正起作用),但我们尚未定义允许用户域实际执行skype_exec_t二进制文件并使进程在skype_t域中运行的规则。
为了实现这一点,我们需要确保在用户执行skype_exec_t时,发生域过渡到skype_t域。这是通过domtrans_pattern调用来处理的。但在允许域过渡之前,我们首先需要允许用户角色使用skype_t域,这通过roleattribute调用来完成。
直到现在,我们主要关注的是类型强制规则(也就是说,根据目标资源的标签授予 SELinux 域特权)。为了允许某些用户运行应用程序,应用程序域本身需要被授予给用户角色。这是通过 SELinux 的基于角色的访问控制(RBAC)模型来支持的。该 RBAC 模型确保某个域(在我们的示例中是skype_t)只能由我们为其配置的角色使用(在我们的示例中是user_r)。其他角色,如 DBA 角色(dbadm_r),可能不需要运行 Skype™应用程序,因此不会被授予访问skype_t域的权限。
注意
不授予一个域并不一定会阻止应用程序在用户域内执行。为了实现这一点,我们需要确保其他角色无法执行该可执行文件类型。我们不会使用userdom_user_application_domain为skype_t和skype_exec_t类型分配一个通用的可执行属性,而是会使用类似以下的方式:
application_type(skype_t)
files_type(skype_exec_t)
allow skype_t skype_exec_t:file { entrypoint mmap_file_perms ioctl lock };
ubac_constrained(skype_t)
由于用户域需要能够执行 Skype™,还需要管理skype_home_t文件(例如,在需要手动干预~/.Skype/或进行备份时),因此我们授予它管理权限以及重新标记权限。当例如恢复备份时,重新标记权限是必需的。
对于用户域,我们将调用刚刚创建的skype_role接口。在这个示例中,我们使用了optional_policy语句。这允许即使某些调用无法解决或不被支持时,策略模块仍然能够被加载。
假设我们需要卸载myskype模块。如果没有optional_policy语句,则myunprivuser模块也需要被卸载,即使这个策略模块可能包含其他对用户域正常工作至关重要的规则(在示例中,我们只调用了skype_role接口,但过一段时间后,模块可能会调用许多其他接口)。如果我们不卸载该模块并且没有使用optional_policy语句,SELinux 将会警告管理员模块之间未解决的依赖关系。
使用optional_policy语句,SELinux 工具知道该调用可能无法解决,在这种情况下,整个块(optional_policy块中的所有内容)将被忽略,同时模块仍然保持加载状态。
还有更多...
在配方开始时,我们提到过非受限用户域能够在没有域转换的情况下执行应用程序。这是可以预期的,因为非受限域的整个理念就是它们没有被限制。
通常,将域转换从非受限域到受限域被认为是不好的做法。只有在非常特殊的情况下,从非受限域到受限域的域转换才有意义(例如,当目标域用于限制潜在易受攻击的应用程序时,例如沙箱域)。
从安全的角度来看,立即限制用户并在(受限)用户域和应用程序域之间使用适当的域转换更有意义。
测试和增强策略
策略准备就绪并加载后,开始从用户的角度测试应用程序,同时密切关注审计日志(以查找拒绝)和应用程序输出。
测试应用程序是策略开发中的一个重要阶段,也是最耗时的任务。在测试过程中,将尝试应用程序的几个功能特性,结果的权限(就 SELinux 而言)需要添加到策略中。
在之前的配方中,如创建骨架策略,我们根据其他策略和常识启用了一个权限集。然而,这些权限还没有被验证和测试过。在这个配方中,我们将断言这些权限确实是需要的,因为我们不想创建一个与其关联权限过多的策略。
如何操作…
测试策略是一个重复的任务。每次尝试意味着需要丢弃启动之前的 AVC 拒绝(因为我们不想包含与测试无关的权限),然后对应用程序进行测试,并记录结果。根据应用程序的表现,新的策略规则将被添加到策略中:
-
写下当前时间戳或在审计日志中创建一个参考点(例如,通过重新加载 SELinux 策略),以便我们知道从审计日志的哪个点开始查看审计事件:
~# semodule -R -
作为最终用户,从终端窗口启动应用程序并观察发生的情况。
-
写下显示的错误(如果有):
~$ skype skype: error while loading shared libraries: cannot restore segment prot after reloc: Permission denied -
查看在审计日志中显示的拒绝信息:
~# ausearch -m avc -ts recent -
对于每个第一次拒绝或与之前显示的错误相关的拒绝,尝试使用适当的调用来增强策略并再次尝试。
它是如何工作的…
在这个阶段,我们正在一步步地增强策略。一些策略开发者喜欢在宽松模式下运行应用程序(要么是将整个系统运行在宽松模式下,要么是将某个特定领域标记为宽松领域),记录所有执行的访问(通过 AVC 拒绝)并根据这些信息增强策略。虽然这可能会使策略更快速地生效,但这些开发者也面临着将过多权限添加到策略中的风险,而这种情况后来很难被挑战或更改。
相反,我们让 SELinux 阻止访问,并观察应用程序如何反应。根据应用程序的错误日志或应用程序的行为以及通过日志看到的 AVC 拒绝,我们可以很好地了解真正需要哪些权限。
例如,在示例中出现的错误的同时,还发生了以下拒绝:
type=AVC msg=audit(1398930752.113:608): avc: denied { execmod } for pid=8943 comm="skype" path="/opt/bin/skype" dev="dm-2" ino=801 scontext=user_u:user_r:skype_t:s0 tcontext=user_u:user_r:skype_exec_t:s0 tclass=file
我们需要集中注意力在第一次出现的拒绝上,而不是所有显示的拒绝。很可能在第一次拒绝之后显示的拒绝是由错误处理程序引发的,不管是由应用程序还是系统引发的,如果为该领域授予适当权限,这些拒绝本来是不会被触发的。尝试授予这些权限会导致权限集定义过于宽泛。
上述拒绝将导致策略中添加以下内容:
# Error 'cannot restore segment prot after reloc'
allow skype_t skype_exec_t:file execmod;
忽略我们不需要的权限
经过反复测试,我们将获得一个可行的策略,即使审计日志中可能仍然会显示拒绝。为了避免让管理员产生警觉,我们可能希望禁用对这些特定拒绝的审计(同时,当然,确保关键访问向量仍由审计守护进程记录)。
如何操作…
为了禁用不影响应用程序行为的某些拒绝的日志记录,触发拒绝,然后按以下步骤注册 dontaudit 语句:
-
对于审计日志中显示的每个拒绝,我们需要找到相应的
dontaudit规则集。考虑以下实例:type=AVC msg=audit(1398936489.877:2464): avc: denied { search } for pid=8241 comm="skype" name="modules" dev="dm-0" ino=1322041 scontext=user_u:user_r:skype_t:s0 tcontext=user_u:object_r:user_home_t:s0 tclass=dir -
在 SELinux 策略中查找与此问题相关的
dontaudit语句:~$ sefindif dontaudit.*user_home_t.*search interface(`userdom_dontaudit_search_user_home_content',` dontaudit $1 user_home_t:dir search_dir_perms; -
将接口调用添加到策略中,重建策略,然后重新加载。重复此过程,直到所有外观上的拒绝不再显示。
它是如何工作的…
应用程序执行的许多操作可以视为外观性操作——虽然在示例中,应用程序确实通过用户文件执行搜索,但它们对于应用程序的正常运行并不是必需的。例如,它可能会搜索整个目录,直到找到自己的文件,而这些文件它是有权限访问的。
通过为这些操作添加 dontaudit 语句,我们确保审计日志保持干净。
如果出现问题,管理员仍然可以禁用策略中的dontaudit语句,显示所有 SELinux 触发的拒绝(即使是那些明确标记为dontaudit的拒绝):
~# semodule -DB
要重新启用dontaudit语句,重新构建并重新加载策略:
~# semodule -B
在某些情况下,可能没有与dontaudit相关的接口可用。在这种情况下,可以创建一个新的接口(作为 SELinux 策略模块的一部分),并在其中定义dontaudit规则。例如,对于一个设置为忽略获取mozilla_home_t内容属性的dontaudit规则,我们将创建一个名为mymozilla的策略模块,并在其中声明mozilla_dontaudit_getattr_home接口。
创建应用程序资源接口
我们的应用程序策略几乎准备好部署了。然而,它目前主要面向最终用户,并且除了通过skype_role接口之外,没有任何与skype_t域(或skype模块管理的其他资源)交互的方式。
在这个过程中,我们将为读取skype_home_t添加一个接口。
如何执行……
除了我们在定义应用程序角色接口过程中创建的skype_role接口之外,我们还需要创建额外的资源接口,以便其他域能够轻松地与新创建的策略交互:
-
打开
myskype.if文件,并添加以下内容:interface(`skype_read_home',` gen_require(` type skype_home_t; ') userdom_search_user_home_dirs($1) allow $1 skype_home_t:dir list_dir_perms; allow $1 skype_home_t:file read_file_perms; allow $1 skype_home_t:lnk_file read_lnk_file_perms; ')
它是如何工作的……
这个过程本身很简单——对于每次与skype模块管理的资源的交互,我们需要创建一个可以被其他模块调用的接口。
每个接口应是完整的。例如,为了读取skype_home_t内容,域首先需要能够搜索用户的主目录(user_home_dir_t,这与user_home_t不同,前者是主目录的类型,而后者是其内容的类型);因此,需要调用userdom_search_user_home_dirs。
然后,将必要的权限分配给域。由于我们在接口名称中没有提供任何类标识符,接口将授予对所有与skype_home_t类型相关的(重要的)类的读取访问权限。
如果我们只想授予对文件的读取访问权限(而不包括directory类),则接口将命名为skype_read_home_files。
添加条件策略规则
我们可以通过条件进一步微调我们的策略。之前识别的一些访问向量在所有情况下可能并不需要,因此通过 SELinux 布尔值将其设为可选并可配置是有意义的。
以下是两个可配置策略候选的访问向量:
-
访问视频和音频设备(为了减少恶意软件或漏洞使应用程序能够访问摄像头或音频设备并监视无防备用户的风险)
-
访问所有用户内容(而不仅仅是
skype_home_t内容)
如何执行……
以下步骤集通过引入布尔值使策略对管理员更加灵活。这些布尔值修改策略的行为,并被添加到策略中。
-
在
myskype.te内,创建两个布尔值的定义。通常在类型声明之前完成此操作。gen_tunable(skype_use_audio, false) gen_tunable(skype_use_video, false) gen_tunable(skype_manage_user_content, false) -
在策略内部,将我们希望通过布尔值触发的语句进行分组:
tunable_policy(`skype_use_audio',` dev_read_sound(skype_t) dev_write_sound(skype_t) ') tunable_policy(`skype_use_video',` dev_read_video_dev(skype_t) dev_write_video_dev(skype_t) ') tunable_policy(`skype_manage_user_content',` userdom_manage_user_home_content_dirs(skype_t) userdom_manage_user_home_content_files(skype_t) ')
它是如何工作的…
gen_tunable 声明将生成管理员可以在系统上切换的布尔值。每个声明的第一个参数是要创建的布尔值的名称,第二个参数设置布尔值的默认值。
一旦定义了布尔值,tunable_policy 语句允许将需要配置的语句调用进行分组。
即使布尔值被禁用,也有可能启用某些规则。例如,对于 skype_manage_user_content,可以使用以下代码:
tunable_policy(`skype_manage_user_content',`
# boolean enabled
userdom_manage_user_home_content_dirs(skype_t)
userdom_manage_user_home_content_files(skype_t)
',`
# boolean disabled
userdom_dontaudit_manage_user_home_content_dirs(skype_t)
userdom_dontaudit_read_user_home_content_files(skype_t)
…
')
布尔值也可以组合,如以下代码所示:
tunable_policy(`use_nfs_home_dirs && skype_manage_user_content',` … ')
在这种情况下,策略组规则仅在两个布尔值都启用时才会生效。
也可以仅在布尔值未设置时启用规则,如以下代码行所示:
tunable_policy(`!use_nfs_home_dirs',` … ')
还有更多...
可调策略是 SELinux 的一个强大扩展。然而,这也有一些注意事项:
-
将 SELinux 布尔值的描述提供给管理员并不简单。描述是通过策略中的注释来定义的,但这对于自定义模块不起作用——需要完整的策略构建才能生成包含所有描述的
policy.xml文件。 -
在
tunable_policy组内无法分配属性。相反,策略开发人员需要使与属性相关的权限可配置(如果可能的话),或者根本不分配该属性。 -
在
tunable_policy组内无法使用命名文件转换。通常情况下,这没什么大不了的——有一些情况下,命名文件转换会依赖于布尔值,但这些情况确实会发生。 -
在
tunable_policy组内无法使用optional_policy语句。相反,首先用optional_policy语句包裹tunable_policy调用。如果一个布尔值会触发多个需要使用optional_policy块的策略调用,则可能需要创建多个块。
然而,目前正在努力从 SELinux 子系统中去除这些缺点。
添加构建时策略决策
我们可能想要关注的最后一个增强功能是构建时策略决策。与 SELinux 布尔值不同,这些是基于构建参数启用(或禁用)的策略块。我们过去已经遇到过其中一些,比如 enable_mcs、enable_mls 以及分发选择参数,如 distro_gentoo 或 distro_redhat。
在这个方案中,我们将启用xdg_manage_downloads_home调用,但仅在为 Gentoo 系统构建策略时启用。
如何执行…
构建时决策通过ifdef语句添加到策略中,正如下一个步骤所展示的:
-
打开
myskype.te并添加以下代码块:ifdef(`distro_gentoo',` xdg_manage_downloads_home(skype_t) ') -
重新构建策略。在 Gentoo 系统中,我们可以通过
sesearch确认现在已授予访问权限,而其他发行版可能根本不知道xdg_downloads_home_t类型:~$ sesearch -s skype_t -t xdg_downloads_home_t -A
它是如何工作的…
参考策略构建系统自动定义了一些可以被ifdef宏使用的参数。构建系统使用/usr/share/selinux/mcs/include/或/usr/share/selinux/devel/include/中的build.conf文件内的定义来生成这些参数。
例如,build.conf中的发行版参数设置如下:
DISTRO ?= gentoo
在Makefile中,这会转换为一个M4PARAM设置:
ifneq ($(DISTRO),)
M4PARAM += -D distro_$(DISTRO)
endif
通过这些M4参数,我们可以使用ifdef语句来查询这些参数的存在并做出构建时的决策。
还有更多内容...
我们可以添加自己的一组参数。为此,我们在调用make命令(用于构建策略模块)之前设置M4PARAM环境变量。
例如,为了支持debug语句,我们可以在策略中设置如下:
ifdef(`debug',` … ')
在策略构建过程中,我们可以按如下方式启用这些语句:
~$ export M4PARAM="-D debug"
~$ make mypolicy.pp
第五章:创建服务器策略
本章将介绍以下几个要点:
-
理解服务
-
明智地选择资源类型
-
基于使用案例区分策略
-
创建资源访问接口
-
创建 exec、run 和 transition 接口
-
创建流连接接口
-
创建管理接口
简介
桌面应用程序策略保护用户免受应用程序内部漏洞或应用程序所带来的不良行为的影响。然而,在服务器上,影响可能要大得多。服务器策略用于保护整个系统免受不良行为、用户的滥用访问或应用程序中的漏洞被利用。
服务通常有较长的生命周期。与桌面应用程序不同,桌面应用通常与用户的日常工作周期一起启动和关闭,而服务往往是持续运行的,24/7 全天候服务。这不仅提供了更大的时间窗口来尝试和利用这些服务,而且在管理员可能不会积极监视的情况下,服务通常在后台运行。
理解服务
设计服务器策略的第一个方面是了解当前的服务。每个服务都有自己的内部架构,理解各种进程和资源如何相互作用至关重要。
只有当我们完全理解内部架构时,才能创建一个正常运行的策略。否则,我们的策略可能会过于宽泛(过多的访问权限)或过于限制。与通常从最终用户角度容易测试的应用程序不同,服务常常有一些更难测试(甚至难以考虑)的活动。
如何做……
就像桌面应用程序一样,理解应用程序的行为对创建良好的 SELinux 策略至关重要。可以通过执行以下步骤进行行为研究和分析:
-
通过查找在线架构图或架构文档,全面研究该服务。
-
尝试在沙盒环境中探索该服务。
-
跟随一些与该服务相关的教程,包括管理任务和最终用户任务。
-
从结构上记录服务应如何使用。
如何运作……
理解服务意味着获得一定的服务管理经验。如果不了解某种数据库技术的工作原理,就几乎不可能为该技术创建服务器策略。
在线研究
大多数服务都有良好的在线架构文档。通过使用互联网搜索引擎,我们可以轻松找到特定服务的架构信息。
在开发服务策略时,最好将类型和域名命名为与所使用的功能服务相似。例如,在 Postfix 架构中,pickup、cleanup、smtpd、qmgr等功能服务是 Postfix 管理员必须处理的基本服务。在 SELinux 策略中,我们应尽量使域名标签保持一致(例如,qmgr服务的域标签为postfix_qmgr_t,maildrop队列的标签为postfix_spool_maildrop_t,以此类推)。
沙箱环境
能够在沙箱环境中玩转服务让我们可以看到其中的相互作用。它还允许我们通过在线教程或管理指南来了解服务的使用。
现在有许多技术可以用来玩转技术。虚拟化允许用户在隔离的环境中运行完整的系统,并促成了虚拟设备的创建。
虚拟设备是可以轻松安装在虚拟化环境中的虚拟镜像。然而,纯粹的虚拟化仍然要求用户安装操作系统、安装服务并进行配置,才能真正开始使用;虚拟设备提供了开箱即用的预配置系统,托管一个或多个服务。
除了虚拟化之外,容器也开始发挥重要作用。与虚拟化不同,运行在容器中的软件与其他软件隔离,但仍然是操作系统本身的一部分。
结构文档
在详细阅读应用程序的架构并可能玩转过软件后,我们可能需要进一步记录服务的架构,以推导出正确的 SELinux 类型和资源,以及与服务相关的接口和角色。
为了不忘记任何重要的内容,可以使用FAMOUS缩写来记录服务的逻辑架构:
-
数据流:它告诉我们哪些外部资源以批处理的方式为服务提供输入,以及服务与哪些外部资源进行交互。
-
管理:它向我们提供有关如何管理服务的信息(命令行界面、用户界面或其他应用程序)。
-
监控:它向我们提供关于使用的日志文件或支持的命令的信息,以验证服务的状态。
-
操作:它记录了所有进程的日常运行行为(以及使用 CRUD 方法——创建、读取、更新、删除的流程)。这通常是在在线研究阶段找到的架构信息。
-
用户与权限:它记录了服务中如何定义和管理用户。还记录了使用了哪些身份验证或授权后端,服务中不同角色的行为如何,等等。
-
与安全相关的功能:这些功能告诉我们有关安全相关的功能,如基于应用程序的访问控制、防火墙要求(在我们的案例中,这对策略网络规则非常重要)等。
有了这些信息,我们可以清楚地概览服务的行为。例如,PostgreSQL 数据库服务的高级视图如下所示:

这样的图示帮助我们后续识别类型,包括涉及的过程和资源。任何与第三方服务提供的服务的交互也会展示出来,因为这些交互会导致需要分配给其他过程的权限(即 SELinux 策略中的接口)。
如果不理解服务的具体工作方式,记录服务的工作流程并不容易。由于服务的复杂性,最好请服务的专家或开发人员一起指导我们理解服务。这些开发人员和工程师以后可以用来挑战正在编写的 SELinux 策略。
另见
开放源代码虚拟设备提供者的非详尽列表如下:
-
Artica (
www.artica.fr) 用于代理、邮件和 NAS 设备。 -
Turnkey Linux (
www.turnkeylinux.org/) 提供超过一百个现成可用的解决方案。 -
Vagrant (
www.vagrantup.com/) 是一个虚拟系统管理平台,拥有大量的 Vagrant box,提供许多免费软件服务的虚拟设备设置。 -
Docker (
www.docker.io/) 不是一个真正的虚拟化设置,而是一种基于容器的方法。从 Docker Index (index.docker.io/),可以自由下载许多容器。
许多商业技术也提供开发虚拟机来进行部署。像 VMware® 这样的虚拟化技术提供商有解决方案交换社区,其中各种技术的虚拟镜像可以自由获取。
明智地选择资源类型
服务与资源进行交互,我们为资源分配的标签被分配给这些资源的细粒度访问控制所使用。最终用户文件(对于系统中拥有 Linux 账户的用户)被标记为 user_home_t,这对于大多数用途来说已足够。然而,当我们处理服务时,资源标签的选择决定了其他应用程序是否以及如何访问这些资源,而且比我们目前为最终用户文件使用的标签更为细粒度。
在 SELinux 策略中有一些关于资源类型选择的最佳实践,我们现在将进行讨论。
如何进行……
服务资源类型需要谨慎选择。它们的命名隐含了资源的功能用途,这已经推动了策略在某种结构中的发展。可以通过完成以下步骤来开发这些类型及其相关权限:
-
查找将在其特定域内运行的进程,并创建域类型。对于每个域,查找该域的入口文件并创建
_exec_t类型。将该类型标记为初始化守护进程类型(当服务通过服务脚本启动时)或 D-Bus 守护进程(当服务通过 D-Bus 启动时)。例如,对于 BIND 服务:type named_t; type named_exec_t; init_daemon_domain(named_t, named_exec_t) -
查找应用程序使用的所有逻辑资源集合。这些通常是特定于服务架构的文件(例如,数据库服务的数据库文件),但不应仅限于文件。
-
为这些资源创建特定的类型。例如,对于 Qemu 虚拟客户机镜像:
type qemu_image_t; files_type(qemu_image_t) -
授予域对这些资源的适当访问权限。例如,
qemu进程(以qemu_t身份运行)将需要对镜像进行管理权限:manage_files_pattern(qemu_t, qemu_image_t, qemu_image_t) -
浏览基础设施资源(PID 文件、日志文件和配置文件)并相应地标记它们。例如,对于
named变量,运行时数据将命名如下:type named_var_run_t; files_pid_file(named_var_run_t) -
授予域对这些资源的适当访问权限,并尽可能启用适当的文件过渡:
allow named_t named_var_run_t:file manage_files_perms; allow named_t named_var_run_t:sock_file manage_sock_file_perms; files_pid_filetrans(named_t, named_var_run_t, { file sock_file });
它是如何工作的…
应用策略始终提供一组通用的权限。它从适当的域定义开始(展示策略将如何结构化),然后是资源访问模式。资源可以是功能性的(特定于正在调查的应用程序)或更多基础设施性的(如日志文件和配置文件)。
域定义
服务域用于识别具有相似功能范围的长期运行进程。例如,BIND named 进程(定义为 named_t)或 Apache httpd 进程(都以 httpd_t 身份运行)。
这些服务域通常由 init 脚本启动,这导致使用 init_daemon_domain 接口。如果服务通过 D-Bus 启动,则使用的接口是 dbus_system_domain。当然,可以使用多个接口:例如,PPP 守护进程支持 init 脚本和 D-Bus。
如果一个服务守护进程由另一个守护进程启动,则只需将进程域标记为域类型,并将可执行类型标记为入口点:
type postfix_bounce_t;
type postfix_bounce_exec_t;
domain_type(postfix_bounce_t)
domain_entry_file(postfix_bounce_t, postfix_bounce_exec_t)
在这种情况下,我们需要为父域(在我们的案例中是 postfix_master_t)提供执行(postfix_bounce_exec_t)和过渡(到 postfix_bounce_t)的权限:
domtrans_pattern(postfix_master_t, postfix_bounce_exec_t, postfix_bounce_t)
逻辑资源
逻辑资源是特定于应用程序功能设计的文件。例如,像 Qemu 这样的虚拟化层将为镜像文件提供逻辑资源(qemu_image_t)。有关 Web 服务器的逻辑资源已经在前面的章节中讨论过(例如,httpd_sys_content_t用于标准系统只读 Web 内容)。
这些资源被声明为常规文件资源,并为各个域授予适当的权限。稍后在文档中,当总结qemu_t域的权限时,可以添加manage_files_pattern行,以允许qemu_t域管理镜像。
通过为每个逻辑资源创建单独的标签,我们可以为其他可能需要与这些资源交互的进程创建接口,而不必授予这些应用程序过多的权限。
以备份应用程序为例,比如 Amanda。实际的备份数据本身(amanda_data_t)应该仅能由 Amanda 应用程序访问。系统上的其他服务管理员不应访问这些文件——备份可能包含敏感信息,因此只有备份工具本身才能访问这些数据。即使是备份管理员,尽管需要管理备份基础设施,也可能不需要直接访问这些数据。
基础设施资源
基础设施资源是经常为应用程序设置的文件类型。
日志文件通过logging_log_file接口标记,并通常以_log_t后缀结尾,例如amanda_log_t。通过将其标记为日志文件,分配给所有日志文件操作(如logging_read_all_logs)的域会自动获得这些权限。通常,会设置文件转换规则,使得在/var/log/中创建的文件自动获得正确的类型。这是通过logging_log_filetrans接口实现的:
type amanda_log_t;
logging_log_file(amanda_log_t)
# Directories created by amanda_t domain in /var/log (var_log_t) get the amanda_log_t type:
logging_log_filetrans(amanda_t, amanda_log_t, dir)
配置文件被标记为常规文件(通过files_type),并且以_conf_t或_etc_t结尾。一些策略开发人员喜欢使用_conf_t表示真实的配置文件,而使用_etc_t表示/etc/目录结构下的其他杂项文件,这些文件不是直接的配置文件。不过在大多数情况下,这仅仅是出于语义上的考虑,因为所有相关域在这两种类型上需要相同的权限。
临时文件通过files_tmp_file接口标记,并以_tmp_t后缀结尾。几乎总会设置文件转换规则,以确保临时文件被正确标记:
type amanda_tmp_t;
files_tmp_file(amanda_tmp_t)
# All files, directories and symbolic links created by amanda_t in a tmp_t location should get the amanda_tmp_t label:
files_tmp_filetrans(amanda_t, amanda_tmp_t, { dir lnk_file file })
PID 文件和其他通用的运行文件通常标记为以_var_run_t结尾,并通过files_pid_file接口标记为 PID 文件。与日志文件一样,通常也会设置文件转换规则:
type amanda_var_run_t;
files_pid_file(amanda_var_run_t)
# Files and sockets created in /var/run should become amanda_var_run_t:
files_pid_filetrans(amanda_t, amanda_var_run_t, { file sock_file })
其他没有给出逻辑资源名称的变量数据通常会以_var_lib_t结尾进行标注。这类文件被标记为常规文件(使用file_type),并可以通过files_var_lib_filetrans定义文件转换。
根据用例区分策略
随着服务的成熟,它们通常会获得更多的功能,但这些功能并不总是必要的。例如,能够根据配置可选连接到各种网络资源的守护进程,SELinux 策略不应允许其始终连接到各种网络资源。
为了管理这些功能,SELinux 策略开发者会包含布尔值,以便根据管理员的需求选择性地切换策略。
如何实现……
布尔值允许策略开发者创建仅在管理员选择使用时才参与访问控制的策略规则。特别是对于服务,这通常用于根据服务的用例有条件地允许权限,具体实现如下:
-
根据配置识别应该标记为可选的策略块。例如,这可能是一组允许 PostgreSQL 连接到其他 PostgreSQL 数据库的策略规则:
corenet_tcp_connect_postgresql_port(postgresql_t) corenet_sendrecv_postgresql_client_packets(postgresql_t) -
对于每个模块,创建一个合适的 SELinux 布尔值,管理员可以轻松识别它作为切换其特定用例的正确布尔值。例如,我们可以创建一个
postgresql_connect_db布尔值:## <desc> ## <p> ## Determine if the PostgreSQL daemons can connect to other databases. ## </p> ## </desc> gen_tunable(postgresql_connect_db, false) -
用
tunable_policy语句将需要切换的策略块围绕起来,针对选择的 SELinux 布尔值,如下所示:tunable_policy(`postgresql_connect_db',` corenet_tcp_connect_postgresql_port(postgresql_t) corenet_sendrecv_postgresql_client_packets(postgresql_t) ')
它是如何工作的……
虽然我们不应通过生成大量布尔值来过度调整策略,但将经常在漏洞利用中被滥用的功能进行隔离是一个良好的做法。
考虑一个数据库引擎。数据库可能具有允许它们连接到其他数据库的功能(例如,设置数据库链接或支持某种集群),但在许多情况下,这些功能是不需要的。如果一个数据库被攻破(例如通过 SQL 注入),最好确保该数据库不能访问其他数据库(以便攻击的数据库被有效隔离)。
在 PostgreSQL 设置中切换此行为的配置可能被命名为postgresql_connect_db(针对特定数据库的连接)或postgresql_connect_all_ports(针对任何目标连接),并按照之前的示例进行开发(该示例包括了内联注释文档,如果该策略旨在成为分发策略或参考策略项目的一部分,则会使用这些注释)。
访问网络上的其他资源是一个常见的功能,如果它不是应用程序的标准行为的一部分,应考虑将其设置为可选。
还有许多其他应该考虑的用例。以下是一个非详尽的列表:
-
可以选择性地执行系统脚本或用户提供脚本的应用程序应通过
_exec_scripts或_exec_user_scripts布尔值进行管理。 -
由于某些功能导致允许的域转换到更高特权的域或增加特权,通常通过
_use_*布尔值进行管理。例如,一个可选择支持 Java 的域可以具有_use_java布尔值。 -
对特定文件系统或设备的访问也通过
_use_*布尔值进行管理,例如_use_cifs(用于 SMB-CIFS 文件系统)或_use_nfs。 -
功能支持(例如 Nginx 对各种协议的支持)可以通过
_enable_*布尔值使其可选,例如nginx_enable_imap_server或nginx_enable_pop3_server。
创建资源访问接口
在定义了所有资源之后,我们现在需要确保其他域可以根据需要使用这些资源。正如我们所看到的,资源可以是功能性的(特定于某个服务)或更具基础设施性质的(如日志文件)。
资源访问是通过 SELinux 策略规则提供的,这些规则需要通过访问接口提供。这些接口随后被第三方 SELinux 策略模块使用,以记录并允许访问资源类型。如果没有访问接口,我们定义的资源类型将不容易被其他策略开发人员访问。
它是如何做到的…
要创建资源访问接口,在模块的.if文件中添加适当的接口定义。例如,要创建一组资源接口以访问 ClamAV 的配置文件,请按照以下步骤操作:
-
对于每个资源,创建一个所需特权的概述。对于文件类资源,这些通常是搜索、读取、写入和管理特权。对于日志文件,一些应用程序只需要追加权限(这确保它们不能修改现有数据,只能向其中添加数据)。
-
在模块的
.if文件中创建接口,并确保正确文档化,如下代码所示:########################################## ## <summary> ## Read clamav configuration files ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> # interface(`clamav_read_config',' gen_require(` type clamd_etc_t; ') files_search_etc($1) allow $1 clamd_etc_t:file read_file_perms; ') -
考虑创建一个
dontaudit接口,并将其分配给可能尝试执行此操作但不需要特权的 SELinux 域:######################################## ## <summary> ## Do not audit attempts to read the clamd configuration files ## </summary> ## <param name="domain"> ## <summary> ## Domain not to audit ## </summary> ## </param> # interface(`clamav_dontaudit_read_config',` gen_require(` type clamd_etc_t; ') dontaudit $1 clamd_etc_t:file read; ')
它是如何工作的…
资源访问接口是允许与通过 SELinux 模块管理的 SELinux 类型进行交互所必需的。构建环境没有默认生成的特权接口集,因此我们需要自己创建这些接口。
人们可能会倾向于仅创建那些已知在短期内会使用的资源接口。然而,建议为所有资源及每个资源单独创建合适的接口,并配备一组连贯的支持特权。这是因为我们无法预测其他人如何使用这些资源,而不创建适当的资源接口会迫使其他开发人员创建自己的my*模块来提供接口。
通过覆盖大多数资源访问模式,我们提供了一套很好的接口,其他开发人员可以使用这些接口,同时保持所有接口都绑定到单一模块中。
即使是与dontaudit相关的接口,对于 SELinux 策略的用户来说也将发挥重要作用。当策略开发者将策略改进提交到代码库时,他们通常不会使用dontaudit,除非他们百分之百确信这些改动只是隐藏了外观上的拒绝,因此可以忽略。结果,默认的 SELinux 系统部署将会在审计日志中记录相当多的拒绝信息,这些信息需要由系统管理员进行审查。
如果管理员认为这些拒绝信息不需要启用,那么他们将需要能够dontaudit这些信息。尽管管理员可以自己创建适当的接口,但如果dontaudit接口定义已经提供,将会更容易一些。
创建 exec、run 和 transition 接口
服务域通常有一些二进制文件,这些文件由用户域或通过其他服务或应用程序域执行。每次执行都需要适当调查,以确定是否需要进行域转换(即,需要为该执行环境创建一个特定域)或该命令是否可以在调用者域的权限内运行。
从接口的角度来看,这是通过_exec、_run和_domtrans接口提供的。
它是如何实现的…
与执行相关的接口允许其他策略模块定义与此应用程序的交互。这个交互可以是常规执行,也可以包含一个域转换,将应用程序域切换到新定义的域。执行接口集创建如下:
-
对于每次执行,其中应用程序本身需要在调用者域中运行(因此不需要进行域转换),创建一个
_exec接口,如下所示:####################################### ## <summary> ## Execute wm in the caller domain ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> # interface(`wm_exec',` gen_require(` type wm_exec_t; ') corecmd_search_bin($1) can_exec($1, wm_exec_t) ') -
对于每次执行,其中域与服务具有相同角色(通常是
system_r)且需要进行域转换的情况,创建一个_domtrans接口,如下所示:########################################## ## <summary> ## Execute vlock in the vlock domain ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed to transition ## </summary> ## </param> # interface(`vlock_domtrans',` gen_require(` type vlock_t, vlock_exec_t; ') corecmd_search_bin($1) domtrans_pattern($1, vlock_exec_t, vlock_t) ') -
对于每次执行,其中域可能没有标准访问权限到应用程序域,并且需要进行域转换的情况,创建一个
_run接口,如下所示:######################################### ## <summary> ## Execute vlock in the vlock domain and allow the specific role the vlock domain ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed to transition ## </summary> ## </param> ## <param name="role"> ## <summary> ## Role allowed to access the vlock domain ## </summary> ## </param> # interface(`vlock_run',` gen_require(` attribute_role vlock_roles; ') vlock_domtrans($1) roleattribute $2 vlock_roles; ')
它是如何工作的…
使用_exec、_run和_domtrans是策略开发中的标准接口模式。在桌面应用程序策略开发过程中,我们创建的_role接口不仅包括域转换和角色支持,还包括与用户域交互的桌面应用程序域相关的资源访问。
在 _run 接口中,提供的唯一权限集是转换到正确的域,并将该域分配到正确的角色(作为 SELinux 基于角色的访问控制的一部分)。通常的做法是,_run 接口的参数顺序是域在前,角色在后——这与 _role 接口不同,后者是先角色再域。
在 _domtrans 接口中,只启用了域转换。通常,_run 接口调用 _domtrans 接口,以便定义两个接口,并由调用者 SELinux 策略模块调用适合的接口。但与 _run 接口不同,_domtrans 接口不会扩展角色,通常由其他模块调用以进行服务域交互。
例如,procmail_t 域(用于 procmail 守护进程)可能会调用 clamscan 应用程序(ClamAV 安装的一部分),需要转换为 clamscan_t。它是通过 clamav_domtrans_clamscan 接口来实现的:
optional_policy(`
clamav_domtrans_clamscan(procmail_t)
')
最后,_exec 接口允许一个域在没有任何转换的情况下执行二进制文件。当一个二进制文件被标记为特定的可执行类型(而不是 bin_t 或 shell_exec_t)时,这个接口就变得必要,因为大多数域没有权限访问这个文件,更不用说执行它了。例如,Postfix 的 local 守护进程可能会调用 clamscan 可执行文件,但不需要转换,导致如下调用:
optional_policy(`
clamav_exec_clamscan(procmail_local_t)
')
另见
- 将新创建的接口分配给角色的过程已在 第六章,设置单独角色 中涵盖。
创建流连接接口
无论是通过特定的可执行类型还是通过通用的 bin_t 标记的命令,仍然保持在调用域中的执行可能需要向调用域分配额外的权限。这些额外的权限可能包括读取配置文件或通过 Unix 域套接字或 TCP/UDP 套接字与主域进行交互。
在本教程中,我们将设置一个流连接接口(因为其他权限增强已经通过常规的资源访问接口或网络访问接口覆盖)。
如何实现…
与应用程序套接字的交互可以通过套接字文件或命名的 Unix 域套接字来完成。这是特定于应用程序的,因此可能需要提前查阅应用程序文档。
对于带有套接字文件的 Unix 域套接字
如果流连接是通过带有套接字文件的 Unix 域套接字进行的,则与应用程序套接字的交互可以通过完成以下步骤来进行:
-
在
.te文件中识别并注册适当的类型。套接字文件通常以_var_run_t后缀结尾,因为它们位于/var/run/中。 -
创建一个调用
stream_connect_pattern的流连接接口,格式如下:interface(`ldap_stream_connect',` gen_require(` type slapd_t, slapd_var_run_t; ') files_search_pids($1) stream_connect_pattern($1, slapd_var_run_t, slapd_var_run_t, slapd_t) ')
对于抽象的 Unix 域套接字
如果流连接是通过抽象的 Unix 域套接字(因此不涉及套接字文件),则创建一个仅提供connectto特权的流连接接口,如下所示:
interface(`init_stream_connect',`
gen_require(`
type init_t;
')
allow $1 init_t:unix_stream_connect connectto;
')
它是如何工作的……
守护进程通常提供与其交互的方法。许多服务支持基于 Unix 域套接字的客户端应用程序(通常在调用者域的特权下运行)与守护进程之间的通信。
在这种情况下,守护进程本身会创建一个套接字文件(通常位于/var/run/目录下),作为某种访问点(应用程序还可以使用抽象命名空间,这样就不再需要套接字文件),并且允许调用者域写入这个套接字,通过它连接到守护进程持有的 Unix 域套接字。特权集由stream_connect_pattern定义提供,并可以通过以下方式可视化表示:

这里最重要的特权是调用者域与守护进程域之间的connectto特权。如果是抽象的 Unix 域套接字,则根本不使用套接字文件,仅需要connectto特权。
这些特权随后会写入以下特定于域的接口,该接口调用stream_connect_pattern定义,并一次性提供适当的特权:
~$ seshowdef stream_connect_pattern
define(`stream_connect_pattern',`
allow $1 $2:dir search_dir_perms;
allow $1 $3:sock_file write_sock_file_perms;
allow $1 $4:unix_stream_socket connectto;
')
如果使用的是面向流连接的应用程序,并且它们的二进制文件没有被标记为bin_t,那么通常会看到_stream_connect接口调用与_exec接口调用一起出现。
创建管理接口
为了结束 SELinux 模块的开发,我们需要创建适当的基于角色的接口。通常,_role接口用于非特权用户角色,而_admin接口则用于提供完全管理服务所需的所有特权。
如何操作……
我们可以稍后分配给用户和角色来管理环境的管理接口,通过以下步骤创建:
-
为守护进程的
init脚本创建一个特定的init脚本类型。例如,对于virt.te中的virtd守护进程,以下策略规则会创建适当的init脚本类型:type virtd_initrc_exec_t; init_script_file(virtd_initrc_exec_t) -
确保通过
.fc文件正确标记这个init脚本:/etc/rc\.d/init\.d/libvirtd -- gen_context(system_u:object_r:virtd_initrc_exec_t,s0) -
从一个框架
_admin接口开始:########################################## ## <summary> ## All rules related to administer a virt environment ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> ## <param name="role"> ## <summary> ## Role allowed access ## </summary> ## </param> # interface(`virt_admin',` gen_require(` … ') -
识别管理员需要访问的所有资源。请记住,管理员可能需要直接修改通过服务相关命令管理的文件——不要剥夺管理员的这个权限。这里常用的模式是
admin_pattern。在接口中添加适当的权限(并且不要忘记更新开头的gen_require块)。考虑以下示例:files_search_tmp($1) admin_pattern($1, virt_tmp_t) -
查阅管理手册,了解管理员可能需要的与进程相关的其他操作。也许有某些信号可以允许发送到守护进程:
# Allow the admin to run strace or other tracing tools against the daemons allow $1 virtd_t:process { ptrace signal_perms }; # Allow admins to view all information related to the processes ps_process_pattern($1, virtd_t) -
允许管理员运行
init脚本:init_labeled_script_domtrans($1, virtd_initrc_exec_t) domain_system_change_exemption($1) role_transition $2 virtd_initrc_exec_t system_r; allow $2 system_r;
它是如何工作的…
_admin接口旨在包含使得一个(否则)无特权的用户能够管理服务所需的所有权限。本质上,这个无特权用户将在这个特定服务中变得有特权,仅获得管理该服务所需的权限,但不会得到更多权限。
我们首先为该服务定义一个特定的init脚本类型。默认情况下,init脚本被标记为initrc_exec_t,只有系统管理员被允许执行它们。由于我们不想赋予特定服务管理员执行任何init脚本的权限,因此我们创建了一个特定的脚本类型(_initrc_exec_t),然后通过_admin接口允许用户执行该特定脚本类型。
然而,后者不仅仅是创建执行权限(这通过init_labeled_script_domtrans调用完成)。执行脚本还意味着该脚本本身必须以system_r角色运行。如果我们不强制执行这一点,那么脚本将(尝试)以调用者域(例如virtadm_r)的角色运行,并失败,因为initrc_t域(用于init脚本的类型)不允许virtadm_r角色使用。
执行文件时的角色转换是通过role_transition指令完成的。在我们的示例中,我们配置了用户角色(如virtadm_r)在执行virtd_initrc_exec_t时转换为system_r角色:
role_transition $2 virtd_initrc_exec_t system_r;
我们还需要允许给定用户角色使用system_r角色,这可以通过allow $2 system_r调用来完成。但即便如此,这还不够。
SELinux 有一个约束,防止转换到system_r角色,因为system_r角色用于所有系统服务,因此是一个高度特权的角色。该约束被定义为只有特定域能够触发到system_r角色的转换。通过domain_system_change_exemption调用,我们将用户域标记为这些特定域之一。
除了与init脚本相关的权限外,大多数_admin接口还提供对模块提供的几乎所有资源的管理权限。为了简化策略开发,使用了admin_pattern调用。这个模式不仅提供了资源的管理权限(读取、写入、执行、删除等),还提供了重新标记权限,允许管理员将文件和目录重新标记为模块中使用的资源类型(或者反过来,将这些类型重新标记为管理员拥有重新标记权限的其他类型)。
通过这些重新标记权限,管理员可以使用restorecon命令对文件进行标签修复(如果在 SELinux 策略中正确定义),或者使用chcon命令专门设置标签。
另见
- 创建新管理角色的内容请见第六章,设置单独角色。
第六章:设置独立角色
在本章中,我们将介绍以下主题:
-
管理 SELinux 用户
-
将 Linux 用户映射到 SELinux 用户
-
使用 sudo 在指定角色中运行命令
-
使用 runcon 在指定角色中运行命令
-
切换角色
-
创建新角色
-
基于条目的初始角色
-
定义角色过渡
-
查看访问权限
介绍
角色提供了一种灵活且可管理的方式来授予多个用户适当的权限。与其将权限分配给单个用户,不如创建角色并将权限授予这些角色。然后,用户被授予该角色并继承与该角色相关联的权限。
在 SELinux 中,角色用于授予访问域的权限。用于管理系统证书的应用程序域被分配给一个或多个角色,从而允许具有该角色的用户可能会过渡到该应用程序域。如果用户角色没有此权限,则无法通过该应用程序域访问管理证书所需的权限。
以下图示显示了 Linux 登录(普通 Linux 帐户)、SELinux 用户、SELinux 角色和 SELinux 域之间的关系:

为了将角色分配给用户,首先将 Linux 帐户映射到 SELinux 用户。一个 SELinux 用户定义了哪些角色是可访问的(因为用户可以分配多个角色),以及该用户最多可以拥有的安全权限(尽管较低的安全权限也可以单独分配给用户)。
在 SELinux 主要用于限制面向网络的服务而非用户的系统中,本章的内容价值较小。这些系统上的所有用户都映射到unconfined_u SELinux 用户,具有默认的用户域unconfined_t,并且几乎不受限制——因此,称之为 unconfined。当这种情况适用时,大多数发行版将 SELinux 策略存储称为 targeted,以反映限制目标是特定应用程序,而不是整个系统。
管理 SELinux 用户
为了授予 Linux 登录正确的角色,我们首先需要创建一个具有这些角色的 SELinux 用户。现有的 SELinux 用户可以轻松修改,如果之前已添加 SELinux 用户,也可以将其从系统中移除。
如何操作…
SELinux 用户管理如下进行:
-
使用
semanage user列出当前可用的 SELinux 用户:~# semanage user -l Labeling MLS/ MLS/ SELinux User Prefix MCS Level MCS Range SELinux Roles git_shell_u user s0 s0 git_shell_r guest_u user s0 s0 guest_r root user s0 s0-s0:c0.c1023 staff_r sysadm_r system_r unconfined_r staff_u user s0 s0-s0:c0.c1023 staff_r sysadm_r system_r unconfined_r sysadm_u user s0 s0-s0:c0.c1023 sysadm_r system_u user s0 s0-s0:c0.c1023 system_r unconfined_r unconfined_u user s0 s0-s0:c0.c1023 system_r unconfined_r user_u user s0 s0 user_r xguest_u user s0 s0 xguest_r -
如果尚未存在 SELinux 用户,在正确的角色设置下,可以使用
semanage user创建它。例如,要创建一个数据库管理 SELinux 用户,运行以下命令:~# semanage user -a -R "staff_r dbadm_r" dbadm_u -
现有用户可以按如下方式进行修改:
~# semanage user -m -R "staff_r dbadm_r" staff_u -
也可以将 SELinux 用户从系统中删除:
~# semanage user -d dbadm_u
它是如何工作的…
当创建一个 SELinux 用户时,SELinux 会更新其 /etc/selinux/ 下的配置文件,以支持该 SELinux 用户。按照功能命名 SELinux 用户是一个常见的最佳实践,因此,数据库管理员(DBA)被命名为 dbadm_u,而网站管理员则被命名为 webadm_u。
可以使用 seinfo 获取管理员可用的角色集:
~# seinfo -r
现有的 SELinux 用户可以被修改。然而,重要的是在修改过程中,已登录的用户必须退出(并可能暂时被锁定)系统。否则,SELinux 策略可能会突然标记他们的会话为无效上下文,并中断这些用户的操作。
当一个 SELinux 用户从系统中删除时,还必须重新标记所有剩余文件,这些文件的上下文中包含该 SELinux 用户。否则,这些文件(以及其他资源)会被标记为无效上下文,使得其他人无法访问这些文件和资源。
一旦创建了 SELinux 用户,它就准备好分配给一个或多个 Linux 用户。
还有更多……
使用 SELinux 用户时,也可以提供 MLS 设置。例如,要设置特定的安全权限,可以使用以下命令:
~# semanage user -a -r s0-s0:c0.c110 dbadm_u
对于 SELinux 用户,这是用户上下文所能拥有的最高安全权限。当我们将用户分配给 SELinux 用户时,可以强制单独设置较低的安全权限,这样就无需为每个不同的安全权限创建单独的 SELinux 用户。
将 Linux 用户映射到 SELinux 用户
有了可用的 SELinux 用户后,我们现在可以将 Linux 用户映射到 SELinux 用户。这将确保用户在登录系统时,会被分配与该 SELinux 用户一致的默认上下文。
如何操作……
为了将 Linux 用户映射到 SELinux 用户,可以执行以下步骤:
-
使用
semanage login列出现有的映射:~# semanage login -l Login Name SELinux User MLS/MCS Range __default__ user_u s0-s0:c0.c1023 root root s0-s0:c0.c1023 system_u system_u s0-s0:c0.c1023 %wheel sysadm_u s0-s0:c0.c1023 -
对于单个用户账户,通过
semanage login将该账户映射到 SELinux 用户:~# semanage login -a -s dbadm_u user1 -
也可以通过主 Linux 组将一组用户分配给一个 SELinux 用户。例如,如果存在
dba组,可以通过以下方式将其分配给一个 SELinux 用户:~# semanage login -a -s dbadm_u %dba -
映射可以轻松修改:
~# semanage login -m -s webadm_u user1 -
如果某个映射不再需要,也可以将其删除:
~# semanage login -d user1
它是如何工作的……
semanage login 应用程序管理 /etc/selinux/ 下的 seusers 文件。此文件由 SELinux 的 pam_selinux.so 认证库使用,在用户登录系统时会被调用。调用时,SELinux 会检查 seusers 文件,以查看 Linux 账户映射到哪个 SELinux 用户。然后,SELinux 会执行一个 SELinux 上下文切换,使得后续的登录过程(包括启动的 shell 或图形环境)会被分配正确的 SELinux 上下文。
创建登录映射不会影响现有的会话,因此如果用户已经登录,最好先让用户注销。此外,用户过去创建的任何文件可能会关联错误的 SELinux 用户。如果 SELinux 中的用户基础访问控制功能启用,则这些文件将不再被该用户访问。在这种情况下,管理员需要强制重新标记这些文件(包括重置 SELinux 用户):
~# restorecon -RF /home/user1
在用户映射和基于组的映射中,seusers文件中第一个提到的匹配特定登录的映射将被使用。
当用户登录并且没有映射与该登录本身匹配时(无论是直接匹配 Linux 帐户名称还是通过组成员身份),SELinux 将查看__default__用户的登录映射。这是一个特殊规则,充当回退规则。在具有无约束用户的系统上,__default__用户通常映射到unconfined_u SELinux 用户。在没有无约束用户的系统上,__default__通常映射到(无特权的)user_u SELinux 用户。
使用 sudo 在指定角色下运行命令
当一个用户被分配多个角色时,他们通常会使用其主要角色(例如staff_r),并仅有选择性地执行其他角色的命令。这可以通过sudo命令来完成,因为这些命令通常还需要不同的 Linux 用户(可以是root或用于 PostgreSQL 数据库服务器上的 DBA 任务的postgresql帐户)。
如何操作…
为了配置sudo以执行正确的角色和类型转换,请执行以下步骤:
-
通过
visudo打开sudoers文件:~# visudo -
定义用户(们)可以执行的命令。例如,要允许
dba组中的所有用户在dbadm_r角色中调用initdb,可以按如下方式定义命令:%dba ALL=(postgres) ROLE="dbadm_r" TYPE="dbadm_t" /usr/sbin/initdb -
现在,
dba组中的用户可以调用initdb,并且当调用initdb时,sudo会自动切换到dbadm_r角色和dbadm_t用户域:~$ sudo -u postgres initdb
它是如何工作的……
用户运行的常规用户域默认情况下没有那么高的特权。尽管可以直接扩展角色和用户域的特权,但通过不同角色提供最佳的隔离性。这样的方式允许无特权的用户域(例如staff_t)被多个不同的组织角色(因此,也可以是 SELinux 用户)使用。
一旦需要执行特权命令,用户将需要切换其活动角色。如果这仅仅是需要执行一小部分命令,并且这些命令还需要切换 Linux 用户(例如切换到postgres运行时帐户),那么通常使用sudo等权限委托工具。
sudo命令是一个支持 SELinux 的应用程序,可以配置为协助切换 SELinux 上下文。如果用户愿意,也可以直接通过命令行完成:
~$ sudo -u postgres -r dbadm_r -t dbadm_t initdb
然而,大多数管理员会希望在sudoers文件中配置此项。这种方式对用户更加友好,因为最终用户不需要不断地传递角色和类型部分的上下文信息来执行命令。
当然,这要求调用sudo的 SELinux 用户具备在dbadm_r角色中运行命令的权限。如果没有,那么即使sudoers文件中提到用户可以执行该命令,转换(因此,命令)仍然会失败,如下所示:
~$ sudo -u postgres initdb
sudo: webadm_u:dbadm_r:dbadm_t:s0-s0:c0.c1023 is not a valid context
另见
欲了解有关sudo和sudoers文件的更多信息,请查阅相关的手册页:
~$ man sudo
~$ man sudoers
sudo应用程序的主要项目网站位于www.sudo.ws。
使用runcon在指定角色下运行命令
使用sudo并非强制性的。SELinux 还提供了一个名为runcon的命令,允许用户在不同的上下文中运行命令。当然,SELinux 的限制仍然适用——用户必须具备在不同上下文中执行命令的适当权限。
如何操作…
使用指定的角色和类型运行命令通过完成以下步骤来实现:
-
确定命令应该在哪个域中运行,通常通过检查可执行文件的上下文并搜索
entrypoint定义来完成:~$ ls -Z auditctl system_u:object_r:auditctl_exec_t auditctl ~$ sesearch -t auditctl_exec_t -c file -p entrypoint -A Found 1 semantic av rules: allow auditctl_t auditctl_exec_t : file { … entrypoint … }; -
调用命令,传递角色和目标类型:
~$ runcon -r secadm_r -t auditctl_t auditctl -l
它是如何工作的…
runcon应用程序告诉 SELinux,命令的调用应该导致类型和角色转换,转向指定的类型(auditctl_t)和角色(secadm_r)。在实际成功之前,SELinux 会进行多个检查和验证。这些检查如下:
-
当前用户是否有权执行
auditctl(在auditctl_exec_t上具有执行权限)? -
是否允许从当前角色(比如
staff_r)切换到新角色(secadm_r)? -
是否有策略允许从当前类型(比如
staff_t)转换到所选类型(auditctl_t)? -
如果执行的文件是
auditctl_exec_t(即entrypoint检查),auditctl_t是否是一个有效的目标域? -
目标上下文(如
staff_u:secadm_r:auditctl_t)是否是一个有效的上下文(这意味着当前 SELinux 用户对给定的角色有访问权限)?
当不需要发生 Linux 用户转换时,可以使用runcon应用程序(尽管这并不排除使用sudo)。以auditctl为例,这意味着 Linux 上的常规访问控制仍然适用——如果当前用户没有访问auditctl所用文件的权限,那么仅使用runcon是无法成功的。
切换角色
当角色转换需要不仅仅是几个命令时,就需要打开一个带有新角色的 shell。这样可以确保整个会话都以分配的新角色运行。从该会话中执行的每个活动都会使用目标角色。
如何操作…
使用sudo或newrole切换角色的步骤如下:
-
如果
sudoers文件允许,可以通过sudo -i或sudo -s来切换角色。如果设置了ROLE和TYPE属性,那么目标 shell 将会有正确的上下文分配:~$ id -Z dbadm_u:staff_r:staff_t:s0 ~$ sudo -u postgres -i Password: ~$ id -Z dbadm_u:dbadm_r:dbadm_t:s0 -
也可以使用
newrole来切换角色:~$ newrole -r dbadm_r
它是如何工作的…
切换角色后获取一个 shell 与执行命令没有太大区别。然而,SELinux 策略可能不允许在目标域中运行 shell 和常规二进制文件。例如,允许通过某个角色使用puppetca_t域的用户将无法在该域中运行 shell,因为puppetca_t不允许通过 shell 使用——它是一个特定命令集的域。
大多数用户角色都有一个默认的用户域。dbadm_r角色的默认用户域是dbadm_t;webadm_r角色的默认用户域是webadm_t。这些用户域具有通过 shell 使用的权限。
newrole命令只需要目标角色,因为它会检查角色的默认类型(该类型在/etc/selinux/mcs/contexts/内的default_type文件中有文档说明),并将其作为目标类型。
创建新角色
角色是 SELinux 策略的一部分。为了创建新角色,不能仅仅执行一些semanage命令。相反,需要创建一个 SELinux 策略模块。
如何操作…
为了创建一个新角色,需要更新 SELinux 策略。可以使用以下步骤来完成此任务:
-
创建一个新的策略模块,以角色名称命名,如
pgsqladm(用于 PostgreSQL 管理员角色)。 -
在策略模块中,调用
userdom_login_user_template接口:userdom_login_user_template(pgsqladm) -
为
pgsqladm_r角色和pgsqladm_t类型分配适当的权限:postgresql_admin(pgsqladm_t, pgsqladm_r) -
编辑
/etc/selinux/mcs/contexts/中的default_type文件,将pgsqladm_t设置为pgsqladm_r角色的默认类型:pgsqladm_r:pgsqladm_t -
编辑
/etc/selinux/mcs/contexts/中的default_contexts文件,告知系统当应用程序触发用户切换时,应该转换到哪些类型。例如,对于本地登录会话,可以使用以下代码来实现这一目的:system_r:local_login_t user_r:user_t … pgsqladm_r:pgsqladm_t … -
现在,构建并加载策略,并验证新角色是否可用:
~# seinfo -r | grep pgsqladm_r
它是如何工作的…
为 SELinux 系统创建新角色需要在多个层面上进行更改。更新 SELinux 策略只是其中之一。
在策略中定义角色
第一步是通过 SELinux 策略创建一个新的角色和用户域。在参考策略中有一些模板,可以轻松地构建新的角色。这些模板之间的关系可以通过以下图示表示:

图中的各个模块表示以下模板:
-
在
userdom_base_user_template中,记录了角色和用户域的基本规则和权限,无论其未来的用途如何。如果需要声明一个具有最小权限的角色,建议使用此模板。 -
在
userdom_login_user_template中,调用了userdom_base_user_template并扩展了与交互式登录相关的权限。当创建一个角色时,目标是直接登录(无需调用newrole或sudo),此接口是必需的。 -
在
userdom_restricted_user_template中,调用了userdom_login_user_template接口,但该用户域还与unpriv_userdomain属性相关联,该属性旨在为对系统安全性影响较小的最终用户域提供服务。 -
userdom_common_user_template接口添加了适用于特权和非特权角色的共同权限和规则。 -
userdom_unpriv_user_template接口同时调用了userdom_common_user_template和userdom_restricted_user_template,用于声明具有交互式登录和一般系统访问权限的非特权角色和用户域。 -
userdom_admin_user_template接口同时调用了userdom_common_user_template和userdom_login_user_template,并创建了一个用于行政管理目的的角色和用户域。
每当调用此类适当的接口时,就会创建合适的角色和类型,并可以在其余的策略模块中使用。
扩展角色权限
在示例中,我们将 PostgreSQL 管理权限分配给 pgsqladm_t 用户域,并允许 pgsqladm_r 角色使用适当的 PostgreSQL 域(如果有的话)。
参考策略通常提供两种类型的接口,可以分配给新角色:
-
管理角色,其接口名称通常以
_admin结尾 -
最终用户角色,其接口名称通常以
_role或_run结尾
管理角色允许访问与特定域相关的所有资源。在 postgresql_admin 接口的情况下,角色和用户域(通过接口传递)可以向 PostgreSQL 服务发送信号,执行 init 脚本(启动或关闭服务),并管理域的各种资源(如数据库文件、配置文件和日志)。
服务几乎总是有一个 _admin 接口。这些接口的命名通常以域名为后缀,例如用于 Puppet 管理的 puppet_admin 和用于 Samba 管理的 samba_admin。有时,当涉及不同的域时,SELinux 策略模块可能有多个管理接口。例如,logging_admin_audit 和 logging_admin_syslog 接口,虽然它们都由同一个 SELinux 策略模块提供,但审计和系统日志服务的管理可以是分开的。
最终用户角色允许用户执行客户端应用程序或与服务交互。类似于 puppet_run_puppetca(允许用户域运行 puppetca 应用程序并过渡到它)和 openvpn_run(允许用户运行 OpenVPN 服务)这样的接口,虽然它们在某种程度上可能具有管理性质,但仍需验证接口的内容。然而,大多数情况下,这是由应用程序端控制的,而不是基础设施端——能够启动 VPN 服务并不意味着用户可以随意操作路由表,即使 VPN 服务域(openvpn_t)有这个权限。
在盲目授予新角色和用户这些接口之前,审查接口是非常重要的。例如,在 PostgreSQL 中,postgresql_role 角色并不允许用户与 PostgreSQL 服务交互;该接口用于支持 SEPostgreSQL(启用 SELinux 的 PostgreSQL),它基于 SELinux 策略为 PostgreSQL 提供额外的访问控制。当用户被分配 postgresql_role 角色时,他们将被授予 PostgreSQL 环境中的基本权限。
为了允许用户与 PostgreSQL 交互,可以使用 postgresql_stream_connect 和 postgresql_tcp_connect 接口。
默认类型和默认上下文
default_types 文件告诉 SELinux 如果没有指定上下文时默认的类型是什么,它被 newrole 等命令使用,以确定用户的默认类型。
default_contexts 文件(可以通过 users/ 子目录下的 SELinux 用户特定文件进行覆盖)通知 SELinux 库和子系统在用户和角色切换后,应该切换到什么特定的 SELinux 类型。例如,cron 守护进程运行在 system_r:crond_t 上下文中,但当它执行用户的 cron 作业时,这些作业本身需要在不同的 SELinux 角色和类型下运行。以下 default_contexts 配置片段会使用户(其角色为 pgsqladm_r)的作业以 cronjob_t 类型(而不是 pgsqladm_t)运行:
system_r:crond_t pgsqladm_r:cronjob_t
这些文件作为基础策略的一部分生成。遗憾的是,没有可以用于提供特定系统更改的default_types.local或default_contexts.local文件。因此,基础 SELinux 策略的更新可能会覆盖这些文件,具体取决于 Linux 发行版如何处理这些文件。如果这些文件被视为配置文件(例如 Gentoo Linux),那么它们不会被系统更新修改;相反,系统管理员会被通知这些文件可能需要更新,保留管理员之前手动所做的更改。
基于入口的初始角色
用户通常会有多个角色与之关联。根据他们与系统的交互方式,可能需要不同的初始角色(和用户域)。假设一个用户本地通过控制台、通过 SSH 远程(用于管理目的)以及通过 FTP(作为最终用户)与系统交互,如下图所示:

我们希望确保用户会话在系统上启动时的默认角色取决于系统的入口点。直接控制台登录可以在管理角色sysadm_r中进行,而远程登录则首先进入staff_r角色(以确保被窃取的 SSH 密钥不能在不知道用户系统密码的情况下用于执行系统管理任务)。使用 FTP 服务器时,应该使用非特权角色ftp_shell_r。
注意
ftp_shell_r角色是一个非默认角色,默认情况下不可用。在这种设置中使用 SELinux 与 FTP 服务器时,要求 FTP 服务器要么支持 SELinux(并支持上下文转换),要么使用 PAM 进行身份验证,而不是使用内部用户帐户。
如何操作…
要配置用户登录或开始会话时使用的角色,请执行以下步骤:
-
首先,确保用户被分配了各种角色:
~# semanage user -m -R "staff_r sysadm_r ftp_shell_r" staff_u -
编辑
default_contexts文件,通过重新排序上下文,确保正确的角色总是排在其他角色之前(或者其他角色根本不被提及):system_r:local_login_t:s0 user_r:user_t:s0 sysadm_r:sysadm_t:s0 staff_r:staff_t:s0 system_r:sshd_t:s0 user_r:user_t:s0 staff_r:staff_t:s0 system_r:ftpd_t:s0 ftp_shell_r:ftp_shell_t:s0 -
检查域是否支持特定的布尔值,明确启用或禁用过渡到特定的域。例如,考虑 SSH 守护进程:
~# setsebool -P ssh_sysadm_login off
它是如何工作的…
当应用程序调用 PAM 设置用户上下文时,PAM 配置将调用由pam_selinux.so文件提供的方法。这些方法将检查default_contexts文件,查看应该为用户设置什么上下文。例如,当pam_selinux.so通过system_r:sshd_t上下文在守护进程中加载时,特定守护进程的行会被解释:
system_r:sshd_t:s0 user_r:user_t:s0 staff_r:staff_t:s0
对于给定的用户,获取支持的角色集合。在我们的例子中,这是staff_r sysadm_r ftp_shell_r。然后逐一查看default_contexts文件中的条目,文件中提到的第一个,也是用户允许的角色,将被使用。
在给定的示例中,由于user_r不是允许的角色,staff_r是列表中的下一个角色。此角色是允许的,因此当用户通过 SSH 登录时,其默认角色将是staff_r角色(其关联的用户域将是staff_t)。
一些域也被配置为允许或禁止直接登录到管理角色。例如,SSH 策略使用一个名为ssh_sysadm_login的 SELinux 布尔值,该值允许过渡到任何用户(ssh_sysadm_login=on)或仅限于无特权用户(ssh_sysadm_login=off),策略方式如下所示:
tunable_policy(`ssh_sysadm_login',`
userdom_spec_domtrans_all_users(sshd_t)
userdom_signal_all_users(sshd_t)
',`
userdom_spec_domtrans_unpriv_users(sshd_t)
userdom_signal_all_users(sshd_t)
')
类似的方法可以很容易地构建到自定义策略中。请注意,使用userdom_spec_domtrans_unpriv_users只允许使用通过userdom_unpriv_user_template创建的守护进程角色和类型,因为此接口分配了unpriv_userdomain属性,该属性由userdom_spec_domtrans_unpriv_users接口使用。
定义角色转换
也可以让 SELinux 在执行某个特定应用时自动切换角色。通常的检查仍然适用(例如角色是否是用户有效的角色,当前用户域是否有执行权限,等等),但之后就不再需要调用runcon或sudo来切换角色。
如何做到这一点…
角色转换可以如下配置:
-
确定需要发生角色转换的可执行文件类型:
~$ ls -Z puppetca system_u:object_r:puppetca_exec_t puppetca -
在 SELinux 策略中,创建一个包含角色转换的接口:
interface(`puppet_roletrans_puppetca',` gen_require(` role puppetadm_r; type puppetca_t, puppetca_exec_t; ') allow $1 puppetadm_r; role_transition $1 puppetca_exec_t puppetadm_r; domtrans_pattern($2, puppetca_exec_t, puppetca_t) ') -
将新创建的接口分配给用户:
puppet_roletrans_puppetca(staff_r, staff_t)
它是如何工作的…
被激活的第一条规则是角色允许规则。这样的规则告诉 SELinux 哪些角色切换是允许的,以及切换的方向。允许的角色切换集合可以使用sesearch查询:
~# sesearch --role_allow
考虑以下puppetadm_r角色的角色允许规则:
allow staff_r puppetadm_r
在这种情况下,只有 staff_r角色被允许切换到puppetadm_r角色。不能从puppetadm_r角色切换回staff_r角色。
第二条规则告诉 SELinux,如果一个标记为puppetca_exec_t的文件由选定角色(在我们的例子中是staff_r)执行,那么该角色应该切换到puppetadm_r。当然,只有当 SELinux 用户被允许目标角色时,才会进行此操作。
第三条规则将在staff_t执行标记为puppetca_exec_t的文件时执行从staff_t到puppetca_t的域转换。
需要注意的是,在大多数情况下,强制角色转换(即通过 SELinux 策略)并不是首选方法,因为它不会为管理员提供任何灵活性。如果实现了这种方法,那么使用多个角色就会变得更加困难,因为一些域被硬编码为特定角色。
查看访问权限
最后,让我们来看一下如何验证分配给用户的访问权限。指定角色和权限使用户能够完成工作,但从安全角度来看,验证哪些用户能够操作某些资源也是非常重要的。审计员将希望了解谁能够,例如,操作 SELinux 策略或读取私钥。
如何操作……
要正确调查访问权限,以下方法可以帮助识别拥有我们希望了解的权限的用户(和进程):
-
验证与 SELinux 无关的文件权限。
-
验证对资源的直接访问(例如读取私钥的权限)。
-
查看谁(用户或应用程序)有权操作 SELinux 策略。
-
检查被授予直接访问文件系统和原始设备的用户和域。
-
查看何时可以直接访问内存。
-
审查谁可以更新认证文件。
-
分析谁可以启动系统。
它是如何工作的……
审查访问权限是一个漫长的过程。仅查看文件所有权(用户和组)以及文件的权限来找出谁实际上能够读取或修改文件(假设我们关注的是文件访问权限)是不够的。还需要检查权限委派工具,如 sudo(通过 sudoers 文件或 LDAP 服务器中的 sudo 配置),以及 setuid 应用程序访问、备份文件访问(当需要检查读取访问时)等。
通过 SELinux 提供的强制访问控制,检查访问权限的策略是这种评估的重要组成部分。sesearch 应用程序可以帮助完成这项任务。
直接访问检查
要检查直接访问权限,我们需要查询访问权限(如对资源的写权限)以及重新标记权限。毕竟,允许更改文件 SELinux 上下文的域理论上可以切换上下文、修改文件并重置上下文。
~# sesearch -t lvm_etc_t -c file -p write -ACST
Found 6 semantic av rules:
allow sysadm_t non_auth_file_type : file { … };
allow portage_t file_type : file { … };
...
~# sesearch -t lvm_etc_t -c file -p relabelfrom,relabelto -ACST
Found 5 semantic av rules:
allow sysadm_t non_auth_file_type : file { … };
allow restorecond_t non_auth_file_type : file { … };
allow setfiles_t file_type : file { … };
…
这段代码不仅显示了拥有权限的用户域,还显示了应用程序域。在权限审查中,必须验证谁可以访问和操作在这些域中运行的进程。这可以通过检查转换权限来完成:
~# sesearch -t setfiles_t -c process -p transition -ACST
对于每个领域,研究谁可以操控这些过程是一个耗时的过程,并且需要对给定领域中运行的应用程序有深入的了解。例如,restorecond 守护进程仅会将文件上下文重置为 SELinux 工具已知的上下文(因此,无法通过 restorecond 临时修改上下文),并且仅限于 restorecond 配置文件中配置的位置。
策略操控
检查 SELinux 策略是不够的,因为策略也可能被操控。加载新策略由多种权限控制,其中包括 load_policy 权限:
~# sesearch -t security_t -c security -p load_policy -ACS
Found 2 semantic av rules:
EF allow kernel_t security_t : security load_policy ; [ secure_mode_policyload ]
EF allow load_policy_t security_t : security load_policy ; [ secure_mode_policyload ]
同样,访问选定域(特别是 load_policy_t 域)也需要进行验证。
如输出所示,操控 SELinux 策略也可以通过一个叫做 secure_mode_policyload 的 SELinux 布尔值进行控制。当该布尔值启用时,加载新策略将不再可能。如果启用并保持该布尔值,即使重启系统也无法解决,除非系统以宽容模式启动。
同样,检查谁可以将系统置于宽容模式也可以进行验证:
~# sesearch -p setenforce -ACS
这一点也由相同的 SELinux 布尔值进行控制。
另一种操控 SELinux 策略的方式是将系统以宽容模式启动,甚至禁用 SELinux。这意味着审查启动文件的访问权限也非常重要(如 boot_t 类型)。
间接访问
也可以通过间接方式访问资源,例如,通过操控原始设备(如磁盘设备或内存)。在 Linux 系统上,访问设备文件本身已经是相当特权的操作。使用 SELinux 后,可能会添加额外的控制措施。
磁盘设备通常被标记为 fixed_disk_device_t。这些文件的访问应该仅限于应用程序域,尽管一些特权用户域可能能够重新标记这些设备节点或操控应用程序域以执行普通用户无法执行的操作。
~# sesearch -t fixed_disk_device_t -ACS
能够操控与系统认证相关的文件的用户,可以通过登录系统并作为不同的用户(该用户拥有所需权限)来授予自己不同的用户角色。这包括访问 /etc/pam.d/(通常标记为 etc_t)或认证库本身(通常标记为 lib_t)。
第七章:选择约束级别
在本章中,我们将介绍以下教程:
-
查找共享资源
-
定义公共辅助域
-
记录公共权限
-
授予所有客户端特权
-
创建通用应用程序域
-
使用模板构建应用程序特定的域
-
使用细粒度的应用程序域定义
介绍
在额外政策的开发过程中,开发人员可以选择使用非常细粒度的政策模型、每个应用程序一个域的模型,或者粗粒度的、基于功能的政策模型。这些约束模型之间的关系如下图所示:

在非常细粒度的政策中,定义了多个域,因此同一个应用程序中功能不同的进程都运行在各自的专门 SELinux 域中。而粗粒度的政策则允许具有相似功能的不同应用程序使用相同的上下文。应用程序级别的政策则介于两者之间:它们专注于一个域(或非常少量的域)来处理一个应用程序。
大多数政策采用每个应用程序一个域的原则进行开发。然而,政策开发中选择的开发模式反映了应用程序的约束级别,因为共享的粗粒度政策可能会导致应用程序与资源之间的互动超过预期,而细粒度的政策则更难开发和维护。
从功能层面来看,我们通常关注共享资源或那些无法与特定应用程序绑定的资源。一个例子是mta SELinux 政策,它管理与基础设施相关的共享内容,例如电子邮件别名(etc_mail_t)、用户邮箱(mail_home_rw_t)、电子邮件传送文件(mail_spool_t)等。
查找共享资源
在政策开发过程中,政策所使用的一些资源可能会与其他政策共享。若是这样,就会创建一个功能驱动的政策模块,将这些公共资源放在其中。这允许其他政策通过功能驱动政策中声明的接口使用这些资源并分配正确的权限。
如何执行……
本教程中的大部分工作是弄清楚哪些资源是共享的。通过完成以下步骤来实现:
-
寻找可能与其他应用程序共享的公共文件和目录,这些文件和目录的所有权不是专门与某个应用程序绑定的,而是更具有功能性的。对于这些资源,将它们声明在功能驱动的政策中。
-
检查是否有与政策功能相关但不特定于某个应用程序的设备。
-
验证是否存在特定用户提供的内容,这些内容在功能上相关但不与特定应用程序绑定,并且在这些情况下最好不要使用默认的用户内容类型(例如
user_home_t)。这些资源需要在功能驱动的策略中声明,并可能需要可定制化:type public_content_t; # customizable files_type(public_content_t) -
创建适当的接口来处理或与这些共享资源交互:
interface(`miscfiles_read_public_files',` gen_require(` type public_content_t; ') read_files_pattern($1, public_content_t, public_content_t) ')
它是如何工作的…
功能驱动的策略模块处理多个应用程序和策略的共享资源。处理多个应用程序功能资源的一些示例策略包括邮件传输代理策略(mta)和 Web 服务器策略(apache)。尽管 Web 服务器策略最初仅用于 Apache HTTPd,但它已经发展成一个更多功能驱动的策略,支持大量的 Web 服务器技术。
共享文件位置
找出哪些资源被认为是功能性的(而非应用特定的)一个有用的方法是设想将一个应用程序替换为另一个。如果我们将一个系统日志记录器(例如 syslog-ng)换成另一个(例如 rsyslog),或者将 Courier-IMAP 换成 Cyrus,哪些资源类型会保持不变?了解多个类似的应用程序有助于发现共享位置在哪里(或是什么)。
然而,具有相似功能需求并不一定意味着它们是共享的。位置也应保持一致(或至少保持一致并位于众所周知的位置)。考虑数据库文件:PostgreSQL 和 SQLite 数据库的数据库文件具有相同的功能目的,但将它们都标记为相同的标签是没有意义的。数据库文件是特定于某个数据库实现的,并需要特定的标签,因此在每个潜在的共享资源中,确保该资源本身可以在多个实现之间共享。
设备节点是一个很好的功能驱动策略示例。一个示例的设备类型定义可能如下所示:
type cachefiles_device_t;
dev_node(cachefiles_device_t)
设备通常在多个应用程序之间共享。大多数设备都在 devices.te 策略模块中定义,声明了适当的接口以允许访问设备(例如 dev_rw_cachefiles 用于对前面提到的 cachefiles_device_t 类型的读/写访问)。然而,/dev/ 中并非所有文件都是这样的设备文件。
考虑 /dev/log 套接字,它用于将日志事件发送到系统日志记录器。无论使用哪种系统日志记录器,这个套接字都是可用的,它通过以下日志记录 SELinux 策略模块提供:
type devlog_t;
files_type(devlog_t)
mls_trusted_object(devlog_t)
mls_trusted_object 接口使设备(标记为 devlog_t)在启用 MLS 的策略中对所有安全级别可访问。
用户内容和可定制化类型
用户提供的内容也非常重要。例如,对于与电子邮件相关的守护进程,用户的 .forward 文件(指示系统将用户的电子邮件转发到哪里)通常位于其主目录中,并且绝对不会由特定应用程序拥有。因此,它的标签(mail_home_t)与功能驱动的策略(mta)相关联。
别忘了通过 userdom_user_home_content 接口将用户内容标记为用户内容;否则,最终用户将无法标记或操作这些文件:
type mail_home_t;
userdom_user_home_content(mail_home_t)
有些用户内容也最好标记为可定制类型。当可定制类型分配给资源时,在标准重标操作(通常由系统管理员执行)中会被忽略,因此该资源的标签不会恢复为 SELinux 配置文件中定义的标签。这对于路径不是固定位置的资源特别有用,这些资源通常不会成为 SELinux 文件上下文定义的一部分。
如果管理员执行强制重标操作,即使资源当前分配的类型是可定制类型,文件的上下文也会被重置:
~# restorecon -R -F /home/*
在模块化策略开发中,没有可用的符号标记类型为可定制类型。为此,类型需要添加到 /etc/selinux/mcs/contexts/ 中的 customizable_types 文件。
将文件标记为可定制类型是一种解决方案,当资源的路径不是固定时。.forward 文件有一个固定的路径,因此无需可定制内容。然而,应公开访问的用户内容(标记为 public_content_t 或 public_content_rw_t)没有固定路径;因此,这些类型默认标记为可定制类型。
当完成完整的策略开发时(例如,通过 Linux 发行版策略或因为开发者控制整个策略而不仅仅是附加模块),则可以将 # customizable 注释放在类型声明后面,如以下 CVS 策略模块示例所示:
type cvs_data_t; # customizable
files_type(cvs_data_t)
参考策略构建系统将在构建过程中自动将类型添加到 customizable_types 文件中。
还有更多...
其他可以考虑的常见资源是 TCP 和 UDP 端口。事实上,面向网络的应用程序会绑定到一个或多个端口,这些端口通常在具有相同功能的应用程序中是相同的。
然而,TCP 和 UDP 端口不能在 SELinux 策略模块中声明;相反,它们需要作为基础策略的一部分进行标记。然而,更新基础策略只能由 Linux 发行版维护者或上游参考策略项目进行。基本规则是,端口名称通常以它们所使用的服务命名:
~$ getent services 6667
ircd 6667/tcp
~$ seinfo --portcon=6667
portcon tcp 6667 system_u:object_r:ircd_port_t
定义常见的辅助域
除了公共资源外,一些应用程序共享相同的一组辅助命令。sendmail命令就是一个很好的例子,它被许多域执行(通常是需要发送电子邮件但不使用 SMTP 协议的应用)。sendmail应用是众所周知的,大多数 MTA 应用都支持它进行命令行电子邮件发送操作。
支持这样的辅助域通常是通过功能驱动的策略来完成的。
如何做…
创建辅助域类似于创建常规应用程序域,但属性的使用使得策略非常灵活,并且可以通过进一步开发的应用程序特定策略模块来使用。让我们以 MTA 定义为例,看看如何完成这项工作:
-
为命令类型定义一个属性:
attribute mta_exec_type; -
为该命令创建一个适当的标签类型,并分配
mta_exec_type属性:type sendmail_exec_t, mta_exec_type; application_executable_file(sendmail_exec_t); -
为该命令配置应用程序域:
type system_mail_t; application_domain(system_mail_t, sendmail_exec_t) -
如果该应用程序用于系统目的,请将域分配给
system_r角色:role system_r types system_mail_t -
如果该应用程序是为最终用户执行的,别忘了包括
_run或_role接口。 -
使接口可被第三方应用域调用,以允许它们与辅助应用进行交互:
interface(`mta_send_mail',` gen_require(` attribute mta_exec_type; type system_mail_t; ') corecmd_search_bin($1) domtrans_pattern($1, mta_exec_type, system_mail_t) ') -
创建另一个接口,允许特定策略标记它们自己的辅助可执行文件,以便用于相同的目的(因为它们可能并不总是使用相同的类型):
interface(`mta_agent_executable',` gen_require(` attribute mta_exec_type; ') typeattribute $1 mta_exec_type; application_executable_file($1) ')
它是如何工作的…
辅助域旨在提供跨多个实现的可重用功能。为了支持多个实现的灵活性,通常会将属性分配给类型,以便可以轻松创建扩展。
再次考虑sendmail的例子。大多数实现会将命令行sendmail应用标记为sendmail_exec_t。然而,也有一些实现,其sendmail二进制文件具有更多功能,尤其是在从实现进程本身调用时。某些实现甚至将该文件作为符号链接指向一个更通用的电子邮件处理程序应用。
例如,Exim 实现使用exim_exec_t,而不是使用sendmail_exec_t。通过使用属性,Exim 策略模块只需调用适当的接口(在这种情况下是mta_agent_executable),这样第三方应用仍然可以执行该命令(即使它是exim_exec_t而不是sendmail_exec_t),并且表现如预期(即,按照 MTA 策略过渡到user_mail_t或system_mail_t域):
type exim_exec_t;
mta_mailserver(exim_t, exim_exec_t)
mta_agent_executable(exim_exec_t)
属性允许其他域与新定义的类型进行交互,而无需更新定义这些域的策略模块。这是因为这些域被授予对所有具有 mta_exec_type 属性的类型的执行权限,并将在执行此类文件时触发域转换到 system_mail_t 助手域。这个权限通过 mta_send_mail 接口提供,这是一个很好的助手域接口示例,可以分配给其他域:
interface(`mta_send_mail',`
gen_require(`
type system_mail_t;
attribute mta_exec_type;
')
corecmd_search_bin($1)
domtrans_pattern($1, mta_exec_type, system_mail_t)
allow $1 mta_exec_type:lnk_file read_lnk_file_perms;
')
记录公共权限
在助手域旁边,大多数基于功能的策略也会将可以分配给域的权限进行分组。这些权限不仅可能用于管理公共资源,还可能用于扩展其他具有功能要求的域,这些扩展由公共策略进行管理。
所有电子邮件守护进程需要能够绑定到适当的 TCP 端口、处理用户邮箱等。通过在功能策略级别捆绑这些公共权限,任何与策略相关的演变都可以立即授予所有继承来自功能策略的权限的域,而不需要单独更新每个域。
如何做……
公共权限可以在多种不同的情况下找到。如何分配公共权限取决于使用案例。以下方法基于 MTA 策略中的电子邮件服务器定义,提供了一种灵活的解决方案:
-
为功能域创建一个属性,以便授予公共权限:
attribute mailserver_domain; -
定义一个接口,将属性分配给指定的域:
interface(`mta_mailserver',` gen_require(` attribute mailserver_domain; ') typeattribute $1 mailserver_domain; ') -
构建一个接口,将与功能相关的公共权限分配给指定的参数。然而,它不应分配属性!这可以通过以下代码完成:
interface(`mta_mailserver_privs,` gen_require(` type mail_home_t; ') allow $1 mail_home_t:file read_file_perms; … ') -
现在,使用新创建的接口为属性授予适当的权限:
mta_mailserver_privs(mailserver_domain) -
如果特定应用程序始终需要继承权限,将该属性分配给它:
mta_mailserver(exim_t) -
然而,如果某个特定应用程序选择性地继承了权限,则使用域接口:
tunable_policy(`nginx_enable_mailproxy',` mta_mailserver_privs(nginx_t) ')
它是如何工作的……
在为域分配权限时,可以采取两种方法:要么将权限分配给属性(然后将其与域关联),要么将权限直接分配给域。选择哪种方法取决于策略的使用方式。由于策略开发的限制,不能选择性地(即通过 SELinux 布尔值触发)分配属性。任何尝试这样做的操作都会导致构建失败,具体如下:
~$ make mymodule.pp
Compiling mcs mymodule module
checkmodule: loading policy configuration from tmp/mymodule.tmp
mymodule.te:23:ERROR 'syntax error' at token 'typeattribute' on line 1309:
#line 23
typeattribute $1 mta_exec_type;
checkmodule: error(s) encountered while parsing configuration
结果是,每当权限可以选择性地授予时(通过 SELinux 布尔值),策略开发者必须确保权限是直接授予的(而不是将属性分配给域)。
然而,在大多数情况下,使用属性来管理域是有意义的。政策本身的大小不会增加太多(因为规则仍然保持在属性级别),管理员可以轻松查询哪些域参与了该功能性方法:
~# seinfo -amailserver_domain -x
mailserver_domain
system_mail_t
exim_t
courier_smtpd_t
通过接口授予权限还可以帮助我们快速查看分配属性的影响,因为我们可以使用seshowif命令:
~$ seshowif mta_mailserver_privs
给出的示例使用的是服务器域方法,但同样的方法也可以应用于客户端。
授予所有客户端特权
使用接口来聚合特权的方法,不仅对具有相同功能目的的域有益,也对客户端有益。通过将特权结合起来,只需更新接口而无需更新所有客户端的策略模块,就能提升客户端的特权。
如何实现…
创建一个客户端接口,可以分配给具有特定功能目的的所有客户端。以下步骤扩展了一个带有反恶意软件支持的示例策略:
-
在反恶意软件通用策略中,创建一个
avcheck_client属性:attribute avcheck_client; -
创建一个将属性分配给客户端域的接口:
interface(`av_check_client',` gen_require(` attribute avcheck_client; ') typeattribute $1 avcheck_client; ') -
创建分配给客户端域的公共特权的接口:
interface(`av_check_client_privs',` … ') -
在创建的接口中,添加需要分配给所有客户端域的特权。例如,要启用 ClamAV
check命令的域过渡,使用以下代码:optional_policy(` clamav_domtrans_check($1) ') -
所有充当客户端的域要么分配了
av_check_client(如果可以分配该属性),要么分配了av_check_client_privs接口。
它是如何工作的…
假设为 ClamAV 开发了一个新的反恶意软件策略,我们希望客户端能够执行clamav_check_exec_t应用程序,并将它们过渡到clamav_check_t域。我们不必更新所有客户端以进行clamav_domtrans_check调用,而是只需在通用反恶意软件策略的av_check_client_privs接口中进行此操作,如下所示:
optional_policy(`
clamav_domtrans_check($1)
')
这确保了所有适当的域——不仅仅是具有avcheck_client属性的域——都能获得必要的特权。
另一个使用该原理的示例是 PulseAudio 策略。提供了一个名为pulseaudio_client_domain的接口,应由 PulseAudio 客户端使用。每当需要更新 PulseAudio 客户端的权限时,策略开发人员只需更新pulseaudio_client_domain接口,而无需更新所有客户端策略模块。
这种方法使政策开发更加灵活高效,因为开发人员不需要更新所有可能的客户端域来添加特权。
创建通用应用程序域
在某些情况下,即使存在多个实现用于相同的功能,创建一个通用应用程序域也是有意义的。例子包括 Java 域(适用于所有流行的 Java™实现)和 init 域。当这种情况发生时,仔细考虑通用应用程序域是否总是足够,或者是否会在稍后需要特定的应用程序域。当这一点不明确时,确保所开发的策略足够灵活,能够兼顾这两种情况。
如何操作……
为了创建一个仍然灵活的通用应用程序策略,以应对未来可能开发的特定策略,请遵循以下步骤:
-
确定(几乎)总是适用于功能域的权限,无论实现是什么。
-
将这些权限分配给一个基础实现。例如,对于 Java™实现,将权限分配如下:
attribute javadomain; # Minimal permissions java_base_runtime_domain(javadomain); type java_t; # Assigns javadomain attribute java_base_runtime(java_t); -
向标准类型添加适用于至少一个(或几个)实现的权限。在我们的例子中,这将是对
java_t的权限。这确保了java_t对大多数 Java™实现是通用可用的。 -
添加适当的文件上下文,以便大多数实现能够受益于通用应用程序策略:
/usr/lib/bin/java[^/]* -- gen_context(system_u:object_r:java_exec_t,s0) /opt/(.*/)?bin/java[^/]* -- gen_context(system_u:object_r:java_exec_t,s0)
它是如何工作的……
在给定的实现下,大多数 Java™实现将在启用 SELinux 的系统上运行时,处于通用的java_t域:它们的可执行文件通过通用文件上下文表达式全部标记为java_exec_t,并且java_t域不仅包含 Java™域的最小权限集(这些权限通过javadomain属性从java_base_runtime_privs接口中获取),还包含一些对多个实现通用的权限。这意味着java_t域在大多数情况下具有比所需更多的权限,因为它需要支持广泛的 Java™实现。
然而,当需要创建一个具有与现有java_t域不同的策略配置文件的特定实现时,策略开发人员可以轻松地将此域标记为 Java 域,从而继承每个 Java™实现所需的权限(例如,因为这些权限是通过 Java™的规范要求的),同时避免与通用java_t域授予的其他权限冲突:
type icedtea_java_t;
java_base_runtime(icedtea_java_t)
通过创建一个更具体的文件上下文定义,新的类型的可执行文件将被分配此标签(因为其他表达式更为通用,SELinux 工具使用最具体的定义优先的方法):
/opt/icedtea7/bin/java -- gen_context(system_u:object_r:icedtea_java_exec_t,s0)
构建一个适当的最小权限规则集并不容易,需要有策略开发经验。如果不确定,使用 SELinux 布尔值可能是个好主意,正如(通用的)cron策略所使用的那样:
# Support extra rules for fcron
gen_tunable(fcron_crond, false)
…
tunable_policy(`fcron_crond',`
allow admin_crontab_t self:process setfscreate;
')
通过这种方法,如果额外权限的数量较小,具体实现仍然可以从通用策略声明中受益。随着策略通过其他实现细节的增强,可能不再需要tunable_policy声明,或者可以单独开发适用于fcron的特定实现。
使用模板构建特定于应用程序的域
特定域的优势在于它们可以仅包含域所需的权限,不包含多余的权限。由于没有其他应用程序实现使用该特定域,权限可以根据应用程序的需求进行定制。
然而,在某些情况下,将类型与基本权限一起自动生成可能是有益的。生成类型是通过模板完成的(而不是接口,尽管接口和模板的底层实现非常相似)。这种方法和开发方式与接口定义保持一致,应该不难让开发人员理解。
一个考虑模板的示例是为各个应用程序自动创建系统cron作业域。通过模板,我们可以自动创建域、可执行类型和临时资源类型,并且正确地记录该域与主cron守护进程之间的交互(这对于传递作业失败或成功信息、处理输出、日志记录等是必要的)。
如何操作…
创建模板类似于创建接口。要创建模板,可以使用以下方法:
-
从
.if文件中的骨架模板开始,但将其命名为template而非interface:template(`cron_system_job_template',` … ') -
添加以下类型声明:
type $1_cronjob_t; type $1_cronjob_exec_t; application_domain($1_cronjob_t, $1_cronjob_exec_t) type $1_cronjob_tmp_t; files_tmp_file($1_cronjob_tmp_t) -
授予主守护进程与新定义的类型之间正确的交互权限,这些类型仍在模板定义中:
allow crond_t $1_cronjob_t:fd use; allow crond_t $1_cronjob_t:key manage_key_perms; domtrans_pattern(crond_t, $1_cronjob_exec_t, $1_cronjob_t) … -
在应用程序策略中,调用模板以创建新类型。例如,要为 Puppet 创建
cron作业域,请将以下代码添加到puppet.te中:cron_system_job_template(puppet) -
使用所需的权限增强(现已可用的)
puppet_cronjob_t域:allow puppet_cronjob_t …
它是如何工作的…
本章之前已讨论过模板的使用。事实上,apache_content_template定义也是一个模板,它创建了额外的类型,并记录了新创建的类型与(主)Web 服务器域之间的交互。
使用模板可以快速开发策略,并且能够正确地隔离权限管理。当主应用程序发生变化并且需要额外的权限时,或者某些权限不再需要时,只需要调整模板。应用更改所需的唯一操作是重新构建 SELinux 策略模块,无需更改它们的单独源文件。
最好的实践是使用前缀和/或后缀标记来表示模板提供的类型,并且模板的名称以_template结尾。从理论上讲,完全可以创建一个模板来生成指定的类型,而不使用任何前后缀表达式,而是要求一次性传递各种类型:
cron_system_job_template(puppet_cronjob_t, puppet_cronjob_exec_t, puppet_cronjob_tmp_t)
然而,在以下情况下,这种方法缺乏灵活性:
-
如果需要支持额外的类型,那么接口 API 本身(传递给它的参数数量及其含义)需要进行更改,这使得这些更改与早期版本不兼容。这一点很重要,因为可能会有一些策略开发人员正在使用这个接口,但他们的策略并未出现在我们正在开发的仓库中,因此我们无法自己重构这些代码。
-
如果某个类型不再需要,则需要更改接口 API 本身(使其与早期版本不兼容),或者使接口忽略特定类型(这很容易变成开发噩梦)。
-
开发人员将需要不断查看类型的顺序和含义,以避免错误地将可执行类型标记为域类型,反之亦然。
这种方法也可能导致创建混淆的类型定义:
cron_system_job_template(puppetjob_t, pj_exec_t, ptmp_t)
通过这种方法,开发人员和管理员可能会忽视类型之间的关系。
使用适当的前缀和后缀标记能够简化管理。使用像cron_system_job_template这样的模板可以清楚地告知开发人员,将会有多个类型匹配*_cronjob_t、*_cronjob_exec_t和*_cronjob_tmp_t。策略开发人员和系统管理员很容易理解这些类型之间的关系。
使用细粒度应用域定义
本章早期使用模板是支持更细粒度应用域定义的开始。与其在主应用程序的同一域内运行工作负载,不如创建专门的类型,旨在优化一个域与另一个域之间的交互,确保赋予特定域的权限保持小且可管理。
使用细粒度应用域向前迈出了一步,允许同一应用程序的进程在其各自的特定域内运行。这并非总是可能的(并非所有应用程序都使用多个独立的进程),但当可以做到时,使用细粒度域提供了一个更安全的环境,在这个环境中,每个任务仅使用该任务所需的权限,尽管整个应用程序通常需要更多权限。
细粒度应用域定义的一个示例实现是后缀策略,它将在本食谱中作为示例使用。Postfix 电子邮件服务器文档齐全,其架构相对稳定,因此成为细粒度策略开发方法的理想候选者。
然而,当使用细粒度应用程序域时,政策的制定和维护本身变得更加困难。进程之间的单独交互变化(例如应用程序的新版本可能会发生这种情况)需要比所有进程都在同一 SELinux 域中运行时更频繁地更新策略。
如何操作……
可以采取以下检查来判断是否使用细粒度的应用程序域是合理的:
-
应用程序架构是否使用多个进程,每个进程都有不同的功能任务?如果没有,那么创建细粒度的应用程序域不会有太大帮助,因为每个域的权限仍然是相同的。
-
是否存在具有不同访问向量的进程(因此它们可能会受到与其他进程不同的威胁)?例如,是否有些进程可以通过网络直接访问,而其他进程是本地的?如果是这样,那么使用细粒度的应用程序域可能有助于减少在漏洞被利用时的影响。
-
是否存在某些进程与其他域(非同一应用程序管理)之间的交互,而其他进程不需要与这些域交互?如果是,那么使用细粒度的应用程序域可能有助于限制资源对其他应用程序的暴露。
-
该应用程序是否支持不同的角色,这些角色可能需要与某些(但不是所有)进程进行交互?一个全权限的应用程序管理员可能仍然需要对所有进程和资源具有管理员权限,但其他角色可能没有这个要求。使用细粒度的应用程序域也能实现细粒度的角色管理。
它是如何工作的……
支持细粒度的应用程序域通常是为了风险缓解。但除了风险缓解外,它还在角色管理以及更高效的管理继承自域的类型方面提供了优势。
降低漏洞风险
考虑下图所示的部分 Postfix 架构:

smtpd 守护进程处理通过 网络 接收电子邮件,因此比本地运行的进程(如 cleanup 进程或甚至 qmgr 进程)更容易受到远程漏洞攻击。
通过将 smtpd 守护进程的资源访问限制为仅其需要的资源,可以防止试图访问队列的漏洞(这些队列通常不被 smtpd 访问,但被 qmgr 使用),因为 smtpd 域(postfix_smtpd_t)中使用的最小权限方法不允许访问 maildrop 队列(postfix_spool_maildrop_t)。
适当的风险降低只有在应用程序的资源(例如特定的队列)也被精细化定义的情况下才可能实现。如果应用程序有多个配置文件,并且这些配置文件由不同的功能进程读取,那么这些配置文件也应该被更具体地标注(例如,路由配置文件和网络设置配置文件)。
如果应用程序资源被通用方式标记,我们就有可能使所有精细化的域对通用资源拥有相同的权限,这使得一个存在漏洞的应用程序更容易被利用,进而对整个应用程序架构造成更大的后果。
角色管理
使用精细化的应用程序域不仅仅是为了减轻漏洞利用。通过单独的域,可以为用户授予角色访问权限,使他们能够执行特定操作,而无需完全的应用程序权限。
例如,可以创建操作员角色,允许操作 Postfix 延迟队列并信号qmgr进程,而不授予这些用户对其他进程的任何特定权限。假设该角色的用户域为postoper_t,则可以按以下方式实现:
postfix_signal_qmgr(postoper_t)
postfix_manage_maildrop(postoper_t)
类型继承与转换
当一个域创建新资源时,这些资源会根据域的标签以及 SELinux 策略中定义的转换来分配一个类型。默认情况下,由域启动的进程(即,在策略中未定义转换时)继承父域的标签,而默认情况下,在目录内创建的文件继承该父目录的类型。在标记网络支持的情况下,数据包会基于父套接字标签进行标记。
有时,资源的创建无法与父域或父资源绑定,这使得 SELinux 无法推断出应分配给该资源的标签。因此,初始 SID由 SELinux 策略提供。这些信息告诉 SELinux 子系统,如果无法推断出标签,该资源的默认标签是什么。
例如,(TCP/UDP)端口和文件的初始 SID 如下:
sid port gen_context(system_u:object_r:port_t,s0)
sid file gen_context(system_u:object_r:unlabeled_t,s0)
初始 SID 的定义是 SELinux 基础策略的一部分,不能通过 SELinux 策略模块进行更改。幸运的是,SELinux 开发人员几乎没有理由去更改初始 SID 的定义。
这些标签继承规则在精细化的应用程序域设计中非常重要。使用多个进程的应用程序也倾向于使用诸如共享内存等资源进行进程间通信(IPC)。当所有进程都使用相同的域时,共享内存的标签也会相同(例如,postgresql_tmpfs_t用于 PostgreSQL 管理的共享内存),并且会应用文件转换:
# /dev/shm/ shared memory
type postgresql_$1_tmpfs_t;
files_tmpfs_file(postgresql_$1_tmpfs_t)
…
fs_tmpfs_filetrans(postgresql_$1_t, postgresql_$1_tmpfs_t, file)
在使用多个域定义时,共享内存段也可能会被标记为不同的标签(当然,取决于哪个进程创建了共享内存段),因此即使是进程间通信(IPC)也能够得到正确的管理。根据创建共享内存段的域,可能会进行单独的文件过渡。
除了文件过渡外,策略开发人员还可以使用domtrans_pattern定义引入域过渡(它改变新创建进程的标签)。在 Postfix 策略中,这用于创建细粒度的进程架构:
domtrans_pattern(postfix_master_t, postfix_postqueue_exec_t, postfix_postqueue_t)
domtrans_pattern(postfix_master_t, postfix_showq_exec_t, postfix_showq_t)
这样的域过渡也可以通过接口来支持,正如我们在前面的章节中看到的,例如postfix_domtrans_smtp接口:
interface(`postfix_domtrans_smtp',`
gen_require(`
type postfix_smtp_t, postfix_smtp_exec_t;
')
corecmd_search_bin($1)
domtrans_pattern($1, postfix_smtp_exec_t, postfix_smtp_t)
')
SELinux 支持的第三种过渡类型是动态域过渡。这样的 SELinux 策略规则通知 SELinux 子系统,进程可以动态地改变其自身类型——无需执行文件。这要求应用程序具备 SELinux 感知能力(即能够与 SELinux 子系统交互)。例如,在 FTP 策略中,提供了以下接口来支持域动态过渡到anon_sftpd_t域:
interface(`ftp_dyntrans_anon_ftpd',`
gen_require(`
type anon_sftpd_t;
')
dyntrans_pattern($1, anon_sftpd_t)
')
在我们的 Postfix 示例中,我们使用了/dev/shm/共享内存,但也有 POSIX 共享内存,它通过shm类进行管理。这个共享内存继承自域本身的标签,因此,如果两个应用程序(如postfix_pickup_t和postfix_cleanup_t)使用 POSIX 共享内存,那么目标标签将从创建共享内存区域的进程继承:
allow postfix_pickup_t postfix_cleanup_t:shm rw_shm_perms;
如果没有细粒度的访问控制,这一切将由单一域(例如postfix_t)处理,并且共享内存的访问控制将非常有限。
第八章:SELinux 调试
在本章中,我们将通过以下方法来查看 SELinux 调试:
-
确定是否是 SELinux 导致的问题
-
分析 SELINUX_ERR 消息
-
记录积极的策略决策
-
检查 SELinux 限制
-
确保 SELinux 规则永远不被允许
-
使用 strace 来澄清权限问题
-
对守护进程使用 strace
-
审计系统行为
引言
在启用 SELinux 的系统上,SELinux 策略定义了应用程序应如何行为。任何行为的改变可能会导致 SELinux 拒绝某些应用程序的操作。因此,最终用户可能会注意到意外的权限问题或异常的应用程序行为。
解决此类问题通常通过分析 AVC 事件来完成。许多资源已经详细讨论了 AVC 事件。基本前提是,AVC 事件使用一组键值对,如下所示:
type=AVC msg=audit(1369306885.125:4702304): avc: denied { append } for pid=1787 comm="syslog-ng" name="oracle_audit.log" dev=dm-18 ino=65 scontext=system_u:system_r:syslogd_t:s0 tcontext=system_u:object_r:usr_t:s0 tclass=file
在这个例子中,我们可以从 AVC 事件中推断出以下内容:
-
该事件是拒绝(
avc: denied) -
被拒绝的操作是追加到文件中(
{ append } … tclass=file) -
尝试追加到文件的进程的 PID 为
1787,名称为syslog-ng(pid=1787 comm="syslog-ng") -
进程的上下文是
syslogd_t(scontext=system_u:system_r:syslogd_t:s0) -
目标文件名为
oracle_audit.log,并且在文件系统中的 inode 号为65,存储在/dev/dm-18逻辑设备中(name="oracle_audit.log" dev=dm-18 ino=65) -
文件的上下文是
usr_t(tcontext=system_u:object_r:usr_t:s0)
然而,有时仅仅找到问题的所在是不够的。幸运的是,调试问题还有许多其他选项。
确定是否是 SELinux 导致的问题
在将问题归咎于 SELinux 子系统和策略之前,重要的是先验证 SELinux 是否真的导致了问题。过多时候,人们花费数小时分析 SELinux 策略和子系统,却最终发现当 SELinux 未启用时,问题依然存在。
如何操作…
为了确保 SELinux 是否(或不)是问题的根源,可以采取以下步骤:
-
是否可以通过应用程序的内部调试系统获取更多信息?考虑以下示例:
~# puppet master Error: Could not find class puppet::agent for foo.bar on node foo.bar ~# puppet master --debug --no-daemonize --verbose -
AVC 拒绝是否与审计日志中显示的问题有关?如果没有,尝试禁用
dontaudit规则并重新尝试:~# semodule -DB -
引发问题的应用程序是否支持 SELinux?大多数支持 SELinux 的应用程序会链接
libselinux.so库,因此我们可以使用ldd或scanelf来验证这是否为事实:~# ldd /usr/bin/dbus-daemon linux-vdso.so.1 => (0x00007fff56df4000) libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f55710ae000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f5570e8f000) libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f5570c72000) libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007f5570a6d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5570850000) librt.so.1 => /lib64/librt.so.1 (0x00007f5570647000) libc.so.6 => /lib64/libc.so.6 (0x00007f55702b3000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f55700af000) /lib64/ld-linux-x86-64.so.2 (0x0000003458000000) -
问题是否与登录相关?如果是,某些应用程序可能不支持 SELinux,但仍然会有所不同,因为它使用 PAM 底层库,该库调用
pam_selinux.so库。 -
如果将应用程序域设置为宽容模式,问题是否仍然存在?要检查这一点,可以执行以下命令:
~# semanage permissive -a portage_t -
如果应用程序域名未知,可以尝试将整个系统置于宽容模式(如果允许的话),看看问题是否仍然存在。如果仍然存在,那么 SELinux 可能并不是问题的根源:
~# setenforce 0
它的工作原理……
确保 SELinux 是问题根源的第一步是领悟真相。许多用于调查 SELinux 问题的小时数,最终都发现问题根本就不在 SELinux 上。
获取更多来自相关应用程序的信息是故障排除的第一步。许多应用程序都有命令行标志,可以增加日志的详细程度,许多守护进程也可以配置为记录更多的内部工作信息。由此产生的调试信息(或者如果应用程序支持,还可以是跟踪信息)将为管理员排查问题提供极大的帮助。
如果额外的日志信息没有帮助,那么需要检查审计日志中是否有 AVC 拒绝信息。由于一些 AVC 拒绝信息在常规操作中可能会被隐藏,因此可能需要临时禁用 dontaudit 规则。不过,不要盯着 AVC 拒绝信息不放,而是要广泛地查看日志文件和审计事件。例如,在下一章节(分析 SELINUX_ERR 信息)中,将讨论特定审计事件类型的更深入分析。
同时,检查系统上的各种日志文件也很重要。如果问题与内核、硬件或核心系统相关,dmesg 的输出非常关键。messages 日志文件(位于 /var/log/ 中)通常会包含系统守护进程出现问题时的线索。
当没有拒绝信息显示,并且没有可以协助故障排除的特定日志时,下一步是确保我们所使用的应用程序不是 SELinux 感知的。
SELinux 感知的应用程序(知道它们运行在启用 SELinux 的系统上,并与 SELinux 子系统交互的应用程序)可以根据加载的 SELinux 策略表现得不同,而无需实际触发 SELinux 子系统中的任何 SELinux 决策。由于它们的感知,内核中的 SELinux 子系统访问控制可能不会被调用,因此即使问题与 SELinux 有关,也不会显示任何日志。
尽管没有 100% 确定的方法来检查应用程序是否是 SELinux 感知的,但最常用的两种方法如下:
-
检查应用程序的二进制文件是否与
libselinux.so库链接 -
检查应用程序是否使用 PAM
如果一个应用程序链接了libselinux.so库,那么它就是 SELinux 感知的,并且能够查询 SELinux 策略,可能会在 SELinux 启用时表现不同,并且通常不管 SELinux 是否处于强制模式或宽容模式。
除了 ldd 命令,还可以使用 pax-utils 包提供的 scanelf 应用程序。该应用程序不需要对二进制文件具有执行权限(而 ldd 命令需要),但缺点是它只显示二进制文件的需求,而 ldd 还会显示库本身所链接的库:
~$ scanelf -n /usr/bin/dbus-daemon
TYPE NEEDED FILE
ET_DYN libexpat.so.1,libselinux.so.1,libaudit.so.1,libcap-ng.so.0,libpthread.so.0,librt.so.1,libc.so.6 /usr/bin/dbus-daemon
使用 PAM 的应用程序也可能受到 SELinux 的影响,因为它们的 PAM 配置可能调用 pam_selinux.so 库(或者不调用它,这同样会对应用程序功能产生影响,因为这样就不会发生转换,导致用户会话仍然以守护进程的上下文运行)。
如果应用程序没有与 SELinux 子系统交互以查询 SELinux 策略,并且它也没有直接处理 SELinux 标签(即它不知道 SELinux 标签并且没有在代码中主动与之交互),那么在宽容模式下运行应用程序应该能显示 SELinux 是否是罪魁祸首。在宽容模式下,SELinux 子系统的访问控制不会阻止任何操作。如果问题在宽容模式下仍然存在,那么很可能不是 SELinux 的问题。
另见
- 更多关于 SELinux 兼容应用程序的信息以及如何编写此类应用程序,详见 第十章,处理 SELinux 兼容应用程序
分析 SELINUX_ERR 消息
当 SELinux 子系统被要求执行一个无效的 SELinux 特定操作时,它会通过审计子系统记录此操作,使用 SELINUX_ERR 消息类型。
准备就绪
确保审计子系统已经启动并运行,因为我们将使用 ausearch 应用程序来(重新)查看审计事件:
~# service auditd start
如何操作…
分析 SELINUX_ERR 消息是通过查看审计日志中的条目并理解各个字段来完成的;可以按照以下步骤进行:
-
记录当前的日期/时间,或者重新加载 SELinux 策略,以便在审计日志中清楚地标明从哪里开始查看:
~# semodule -R -
在应用程序中触发该行为。
-
请求审计子系统显示最后的
SELINUX_ERR和MAC_POLICY_LOAD类型的事件:~# ausearch -m SELINUX_ERR,MAC_POLICY_LOAD -ts recent -
查看消息的开头,了解 SELinux 通知我们什么问题。
它是如何工作的…
SELinux 子系统会记录任何不正确的请求。如果是应用程序行为,通常会通过 AVC 类型记录;但当请求是 SELinux 特有且不正确时,会显示 SELINUX_ERR 消息类型。在此示例中,我们还查找了 MAC_POLICY_LOAD 类型,因此我们知道 SELinux 策略在何时重新加载,从而为调查提供了一个良好的起点。
以下是一些 SELINUX_ERR 消息的示例:
-
security_compute_sid: 无效的上下文 -
security_validate_transition: 拒绝 -
security_bounded_transition: 拒绝
还存在一些其他消息,尽管这些消息主要与 SELinux 内部问题有关(与 Linux 内核中的 SELinux 子系统相关,例如支持的 netlink 类型),这些问题需要由 SELinux 维护者自行解决,而不是由策略开发者解决。
无效上下文
在 RBAC 和 SELinux 用户规则不允许的情况下创建无效上下文时,会触发无效上下文。这通常发生在域转换期间,目标类型对于该角色不被允许的情况下:
time->Wed Aug 4 03:19:04 2014
type=SYSCALL msg=audit(10590262134.246:135): arch=c000003e syscall=59
success=no exit=-13 a0=187b190 a1=187b120 a2=187ac30 a3=7ffff2dc3ec0 items=0
ppid=14696 pid=15085 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
egid=0 sgid=0 fsgid=0 tty=(none) ses=21 comm="logwatch" exe="/usr/bin/perl"
subj=system_u:system_r:logwatch_t:s0-s0:c0.c1023 key=(null)
type=SELINUX_ERR msg=audit(10590262134.246:135): security_compute_sid:
invalid context system_u:system_r:logwatch_mail_t:s0-s0:c0.c1023 for
scontext=system_u:system_r:logwatch_t:s0-s0:c0.c1023
tcontext=system_u:object_r:sendmail_exec_t:s0 tclass=process
另一个无效上下文的原因可能是触发了角色转换,但此角色不允许 SELinux 用户:
type=SELINUX_ERR audit(1257378096.775:46): security_compute_sid: invalid context
dbadm_u:system_r:mysqld_safe_t:s0 for scontext=dbadm_u:dbadm_r:initrc_t:s0
tcontext=system_u:object_r:mysqld_safe_exec_t:s0 tclass=process
在这两种情况下,重要的是查看提供的上下文和scontext以及tcontext字段。这些字段显示了 SELinux 找到的无效上下文(提供的上下文),以及发起动作的源(启动动作的域)和对象上下文(决定新上下文的标签)。基于这些信息,应该很容易推断出错误的原因。
第一个例子展示了试图从允许system_r角色的logwatch_t域转换到不允许system_r角色的logwatch_mail_t域的尝试。要解决此问题,需要允许logwatch_mail_t角色给system_r角色:
allow system_r types logwatch_mail_t;
第二个例子是通过角色转换触发的。数据库管理员启动一个init脚本,导致dbadm_u:dbadm_r:initrc_t上下文。该域执行mysqld_safe应用程序(其文件标记为mysqld_safe_exec_t),通过 SELinux 策略尝试执行到system_r角色的角色转换。虽然system_r:mysqld_safe_t上下文是一个有效集合,但数据库管理用户本身不被允许system_r角色。
在第二个例子中的主要问题是不应该使用起始上下文(dbadm_u:dbadm_r:initrc_t)。initrc_t域应仅允许system_r角色。这本身要求dbadm_u SELinux 用户也被允许system_r角色。因此,即使允许system_r角色是正确的解决方案,但示例中采取的方法是错误的(从initrc_t到mysqld_safe_t的角色转换,而不是在实例化initrc_t时进行角色转换)。
拒绝转换验证
考虑以下错误消息,当一个init脚本尝试增加文件的敏感性时出现:
type=SELINUX_ERR audit(125482134923.234:25): security_validate_transition:
denied for oldcontext=system_u:object_r:selinux_config_t:s0
newcontext=system_u:object_r:selinux_config_t:s15:c0-c1023
taskcontext=system_u:system_r:initrc_t=s0-s16:c0.c1023 tclass=file
当执行文件转换但目标安全上下文不被允许时,会出现这样的消息。SELinux 验证其是否允许;如果不允许,则通过消息记录此情况。
AVC 类似的拒绝将在此处生效,但访问向量缓存系统仅能验证成对上下文(源和目标上下文),而转换验证需要在三个级别上进行(旧文件上下文、新文件上下文和进程上下文)。
解决此错误的方法是允许 initrc_t 提升文件的安全级别(通过 mls_file_upgrade 接口),或者干脆不让 init 脚本域尝试更新文件的 MLS 等级。
被拒绝的安全受限转换
一个发生安全受限转换的示例是当 mod_selinux 模块与 Apache 一起使用时(Apache 使用受限域和转换来处理单个请求)。当目标域没有受到源域的限制(即,SELinux 策略没有通过 typebounds 语句防止目标域执行源域未允许的操作)时,将显示以下错误:
type=SELINUX_ERR msg=audit(1245311998.599:17):
op=security_bounded_transition result=denied
oldcontext=system_u:system_r:httpd_t:s0
newcontext=system_u:system_r:guest_webapp_t:s0
当这种情况发生时,主应用程序域(例如当为线程进行转换时)请求受限转换,但目标域未标记为受限域。
请注意,这与给定受限域更多权限的情况不同——在这种情况下,当特定权限被调用时,SELinux 会拒绝这些权限,并显示 AVC 拒绝。
还有更多…
SELinux 日志记录和审计日志记录正在不断改进。当前正在进行的工作包括使审计日志更易于通过脚本解析,并提供更多信息。例如,在写作时,已接受一个补丁,旨在在 AVC 日志中添加宽松状态信息。
另请参见
对 AVC 消息的更深入分析和解释可以参考SELinux 系统管理,Packt 出版社。关于 SELinux 审计事件的更多资源可通过以下链接获取:
-
www.selinuxproject.org/page/NB_AL(包括 AVC 事件中所有可能字段的概述) -
wiki.gentoo.org/wiki/SELinux/Tutorials/Where_to_find_SELinux_permission_denial_details
记录正向策略决策
在某些情况下,系统执行的操作可能是管理员未预料到的,但这些操作是 SELinux 策略允许的,这使得调试潜在问题变得更加困难。某个应用程序可能是 SELinux-aware,导致其行为依赖于 SELinux 策略,而实际上并未使用 SELinux 子系统来强制执行访问。SELinux 策略也可能配置为与预期不同的行为。
在这种情况下,记录实际上被允许的活动而不是被拒绝的活动可能很重要;例如,记录域转换以确保转换确实发生。
如何做到这一点…
为了记录域转换,按照以下步骤创建 SELinux 策略:
-
确定需要关注的源域和目标域。
-
创建一个 SELinux 策略,在我们想要记录的访问向量上调用
auditallow语句:auditallow initrc_t postgresql_t:process transition; -
构建并加载 SELinux 策略,尝试重现该情况。
-
查看审计日志并检查是否显示了 AVC 授予消息:
type=AVC msg=audit(1401379369.009:6171): avc: granted { transition } for pid=4237 comm="rc" path="/usr/lib64/postgresql-9.3/bin/pg_ctl" dev="dm-3" ino=821490 scontext=system_u:system_r:initrc_t:s0 tcontext=system_u:system_r:postgresql_t:s0 tclass=process
它是如何工作的…
在 SELinux 支持的众多策略语句中,auditallow语句很有趣,并且不会改变 SELinux 做出的决策:拥有auditallow语句并不允许执行该操作,而是如果该操作被允许(通过另一个allow语句),则让 SELinux 子系统记录下来。
这使得 SELinux 策略开发人员和系统管理员可以明确要求 SELinux 子系统在做出授予而非拒绝决策时通知他们。
使用auditallow语句,我们可以跟踪 SELinux 策略决策,并帮助开发策略和调试应用程序行为,特别是当一个进程在非常短的时间内被调用时,因为这会使管理员难以查看进程的上下文是否正确(可以通过ps -Z或者检查/proc/<pid>/的上下文来查看)。
一些管理员可能希望在他们调用的脚本或命令中添加一些额外的日志记录(例如捕获id -Z的输出)。然而,SELinux 策略可能不允许脚本执行id命令,更不用说显示其输出或将输出定向到特定日志文件。
为 SELinux 策略增加额外的日志类型、启用终端输出、允许执行二进制文件等,会带来一定的开销,仅仅为了确定进程的上下文是否正确。使用auditallow语句是一个很好的解决方案。
当然,它不仅限于域转换。如果一个文件被更改,且管理员或工程师不确定是哪个进程或哪个上下文导致了更改,那么可以在文件标签上启用 SELinux 审计写入,如下所示:
auditallow domain postgresql_etc_t:file write;
借助 AVC 日志中的附加信息,我们可以看到在特定上下文(scontext)下运行的哪个进程(PID)负责向文件写入数据。
查看 SELinux 约束
一些拒绝是由 SELinux 约束引起的——这些是由 SELinux 策略施加的额外限制,不仅仅基于 SELinux 类型,还基于 SELinux 角色和 SELinux 用户。这通常在拒绝信息中并不明确。
audit2why应用程序有助于通知开发人员,拒绝是由于约束违规引起的:
~# ausearch -m avc -ts recent | grep type=AVC | audit2why
type=AVC msg=audit(1401134596.932:62843): avc: denied { search } for pid=19384 comm="mount.nfs4" scontext=system_u:system_r:mount_t:s0 tcontext=system_u:object_r:nfs_t:s0 tclass=dir
Was caused by:
Policy constraint violation.
May require adding a type attribute to the domain or type
to satisfy the constraint.
Constraints are defined in the policy sources in
policy/constraints (general), policy/mcs (MCS), and
policy/mls (MLS).
然而,这并非总是如此,因此我们需要找到一种方法来调查拒绝是否也来源于约束违规。
如何操作…
尽管 SELinux 约束可以很容易地查询,但目前它们仍然难以使用。以下方法有助于验证某个约束是否适用于正在调查的特定 AVC 拒绝:
-
查看 SELinux 策略,查看被拒绝的访问是否有 AVC 允许规则:
~$ sesearch -s staff_t -t user_home_t -c file -p read -A Found 1 semantic av rules: allow staff_t user_home_t : file { … read … }; -
假设有一个允许规则,请查看是否有适用于该操作的约束。这考虑到类(在示例中为
file)和权限(在示例中为read):~$ seinfo --constrain | grep 'constrain .* file .* read' -A 1 -
如果可能存在约束,请查看源和目标上下文的属性,因为这通常是约束在策略中如何记录的方式:
~$ seinfo -tstaff_t -x ~$ seinfo -tuser_home_t -x -
在 SELinux 策略中,查看
constraints文件(通常位于${POLICY_LOCATION}/policy/),以及mcs或mls文件(如果策略使用了 MCS 或 MLS),并检查请求的类和权限上的约束,验证是否有涉及所述属性的表达式。
它是如何工作的…
目前验证约束比较困难。幸运的是,目前约束不多,但无法轻松验证和查看约束对开发人员来说仍然是一个麻烦。
随着 seinfo --constrain 输出的增加(这是查询约束的唯一可用方法,除非读取源代码),复杂性也随之增加,seinfo --constrain 输出有以下几个缺点:
-
目前尚未提供有关约束的任何名称(因此很难引用约束)
-
它使用 逆波兰表示法 (RPN),这对用户并不友好(尽管对于计算机非常强大,但人们通常不擅长流利地阅读 RPN)
-
它显示了扩展的属性,因此我们得到的是类型的大量列表,而不是有限的一组属性
constraints、mcs 和 mls 文件中的约束定义(这些文件仅通过策略源代码访问)更容易查看。以下示例来自 constraints 文件;mcs 和 mls 文件中的约束将使用 mlsconstrain 关键字:
constrain process { transition dyntransition noatsecure siginh rlimitinh }
(
..r1 == r2
..or ( t1 == can_change_process_role and t2 == process_user_target )
..or ( t1 == cron_source_domain and t2 == cron_job_domain )
..or ( t1 == can_system_change and r2 == system_r )
..or ( t1 == process_uncond_exempt )
);
所展示的控制使用属性,这些属性更容易与特定情况匹配。它还展示了约束的灵活性。除了纯粹的面向类型的规则(t1 和 t2)外,约束还可以与角色(r1 和 r2)一起使用,并且能够处理 SELinux 用户(u1 和 u2)。数字用于区分主体(1)和对象(2)。
例如,在约束语言中,如果 SELinux 用户相等,或者主题的 SELinux 用户是 system_u,则允许某些操作,文档中将如下记录:
(
u1 == u2
or ( u1 == system_u)
)
seinfo --constrain 命令的输出具有一个优点,即计算机程序容易解析。可以编写计算机程序或脚本,使用 seinfo 的输出以树状结构的方式可视化约束信息。
以下由 GraphViz 生成的图展示了适用于文件读取的 UBAC 约束,仅显示用户域和 user_home_t 类型(以免图表过于繁杂):

该图展示了如何构建 UBAC 约束。文件读取是被禁止的(无论政策中制定了什么类型的强制规则),除非它们与图中显示的某一规则匹配,规则如下:
-
该主题(域)和对象(资源)的 SELinux 用户相同
-
该主题的 SELinux 用户是
system_u -
该对象的 SELinux 用户是
system_u -
该主题的 SELinux 类型与任何提到的类型不匹配(图示中仅显示了一个子集)
-
该对象的 SELinux 类型与任何提到的类型不匹配(图示中仅显示了一个子集)
-
该主题的 SELinux 类型是
sysadm_t
另见
更多关于 SELinux 约束的信息可以在以下资源中找到:
确保 SELinux 规则永远不被允许
可以在 SELinux 策略中包含一些声明,确保某个特定的访问向量无法被允许,即使以后通过增强 SELinux 策略也无法实现。这是通过 neverallow 声明来完成的。
如何操作…
要将 neverallow 声明包含到策略中并执行它们,请按照以下步骤操作:
-
在
/etc/selinux/semanage.conf中,通过将expand-check变量设置为1,启用对neverallow声明的支持:expand-check=1 -
创建一个 SELinux 策略,列出应显式禁止的访问向量。考虑以下实例:
neverallow user_t system_mail_t:process transition; -
构建并加载策略。
-
生成另一个策略,允许该声明并尝试加载它:
~$ semodule -i mytest.pp libsepol.check_assertion_helper: neverallow violated by allow user_t system_mail_t:process { transition }; libsemanage.semanage_expand_sandbox: Expand module failed semodule: Failed!
它是如何工作的…
并非所有发行版默认启用断言检查,因为它们在策略构建过程中会带来一些性能损失。有些发行版可能会因为此问题而存在策略不兼容性,因为如果禁用断言,则 neverallow 声明将永远不会被处理:neverallow 声明实际上不是一个策略决定,而更像是一个影响新策略加载的规则,它由策略链接器强制执行(策略链接器将不同的策略模块合并为最终的策略二进制文件)。从(失败的)输出可以推断出,neverallow 声明作为断言实现。
一些 neverallow 声明作为基础策略的一部分是可用的。例如,以下声明确保只有具有 selinux_unconfined_type 或 can_load_policy 属性的域才能实际加载 SELinux 策略:
neverallow ~{ selinux_unconfined_type can_load_policy } security_t:security load_policy;
本示例使用了否定运算符(~),表示 除非提到的那些类型之外的所有类型。
与约束(也可以用于实现限制)不同,neverallow 声明通过不接受任何违反规则的策略来提供帮助。通过模块也可以添加 neverallow 规则,这与约束不同,后者必须是基础 SELinux 策略的一部分(因此受 Linux 发行版、上游策略或管理完整策略的开发人员的控制,而不是单个 SELinux 策略模块)。
/etc/selinux/semanage.conf 中的 expand-check 变量告诉 SELinux 用户空间库需要检查该断言。如果该变量设置为 0,则 neverallow 语句将对策略及其加载没有任何影响。
使用 strace 来澄清权限问题
strace 应用程序是 Linux 系统上一个流行的调试工具。它允许开发人员和管理员查看应用程序发出的各种系统调用。由于 SELinux 通常对特定的系统调用有访问控制,使用 strace 在调试权限问题时会非常有用。
如何操作…
正确使用 strace,请按照以下步骤操作:
-
启用
allow_ptrace布尔值:~# setsebool allow_ptrace on -
使用
strace运行应用程序:~$ strace -o strace.log -f -s 256 tmux -
在生成的日志文件中,查找需要调试的错误信息。
它是如何工作的…
需要切换 allow_ptrace 布尔值(在某些发行版中,也提供反向布尔值 deny_ptrace),使得调用 strace 的域可以对目标域使用 ptrace(strace 用于查看系统调用的方法)。由于 ptrace 方法可能会成为安全隐患(例如,它允许读取目标进程的内存),因此默认情况下是禁用的。
一旦应用程序通过 strace 启动,日志文件将包含所有相关的系统调用信息。当然,对于较大的应用程序或守护进程,日志文件可能会变得非常庞大,因此限制 strace 操作仅针对特定的系统调用子集是有意义的,如下命令所示:
~$ strace -e open,access -o strace.log -f -s 256 tmux
在此示例中,仅查看 open 和 access 系统调用。
在生成的日志文件中,SELinux 权限通常会导致系统调用失败,并显示 EACCES (Permission denied) 错误代码:
7313 stat("/", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
7313 stat("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
7313 stat("/home/swift", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0
7313 stat("/home/swift/.pki", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
7313 stat("/home/swift/.pki/nssdb", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
7313 statfs("/home/swift/.pki/nssdb", 0x3c3cab6fa50) = -1 EACCES (Permission denied)
尽管大多数访问会显示 AVC 拒绝,但这些拒绝通常无法全面说明拒绝发生的阶段。通过使用 strace,我们可以跟踪应用程序执行的逻辑。
有时,错误发生的原因并不明显。在这种情况下,可能需要分别在强制模式和宽松模式下运行应用程序,并查看 strace 日志中的差异。
使用 strace 调试守护进程
strace 应用程序不仅适用于命令行应用程序,也适用于守护进程。调试守护进程的一种常见方法是从命令行启动它们,可能带有特定的调试标志,这样守护进程就不会分离并在后台运行。然而,在 SELinux 上通常无法做到这一点:策略不允许守护进程作为命令行前台进程运行。
如何操作…
使用 strace 调试守护进程的方法与调试命令行相似,重点是进程 ID 而不是命令:
-
查找守护进程的进程 ID:
~$ pidof postgres 2557 -
使用
strace附加到正在运行的进程:~$ strace -o strace.log -f -s 256 -p 2557 -
指定要监视的系统调用。例如,绑定或连接端口或套接字时的权限问题可以按如下方式进行过滤:
~$ strace -e poll,select,connect,recvfrom,sendto -o strace.log -f -s 256 -p 2557 -
按Ctrl + C中断
strace会话;不用担心,守护进程会继续在后台运行,不会受到影响。
它是如何工作的……
一种常见的调试守护进程的方法是从命令行将守护进程启动到前台,但在 SELinux 系统上通常不起作用:
~$ postgres -D /etc/postgresql-9.3 --data-directory=/srv/pgsql/data
LOG: could not bind IPv6 socket: Permission denied
WARNING: could not create listen socket for "localhost"
FATAL: could not create any TCP/IP sockets
如果用户有权直接执行守护进程二进制文件(这通常不是默认设置),则守护进程通常会以用户域的权限运行——而用户域通常没有执行守护进程所需的权限——因为没有从用户域到守护进程域的过渡。
通过使用strace对守护进程进行调试,可以更详细地调试它们。strace应用程序会绑定到进程(使用ptrace方法),并通知守护进程执行的每一个系统调用。-f选项还确保守护进程启动的新进程(例如,工作进程)也会被strace监控。
要结束strace会话,只需杀死strace会话或通过Ctrl + C中断它即可。守护进程本身不会受到影响。
还有更多...
还有许多其他系统分析工具,可以以类似的方式使用。例如,SystemTap 和 Sysdig,以及正在积极开发的 Linux 版本的 DTrace。
另见
以下资源更详细地介绍了strace、SystemTap 和 Sysdig 的使用:
审计系统行为
另一种调试应用程序行为的方法是通过 Linux 审计,尤其是在不清楚哪个进程负责执行特定操作时,因为这可能会使 SELinux 开发变得更加困难。当开发人员不知道需要为哪些域更新权限,或者不清楚如何创建资源时,Linux 审计子系统可以提供帮助。
通过 Linux 审计子系统,管理员可以启用规则来记录活动。在审计日志中,还会显示主题(进程)的 SELinux 上下文,从而使 SELinux 开发人员能够正确地识别需要操作的域。
如何做……
让我们来看一下如何通过以下步骤询问 Linux 审计子系统,哪个进程负责在用户的主目录中创建特定的目录:
-
作为 root Linux 用户(并且在具有足够权限的 SELinux 角色下),告诉审计子系统记录所有写入和属性更改操作,尤其是在用户的主目录中:
~# auditctl -w /home/john/ -p wa -k policydev -
执行必要的操作来触发需要调试的行为。
-
使用
policydev键查询审计子系统中的最近审计事件:~# ausearch -ts recent -k policydev -
后来,再次禁用审计规则,以防审计日志被与开发相关的事件弄得杂乱无章:
~# auditctl -W /home/john/ -p wa -k policydev
它是如何工作的……
Linux 审计子系统使用审计规则来识别哪些活动需要记录到审计日志中。可以使用auditctl命令(审计控制)来操作这些规则。
在我们的示例中,添加了一个针对/home/john/路径的规则(-w /home/john),用于记录写入和属性变化(-p wa)。事件会被标记,所谓的标记是一个名为policydev的键。管理员可以自由选择这个键,它的目的是构建审计事件并简化搜索查询。
当执行auditctl命令时,规则立即生效,因此在执行测试后,审计事件会显示如下:
time->Sun Jun 8 11:16:47 2014
type=PATH msg=audit(1402219007.623:80705): item=1 name=".dcinforc" inode=8364 dev=fd:0c mode=040755 ouid=475395 ogid=475395 rdev=00:00 obj=user_u:object_r:user_home_t:s0 nametype=CREATE
type=PATH msg=audit(1402219007.623:80705): item=0 name="/home/john" inode=229 dev=fd:0c mode=040700 ouid=475395 ogid=475395 rdev=00:00 obj=user_u:object_r:user_home_dir_t:s0 nametype=PARENT
type=CWD msg=audit(1402219007.623:80705): cwd="/home/john"
type=SYSCALL msg=audit(1402219007.623:80705): arch=c000003e syscall=83 success=yes exit=0 a0=7fff33d50330 a1=1ff a2=7fff33d50330 a3=a items=2 ppid=23132 pid=23929 auid=475395 uid=475395 gid=475395 euid=475395 suid=475395 fsuid=475395 egid=475395 sgid=475395 fsgid=475395 tty=pts3 ses=11203 comm="java" exe="/usr/bin/java" subj=user_u:user_r:java_t:s0 key="policydev"
日志显示,负责在用户主目录下创建名为.dcinforc/的目录的是一个java进程。这里需要考虑的重要字段是nametype=CREATE(告诉我们创建了一个对象)和syscall=83(告诉我们审计子系统捕获到的系统调用——在这种情况下是mkdir系统调用)字段,当然,还有subj=和obj=参数。
从示例中,我们可以看到有两个不同的obj=参数:
-
第一个,
obj=user_u:object_r:user_home_t:s0,是针对已创建目录提到的,它告诉我们新创建的目录接收到的标签是什么。 -
第二个,
obj=user_u:object_r:user_home_dir_t:s0,是针对父目录(nametype=PARENT)提到的,告诉我们.dcinforc/目录所在的目录标签是什么。
现在,这只是创建目录的一个示例,但审计系统可以捕获许多类型的活动。这时,syscall=字段就变得非常重要。这个字段告诉我们审计子系统捕获并记录的具体系统调用是什么。
可以在相应的C头文件中找到系统调用及其关联的编号。例如,/usr/include/asm/unistd_64.h文件(通过/usr/include/syscalls.h间接引用)包含以下代码:
#define __NR_rename 82 __SYSCALL(__NR_rename, sys_rename)
#define __NR_mkdir 83 __SYSCALL(__NR_mkdir, sys_mkdir)
#define __NR_rmdir 84 __SYSCALL(__NR_rmdir, sys_rmdir)
通过这一点,我们知道该目录是通过mkdir系统调用创建的,而不是通过其他方式(例如,先创建一个不同的目录然后重命名)。
还有更多……
审计子系统在启动时接收需要跟踪的规则。大多数 Linux 发行版在/etc/audit/目录下提供一个名为audit.rules的文件,其中包含了需要被捕获和记录的各种命令、位置和系统调用。然后,审计守护进程的init脚本会在启动时读取这个文件。
如果我们需要自动加载某些规则——而不仅仅是进行短期测试的时间——那么建议将这些规则添加到audit.rules脚本中,并附上适当的注释说明为何需要进行跟踪。
现在,我们只是在示例中使用了基于路径的审计功能。然而,Linux 审计子系统可以做的远不止于此。例如,可以审计特定的系统调用。这使管理员能够密切关注可疑的系统调用使用,比如使用unshare(用于 Linux 命名空间):
~# auditctl -a entry,always -S unshare -k namespace_suspect
另见
- 适合使用的一套默认审计规则已在《Red Hat 企业版 Linux 的 CIS 安全基准》中提到,详情请见
benchmarks.cisecurity.org/
第九章. 对齐 SELinux 和 DAC
在本章中,我们的重点将放在以下一组方案上:
-
为常规服务分配不同的根目录位置
-
为 SELinux 感知的应用程序使用不同的根目录位置
-
使用文件 ACL 分享用户内容
-
启用多实例化目录
-
配置能力而不是 setuid 二进制文件
-
使用组成员身份进行基于角色的访问控制
-
文件备份与恢复
-
管理应用程序网络访问
引言
SELinux 是一种访问控制机制,它与 Linux 提供的常规访问控制一起工作。确保这些不同的访问控制系统能够良好协同是非常重要的,因为它们各自都有优点和用途。
Linux 上的常规 DAC 安全服务已经相当强大,并且几乎每个 Linux 版本发布时都会进一步扩展。命名空间、扩展的访问控制、额外的 chroot 限制以及其他服务都被加入到 Linux 生态系统中,进一步支持 Linux 系统的加固。
在加固系统的过程中,SELinux 只是另一个防御层。仅仅将所有精力集中在 SELinux 上是一个重大的错误,因为 SELinux 也有其缺点。通过正确启用 Linux DAC 控制并调整 SELinux,使其与这些控制系统良好配合,可以使 Linux 系统在应对漏洞和攻击时更加韧性。
为常规服务分配不同的根目录位置
不同的根目录位置,也称为 chroot,是 Linux 系统的一项重要功能,旨在禁止直接访问指定目录位置之外的文件资源。从 chroot 中可以访问的环境称为 jail 或 chroot jail。在 chroot jail 中运行的应用程序会在不同的根目录下启动,其中仅托管应用程序正常运行所需的文件。
尽管通常被视为一种安全功能,但 chroot 本身并非出于这个目的。然而,通过正确的方法,chroot 可以增强应用程序的安全配置。
例如,在出现漏洞的情况下,成功的利用可能只能访问 chroot 中可用的文件。其他敏感文件,例如与身份验证相关的文件或其他服务配置文件,在 chroot 内是无法访问的(假设被利用的应用程序没有越狱 chroot 限制的权限)。
设置任何服务的 chroot 环境的步骤是类似的,但 chroot 的最终结果永远不会相同:根据被限制的应用程序,不同的文件需要在 chroot 中可用。
准备工作
查找需要被限制的应用程序。这些应用程序必须是最终服务,因为这些应用程序与其他应用程序或服务几乎没有交互。否则,所有这些其他应用程序和服务也需要在相同的 chroot 中可用。
通常,主要的目标是那些在互联网上使用非常广泛的服务。这些服务的漏洞通常会更积极地被搜索和开发,当发现漏洞并开发出利用代码时,恶意用户或团体会迅速扫描互联网寻找易受攻击的版本进行攻击。
如何操作……
下一组步骤展示了如何设置 chroot 环境并通知 SELinux 关于 chroot 的信息。我们以 BIND DNS 服务器作为示例服务,并将 /var/chroot/ 作为 chroot 位置:
-
创建 chroot 位置并添加必要的子目录:
~# mkdir -p /var/chroot/dev ~# mkdir -p /var/chroot/etc/bind ~# mkdir -p /var/chroot/var/bind/{sec,pri,dyn} ~# mkdir -p /var/chroot/var/{log,run} ~# chown root:named /var/chroot ~# chmod 750 /var/chroot ~# chown -R named:named /var/chroot/var/* -
复制应用程序所需的所有文件:
~# cp /etc/named.conf /var/chroot/etc/ ~# cp /etc/localtime /var/chroot/etc/ ~# cp -a /var/named/* /var/chroot/var/named/ -
创建应用程序需要的设备文件:
~# mknod /var/chroot/dev/null c 1 3 ~# mknod /var/chroot/dev/random c 1 8 ~# chmod 666 /var/chroot/dev/* -
由于 BIND 服务支持 chroot,我们不需要将其二进制文件和库复制到 chroot 位置。但是,并非所有服务都原生支持 chroot。当遇到这种情况时,我们还需要复制二进制文件和库。
-
现在,重新标记 chroot 中的文件,以便它们获得正确的 SELinux 标签:
~# setfiles -r /var/chroot/ /etc/selinux/mcs/contexts/files/file_contexts /var/chroot/ -
使用适当的选项启动应用程序以启用 chroot 支持。一些 Linux 发行版已经为 BIND 服务支持 chroot 信息。通常,它要求通过
-t /var/chroot/选项启动named应用程序。如果应用程序不原生支持 chroot,请使用chroot命令本身:~# chroot /var/chroot/ su - named -c /usr/sbin/named -
如果应用程序原生支持 chroot,它可能需要
chroot功能。这可以通过以下 SELinux 策略接口中的sys_chroot权限来支持:corecmd_exec_chroot(named_t)
它是如何工作的……
设置 chroot 环境通常是一个反复试验的过程;不过,对于更受欢迎的服务,互联网上有许多教程可以让设置 chroot 更加简单。
使用的基本方法是四个步骤:
-
创建 chroot 位置和目录结构。
-
安装必要的文件,并在必要时安装应用程序的二进制文件和库。
-
更新资源的 SELinux 标签。
-
调用 chroot 二进制文件或使用应用程序内置的
chroot功能。
创建 chroot 位置时,我们需要确保其结构类似于真实的根目录位置(即/位置);至于应用程序,它会将该 chroot 位置视为整个文件系统。
安装哪些文件是另一个问题,拥有在线资源来告知我们该怎么做是很大的帮助。但是,如果这些在线资源缺失,我们仍然可以找到哪些文件是必需的。
例如,我们可以使用 ldd 或 scanelf 应用程序:
~# ldd /usr/sbin/named
linux-vdso.so.1
liblwres.so.90 => /usr/lib64/liblwres.so.90
libdns.so.100 => /usr/lib64/libdns.so.100
libbind9.so.90 => /usr/lib64/libbind9.so.90
libisccfg.so.90 => /usr/lib64/libisccfg.so.90
libisccc.so.90 => /usr/lib64/libisccc.so.90
libisc.so.95 => /usr/lib64/libisc.so.95
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2
但通常,反复试验的方法最有效。只需在 chroot 中启动应用程序,记录其错误并解决它们。
对于 SELinux,这里需要注意的是 chroot 应该正确标记。例如,考虑 /var/chroot/etc/named.conf。SELinux 策略将假定该文件标记为 named_conf_t。然而,文件本身的路径(/var/chroot/etc/named.conf)暗示着它的标签为 var_t,因为 /var/ 的标签是 var_t,并且没有针对我们所定义的任何子目录或文件的标签定义。
setfiles 命令允许我们通过不同的根位置重新标记位置,这样 /var/chroot/etc/named.conf 就会被标记为与 /etc/named.conf 相同的标签。然而,注意系统的重新标记操作之后,需要再次使用 setfiles 命令,因为 SELinux 配置并未意识到标签的改变。
最后,应用程序本身需要在 chroot 内部启动或通过其内建的 chroot 支持启动。支持 chroot 的应用程序可以通过其配置文件和启动选项进行调整,以确保它们在 chroot 环境中运行。如果这不可行,则应该通过一个 init 脚本启动该应用程序,该脚本调用 chroot 命令,并很可能与 su 应用程序一起使用,以允许切换到其他用户。
还有更多...
chroot 是一种相对原始但强大的减少漏洞影响的方法。然而,确实存在逃逸 chroot 的方法。幸运的是,存在一些内核补丁,能显著提高 chroot 的安全性。一个流行的更新是由 grsecurity 团队维护的 (www.grsecurity.net)。
使用 grsecurity 的 chroot 限制,内核可以通过以下选项进行配置:
-
禁止在 chroot 内部发起文件系统的挂载和重新挂载操作
-
禁止在 chroot 内部执行 chroot 操作
-
禁止在 chroot 内部执行
pivot_root调用 -
强制 chroot 应用程序的当前工作目录为 chroot 根目录
-
禁止在 chroot 内部执行
setuid和setgid chmod操作 -
禁止通过指向 chroot 外部的打开文件描述符来更改目录
-
禁止连接到 chroot 外部创建的共享内存
-
禁止访问 chroot 外部创建的 Unix 域套接字
-
禁止向 chroot 外部的进程发送信号
除了这些选项,还有许多其他选项。这些选项使得 chroot 监狱比最初预期的更加注重安全性,并且为防范漏洞提供了非常强大的缓解措施。
另见
关于 chroot 监狱和特别是 BIND chroot 的资源有很多:
-
在
www.unixwiz.net/techtips/bind9-chroot.html上,关于在 chroot 监狱中构建和配置 BIND 9 的内容有很详细的讲解,并且提供了指向其他 BIND 相关资源的链接 -
在同一站点,可以找到 Unix
chroot()操作的最佳实践:www.unixwiz.net/techtips/chroot-practices.html -
Jailkit 项目(
olivier.sessink.nl/jailkit/)提供了一套用于管理 chroot 监狱的实用工具
为支持 SELinux 的应用程序使用不同的根位置
支持 SELinux 的应用程序在运行于 chroot 位置时有更多的需求。它们需要访问 SELinux 子系统(从 chroot 内部)以及可能需要的 SELinux 配置条目。这包括启用 PAM 的服务,因为这些服务的用户登录可能需要访问 SELinux 用户配置文件(如 seusers 文件和默认上下文)。
如何操作...
首先,像之前一样创建常规的 chroot 位置。为了更新系统以支持在 chroot 内运行的支持 SELinux 的应用程序,完成以下步骤:
-
在 chroot 中挂载 SELinux 文件系统到
/sys/fs/selinux/,以便应用程序可以查询 SELinux 策略:~# mkdir -p /var/chroot/sys/fs/selinux ~# mount -t selinuxfs none /var/chroot/sys/fs/selinux -
可选地,创建
/var/chroot/etc/selinux/位置并将当前定义复制到其中:~# cp -a /etc/selinux/ /var/chroot/etc/ -
更新
seusers文件(在/var/chroot/etc/selinux/mcs/)以仅包含在 chroot 内所需的 SELinux 用户映射。
它是如何工作的...
支持 SELinux 的应用程序通常需要访问 SELinux 文件系统(/sys/fs/selinux/)和内核提供的伪文件系统,以便与 SELinux 子系统进行交互。这通常被视为一种更危险的情况,因为这通常会让应用程序以更高权限的用户身份运行,并访问不再受到 chroot 保护的系统资源。这会降低 chroot 监狱作为安全措施的有效性。
如果应用程序本身不支持 chroot,我们将不得不将 /sys/fs/selinux/ 文件系统暴露给被 chroot 的应用程序。如果应用程序默认支持 chroot,它可能会在咨询 SELinux 后调用 chroot(即从非 chroot 父进程)并在 chroot 内运行工作进程或用户进程。这是 OpenSSH 支持的 chroot SFTP 用户的情况。
也可能只需将 SELinux 文件系统挂载到 /selinux/(一个已弃用但仍受支持的 SELinux 文件系统位置)内的 chroot 中。这样,就不需要创建虚假的 /sys/fs/ 位置:
~# mount -t selinuxfs none /var/chroot/selinux
/etc/selinux/ 位置并非总是需要的,因此默认情况下不应在 chroot 内部提供访问权限。但是,支持 SELinux 的应用程序,如果使用 SELinux 用户和角色转换,或者主动修改文件上下文,则需要能够读取 /etc/selinux/ 内的文件。
根据 chroot 监狱的目的,可能也可以使用 /etc/selinux/ 位置的只读绑定挂载:
~# mount -o bind /etc/selinux /var/chroot/etc/selinux
~# mount -o remount,ro /var/chroot/etc/selinux
之后需要重新挂载,以将其标记为只读。绑定挂载本身不允许传递额外的挂载选项,因此我们无法立即使用 ro 挂载选项进行挂载。当然,使用只读绑定挂载后,已经无法/不需要再修改 seusers 文件。
另见
- 关于 SFTP chroot 的详细指南可以在
wiki.archlinux.org/index.php/SFTP_chroot和en.wikibooks.org/wiki/OpenSSH/Cookbook/SFTP找到
使用文件 ACL 共享用户内容
访问控制列表允许对文件进行更细粒度的访问控制。与使用公共组所有权不同,文件的访问可以单独授予用户或用户组。
然而,SELinux 启用的访问控制也应根据这种情况进行调整。SELinux 中的基于用户的访问控制限制等特性,可能会完全阻止共享用户内容的访问,无论文件上设置了什么 ACL。
如何操作……
假设某用户希望允许一组文件和目录的读取及读写访问,可以使用以下步骤:
-
在用户的主目录外创建一个可访问的位置:
~# mkdir -p /home/share/ ~# chmod 1777 /home/share/ -
创建一个可以用于共享资源的 SELinux 文件类型:
type user_share_t; files_type(user_share_t) -
创建一个允许用户管理资源的界面:
interface(`userdom_admin_user_share',' gen_require(` type user_share_t; ') admin_pattern($1, user_share_t) ') -
将该类型分配给新位置:
~# semanage fcontext -a -t user_share_t "/home/share(/.*)?" ~# restorecon -R /home/share/ -
将该界面分配给将参与此资源共享开发的用户域:
userdom_admin_user_share(user_t) -
将需要共享的文件移到用户的主目录外,因为主目录的 SELinux 上下文不允许在其中共享资源。
~$ cp -r sharedfiles/ /home/share && rm -r sharedfiles/ -
分配允许(有限用户集)用户进行适当访问的 ACL:
~$ setfacl -R -m u:user1:rX /home/share/sharedfiles ~$ setfacl -R -m u:user2:rwX /home/share/sharedfiles ~$ setfacl -m "default:u:user2:rwX" /home/share/sharedfiles ~$ setfacl -m "default:u:user0:rwX" /home/share/sharedfiles ~$ setfacl -m "default:u:user1:rX" /home/share/sharedfiles
它是如何工作的……
文件级访问控制可以与 SELinux 访问控制完美配合使用。然而,需要特别小心,确保这两种控制机制(文件 ACL 和 SELinux 策略)不会相互干扰。SELinux 可能会禁止一些预期中的访问(例如,由于 SELinux 限制而不是类型强制设置),但同时也需要正确管理文件访问控制,以保持系统行为的一致性。
在这些操作中,共享的文件被移到用户的主目录之外。这主要是因为 SELinux 的 UBAC 特性,禁止不同 SELinux 用户访问彼此的常规资源(例如标记为 user_home_t 的资源,也包括 user_home_dir_t)。由于在 UBAC 限制下,user_home_dir_t 不允许其他 SELinux 用户访问,因此映射到不同 SELinux 用户的用户将无法进入并浏览共享用户的主目录,不论是否安装了 ACL。
不是所有系统都启用了 UBAC,或者共享可能仅限于单个 SELinux 用户,因此这种方法并不总是必要的。不过,使用不同的位置可以实现更好的管理。考虑以下情况:当第一个用户离开公司时,但他的团队希望继续访问和管理共享资源。如果删除用户的主目录,这些资源将消失。
将文件移动到不同的位置后,下一步是将文件标记为所有用户都可以访问的文件类型,但该类型不会受到 UBAC 特性限制。具有 ubac_constrained_type 属性的文件类型不能用于共享,因此创建一个新的文件类型,将其标记为常规文件。然后,用户域被授予此类型的管理权限(不仅可以管理文件,还可以将文件重新标记为 user_share_t 类型或从其标记为 user_share_t 类型)。这确保了 SELinux 不会阻止对共享资源的访问,同时仍然防止未经授权的域访问这些资源。
也可以选择一个已经被用户访问的文件类型,例如 nfs_t 类型(前提是 SELinux 布尔值 use_nfs_home_dirs 已设置)。然而,分配一个功能上用于不同目的的类型(nfs_t 用于 NFS 挂载的文件系统)可能会导致其他域也能访问这些资源。因此,管理员需要仔细考虑每个选择的理由和后果。
在 /home/share/ 位置标记为 user_share_t 类型后,原始用户将资源复制到新位置,并从当前目录中删除它们。这种方法(复制并删除)用于确保资源继承目标位置的标签(user_share_t),而不是保持与原始文件位置(user_home_t)相关联的标签,这在使用 mv 命令移动文件时会发生。最近的 coreutils 包提供了对 mv -Z 的支持,可以直接移动资源,同时仍然为资源提供正确的上下文。
用户的第三种方法是先移动资源,然后重新标记它们:
~$ mv sharedfiles/ /home/share/
~$ chcon -R -t user_share_t /home/share/sharedfiles/
最后,在所有 SELinux 规则和支持到位后,文件访问控制将在共享资源上启用,并且启用默认的 ACL,以便其他用户的写操作会自动继承写入资源上的正确 ACL,确保所有在共享资源上合作的用户无需不断地设置文件的 ACL。
如果没有默认的 ACL,其他用户可能会在 sharedfiles/ 中创建没有设置 ACL 的文件,从而阻止其他用户访问这些资源。
还有更多...
另一种可行的方法是使用 setgid 组所有权。例如,如果所有参与共享文件访问的用户都在 shrgrp 组中,则以下设置将自动使在该目录中创建的所有文件都具有 shrgrp 组所有权:
~$ chgrp -R shrgrp /home/share/sharedfiles/
~$ find /home/share/sharedfiles/ -type d -exec chmod g+s '{}' \;
这要求用户具有正确的 umask 设置(例如 007 或更低),以便新创建资源的组权限允许组成员读取和写入。
启用多实例目录
在 Linux 和 Unix 系统中,/tmp/ 和 /var/tmp/ 位置是所有用户可写的。它们用于提供一个公共的临时文件位置,并通过粘滞位进行保护,以防止用户删除他们不拥有的文件,尽管该目录是世界可写的。
尽管采取了这种措施,但 /tmp/ 和 /var/tmp/ 位置仍然存在攻击历史,例如符号链接的竞态条件以及通过(临时或非临时)世界或组可读文件泄露信息。
多实例目录为此问题提供了一个简洁的解决方案:用户获得自己的、私有的 /tmp/ 和 /var/tmp/ 实例。这些目录实例在登录时会在不同的位置创建,然后在该特定用户会话中将其显示(挂载)到 /tmp/ 和 /var/tmp/ 位置。通过使用 Linux 命名空间,这种挂载对用户会话是本地的——其他用户有他们自己对挂载的视图,对于管理员来说,多实例化未启用,因此他们保持系统的全局视图。
如何操作……
要启用 /tmp/ 和 /var/tmp/ 的多实例化,请遵循以下步骤:
-
创建
/tmp-inst/和/var/tmp/tmp-inst/位置:~# mkdir /tmp-inst/ /var/tmp/tmp-inst/ ~# chmod 000 /tmp-inst/ /var/tmp/tmp-inst/ -
为这些位置设置标签为
tmp_t:~# semanage fcontext -a -t tmp_t -f d /tmp-inst ~# semanage fcontext -a -t tmp_t -f d /var/tmp/tmp-inst -
编辑
/etc/security/namespace.conf并添加以下定义:/tmp /tmp-inst/ level root,adm /var/tmp /var/tmp/tmp-inst/ level root,adm -
编辑用于登录的 PAM 配置文件,例如
system-login,并在pam_selinux.so后面的 session 组中添加以下行:session required pam_namespace.so -
启用
allow_polyinstantiationSELinux 布尔值:~# setsebool -P allow_polyinstantiation on
工作原理……
多实例目录的系统准备工作要求这些目录本身可用并具有正确的权限设置。当父目录(如 /tmp/)是 tmpfs 挂载时,我们不能在其中创建多实例目录(例如 /tmp/tmp-inst/),因为该目录在重启后会消失(除非通过 init 脚本添加);因此,将 /tmp-inst/ 设置为单独的位置是必要的。当然,管理员仍然可以选择将该位置本身设置为 tmpfs 挂载——重要的是该目录必须存在并具有正确的权限(由 000 权限集表示)。
在示例中,假设 /var/tmp/ 不是一个 tmpfs 挂载,因此我们可以在其中定义多实例目录。
多实例化目录的配置文件是位于/etc/security/下的namespace.conf文件。在其中,挂载点与创建多实例化目录的目录一起列出:
/tmp /tmp-inst/ level root,adm
第三列定义了多实例化的方法。在非 SELinux 系统中,最常用的方法是user方法,它基于用户名创建目录。在启用 SELinux 的系统中,方法必须是level或context。
在level方法的情况下,目录是基于用户名和用户会话的 MLS 级别创建的。context方法则是基于用户名和安全上下文创建目录。这使得可以根据用户的角色隐藏临时数据,从而减少意外数据泄露的可能性。
管理员可以访问多实例化目录,因为它们被排除在多实例化之外:排除的用户列表在namespace.conf文件的第四列中配置。管理员仍然可以看到动态创建的目录:
~# ls -l /tmp-inst/
drwxrwxrwt. 2 root root 4096 Jun 22 12:31 system_u:object_r:tmp_t:s0_user1
drwxrwxrwt. 2 root root 4096 Jun 22 12:30 system_u:object_r:tmp_t:s0_user2
接下来,修改 PAM 配置文件以启用pam_namespace.so库。要找到需要编辑的 PAM 配置文件,请查找调用pam_selinux.so的 PAM 配置文件:
~# cd /etc/pam.d
~# grep -l pam_selinux.so *
system-login
在此示例中,system-login PAM 配置文件是唯一调用pam_selinux.so的文件,因此在该文件中添加了pam_namespace.so行。该行必须添加在pam_selinux.so调用之后,因为pam_namespace.so文件使用用户的上下文来决定如何调用实例化的目录。如果pam_selinux.so尚未被调用,则无法获取此信息,导致登录失败。
最后,启用 SELinux 布尔值allow_polyinstantiation,使得适当的域具有创建(并更改上下文)适当目录、使用命名空间、检查用户上下文等的权限。
还有更多...
管理员可以进一步操作,不仅仅是在需要时创建目录。在多实例化目录的设置过程中,会调用名为namespace.init的脚本,该脚本位于/etc/security/目录下,用于进一步处理这些目录的创建和修改。
此脚本可以调整为将文件复制到实例化目录(该文件通常已经包含多实例化主目录的逻辑),或进行其他更改,进一步调整用户会话的设置。
systemd init系统也支持通过PrivateTmp指令为服务提供私有的/tmp/目录,而不是为最终用户提供。
配置能力而非 setuid 二进制文件
Linux 能力允许在用户和应用层面上进行粗粒度的内核安全授权。在能力机制出现之前,管理员只能通过 setuid 应用程序为用户授予额外的权限:这些应用程序在执行时会继承应用程序所有者的权限(通常是 root)。有了能力机制后,权限集合可以进一步限制。
例如,可以为 ping 应用程序授予 cap_net_raw 能力,这样它就不再需要 setuid。根据不同的配置,可能需要为用户授予使用该能力的权限(如果应用程序已设置了相应的标志),或者立即授予该能力(不考虑用户设置)。
如何操作……
要与 SELinux 一起使用能力,请执行以下步骤:
-
启用应用程序所需的能力,作用于应用程序二进制文件:
~# setcap cap_net_raw+ei /bin/ping -
对于允许使用
net_raw能力的用户,在/etc/security/capability.conf中添加适当的配置(每个用户一行):cap_net_raw user1 -
将使用该能力的 SELinux 域需要获得使用该能力的权限。对于常见的应用程序,通常这些权限已经设置好。
allow ping_t self:capability net_raw; -
被允许修改其进程能力集的 SELinux 域,必须具有
setcap权限:allow local_login_t self:process setcap; -
编辑 PAM 配置文件,通过相应的服务来允许使用能力,并在
auth配置块中添加以下行:auth required pam_cap.so -
如果需要追踪/审计能力,可以使用 SELinux 的
auditallow语句:auditallow domain self:capability net_raw;
它是如何工作的……
当前进程允许使用的能力称为已许可的能力。处于活动状态的能力是有效的能力。第三种能力集合是可继承的能力。
在示例中,我们为 ping 应用程序启用了 cap_net_raw 能力,并标记为如果继承时生效。换句话说,默认情况下它并不会启用(允许)。如果我们希望立即启用 cap_net_raw 能力,则需要使用有效且已许可的权限集:
~# setcap cap_net_raw+ep /bin/ping
支持能力的应用程序不需要设置 effective 位。它们将在需要时通过适当的系统调用来启用(和丢弃)这些能力(这就是为什么这些领域需要 setcap 权限的原因)。如果 ping 支持能力,那么以下设置对我们的示例就足够了:
~# setcap cap_net_raw+i /bin/ping
接下来,允许使用 cap_net_raw 能力的用户(通过所选应用程序)需要在其继承的能力集中获得 cap_net_raw 能力。这可以通过 /etc/security/ 中的 capability.conf 文件以及通过在正确的 PAM 配置文件中调用 pam_cap.so 模块来实现。使用 PAM 配置文件还允许我们根据用户登录的服务来区分能力。
要检查当前启用的能力,用户可以执行capsh应用:
~$ /sbin/capsh --print | grep ^Current
Current: cap_net_raw+i
要查看文件上的能力,可以使用getcap应用:
~$ getcap /bin/ping
/bin/ping = cap_net_raw+ei
最后,通过auditallow语句审计能力的使用情况,可以告诉我们何时(以及由谁)使用了某个能力,尽管同样的事情可以通过 Linux 审计子系统在没有 SELinux 策略的情况下完成,审计setcap系统调用。
另请参阅
- 能力在 Chris Friedhoff 的POSIX 能力与文件 POSIX 能力页面中得到了很好的解释(
www.friedhoff.org/posixfilecaps.html)
使用组成员关系进行基于角色的访问控制
在更大的环境中,访问控制通常基于组成员关系授予。与单独的权限管理相比,组成员关系更容易管理:只需将用户添加或移出组即可自动授予或撤销权限,管理员可以轻松地根据组成员关系找出用户将拥有哪些权限。
如何操作…
为了使用组成员关系作为分配权限的高级方法,管理员需要注意以下方面:
-
将用户添加到他们应属于的组中:
~# gpasswd -a user1 dba ~# gpasswd -a user1 dev -
将适当的 SELinux 用户分配给该组:
~# semanage login -s dbadm_u %dba -
限制仅应由特定组调用的二进制文件和库:
~# chgrp -R dev /usr/lib/gcc /usr/x86_64-pc-linux-gnu/gcc-bin ~# chmod -R o-rx /usr/lib/gcc /usr/x86_64-pc-linux-gnu/gcc-bin -
在
sudoers文件中使用组表示法将特定权限授予组成员:%dba ALL=(ALL) TYPE=dbadm_t ROLE=dbadm_r NOPASSWD: initdb
它是如何工作的…
使用组使得权限管理变得更加简单。最终,这使得管理员只需管理用户的组成员关系,并根据组自动分配权限。
我们可以为组授予一个 SELinux 用户,并通过组成员关系决定普通用户登录的 SELinux 用户。当然,用户可以属于多个组。对于 SELinux 来说,是seusers文件的顺序决定以下映射的使用:
-
单个用户的 SELinux 用户映射优先于组映射
-
如果没有为该用户定义单独的 SELinux 用户映射,那么在
seusers文件中第一个使用该 Linux 用户所属组的组映射将决定 SELinux 用户映射。
因此,如果一个用户是两个组的成员(例如,dba和web),并且这两个组分别有映射到dbadm_u(针对dba组)和webadm_u(针对web组),那么seusers文件中第一个映射将决定该用户的 SELinux 用户。
为了覆盖此行为,您可以单独添加用户,或创建另一个组(比如dbaweb),将该用户也加入该组,并将该组映射放在seusers文件中的最前面。
当只有特定用户组被允许访问某个应用程序,但该应用程序不使用任何特定的 SELinux 域时,管理员使用 Linux DAC 权限来限制对该应用程序的访问可能更灵活。通过仅允许特定组(在我们的例子中是 dev)对应用程序及其应用程序库具有读取和执行权限,我们可以轻松限制访问。
另一种方法是为文件贴上新的 SELinux 类型标签,并授予适当的域对这些类型的访问权限。然而,这可能导致需要访问这些类型的大量域(因此需要大量的策略开发工作),而 Linux DAC 方法则容易实现。
备份和恢复文件
系统可用性和服务安全性的一个重要方面是提供备份和恢复服务。对于许多人来说,拥有文件副本可能看起来足够作为备份方法。然而,备份内容应包含的不仅仅是文件的内容。
如何操作…
选择备份解决方案时,请确保检查以下内容:
-
应该备份文件的扩展属性选择(而不仅仅是
security.selinux属性)。 -
当文件恢复到原始位置时,SELinux 上下文也应该一并恢复。如果备份解决方案不支持 SELinux 上下文,则应在恢复文件后,运行
restorecon命令来恢复 SELinux 上下文。 -
当文件恢复到临时区域时,SELinux 上下文不应恢复。相反,管理员应将文件放回原位,然后再恢复上下文。
-
/etc/selinux/目录中的 SELinux 配置应该进行备份,即使没有使用完整系统备份。每当策略或文件上下文定义发生更改时,备份文件时也应将这些内容一并备份。
它是如何工作的…
文件标签存储为 security.selinux 扩展属性。由于策略的运行基于所有相关对象的标签,若不备份和恢复文件标签,可能会影响系统在恢复操作后的正常运行。
当备份解决方案不支持扩展属性时,必须通过 semanage fcontext 命令正确设置所有标签。这是确保在恢复后,管理员能够运行 restorecon 命令来重置文件标签的唯一方法:
~# tar xvf /path/to/last_backup.tar.gz etc/named.conf
~# restorecon /etc/named.conf
然而,强烈建议选择支持扩展属性的备份解决方案,因为许多与 Linux 相关的设置都是以扩展属性的形式存储的。例如,文件 ACL 也是以扩展属性存储的:
~$ getfattr -m . -d named.conf
# file: named.conf
security.selinux="system_u:object_r:named_conf_t:s0"
system.posix_acl_access=0sAgAAAAEABgD/////AgAGAOo…
系统中可以使用的其他扩展属性包括 PaX 标记(user.pax.flags)、IMA 和 EVM 哈希(security.ima和security.evm)以及能力(security.capability)。但这里也存在问题:某些属性不应该(或不能)恢复。例如,IMA 和 EVM 属性由 Linux 内核处理,无法通过用户工具进行操作。
除了文件标签之外,SELinux 策略的备份和恢复也应该集成,尤其是在修改过 SELinux 策略的系统上。如果恢复后策略发生变化,可能会缺少类型或标签失效。
管理应用程序网络访问
在 Linux 系统上,iptables(以及最近的nftables)是事实上的主机级防火墙技术。管理员无疑会使用它来防止未授权的系统访问服务。我们还可以使用iptables来标识和标记网络数据包,只允许授权的应用程序(域)发送或接收这些网络数据包。
默认情况下,SELinux 策略支持客户端和服务器数据包,并允许常见的域访问其客户端和/或服务器数据包。例如,Web 服务器域(如httpd_t)将具有发送和接收http_server_packet_t数据包的权限:
allow httpd_t http_server_packet_t:packet { send recv };
这通过corenet_sendrecv_http_server_packets接口提供。启用数据包标签可以通过iptables轻松完成,如本食谱所示。但为了正确管理网络访问,需要创建自定义数据包类型,确保不使用任何默认的允许访问。
如何实现…
仅允许授权的域访问特定的网络数据包(数据报和数据流),请使用以下方法:
-
确定需要允许的流量。例如,我们可能只希望来自
10.11.12.0/24的 DNS 请求被dnsmasq_t域接受,而来自10.13.14.0/24的请求则被named_t域接受。 -
创建两个新的数据包类型:
type dnsmasq_server_packet_t; corenet_server_packet(dnsmasq_server_packet_t) type named_server_packet_t; corenet_server_packet(named_server_packet_t) -
允许这些数据包的域发送和接收权限:
allow dnsmasq_t dnsmasq_server_packet_t:packet { send recv }; allow named_t named_server_packet_t:packet { send recv }; -
根据情况标记传入流量:
~# iptables -t mangle -A INPUT -p tcp -s 10.11.12.0/24 --dport 53 -j SECMARK --selctx "system_u:object_r:dnsmasq_server_packet_t:s0" ~# iptables -t mangle -A INPUT -p udp -s 10.11.12.0/24 --dport 53 -j SECMARK --selctx "system_u:object_r:dnsmasq_server_packet_t:s0" ~# iptables -t mangle -A INPUT -p tcp -s 10.13.14.0/24 --dport 53 -j SECMARK --selctx "system_u:object_r:named_server_packet_t:s0" ~# iptables -t mangle -A INPUT -p udp -s 10.13.14.0/24 --dport 53 -j SECMARK --selctx "system_u:object_r:named_server_packet_t:s0"
它是如何工作的…
通过使用自定义的网络数据包标签,可以使用 SELinux 策略来管理特定应用程序的进出访问。尽管多个应用程序可以接受传入的 DNS 请求,本食谱展示了如何确保只有一个应用程序能够处理通过某个过滤器的请求。
每当使用iptables启用 SECMARK 标签时,Linux 内核将自动对所有数据包启用 SECMARK 标签。管理员未特别标记的数据包将被标记为unlabeled_t类型。某些域可以通过corenet_sendrecv_unlabeled_packets接口(或kernel_sendrecv_unlabeled_packets接口)处理unlabeled_t数据包。然而,如果不是这种情况,那么这些域将无法再处理网络流量。
因此,建议对其他传入(和传出)流量使用标准标签。为了识别哪些传入流量应该被标记,我们可以利用netstat输出的帮助:
~# netstat -naptZ | awk '/LISTEN/ {print $4,$6,$7,$8}'
0.0.0.0:13500 LISTEN 6489/mysqld system_u:system_r:mysqld_t:s0
0.0.0.0:80 LISTEN 23303/httpd system_u:system_r:httpd_t:s0
10.11.12.122:53 LISTEN 4432/dnsmasq system_u:system_r:dnsmasq_t:s0
10.13.14.42:53 LISTEN 5423/named system_u:system_r:named_t:s0
基于此输出,将适当的流量标记为mysqld_server_packet_t和http_server_packet_t,将允许这些域访问它们的传入网络流量。
通过为dnsmasq_t和named_t创建额外的类型,这些应用程序只能处理与这些数据包类型相关的请求。如果管理员更改了其中一个 DNS 服务器的配置,则网络数据包标签仍将确保来自先前标识的网络段的 DNS 请求无法被错误的 DNS 服务器使用,即使防火墙规则允许该流量通过。
使用sesearch,查询策略以查看哪些应用程序(域)能够发送和接收特定的数据包非常简单:
~# sesearch -t dns_server_packet_t -ACTS
Found 10 semantic av rules:
allow nova_network_t dns_server_packet_t : packet { send recv } ;
allow corenet_unconfined_type packet_type : packet { send recv relabelto flow_in flow_out forward_in forward_out } ;
allow named_t dns_server_packet_t : packet { send recv } ;
allow vmware_host_t server_packet_type : packet { send recv } ;
allow dnsmasq_t dns_server_packet_t : packet { send recv } ;
allow kernel_t packet_type : packet send ;
allow iptables_t packet_type : packet relabelto ;
ET allow squid_t packet_type : packet { send recv } ; [ squid_connect_any ]
DT allow icecast_t packet_type : packet { send recv } ; [ icecast_connect_any ]
DT allow git_session_t server_packet_type : packet { send recv } ; [ git_session_bind_all_unreserved_ports ]
同样的方法也可以从客户端层面采用。邮件服务器可能需要连接到其他邮件服务器,这意味着传出的数据可以被标记为mail_client_packet_t(如果我们使用默认的流量标签)。然而,如果我们希望确保只有邮件服务器能够连接到其他邮件服务器(而没有其他域也有权限发送和接收mail_client_packet_t数据包),则可以使用新的数据包类型。
另见
有关 SECMARK 标签的更多信息,请参考以下资源:
-
Paul Moore 的过渡到 Secmark,详见
paulmoore.livejournal.com/4281.html -
James Morris 的基于 Secmark 的 SELinux 网络控制,详见
james-morris.livejournal.com/11010.html
第十章:处理 SELinux 感知应用程序
在本章中,我们将通过以下几种方案介绍如何处理 SELinux 感知应用程序:
-
控制 D-Bus 消息流
-
限制服务的所有权
-
理解 udev 的 SELinux 集成
-
使用 cron 和 SELinux
-
以编程方式检查 SELinux 状态
-
在 C 中查询 SELinux 用户空间配置
-
通过代码方式查询 SELinux 子系统
-
在新上下文中运行新进程
-
读取资源的上下文
介绍
对于大多数应用程序,Linux 内核中的 SELinux 子系统能够在不与其他应用程序和组件进一步交互的情况下执行安全控制。然而,存在一些无法由 SELinux 子系统自动处理的操作。一些应用程序为特定用户执行命令,但目标域无法从正在执行的应用程序的路径中推断出来,因此无法基于标签进行类型转换。
解决这个问题的一个方法是使应用程序成为 SELinux 感知的,让应用程序查询 SELinux 子系统,以确定新执行的应用程序应该使用什么上下文。一旦获得上下文,应用程序就可以指示 SELinux 子系统将该上下文分配给接下来将启动的进程。
当然,问题不仅仅是决定进程应该处于哪个上下文。应用程序也可以检查 SELinux 策略,并根据策略进行操作,而不是通过 Linux 内核强制执行策略。如果应用程序使用 SELinux 获取更多有关会话的信息,并根据这些信息设置上下文,那么我们称这些应用程序为 SELinux 感知的。
查看应用程序是否为 SELinux 感知的最简单方法是检查文档,或者检查它是否与 libselinux.so 库链接:
~$ ldd /usr/sbin/crond | grep selinux
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fa53299a000)
一些 SELinux 感知应用程序不仅查询信息,还对 SELinux 子系统无法控制的对象执行决策。这些对象的例子包括 安全增强型 PostgreSQL (SEPostgreSQL) 应用程序中的数据库对象或 D-Bus 服务。尽管它们在 SELinux 策略中有所表示,但它们并不是常规 Linux 操作系统的一部分,而是由应用程序本身拥有的。这些 SELinux 感知应用程序被称为 用户空间对象管理器。
无论应用程序如何处理其 SELinux 特定代码,在系统上使用这些应用程序时,了解应用程序中 SELinux 代码的工作方式非常重要,因为标准方法(查看 AVC 拒绝记录,看看是否需要更改上下文或调整策略)在这些情况下可能根本不起作用。
控制 D-Bus 消息流
D-Bus 在 Linux 上的实现是一个 SELinux 感知的应用程序示例,充当用户空间对象管理器。应用程序可以在总线上注册自己,并且可以通过 D-Bus 在应用程序之间发送消息。这些消息也可以通过 SELinux 策略进行控制。
准备工作
在查看与消息流相关的 SELinux 访问控制之前,重要的是先关注一个 D-Bus 服务,查看它的认证方式(以及消息如何在 D-Bus 中传递),因为这会反映在 SELinux 集成中。
转到 /etc/dbus-1/system.d/(该目录存放 D-Bus 服务的配置文件),查看其中的配置文件。例如,dnsmasq 的服务配置文件如下所示:
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="uk.org.thekelleys.dnsmasq"/>
<allow send_destination="uk.org.thekelleys.dnsmasq"/>
</policy>
<policy context="default">
<deny own="uk.org.thekelleys.dnsmasq"/>
<deny send_destination="uk.org.thekelleys.dnsmasq"/>
</policy>
</busconfig>
该配置告诉 D-Bus,只有 root Linux 用户被允许拥有 uk.org.thekelleys.dnsmasq 服务,并向该服务发送消息。其他用户(根据默认策略进行管理)被拒绝执行这些操作。
在启用 SELinux 的系统中,单纯使用 root 作为最小粒度的权限是不够的。因此,我们需要了解 SELinux 策略如何在 D-Bus 中提供细粒度的访问控制。
如何操作……
要通过 SELinux 控制 D-Bus 消息流,请执行以下步骤:
-
确定我们感兴趣的 D-Bus 服务所属应用的域。对于
dnsmasq应用来说,这将是dnsmasq_t:~# ps -eZ | grep dnsmasq | awk '{print $1}' system_u:system_r:dnsmasq_t:s0-s0:c0.c1023 -
确定希望向服务发送消息的应用的域。例如,这可以是
sysadm_t用户域。 -
允许这两个域通过 D-Bus 消息相互交互,方法如下:
gen_require(` class dbus send_msg; ') allow sysadm_t dnsmasq_t:dbus send_msg; allow dnsmasq_t sysadm_t:dbus send_msg;
它是如何工作的……
当一个应用连接到 D-Bus 时,其连接的 SELinux 标签会作为发送消息时检查的标签。由于这种连接没有过渡,因此连接的标签就是进程本身的上下文(域);因此在示例中选择了 dnsmasq_t。
当 D-Bus 接收到发送消息到服务的请求时,D-Bus 会检查 SELinux 策略中的 send_msg 权限。它通过将会话的信息(源和目标上下文以及请求的权限)传递给 SELinux 子系统,来计算是否应允许访问。然而,访问控制本身并不由 SELinux 强制执行(它只是提供反馈),而是由 D-Bus 自身执行,因为管理消息流是 D-Bus 的责任。
这也是为什么在开发 D-Bus 相关策略时,需要在策略模块中显式提及类和权限。没有这一点,开发环境可能会报错,声称 dbus 不是有效的类。
D-Bus 检查发送消息的客户端的上下文以及服务的连接上下文(这两个都是域标签),并查看是否允许send_msg权限。由于大多数通信是双向的(发送消息然后接收回复),因此权限在两个方向上都会被检查。毕竟,发送回复本质上就是在相反方向上发送消息(从策略角度来看)。
如果规则位于用户域中,可以使用dbus-send验证此行为。例如,要查看服务提供的对象,可以对该服务调用 D-Bus introspection:
~# dbus-send --system --dest=uk.org.thekelleys.dnsmasq --print-reply /uk/org/thekelleys/dnsmasq org.freedesktop.DBus.Introspectable.Introspect
当 SELinux 没有正确的send_msg允许规则时,D-Bus 会在其服务日志中记录以下错误(但不会出现 AVC 拒绝,因为不是 SELinux 子系统拒绝了访问):
Error org.freedesktop.DBus.Error.AccessDenied: An SELinux policy prevents this sender from sending this message to this recipient. 0 matched rules; type="method_call", sender=":1.17" (uid=0 pid=6738 comm="") interface="org.freedesktop.DBus.Introspectable" member="Introspect" error name="(unset)" requested_reply="0" destination="uk.org.thekelleys.dnsmasq" (uid=0 pid=6635 comm="")
当策略允许send_msg权限时, introspection 会返回一个 XML 输出,显示该服务提供的方法和接口。
还有更多…
当前的 D-Bus 实现是纯用户空间实现。随着越来越多的应用程序依赖于 D-Bus,正在进行创建基于内核的 D-Bus 实现,即 kdbus。该项目的具体实现细节尚未完成,因此尚不清楚目前适用于 D-Bus 的 SELinux 访问控制是否仍然适用于 kdbus。
限制服务所有权
在总线注册的应用程序拥有服务名称。uk.org.thekelleys.dnsmasq 服务名称就是其中一个例子。D-Bus 策略在 /etc/dbus-1/system.d/(如果服务用于会话总线而不是系统总线,则为session.d/)中的 busconfig XML 文件中声明,为 D-Bus 提供信息以决定何时允许获取特定服务的所有权。
多亏了 D-Bus 的 SELinux 集成,可以添加额外的约束,确保只有授权的应用程序才能拥有特定服务。
如何执行…
要通过 SELinux 策略限制服务所有权,请按照以下步骤操作:
-
在服务的 D-Bus 配置文件中,确保
own权限得到了适当保护。例如,确保只有rootLinux 用户可以拥有该服务:<policy user="root"> <allow own="uk.org.thekelleys.dnsmasq" /> </policy> -
如果运行时服务帐户可能不同,则也可以声明
group=参数,而不是user=参数。 -
接下来,声明与服务关联的标签:
<selinux> <associate own="uk.org.thekelleys.dnsmasq" context="dnsmasq_t" /> </selinux> -
在 SELinux 策略中,声明哪些域可以获取该服务:
gen_require(` class dbus acquire_svc; ') allow dnsmasq_t self:dbus acquire_svc;
它是如何工作的…
D-Bus 配置允许管理员定义何时可以获取特定服务的所有权。大多数服务会定义允许拥有服务的用户(或组),如示例所示。但对于系统服务,仅声明 Linux 根用户可以拥有特定服务显然不够精细。
进入 SELinux。通过在 busconfig XML 文件中的关联定义,D-Bus 会告知任何尝试拥有该特定服务的应用程序域必须具备在所述上下文中对 dbus 类的 acquire_svc 权限。
通过这种方法,管理员可以确保即使其他域以 Linux root 用户身份运行,也不允许拥有该服务。
尽管通常方法是为目标标签要求应用程序本身的上下文,但也可以使用不同的上下文。例如,可以声明一个新的类型,如 dnsmasq_dbus_t,然后将 SELinux 策略设置为如下:
allow dnsmasq_t dnsmasq_dbus_t:dbus acquire_svc;
还有更多……
D-Bus 应用程序在 /etc/selinux/mcs/contexts/ 目录下有一个配置文件,文件结构相同,名为 dbus_contexts。这是 D-Bus 所有权的默认上下文定义(如果无法通过其他方式推断,则应使用哪个上下文)。默认情况下,D-Bus 已经完全意识到需要使用哪些上下文,因此不再提供任何 SELinux 特定的设置,并且不建议再修改此文件。
然而,了解该文件的存在及其使用仍然很有用,特别是当 D-Bus 在容器、chroot 或其他环境中执行时,因为如果该文件缺失,D-Bus 会抱怨:
Failed to start message bus: Failed to open "/etc/selinux/mcs/contexts/dbus_contexts": No such file or directory
如果需要禁用 D-Bus 中的 SELinux 支持(但不重新构建 D-Bus),则编辑 /etc/dbus-1/system.conf 和 session.conf 文件,并删除以下行:
<include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
了解 udev 的 SELinux 集成
udev 设备管理器负责在 /dev/ 结构内处理设备文件的变动。由于许多设备文件具有不同的上下文,如果没有 SELinux 感知,udev 策略就需要通过许多命名文件转换来增强。例如,对于设备 /dev/mydevice 转换为 mydevice_t 类型的命名文件转换如下所示:
dev_filetrans(udev_t, mydevice_t, chr_file, "mydevice")
然而,当需要为 /dev/mydevice1、/dev/mydevice2 等设备文件打上标签时,每个可能的名称都需要在策略中迭代(命名文件转换不支持正则表达式)。幸运的是,udev 具有 SELinux 感知能力,因此无需为每个设备文件创建策略增强。
这个方法向我们展示了何时需要额外的策略增强,何时不需要。
如何做……
要了解 udev 的 SELinux 集成如何工作,可以遵循以下决策标准:
-
每当 udev 在具有
device_t标签的目录中创建设备文件时,如果目标类型被赋予device_node属性,则 udev 会根据 SELinux 子系统通过file_contexts定义的标签自动为设备文件打上标签。 -
如果父目录未使用
device_t类型,则确保 udev 对该目标类型具有管理权限。 -
如果目标文件上下文没有与
device_node属性相关联,则授予 udev 适当的relabelto权限。 -
如果 udev 的规则配置为创建符号链接,则需要确保链接的标签保持为通用的
device_t类型。
它是如何工作的……
udev 应用程序是一个标准的 SELinux 感知应用程序,它通过查询上下文定义与 SELinux 用户空间交互,或者通过查询的上下文创建新的设备文件,或在之后重新标记设备文件。
通过查询上下文定义(而不是依赖 SELinux 策略),管理员可以轻松修改不同设备名称的规则,或者为新设备类型提供支持,而无需增强与 udev_t 相关的策略。管理员只需要配置适当的文件上下文定义:
~# semanage fcontext -a -t mydevice_t -f -c /dev/mydevice[0-9]*
然而,如果目标设备类型(mydevice_t)没有与 device_node 属性关联,那么 udev_t 将无法重新标记该设备类型。这个属性对于 udev_t 的支持至关重要,因为它通过该属性对所有设备节点具有重新标记(和管理)权限。
如果一个 udev 规则要求创建一个未与 device_node 属性关联的设备文件(或者是其他文件——请求的文件不一定是设备文件),那么如果默认的上下文关联(即通过父目录的类型继承)不足以满足要求,则需要更新 SELinux 策略。
出于同样的原因,符号链接必须保持为 device_t,因为 SELinux 策略不处理符号链接的不同类型。
当然,udev 中的 SELinux 支持也有其影响,尤其是在设备文件在 udev 管理之外创建时。如果是这种情况,管理员必须确保文件的标签被正确修正,因为错误的设备类型可能导致系统故障。
一个常见的方法是重新标记整个 /dev/ 结构(这通常是通过分发版的 init 脚本在初始 RAM 文件系统或 devtmpfs 挂载中对默认设备文件创建及其默认的 device_t 类型进行处理):
~# restorecon -R /dev
使用 cron 与 SELinux
另一个 SELinux 感知应用程序的例子是 cron。实际上是多个 cron 实现,因为并不存在单一的 cron 应用程序。常见的 cron 实现包括 vixie-cron、cronie 和 fcron。
cron 实现调用命令时,会以特定的 Linux 用户身份执行这些命令。由于这些命令并不是一成不变的(cron 的主要目的就是允许为特定用户甚至系统本身执行任何命令),因此无法轻易创建出足够细粒度的策略来支持 cron 提供的所有功能。毕竟,对于 SELinux 本身来说,cron 为某个用户或另一个用户调用命令并没有区别:涉及的只是 cron 域(crond_t)和命令的目标类型(例如 bin_t)。
由于这个原因,许多 cron 实现都已支持 SELinux,使得 cron 实现能够选择正确的目标上下文。
如何操作…
为了正确与支持 SELinux 的 cron 交互,必须按照以下步骤操作:
-
确保 crontab 文件正确标记:用户 crontab 使用
user_cron_spool_t,系统 crontab 使用system_cron_spool_t。 -
检查
/etc/selinux/mcs/contexts/default_contexts或/etc/selinux/mcs/contexts/users/*以获取system_r:crond_t域的目标上下文。 -
让 crontab 文件上下文作为目标域的入口点。例如,如果用户的目标域是其自身的用户域(如
user_t),则user_cron_spool_t必须作为user_t的入口点。 -
如果用户作业的目标域是用户域,则将
cron_userdomain_transition布尔值设置为on;如果目标域应为cronjob_t域,则将其设置为off。
它是如何工作的…
当 cron 支持 SELinux 时,确保它运行在 crond_t 域内至关重要。它的内部 SELinux 代码将查询 SELinux 策略,以查看通过应用程序的用户的目标域是什么,如果 cron 没有在 crond_t 域中运行,则此查询将无法返回正确的域集:
~# ps -efZ | grep fcron | awk '{print $1}'
system_u:system_r:crond_t:s0-s0:c0.c1023
在从 cron 启动用户作业之前,cron 应用程序会检查用户 crontab 文件的文件上下文。然后使用这个文件上下文来查看用户作业的目标域是否将用户 crontab 文件上下文作为入口点。
要了解当前的目标域是什么,我们可以使用 getseuser 辅助应用程序:
~# getseuser hannah system_u:system_r:crond_t:s0
seuser: user_u
Context 0 user_u:user_r:cronjob_t:s0
在这种情况下,目标域是 cronjob_t。可以通过 default_contexts(或用户特定上下文)文件来确认这一点:
~# grep crond_t /etc/selinux/mcs/contexts/users/user_u
system_r:crond_t user_r:cronjob_t
如果目标域应为用户域,则需要切换正确的布尔值并相应地调整上下文文件:
~# setsebool cron_userdomain_transition on
~# grep crond_t /etc/selinux/mcs/contexts/users/user_u
system_r:crond_t user_r:user_t
知道目标域后,最后需要的就是确保用户 cron 作业文件上下文作为该域的入口点,这通常是大多数 cron 实现会检查的一种访问控制:
~# sesearch -s user_t -t user_cron_spool_t -c file -p entrypoint -A
Found 1 semantic av rules:
allow user_t user_cron_spool_t : file entrypoint ;
还有更多…
并非所有的 cron 实现都支持 SELinux。如果实现不支持 SELinux,则所有 cron 作业将都在一个单一的 cron 作业容器中运行(用户 cron 作业使用 cronjob_t,系统 cron 作业使用 system_cronjob_t),并且使用 system_u SELinux 用户和 system_r SELinux 角色。
通过编程方式检查 SELinux 状态
如果需要创建一个支持 SELinux 的应用程序,可以使用多种编程语言。libselinux 包通常提供多个编程和脚本语言的绑定。在接下来的示例中,将使用 C 编程语言作为示例实现。
支持 SELinux 的第一步是检查 SELinux 的状态。在这个示例中,我们将展示如何创建一个与 libselinux 库链接并检查 SELinux 状态的应用程序。
准备工作
由于我们要更新一个 C 应用程序,这一系列步骤假定你具有基本的 C 编程知识。本书下载包中可以找到一个示例 C 应用程序,使用了此(以及其他)食谱中的所有输入。
如何操作…
为了链接libselinux并检查当前的 SELinux 状态,可以使用以下步骤:
-
创建一个 C 应用程序代码文件,并通过编译器指令引用 SELinux 头文件:
#ifdef SELINUX #include <selinux/selinux.h> #include <selinux/av_permissions.h> #include <selinux/get_context_list.h> #endif -
在应用程序中,如果不需要内建 SELinux 支持(即编译器指令未设置时),则相关的 SELinux 函数调用应返回
success:int selinux_prepare_fork(char * name) { #ifndef SELINUX return 0; #else … #endif }; -
在 SELinux 函数内部,使用
is_selinux_enabled()函数调用检查 SELinux 是否启用:int rc; rc = is_selinux_enabled(); if (rc == 0) { … // SELinux is not enabled } else if (rc == -1) { … // Could not check SELinux state (call failed) } else { … // SELinux is enabled }; -
添加一个检查,看看 SELinux 是处于宽容模式还是强制模式。当然,只有在启用 SELinux 时,才需要进行此检查:
rc = security_getenforce(); if (rc == 0) { … // SELinux is in permissive mode } else if (rc == 1) { … // SELinux is in enforcing mode } else { … // Failed to query state }; -
在与
libselinux链接时构建应用程序:~# gcc -o test -DSELINUX -lselinux test.c
它是如何工作的…
libselinux库提供了所有必要的功能,以便应用程序查询 SELinux 并与 SELinux 子系统进行交互。当然,在开发应用程序时,仍然需要确保 SELinux 支持是一个编译时的可选项:并非所有 Linux 系统都启用了 SELinux,因此,如果应用程序默认与libselinux链接,那么所有目标系统都需要安装必要的依赖项。
但即便是与libselinux链接的应用程序,也必须能够支持 SELinux 已禁用的系统;因此,需要使用is_selinux_enabled()来检查 SELinux 状态。
然而,这个is_selinux_enabled()函数并不会返回其他信息(例如,加载了哪个策略)。要检查 SELinux 是否在宽容模式下运行,可以使用security_getenforce()进行调用。
一个定义良好的应用程序应当使用此状态来调整其行为:如果应用程序运行在宽容模式下,则应尽量避免在应用逻辑中强制执行 SELinux 策略相关的决策。
参考之前食谱中的 cron 示例:如果 crontab 文件的上下文未知,无法作为所选域的入口点,则应用程序应该记录这一点,但仍继续工作(因为模式设置为宽容模式)。遗憾的是,大多数支持 SELinux 的应用程序并未根据 SELinux 的宽容状态改变其行为,仍可能像在强制模式下运行一样失败(或执行不同的逻辑)。
还有更多…
还有其他类似的方法可以用来查询 SELinux 状态。
例如,is_selinux_mls_enabled()方法返回一个值,指示 SELinux 是否在 MLS 模式下运行。这很有用,因为某些与上下文相关的方法在启用 MLS 时需要级别信息,因此可能需要查询状态并根据 MLS 状态更改方法调用。
与security_getenforce()类似的函数是security_setenforce()。从名称可以推测,这允许应用程序切换 SELinux 的强制模式。当然,只有当应用程序所在的域具备适当的 SELinux 权限时,这才是可能的。
在 C 中查询 SELinux 用户空间配置
在本示例中,我们将查询 SELinux 用户空间,以根据当前进程的上下文为给定用户获取默认上下文。进程负责事先收集该用户的 Linux 用户名。
如何实现…
按如下方式查询 SELinux 配置:
-
获取进程的当前上下文:
char * curcon = 0; rc = getcon(&curcon); if (rc) { … // Getting context failed if (permissive) { … // Continue with the application logic, ignoring SELinux stuff } else { … // Log failure and stop application logic }; }; -
获取 Linux 用户名(假设存储在
name变量中),并获取 SELinux 用户:char * sename = 0; char * selevel = 0; rc = getseuserbyname(name, &sename, &selevel); if (rc) { … // Call failed. Again check permissive state … // and take appropriate action. freecon(curcon); }; -
现在,根据获取到的 SELinux 用户(
sename)和当前上下文(该方法通过NULL变量自身处理)获取默认上下文:char * newcon = 0; rc = get_default_context(sename, NULL, &newcon); if (rc) { … // Call failed. Again check permissive state … // and take appropriate action. freecon(curcon); };
它的工作原理…
在第一个代码块中,通过getcon()方法获取当前进程的上下文。对于这个示例的最终结果,显式地获取当前上下文并非必须——稍后调用的get_default_context()方法会根据当前上下文做出决定(通过第二个参数,这在本示例中是NULL)。然而,知道当前上下文对于日志记录以及查询 SELinux 策略本身来说是非常重要的(就像我们将在下一个示例中所做的那样)。
下一步是根据给定的 Linux 用户获取 SELinux 用户。sename(SELinux 用户)和selevel(SELinux 敏感度)变量由getseuserbyname()方法填充,传入 Linux 用户名(它是一个常规的char *变量)。
最后,当 SELinux 用户可用时,调用get_default_context()获取默认上下文,并将其存储到第三个参数(newcon)中。如果需要从与当前上下文不同的上下文中获取默认上下文,那么第二个参数就不应是NULL,而应该是要查询的上下文:
rc = get_default_context(sename, curcon, &newcon);
还有更多…
在 SELinux 感知的应用程序中,其他一些方法可能也很有趣。
比如,getprevcon()方法返回的是进程的先前上下文,而不是当前上下文。这个先前的上下文通常是父进程的上下文,虽然对于可以执行动态转换的应用程序来说,这也可能是当前进程的先前上下文。
这些信息也可以从/proc/文件系统中获取,在进程的attr/子目录下可以查看current和prev文件:
~$ id -Z
staff_u:staff_r:staff_t:s0
~$ newrole -r sysadm_r
Password:
~$ id -Z
staff_u:sysadm_r:sysadm_t:s0
~$ cat /proc/$$/attr/current
staff_u:sysadm_r:sysadm_t:s0
~$ cat /proc/$$/attr/prev
staff_u:staff_r:newrole_t:s0
如上所示,运行newrole切换角色后,进程所在的最后一个域是newrole_t域(然后执行域和角色的转换,切换到当前的上下文)。
允许执行动态转换(即不需要启动新命令)的应用程序,可以使用setcon()方法从当前上下文切换到新上下文。
get_default_context()方法也是一组更大方法家族的一部分。例如,当用户分配了多个角色时,特定转换可能允许多个上下文。get_ordered_context_list()方法返回支持的上下文列表(而get_default_context()方法只返回第一个)。通过使用get_ordered_context_list_with_role()方法,可以通过角色过滤出特定上下文。
在启用 MLS 的系统上,get_default_context_with_level()或get_default_context_with_rolelevel()将对结果上下文应用指定的级别。
另一个可用的方法是get_default_type()方法,它返回给定角色的默认类型。与其他方法一样,这会导致 SELinux 代码查询位于/etc/selinux/中的配置文件;在此特定情况下,是位于/etc/selinux/mcs/contexts/中的default_type文件。
从代码角度审查 SELinux 子系统
为了查询 SELinux 策略,我们已经看到了如何使用sesearch命令和其他 SELinux 工具。从代码角度来看,SELinux 策略可以通过security_compute_av_flags方法查询。
准备工作
curcon和newcon变量可以通过诸如getcon()(获取当前上下文)或get_default_context()(如我们在前面的示例中所见)等方法填充。
如何实现…
作为示例,我们希望查询两个进程域之间的转换权限。为此,使用以下方法:
-
首先,调用
security_compute_av_flags()方法:struct av_decision avd; rc = security_compute_av_flags(curcon, newcon, SECCLASS_PROCESS, PROCESS__TRANSITION, &avd); if (rc) { … // Method failed. freecon(curcon); freecon(newcon); }; -
现在阅读响应:
if (!(avd.allowed & PROCESS__TRANSITION)) { … // Transition is denied }; -
检查当前上下文是否为宽容域:
if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) { … // Domain is permissive };
它是如何工作的…
security_compute_av_flags()方法是 C 语言中与sesearch(粗略来说)等效的方法。它接受源和目标上下文、类别和权限,并将查询结果存储在一个特定的结构体中(struct av_decision)。
类别和权限条目可以从flask.h(类别声明)和av_permissions.h(权限声明)头文件中获取,这些头文件位于/usr/include/selinux/目录下。
查询结果是通过检查权限是否出现在决策结果中来获得的。
除了权限查询,另一个需要验证的重要方面(而这一点通常会被 SELinux 意识到的应用忽略)是检查域本身是否标记为宽容模式。毕竟,即使在启用了 SELinux 并且 SELinux 处于强制模式的系统上,某些域仍然可以标记为宽容模式。
SELINUX_AVD_FLAGS_PERMISSIVE标志是添加到查询响应中的标志(struct av_decision),它允许开发人员查询域的宽容状态。掌握这些信息后,SELinux-aware 应用程序仍然可以决定继续进行,即使策略拒绝某些活动,就像用户请求的那样。
还有更多…
还有其他方法可以查询 SELinux 策略,这些方法可能会被支持 SELinux 的应用程序使用。
例如,使用selinux_check_access(),应用程序可以查询 SELinux 策略,查看给定的源上下文是否对目标上下文的特定类和权限具有访问权限。这与security_compute_av_flags()不同,因为该方法使用类和权限的字符串,并且根据 SELinux 的强制执行状态或特定域的宽容性返回不同的结果。
在新上下文中运行新进程
有时候,在调用新任务或进程时,无法强制指定特定的域。只有在源域和文件上下文(应用程序或任务执行的上下文)对目标上下文的决定性非常明确时,才能通过 SELinux 策略启用默认的过渡规则。
在可以为不同目标域运行相同命令(或以相同上下文执行命令)的应用程序中,支持 SELinux 至关重要。
本示例将展示如何强制为新进程指定特定域。
准备就绪
本示例中使用的newcon变量可以通过像get_default_context()这样的方式来填充,正如我们在之前的示例中看到的那样。
它是如何做的…
要在特定上下文中启动进程,请执行以下步骤:
-
告诉 SELinux 新上下文应该是什么:
int rc = setexeccon(newcon); if (rc) { … // Call failed freecon(newcon); }; -
分叉并执行命令。例如,要执行
id -Z,可以使用以下代码:pid_t child; child = fork(); if (child < 0) { … // Fork failed } else if (child == 0) { int pidrc; pidrc = execl("/usr/bin/id", "id", "-Z", NULL); if (pidrc != 0) { … // Command failed }; } else { … // Parent process int status; wait(&status); };
它是如何工作的…
希望新执行任务在特定上下文中运行的应用程序,需要告知 SELinux 子系统,下一个execve、execl或其他exec*方法应该导致子进程在新域中运行。
当然,尽管政策中不再需要自动域过渡(因为这需要明确的决定,而如果源域和文件上下文对不同目标上下文相同时,这是不可能的),但 SELinux 策略仍然必须允许过渡策略。
allow crond_t self : process setexec;
allow crond_t staff_t : process transition;
setexec权限允许源域明确告诉 SELinux 子系统任务应该在哪个上下文中运行。没有此权限,setexeccon()调用将失败。
还有更多…
setexeccon()方法有一个类似的方法叫做getexeccon()。该方法返回执行新进程时将被分配的上下文(这将验证上一次setexeccon()调用的结果)。
另一个类似的方法是setexecfilecon()方法。该方法允许支持 SELinux 的应用程序在处理基于文件的过渡信息时,考虑 SELinux 策略的决策。因此,如果执行特定文件时已知的域过渡存在,则会遵循该域过渡。如果没有,则使用setexecfilecon()方法提供的回退类型:
char * fallbackcon = "system_u:object_r:openscap_helper_script_t:s0";
char * filename = "/usr/libexec/openscap/probe_process";
…rc = setexecfilecon(filename, fallbackcon);
在此示例中,如果probe_process文件的上下文在 SELinux 策略中被用来在当前应用程序调用时创建自动域转换,则该目标域将用于应用程序执行。然而,如果probe_process文件的上下文是不会触发任何自动域转换的,那么fallbackcon上下文将用于下一个应用程序执行。
读取资源的上下文
当然,如果应用程序是 SELinux 感知的,获取资源的上下文也很重要。这可能是为了日志记录,或决定要转换到哪个域(基于资源上下文、当前上下文、用户名等)。
如何执行...
要读取资源的上下文,可以使用以下方法:
-
给定一个文件路径,以下调用
getfilecon()将提供文件的上下文:security_context_t filecon = 0; char * path = "/etc/passwd"; rc = getfilecon(path, &filecon); if (rc < 0) { … // Call failed }; … // Do stuff with the context freecon(filecon); -
要获取一个进程的上下文,假设
pid变量(类型为pid_t)已经包含正确的进程 ID,使用以下代码:security_context_t pidcon = 0; rc = getpidcon(pid, &pidcon); if (rc < 0) { … // Call failed }; … // Do stuff with the context freecon(pidcon);
它是如何工作的...
SELinux 库提供了多种方法来获取资源的上下文。文件和进程类型在示例中有展示,但也有其他方法。例如,使用fgetfilecon()方法,可以获取文件描述符的上下文。所有这些方法都会以标准字符串(char *)格式返回上下文。
在获取资源的上下文后,当不再使用该上下文时,释放它非常重要。否则,应用程序将发生内存泄漏,因为没有其他方法清理上下文。
还有更多...
当使用标记化网络(例如,支持 CIPSO/NetLabel 或标记化 IPSec)时,可以使用getpeercon()方法获取参与通信会话的对等方的上下文。
在查询上下文的同时,也可以告诉 SELinux 子系统文件创建应该立即使用特定上下文创建该文件。为此,可以使用setfscreatecon()方法——这是近期 udev 版本在/dev/中创建新设备文件时使用的方法。


浙公网安备 33010602011771号