SELinux

1. 引言

安全增强式 Security-Enhanced Linux(SELinux)是一个在内核中实践的强制访问控制(MAC)安全性机制。SELinux 首先在 CentOS 4 出现,并在后续的CentOS发行版中获得重大改善。这些改善代表用 SELinux 解决问题的方法亦随着时间而改变。

1.1. 部份问题所在

要更了解 SELinux 为何是重要、及能够为你做什么,最简单的方法就是参考一些例子。在未启用 SELinux 的情况下,要控制用户的文件访问权,唯有通过酌情访问控制(DAC)方法如文件权限或访问控制清单(ACL)。不论用户或程序都可以将不安全的文件权限赋予其它人,或反过来访问系统在正常运作下无须访问的部份。举个例说:

基本上在传统 DAC 模式只在两个权限级别,root 及用户,而当中不能简易地实施最小权限的理念。很多由 root 引导1的进程在后期会撇除它们的权限并以受限制的用户身份来运行,有些则会在 chroot 的情况下执行,但这些安全措施都是酌情的。

1.2. 解决方案

SELinux 更能遵从最小权限的理念。在缺省的 enforcing 情况下,一切均被拒绝,接着有一系列例外的政策来允许系统的每个元素(服务、程序、用户)运作时所需的访问权。当一项服务、程序或用户尝试访问或修改一个它不须用的文件或资源时,它的请求会遭拒绝,而这个行动会被记录下来。

由于 SELinux 是在内核中实践的,应用程序无须被特别编写或重写便可以采用 SELinux。当然,如果一个程序特别留意稍后所提及的 SELinux 错误码,它的运作可能会更畅顺。假若 SELinux 拦阻了一个行动,它会以一个标准的(至少是常规的)「拒绝访问」类错误来汇报给该应用程序。然而,很多应用程序不会测试系统函数所返回的错误码,因此它们也许不会输出消息解释问题所在,或者输出错误消息。

理论上,下列样例方案可提供更高安全度:

不过截至第 6 版的 CentOS,这些方案都不包含在SELinux 规则内。这是一个发展中的领域,事实上亦不会在短期内落实。这是由于上游发行者的系统管理员客户群难以接受上述的做法,势必会「啼哭」一番,并增加上游的支持工作量。

2. SELinux 模式

SELinux 拥有三个基本的操作模式,当中 Enforcing 是缺省的模式。此外,它还有一个 targetedmls 的修饰语。这管制 SELinux 规则的应用有多广泛,当中 targeted 是较宽松的级别。

SELinux 的模式可以通过 Adminstration 选单里的 SELinux 图像管理界面、或者在命令行执行 system-config-selinux 来查看及更改(SELinux 图像管理界面是 policycoreutils-gui 组件的一部份,缺省是不会被安装的)。

较喜欢命令行的用户可使用 sestatus 这个指令来查看现时的 SELinux 状况:

# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 21
Policy from config file:        targeted

setenforce 这个指令可以即时切换 EnforcingPermissive 这两个模式,但请注意这些改动在系统重新开机时不会被保留。

要令改动过渡系统开机,请在 /etc/selinux/config 内修改 SELINUX= 这一行为 enforcing、permissive 或 disabled。例如:SELINUX=permissive。

注: 当你由 Diabled 切换至 PermissiveEnforcing 模式时,我们强烈推荐你重新引导系统并重新标签文件系统。

3. SELinux 政策

正如我们所说,SELinux 遵从最小权限这个理念;在缺省情况下一切均被拒绝,而系统的每部份有一个政策来允许它访问所需的功能。这个描述用来形容严格型政策最为贴切。不过要令这类政策适用于企业级 Linux 可能会应用到的各个环境,编写方面是困难的。后果可能是 SELinux 会为系统管理员及用户产生很多问题,而系统管理员索性停用 SELinux 而不解决这些问题,最后还是违背了内置的保护。

SELinux 的设计允许调配不同类型的政策。CentOS 内的缺省政策是针对型政策,专门「针对」和规限选定的系统进程。CentOS 4 只定义了 15 个目标(包括 http、named、dhcpd、mysqld)。随后在 CentOS 5 这个数字已升超过 200 个目标。

系统内其它一切系统进程、用户程序和所有自制应用程序都在不受规限的本地下运行,并且不纳入 SELinux 的保护范围内。

这样做的目标之一,是要令每个已安装及在开机时执行的进程,都缺省在一个受规限的本地下运行。针对型政策在设计时尽量保护最多的主要进程而不会对用户的经验产生不利影响,所以多数用户甚至乎不应察觉 SELinux 正在运行中。

4. SELinux 访问控制

SELinux 拥有三种访问控制方法:

所有进程及文件都拥有一个 SELinux 的安全性脉络。让我们查看 Apache 的主页,/var/www/html/index.html 的 SELinux 安全性脉络来看看它们如何运作:

$ ls -Z /var/www/html/index.html -rw-r--r--  username username system_u:object_r:httpd_sys_content_t /var/www/html/index.html 

注: -Z 这个标旗在多数工具内都可用来显示 SELinux 安全性脉络(例如:ls -Z、 ps axZ 等)。

除了标准的文件权限及拥有权,我们更可以看到 SELinux 脉络栏:system_u:object_r:httpd_sys_content_t。

这是建基于「用户:角色:类型:多层保障」。在上述例子里,「用户:角色:类型」栏都有显示,而「多层保障」是隐藏的。在缺省的针对型政策里,类型是用来实施「强制类型」的重要字段,在这里它是 httpd_sys_content_t。

现在让我们看看 Apache 网页服务器,httpd,这个进程的 SELinux 安全性脉络:

$ ps axZ | grep httpd
system_u:system_r:httpd_t        3234 ?        Ss     0:00 /usr/sbin/httpd

从类型栏我们看出 Apache 在 httpd_t 这个类型本地内运行。

最后,让我们看看位于我们的主目录内的一个文件的安全性脉络:

$ ls -Z /home/username/myfile.txt
-rw-r--r--  username username user_u:object_r:user_home_t      /home/username/myfile.txt

它的类型是 user_home_t,这是位于每个户主目录内的文件的缺省类型。

唯有相似的类型才可互相访问,因此以 httpd_t 运行的 Apache 可以读入拥有 httpd_sys_content_t 类型的 /var/www/html/index.html。由于 Apache 在 httpd_t 这个本地内运行但不属 username 这个用户,纵使 /home/username/myfile.txt 可供任何人读入,Apache 却不能访问该文件,因为它的 SELinux 安全性脉络并不是 httpd_t 类型。倘若 Apache 被人占用,又假设它仍未取得更改 SELinux 标签至另一个脉络的 root 权限,它将会不能引导 httpd_t 本地外的进程(藉此防止权限升级),或访问与 httpd_t 本地不相关的文件。

5. 排除 SELinux 疑难

你终有一天会被 SELinux 阻止你访问所需的东西,而且要解决这个问题。SELinux 拒绝某个文件、进程或资源被访问的基要原因有数个:

头三个情况我们可以处理,而发出警报及通告正正是第四个情况的预期表现。

日志档是排除任何疑难的关键,而 SELinux 亦不例外。SELinux 缺省会通过 Linux 审计系统 auditd 将日志写在 /var/log/audit/audit.log 内,而该务服缺省为启用的。假若 auditd 长驻程序并未运行,信息将会被写进 /var/log/messages。SELinux 的日志都以 AVC 这个关键字作标签,让 grep 等程序可轻易地把它们从其它信息中过滤出来。

由 CentOS 5 起,你可以用 SELinux 排除疑难工具协助你分析日志档,将它们转换为供人阅读的格式。这个工具包含一个以可读格式显示信息及解决方案的图像界面、一个桌面通报图示、与及一个长驻进程,setroubleshootd,它负责查阅新的 SELinux AVC 警告并传送至通报图示。不运行 X 服务器的用户可设置以电邮通报。SELinux 排除疑难工具是由 setroubleshoot 组件所提供的。这个工具可以从 X 窗口图像管理员的「系统」选单或从命令行引导:

sealert -b 

不运行 X 服务器的人可以通过命令行产生供人阅读的报告:

sealert -a /var/log/audit/audit.log > /path/to/mylogfile.txt 

5.1. 重新标签文件

chcon 这个指令可以用来更改一个或多个文件与目录的 SELinux 安全性脉络,正如 'chown' 或 'chmod' 可以用来更改一个文件的拥有者或标准权限。

让我们看一些例子。

就以 Apache 为例,假设你想修改 DocumentRoot 以另一个位置来伺服网页,替换缺省的 /var/www/html 目录。譬如说我们在 /html 创建了一个目录(又或者挂载点),然后在那里创建一个 index.html 档:

# mkdir /html
# touch /html/index.html
# ls -Z /html/index.html
-rw-r--r--  root root user_u:object_r:default_t        /html/index.html
# ls -Z | grep html
drwxr-xr-x  root root user_u:object_r:default_t        html 

我们可以见到 /html 这个目录以及 /html/index.html 这个文件都拥有缺省的 default_t 安全性脉络类型。如果我们打开浏览器并尝试查看该页,SELinux 将会正确地拒绝它们被访问并记录错误,因为该目录与文件拥有不正确的安全性脉络。我们须要设置供 Apache 使用的 httpd_sys_content_t 正确安全性脉络。

# chcon -v --type=httpd_sys_content_t /html
context of /html changed to user_u:object_r:httpd_sys_content_t
# chcon -v --type=httpd_sys_content_t /html/index.html
context of /html/index.html changed to user_u:object_r:httpd_sys_content_t
# ls -Z /html/index.html
-rw-r--r--  root root user_u:object_r:httpd_sys_content_t    /html/index.html
# ls -Z | grep html
drwxr-xr-x  root root user_u:object_r:httpd_sys_content_t    html 

我们同样也可以利用 -R 这个回递标旗同时将它们的脉络设置:

# chcon -Rv --type=httpd_sys_content_t /html 

以这个方式更改安全性脉络在系统重新开机后仍会获保留,直至该部份文件系统被重新标签。这个动作也算颇常,因此正确的解决方法,就是于测试后编写一条自定的规则(所谓的政策模块),并把它与本地的规则进行合并。它将会是原有的 200 多条规则外的一条规则。要作出永久性、能过渡文件系统重新标签的安全性脉络改动,我们可以采用 SELinux 管理工具,或者在命令行执行 semanage 这个指令:

semanage fcontext -a -t httpd_sys_content_t "/html(/.*)?" 

如此便会将 /html 以下的一切加入 httpd_sys_content_t 这个文件脉络类型。

5.2. 撤消缺省的安全性脉络

restorecon 这个指令可以用来撤消为文件缺省的安全性脉络。

让我们再次以 Apache 作为样例。设假有位用户在他的主目录内编辑了一个 index.html 档并将该文件迁移(mv)至 DocumentRoot 的 /var/www/html 内。纵使复制(cp)这个指令普遍会沿用目标目录或文件的安全性脉络,迁移(mv)指令则会保留源文件的安全性脉络。我们可以利用 chcon 这个指令来更改问题文件的安全性脉络,但由于这些文件已经位于 Apache 缺省的 DocumentRoot(/var/www/html)内,我们只须撤消这个目录或文件的安全性脉络便成了。要单单撤消 index.html 档的脉络,我们可以利用:

# restorecon -v /var/www/html/index.html 

如果要以回递的方式撤消整个目录的缺省安全性脉络:

# restorecon -Rv /var/www/html 

除此之外,如果我们只想检查 /var/www/html 目录内有哪些文件的安全性脉络需要被撤消,我们在采用 restorecon 时可以应用 -n 这个标旗来防止重新标签的行动:

# restorecon -Rv -n /var/www/html 

5.3. 重新标签整个文件系统

有时我们也许会需要将整个文件系统重新标签,虽然这是在启用已停用的 SELinux 时,或在 SELinux 政策由缺省的针对型改为严格型时才有真正需要。要在重新开机后自动将整个文件系统重新标签,请执行:

# touch /.autorelabel
# reboot 

假若一个系统在升级至 CentOS-5.2 时停用了 SELinux,然后 SELinux 被启用,重新标签整个文件系统时或许会失败。如果以上的步骤不能正确地将整个文件系统重新标签,请尝试先执行 genhomedircon 这个指令:

# genhomedircon
# touch /.autorelabel
# reboot 

5.4. 允许访问某个端口

我们或许会想容让 Apache 连结至某个非标准的端口并聆听对内的连接。SELinux 的政策缺省只允许每个服务访问被公认与该服务有关的端口。如果我们想容让 Apache 在 tcp 端口 81 上聆听,我们可以利用 semanage 这个指令来新增一条规则批准此事:

# semanage port -a -t http_port_t -p tcp 81 

你可以这样令 SELinux 完整地列出每个服务可访问的端口:

# semanage port -l 

6. 自定 SELinux 政策

通过设置选项的二元值,你可以微调 SELinux 政策而不必重新编译政策的源代码。这些选项包括允许用户在 Samba 下分享他们的主目录,或者允许 Apache 从用户的主目录伺服文件。否则这些都会被 SELinux 政策所拒绝。

有另一版 Wiki 页是关于二元值的。

7. 利用 audit2allow 创建自定 SELinux 政策模块

在某些情形下,上述方法都不能解决问题,而我们必须通过创建自定的政策模块来扩展 SELinux 政策,允许一组特定的状态出理。其中一个例子就是在 smtp 邮件服务器上增加 postgrey 服务。我们的 smtp 服务器须要通过一个 Unix 通讯端与 postgrey 沟通,但缺省的 SELinux 政策却禁止我们的 smtp 服务器如此做。结果该服务会被 SELinux 所封锁。这个问题不能通过更改或撤消文件的安全性脉络来解决,也没有可供切换二元值。我们可以通过一个二元值来停止 SELinux 保护 smtp 服务器,这样总比完全停用 SELinux 好,但不太理想。

如果我们将 SELinux 切换至 Permissive 模式并让邮件服务器运行一段时间,我们便可以在允许访问的情况下记录 SELinux 的问题。查看日志时,我们会看见以下 SELinux AVC 信息:

type=AVC msg=audit(1218128130.653:334): avc:  denied  { connectto } for  pid=9111 comm="smtpd" path="/var/spool/postfix/postgrey/socket"
scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket
type=AVC msg=audit(1218128130.653:334): avc:  denied  { write } for  pid=9111 comm="smtpd" name="socket" dev=sda6 ino=39977017
scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:object_r:postfix_spool_t:s0 tclass=sock_file 

接着我们可以用 audit2allow 来产生一组允许所需行动的政策规则。我们我可创建一个本地的 postgrey 强制类型政策档(postgreylocal.te):

# grep smtpd_t /var/log/audit/audit.log | audit2allow -m postgreylocal > postgreylocal.te
# cat postgreylocal.te
module postgreylocal 1.0;
require {
        type postfix_smtpd_t;
        type postfix_spool_t;
        type initrc_t;
        class sock_file write;
        class unix_stream_socket connectto;
}
#============= postfix_smtpd_t ==============
allow postfix_smtpd_t initrc_t:unix_stream_socket connectto;
allow postfix_smtpd_t postfix_spool_t:sock_file write; 

在上面我们看见如何从 audit.log 筛选有关 smtp 服务器的问题,并将这些问题导向 audit2allow,让它产生一组规则,是它认为可用来允许被 SELinux 政策所封锁的行动。查阅这些规则时,我们可发现该 smtp 服务器想连接及写进一个 Unix 通讯端,而从日志里我们看见这个 Unix 通讯端正正是 postgrey 服务所聆听的那个。既然这一些都合情合理,我们可以续继用 audit2allow 创建一个自定的政策模块,允许这些行动:

# grep smtpd_t /var/log/audit/audit.log | audit2allow -M postgreylocal 

接着我们利用 semodule 这个指令将我们的 postgrey 政策模块装入现有的 SELinux 政策内:

semodule -i postgreylocal.pp 

如此便会将我们的 postgrey 政策模块新增到 /etc/selinux/targeted/modules/active/modules/postgreylocal.pp。我们可以通过 semodule -l 来检查该政策模块已被正确地装入。

然后我们可以继续监视 SELinux 的日志来确定自定的政策模块有效用。满意时,我们便可以重新启用 SELinux 的 Enforcing 模式,让功能已全备的 smtp 服务器再次享有 SELinux 的保障。

7.1. 手动式自定政策模块

audit2allow 在大多数情况下都可以自动创建一个自定政策模块来解决某个特定问题,但有时它未能完全正确,而我们也许会想以人手编辑并编译该政策模块。就以下列的 AVC 审计日志为例:

Summary:
SELinux is preventing postdrop (postfix_postdrop_t) "getattr" to
/var/log/httpd/error_log (httpd_log_t).
Detailed Description:
SELinux denied access requested by postdrop. It is not expected that this access
is required by postdrop and this access may signal an intrusion attempt. It is
also possible that the specific version or configuration of the application is
causing it to require additional access.
Allowing Access:
Sometimes labeling problems can cause SELinux denials. You could try to restore
the default system file context for /var/log/httpd/error_log,
restorecon -v '/var/log/httpd/error_log'
If this does not work, there is currently no automatic way to allow this access.
Instead, you can generate a local policy module to allow this access - see FAQ
(http://fedora.redhat.com/docs/selinux-faq-fc5/#id2961385) Or you can disable
SELinux protection altogether. Disabling SELinux protection is not recommended.
Please file a bug report (http://bugzilla.redhat.com/bugzilla/enter_bug.cgi)
against this package.
Additional Information:
Source Context                system_u:system_r:postfix_postdrop_t
Target Context                root:object_r:httpd_log_t
Target Objects                /var/log/httpd/error_log [ file ]
Source                        postdrop
Source Path                   /usr/sbin/postdrop
Port                          <Unknown>
Host                          sanitized
Source RPM Packages           postfix-2.3.3-2
Target RPM Packages
Policy RPM                    selinux-policy-2.4.6-137.1.el5
Selinux Enabled               True
Policy Type                   targeted
MLS Enabled                   True
Enforcing Mode                Enforcing
Plugin Name                   catchall_file
Host Name                     sanitized
Platform                      Linux sanitized 2.6.18-53.1.21.el5 #1 SMP Tue
                              May 20 09:35:07 EDT 2008 x86_64 x86_64
Alert Count                   599
First Seen                    Wed Jul  2 08:27:15 2008
Last Seen                     Sun Aug 10 22:47:52 2008
Local ID                      c303a4ea-8e7a-4acc-9118-9cc61c6a2ec8
Line Numbers
Raw Audit Messages
host=sanitized type=AVC msg=audit(1218397672.372:352): avc:  denied  { getattr } for  pid=4262 comm="postdrop"
path="/var/log/httpd/error_log" dev=md2 ino=117005 scontext=system_u:system_r:postfix_postdrop_t:s0
tcontext=root:object_r:httpd_log_t:s0 tclass=file
host=sanitized type=SYSCALL msg=audit(1218397672.372:352): arch=c000003e syscall=5 success=no exit=-13 a0=2
a1=7fffd6febca0 a2=7fffd6febca0 a3=0 items=0 ppid=4261 pid=4262 auid=4294967295 uid=48 gid=48 euid=48 suid=48
fsuid=48 egid=90 sgid=90 fsgid=90 tty=(none) comm="postdrop" exe="/usr/sbin/postdrop"
subj=system_u:system_r:postfix_postdrop_t:s0 key=(null) 

就上述错误执行 audit2allow 并查阅所得的 postfixlocal.te 政策档时,我们会看见:

# grep postdrop /var/log/audit/audit.log | audit2allow -M postfixlocal
# cat postfixlocal.te
    module postfixlocal 1.0;
    require {
            type httpd_log_t;
            type postfix_postdrop_t;
            class dir getattr;
            class file { read getattr };
    }
    #============= postfix_postdrop_t ==============
    allow postfix_postdrop_t httpd_log_t:file getattr; 

我希望第一件引起你关注的事,就是 postdrop 为何须要访问 /var/log/httpd/error_log?这应该在我们预料之外,因此我们便要评估应否允许这个行动。我们有数个选择:我们可以忽略这个错误并容让 SELinux 继续封锁及记录这些访问的行动,又或者我们可以允许这些行动并创建 audit2allow 所推荐的自定政策模块。我们也可以选择编辑这个自定政策模块的 .te 档并防止审计这个特定错误,并让 SELinux 继续封锁该访问权。我们可以编辑 allow 这一行,并改为 dontaudit

    #============= postfix_postdrop_t ==============
    dontaudit postfix_postdrop_t httpd_log_t:file getattr; 

现在我们可以手动地编译及装入已修改的自定政策模块:

# checkmodule -M -m -o postfixlocal.mod postfixlocal.te
# semodule_package -o postfixlocal.pp -m postfixlocal.mod
# semodule -i postfixlocal.pp 

postdrop 访问 /var/log/httpd/error_log 时仍然会被 SELinux 封锁,但我们再不会在发生封锁时接收到警报,或在日志内填满错误信息。

8. 总结

这篇文章尝试向 SELinux 的新用户概述如何应用它。SELinux 缺省会被安装及启用,对多数用户来说,它会默默地提高系统的安全性。SELinux 适用于各类型的安装,包括:服务器、工作台、桌面计算机、及笔记本。

虽然 SELinux 对不熟识它的用户来说似乎很复杂及令人生畏,但这不是在安装时停用它的一个理由。若果 SELinux 确实带来问题,你可以简单地将它切换至 Permissive 模式,到时它便会将问题记录下来,而不会进行封锁。当问题出现时,这篇文章所示范的技巧可以用来排除疑难及解决它们。

9. 额外资源

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/

http://fedoraproject.org/wiki/SELinux

http://docs.fedoraproject.org/en-US/Fedora/13/html-single/Security-Enhanced_Linux/

http://danwalsh.livejournal.com/

10. 用户备注及陷阱

此部份由一位靠此页学懂 SELinux 的用户所提供。此文档是一个优越及详尽的资源。不过,它有点枯燥。它违漏了数个实际的重点,令我尝试正式做事时感到很沮丧。留意这里针对 CentOS 6。

  1. semanage 收录在 policycoreutils-python 这个组件内,它缺省是不被安装的。留意 policycoreutils 是另一个组件。CentOS 7 似乎已经缺省安装 semanage。它依然位于 policycoreutils-python 组件内。

  2. 在管理系统时,寻找适用的脉络是困难的。其中一个初始化点是 ls -Z。查看一个组件所安装的目录及数据,然后复制所采用的脉络。下一个工具是 seinfo -t,它会列出系统现正应用的所有脉络。grep 你的应用程序的名称。

  3. 某些情况可通过运用 public_content_rw_t 脉络来解决。一位用户有个目录要由 NFS、Samba 及 Apache 共享。此脉络允许这样做。它也可构成一个安全性漏洞,因此在重视安全的系统上请特别留意。

  4. 不要忘记 chcon 的 -t 参变。它单单设置脉络种类,这也是你普要做的事情,而且较指定 ls -Z 所汇报的整句符串更为容易。

  5. 应用 audit2allow 比这里的介绍来得简单。当两个脉络出现冲突时,请从 audit.log 找出错误信息并将它们放入独立的文本内。然后这样把错误传给 audit2allow

audit2allow -M mynewpolicyname <errors.txt

module mynewpolicy 1.0;

require {
        type spamc_t;
        type postfix_pipe_tmp_t;
        class file { read write };
}

#============= spamc_t ==============
allow spamc_t postfix_pipe_tmp_t:file { read write };

Translation of revision 36

zh/HowTos/SELinux (last edited 2015-06-27 02:36:43 by TimothyLee)