服务器架设篇 - RockyLinux 9

第三章、SELinux 初探

SELinux 不是防火墙,大致的目的是在保护由于自己 server 的用户耍白痴可能造成的错误!

最近更新时间: 2023/08/04

说到 SELinux 许多人都很恨它,因为设置方面真的是很点困扰,而且经常还有许多的『例外』状况需要处理。 不过,近期以来的 SELinux 应该算是比较稳定些,而且,大概只要知道几个小细节,就很容易找到处理方向。 只是,如果想要用 Linux 进行类似专题、项目开发的话,其实,暂时转成宽容模式,大概还是需要的...。 总之,对于正规互联网服务或者是管理企业内部员工的权限来说,这东西目前还是很有用途的! 所以,还是得要让我们了解一下这东西才行!

请注意,这一章的所有练习环境,都在前一章节创建的 VM 里面实做喔!不要在 KVM host 里面动作喔!

3.1、SELinux 简介

SELinux 全名其实是『 Security Enhanced Linux 』的意思,这家伙最早是由美国国家安全局开发出来的, 会想要做这个东西的原因,其实是早期 Unix 的系统中,如果你将某个目录设置成为 777 (drwxrwxrwx) 之后, 那么该目录就变成所有人都可以访问的情境!对于内部某些敏感数据来说,很可能由于人为的设置错误, 导致数据可能被第三人窃取或删除...管理员想哭都哭不出来的啦!

早期网络常常有人说:『ㄟ~我某个目录或文件,无法读取耶!怎么办啊?』很多人就会说,那你就 chmod -R 777 /some/dir 就好了。 结果...就造成管理员很大很大的困扰了!因为这种『攻击』不是来自于网络怪客,而是来自于自家的用户啊! 所以说,鸟哥自己认为, SELinux 主要管理的,应该算是自家的小白用户...

所以,SELinux 开发的目的,就是在防止上述的自由选定访问控制 (Discretionary access control, DAC) 造成的影响, 取而代之的,是利用强制访问控制 (Mandatory access control, MAC) 的方法来进行文件的访问。 在 MAC 的访问方法中,并没有所谓的 root 的用户概念,而是通过安全本文来限制进程的读写能力。

  • SELinux 的简单运作示意

另外,SELinux 并不是取代了传统的 rwx 权限,而是在通过文件权限的判定之后,『再加』一层防护, 该层 SELinux 的防护,可以『针对某些网络进程可以读取的文件之安全本文类型』进行限制, 因此,如果被读写目标文件的安全本文设计无法与相关进程匹配,那么该进程就无法读写目标文件了。

上面的说法其实很抽象,我们来画张图解释解释好了。如下图所示,要启动网络服务,总是得要运行某些脚本或程序。 这时 SELinux 就开始进行防护!如果你的设置档设计错误,无法符合当初 SELinux 指定的缺省规则 (rule), 那该脚本或程序就无法顺利加载到内存当中了 (蓝色箭头部份,很可能被 SELinux 抵挡)。若通过缺省规则而加载软件成为网络进程, 那该网络进程想要读取某些系统上面的文件时,如果网络进程与文件的安全本文设计不符,那也无法读到该文件! 这也是 SELinux 最主要限制的地方喔!(红色箭头部份)

SELinux 运作图标
图 3.1-1、SELinux 运作图标

这么限制的好处是,因为没有 root 的用户概念,因此,上图当中,你的 httpd process 被攻击而被破解了! 那也没关系,因为除了原本 httpd process 可以读写的位置之外,系统的其他目录,httpd 是没有访问权的! 举例来说, /etc 目录的安全本文与 httpd process 能读取的并不相同,因此你的 /etc/ 目录,就不可能被有问题的 httpd 访问了!这样起码能够作到一定程度的保护。

  • SELinux 在哪里?在 inode 纪录中

从上面的简单介绍,我们大概知道每个网络程序应该会有相对的 SELinux 安全本文,然后加载到内存之后, 会取得其相对的进程 SELinux 类型,而这个进程能不能读某个文件,也得要看该文件的安全本文类型才行。 那么,这些安全本文与进程的 SELinux 类型,是纪录在哪里呢?基本上,就是纪录在文件的 i-node 里面啦! 回想一下,当初你在救援系统时,如果有进入到 rd.break 这个救援环境,离开 chroot 时,是不是需要『 touch /.autorelabel 』呢? 这个动作不是会搞很久嘛?那就是 SELinux 要重建系统内所有文件的 SELinux 表头数据,而需要于各文件的 i-node 内重新创建 SELinux 安全本文的缘故! 现在懂了吧!

# 前往 VM 系统,查看一下 /usr/sbin/chronyd 的 SELinux 类型
[root@localhost ~]# stat /usr/sbin/chronyd
  File: /usr/sbin/chronyd
  Size: 353408          Blocks: 696        IO Block: 4096   regular file
Device: fd03h/64771d    Inode: 17318583    Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:chronyd_exec_t:s0
Access: 2023-04-17 07:40:35.000000000 +0800
Modify: 2023-04-17 07:40:35.000000000 +0800
Change: 2023-08-04 13:37:38.809963808 +0800
 Birth: 2023-08-04 13:37:38.806963845 +0800

如上所示,通过 stat 取出该文件名相关的纪录,最重要就是那个 Context 行,Context 就是安全本文啰! 因此,这个 chronyd 运行档,安全本文就是『system_u:object_r:chronyd_exec_t:s0』用冒号 (:) 隔开, 共分数个字段,现行主要的限制,其实仅有针对第三字段,也就是 chronyd_exec_t 那个项目而已! 所以,我们知道这个运行档的安全本文类型就是 chronyd_exec_t 的意思。除了这个 stat 之外,我们其实可以通过 ls 搭配 -Z 同样可以看到这个安全本文数据:

[root@localhost ~]# ll -Z /usr/sbin/chronyd
-rwxr-xr-x. 1 root root system_u:object_r:chronyd_exec_t:s0 353408 Apr 17 07:40 /usr/sbin/chronyd

图 3.1-1 可以看到,文件有安全本文,那么进程有嘛?其实也是有的! 观察进程的安全本文,可以通过 ps -Z 的参数来查找得到!

# 查出 chronyd 进程的 PID 含有的安全本文
[root@localhost ~]# ps auxZ | egrep 'chrony|LABEL'
LABEL                           USER   PID %CPU %MEM    VSZ   RSS TTY  STAT START TIME COMMAND
system_u:system_r:chronyd_t:s0  chrony 616  0.0  0.1  84436  3252 ?    S    16:02 0:00 /usr/sbin/chronyd -F 2

可以看到进程也是有安全本文相关数据,只是在 ps 里面被称为是标签 (LABEL) 而已。同样的, chronyd 进程的 SELinux 类型为 chronyd_t 喔!

3.2、SELinux 运作方式与安全本文

上面讲的是比较概念性的运作方法,实际上,SELinux 的运作,可以参考下列的流程表。 在传统的 DAC,亦即是传统的 rwx 权限访问控制的分析后,若进程可以进行访问,则开始进入到 SELinux 的控制流程中。 若进程无法访问,那当然就不会进入 SELinux 的控制流程啦!

在 SELinux 的控制流程中,一个主体 (subject) 想要取用某个目标 (Object) 时,得要经过完整的 SELinux 流程之后, 最终才能够实际读写到文件的数据 (file data)。所以,MAC 的目的是增加 DAC 的不足,而不是用来取代 DAC 喔! 事实上,还是得要先通过 rwx 传统权限的控制才行!

SELinux 运作图标
图 3.2-1、进程若要读取文件对象时,需要经过的SELinux关卡
  • 主体 (Subject):
    一般所谓的主体指的大部分就是进程,尤其是网络方面的进程,本章谈到的主体大部分都是进程。
  • 目标 (Object):
    主体进程能否访问『目标资源』的意思,一般目标指的大部分都是文件,不过,也有例外喔!例如, 主体可能会想要启动网络端口口 (port),这时目标就变成是端口口资源了!但是,大部分指的都是文件就是了。
  • SELinux 模式 (Mode):
    SELinux 根据打开与否,共有三种模式,分别是关闭 (Disabled)、宽容模式 (Permissive) 与强制模式 (Enforcing)。 根据上图的箭头,你可以知道 disabled, permissive 模式下,其实 SELinux 并不会真的进行进程访问的管制! 只有 enforcing 模式才会真的进行各个项目的管理!
  • 政策与规则 (Policy, rules, boolean):
    由于主题进程与目标文件数量庞大,因此 SELinux 会依据某些服务来制订基本的访问安全性政策。 这些政策内还会有详细的规则 (rule) 来指定不同的服务开放某些资源的访问与否,另外,某些功能是否放行 (SELinux boolean) 也是需要控制与了解的。在目前的 RockyLinux 9 里面主要提供 3 个政策,缺省为 targeted 政策。相关政策说明可以参考 /etc/selinux/config 的内容:
    • targeted:缺省政策,大部分针对网络进程,对本机进程 (如bash) 限制较少。
    • minimum:最小管制政策,修改 targeted 政策,保留仅有选择的进程才受管理。
    • mls (Multi Level Security protection):完整的 SELinux 管理,限制非常严格
  • 安全本文 (security context):
    每一个主体进程能够访问的安全本文并不相同,SELinux 会规范主体进程能够访问的安全本文类型, 当目标文件的安全本文为主体进程可访问的类型时,此时主体进程方可进行目标文件的访问。 这个安全本文 (security context) 有点像是 SELinux 的 rwx 的概念!如果 subject 要访问的 object 两者的安全本文无法匹配, 那在 enforcing 的模式下,这个访问就会出现权限不符的错误消息!此时,无论你的权限设置如何开放, 都无法让进程读取到文件喔!

在上面整体的说明中,重点在『主体』如何取得『目标』的资源访问权限!由上图我们可以发现, 进程的读写,还是需要先经过 rwx 的权限分析,如果 rwx 权限原本就被抵挡,那么后续 SELinux 流程就不会启动。 若通过 rwx 的权限且确定可以读写之后,则:SELinux 在取得主体进程与预备访问的目标资源之安全本文类型后, 开始搜索两者在 SELinux 当中的规则,若确认匹配可行,就予以放行,否则就予以抵挡。

  • 安全本文

再次强调,安全本文的纪录主要在文件的 inode 当中喔~进程也会有安全本文的纪录~让我们再次回想一下, 如何取得安全本文的数据呢?

# 进程的安全本文取得
[root@localhost ~]# ps -auxZ | egrep 'bash|chronyd|LABEL'
LABEL                                                 USER   ...  COMMAND
system_u:system_r:chronyd_t:s0                        chrony ...  /usr/sbin/chronyd -F 2
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root   ...  -bash

# 文件的安全本文观察
[root@localhost ~]# ll -Z /usr/sbin/chronyd /etc/chrony.keys
-rw-r-----. 1 root chrony system_u:object_r:chronyd_keys_t:s0    540 Aug 29  2022 /etc/chrony.keys
-rwxr-xr-x. 1 root root   system_u:object_r:chronyd_exec_t:s0 353408 Apr 17 07:40 /usr/sbin/chronyd

[root@localhost ~]# ll -Zd /root/anaconda-ks.cfg /home/vbird
drwx------. 2 vbird vbird unconfined_u:object_r:user_home_dir_t:s0  113 Aug  4 13:43 /home/vbird
-rw-------. 1 root  root  system_u:object_r:admin_home_t:s0        1204 Aug  4 13:05 /root/anaconda-ks.cfg

大部分只要记得前面三个字段就好!这三个字段的功能分别是:

User_Identify:role:type
SELinux用户别:角色:类型
  • SELinux 用户识别(User Identify):相当于帐号方面的身份,因为主要跟 SELinux 用户类别有关, 所以结尾都是 _u 的模样。可以粗分为两种类型:
    • system_u:表示系统进程方面的识别,大部分指的就是有被管理限制的情况
    • unconfined_u:表示没有限制的情况
  • 角色 (Role):通过角色字段,我们可以知道这个数据是属于进程、文件资源还是代表用户。一般的角色有:
    • object_r:代表的是文件或目录等文件资源,这应该是最常见的啰;
    • system_r:代表的就是有被管理限制的进程啦!
    • unconfined_r:表示没有限制的情况
  • 类型 (Type):在缺省的 targeted 政策中, User Identify 与 Role 字段基本上是不重要的! 重要的在于这个类型 (type) 字段!基本上,一个主体进程能不能读取到这个文件资源,与类型字段有关! 而类型字段在文件与进程的定义不太相同,分别是:
    • type:在文件资源 (Object) 上面称为类型 (Type);
    • domain:在主体进程 (Subject) 则称为领域 (domain) 了!

安全本文的类型数据相当多!稍等我们再来查找安全本文的限制!大致上先了解到这里即可。

3.3、SELinux 的三种模式

图 3.2-1 当中,我们可以知道 SELinux 共有三种模式,分别是 disabled, permissive 与 enforcing 三种,根据图标的样子,我们大概可以这样看这三种模式:

  • Disabled:其实就是关闭 SELinux,在这种模式底下,文件系统内的所有 inode 所纪录的 SELinux 安全本文会全部消失! 等于就是没有 SELinux 啦!所以,不要随便转到这种模式!
  • Permissive:宽容模式,根据图标,我们也知道宽容模式其实也没有实际进行主体与目标的管制啊! 那这个模式在干麻?仔细看图标,你会发现到宽容模式多了个箭头到 log 上头!那个 log 注册表, 大部分就是纪录到 /var/log/messages 里面去!这个宽容模式很常应用在 debug 当中!当出现主体无法取得目标时, 你可以将模式更改为 permissive,并且再次重现错误后,就可以到 log 去看一下有没有解决方案呢!
  • Enforcing:强制模式,主体会开始被 SELinux 政策、规则、功能条件、安全本文比对分析所管理,若达成匹配,就可以准备访问目标资源了。
除非你的 RockyLinux 主要用在内网,而且不会接触到互联网的情境,否则,尽量不要将 SELinux 调整成为 disabled。 这是因为 disabled 会将文件系统内的 inode 所纪录的安全本文全部删除,因为内核不支持了...因此, 若要重新激活 SELinux,你就得要重新开机,然后让 Linux 内核重新去文件系统里面,将全部的文件重新给予缺省的安全本文! 这会花费相当多时间!还记得『 touch /.autorelabel 』嘛?就是在做这个动作啦!
  • SELinux 模式的观察与转换

宽容模式与强制模式,可以在不重新开机的情况下直接进行转换!因此,想要判断某个主体无法访问目标的原因是 SELinux 或是传统权限时,就可以将 SELinux 模式暂时转到宽容模式去测试即可。那如何观察目前的 SELinux 模式呢? 使用 getenforce 即可。

# 观察目前的 SELinux 模式
[root@localhost ~]# getenforce
Enforcing

# 暂时将 SElinux 模式调整成为 permissive
[root@localhost ~]# setenforce [0|1]
[root@localhost ~]# setenforce 0
[root@localhost ~]# getenforce
Permissive

# 显示详细的 SELinux 设置值
[root@localhost ~]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   permissive
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      33

# 赶紧将模式转回 Enforcing
[root@localhost ~]# setenforce 1
  • SELinux 模式的设置方式

缺省的 SELinux 设置档为 /etc/selinux/config,但是,我们也可以在开机阶段,在 Linux 内核强迫打开或关闭 SELinux 的。 所以,要观察 SELinux 的初始设置,可能得要观察两个地方呢!分别是上述的设置档,还有 grub 的设置。 当然,你也可以直接观察目前的内核参数,以确认 SELinux 是否由内核参数所影响。

# 修改缺省的设置档,指定开机为 SELinux 模式
[root@localhost ~]# vim /etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted
# 其实,这个设置档就是指定 SELinux 模式与政策的文件

# 检查目前的内核是否有 SELinux 的参数
[root@localhost ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt3)/boot/vmlinuz-5.14.0-284.18.1.el9_2.x86_64 root=UUID=e81ba...
# 查找看看有没有 selinux=[0|1] 这个关键字符串!

# 检查 grub.cfg 有没有 selinux 的关键字
[root@localhost ~]# grep selinux /boot/grub2/grub.cfg
思考例题:你的 SELinux 似乎不是在 Enforcing 的阶段,如何检查并且修改,同时未来每次都生效呢?可以这样处理:
  • 使用 getenforce 观察目前你的系统使用哪种 SELinux 的模式?
  • 使用 setenforce [0|1] 搭配 getenforce 来观察 SELinux 的模式变化!
  • 观察 /etc/selinux/config 这个文件的内容,看如何设置缺省的 SELinux 模式。
  • 观察 /boot/grub2/grub.cfg 里面的设置,若有 selinux=0 亦代表缺省关闭 SELinux 的模式,而成为 disable 喔!

3.4、SELinux boolean 的观察与修改

图 3.2-1当中,进入整个 SELinux 的进程当中的第二关,除了得要参考政策内的规则外, 某些特定功能 (SELinux boolean) 是否启动,也是相当重要的!举例来说,看看网页服务器能不能提供一般帐号家目录的读取权限, 就是通过这里的规范来额外指定的。如果这里的功能规范当中,不允许网页服务器读取个人家目录, 那么,即使个人家目录的安全本文类型是正确的,网页服务器也会无法读取喔!

  • 使用 getsebool 及 semanage boolean 查找各个规则

查找目前所有 SELinux boolean 功能的状态是打开还是关闭,最简单可以通过 getsebool 来查找即可:

[root@localhost ~]# getsebool -a
abrt_anon_write --> off
abrt_handle_event --> off
.....
zoneminder_anon_write --> off
zoneminder_run_sudo --> off
# 功能规范真的太多了!通过 grep 来截取看看

# 查找看看有没有 http 开头,home 后续存在的功能规范
[root@localhost ~]# getsebool -a | grep 'http.*home'
httpd_enable_homedirs --> off

# 已经知道 httpd_enable_homedirs 功能规范名称时
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off

除了 getsebool 之外,我们可以通过万用工具,就是 semanage 这个软件,里面的 boolean 指令来查找!

# 需要先安装 semanage 的软件!名称为 policycoreutils-pythone-utils
[root@localhost ~]# yum whatprovides '*bin/semanage'
policycoreutils-python-utils-3.5-1.el9.noarch : SELinux policy core python utilities
Repo        : appstream
Matched from:
Other       : *bin/semanage

[root@localhost ~]# yum -y install policycoreutils-python-utils

# semanage boolean 的简单 help
[root@localhost ~]# semanage boolean -h
....
  -l, --list            List records of the boolean object type
  -1, --on              Enable the boolean
  -0, --off             Disable the boolean

[root@localhost ~]# semanage boolean --list
SELinux boolean                State  Default Description
....
httpd_enable_homedirs          (off  ,  off)  Allow httpd to enable homedirs
....

这样也能很轻松的找到需要的 SELinux boolean 说明!基本上,这些功能规范说明目前你可能还看不太懂, 这是因为可能你不具备某些特定的网络服务经验。没关系!等到后面许多服务器章节实做完之后, 对这些功能规范的名称,你大概就一看就懂了!所以,不用担心!慢慢来!

其实,还有个名为 setools-console 的软件可以安装,安装完成之后,还会有 sesearch 与 seinfo 等指令可以介绍。 不过,目前的 RockyLinux 其实含有 setroubleshoot 的问题分析软件,出现 SELinux 问题可以直接通过 /var/log/messages 的纪录来解决~所以,渐渐的,连鸟哥都忘记这些工具的存在了!
  • 使用 setsebool -P 或 semanage boolean --[on|off] 来修改

如果发现到某些功能规范没有激活,想要激活这些功能时,该怎么办呢?既然查看是 getsebool, 想当然尔,设置应该就是 setsebool 啰!

# 将刚刚查找到的 httpd_enable_homedirs 设置为 on
[root@localhost ~]# setsebool -P httpd_enable_homedirs 1
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> on
# 设置值, 1 或 on 都可以激活, 0 或 off 都可以关闭!

特别注意的是,setsebool 缺省修改的是『目前的状况』,如果想要连同下次开机都使用相同的设置, 那直接加上 -P 的选项来处理即可!所以,将它背下来! setsebool 就是要 -P !!

# 使用 semanage boolean 关闭 httpd_enable_homedirs 测试看看
[root@localhost ~]# semanage boolean httpd_enable_homedirs --modify --off
[root@localhost ~]# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off
# 虽然比较麻烦,不过,还是稍微熟悉一下 semanage 较佳!

3.5、SELinux 安全本文的修改

前面我们谈到 SELinux 安全本文,在进程上面,可以查看的指令有『 ps -Z 』之类的方式,而文件的安全本文, 则是通过『 ll -Z 』或者是『 stat 』这个指令来查找。那么修改呢?该如何进行安全本文的修改? 注意喔,修改时,请修改安全本文的类型即可,不要更动到身份识别或者是角色字段喔!

  • 使用 chcon 修改安全本文类型

最简单的修改方式是通过 chcon 来修改即可!例如底下的范例:

[root@localhost ~]# chcon [OPTION]... [-t TYPE] FILE...
[root@localhost ~]# chcon [OPTION]... --reference=RFILE FILE...
选项与参数:
-t  :后面接安全性本文的类型字段!例如 httpd_sys_content_t
--reference=RFILE:拿文件名为 RFILE 当范例来修改后续接的文件的类型!

# 将 /etc/hosts 拷贝 /dev/shm/hosts,并修改类型为 etc_t
[root@localhost ~]# cd /dev/shm
[root@localhost shm]# cp -a /etc/hosts .
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:net_conf_t:s0 158 Jun 23  2020 hosts

[root@localhost shm]# chcon -t etc_t hosts
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:etc_t:s0 158 Jun 23  2020 hosts

# 将类型改成与 /var/spool/mail 相同
[root@localhost shm]# ll -Zd /var/spool/mail/
drwxrwxr-x. 2 root mail system_u:object_r:mail_spool_t:s0 19 Jul 21 15:01 /var/spool/mail/

[root@localhost shm]# chcon --reference=/var/spool/mail hosts
[root@localhost shm]# ll -Z hosts
-rw-r--r--. 1 root root system_u:object_r:mail_spool_t:s0 158 Jun 23  2020 hosts
  • 使用 semanage fcontext 查找目录/文件缺省的类型

不知道你会不会好奇,既然 SELinux 模式可以在 disable 与 enforcing 之间切换,只是转为 enforcing 时, 可能会花费一段时间,让内核对全系统的文件进行重新设置 (relabel) 的动作!那么表示,每个文件/目录, 可能都会有默认值啰!那如何查找默认值呢?通过 semanage 吧!这样做看看:

# 找到 /etc/sysconfig 相关的缺省 SELinux 安全本文类型
[root@localhost ~]# semanage fcontext --list | grep /etc/sysconfig
SELinux fcontext              type           Context
/etc/sysconfig/.*l2tpd        regular file   system_u:object_r:l2tp_conf_t:s0
/etc/sysconfig/MailScanner    regular file   system_u:object_r:mscan_etc_t:s0
.....

很简单就可以查看到某个文件/目录的缺省 SELinux 安全本文类型了。好!那如果不是在正规目录, 缺省的安全本文类型又是什么呢?基本上,看一下上述数据最前头输出的几行就知道了!

[root@localhost ~]# semanage fcontext --list | head
SELinux fcontext   type          Context
/                  directory     system_u:object_r:root_t:s0
/.*                all files     system_u:object_r:default_t:s0
/[^/]+             regular file  system_u:object_r:etc_runtime_t:s0
.....

原来缺省会是 default_t 喔!

  • 使用 semanage fcontext 修改/设置某目录默认值

假设我们预计创建一个名为 /www 的目录,这个目录的内容主要就是给网页服务器使用的!因此, 主要的 SELinux 类型应该指定为与 /var/www 这个目录相同。那该如何处理呢? 基本上,你可以这样处理看看。

# 基本的设置语法,如下所示,只要改 type 与目录位置即可
[root@localhost ~]# semanage fcontext -a -t type "/some/dir(/.*)?"

# 1. 先找到 /var/www 的类型为何
[root@localhost ~]# semanage fcontext -l | grep '/var/www('
/var/www(/.*)?              all files   system_u:object_r:httpd_sys_content_t:s0
/var/www(/.*)?/logs(/.*)?   all files   system_u:object_r:httpd_log_t:s0

# 2. 创建所需目录,并且查看默认值
[root@localhost ~]# mkdir /www
[root@localhost ~]# echo check > /www/index.html
[root@localhost ~]# ll -Zd /www /www/index.html
drwxr-xr-x. 2 root root unconfined_u:object_r:default_t:s0 24 Jul 22 14:40 /www
-rw-r--r--. 1 root root unconfined_u:object_r:default_t:s0  6 Jul 22 14:40 /www/index.html
# 果然默认值是 default_t 呢!

# 3. 增加 /www 缺省为 httpd_sys_content_t 的类型
[root@localhost ~]# semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?"
[root@localhost ~]# semanage fcontext -l | grep '^/www'
/www(/.*)?    all files     system_u:object_r:httpd_sys_content_t:s0

通过这些步骤,很轻松的就完成了非正规目录的缺省 SELinux 安全本文类型设置值!

  • 使用 restorecon 复原默认值

现在,我们已经规划好了 /www 的缺省类型,那,我们还需要使用 chcon 一个一个慢慢调整 SELinux 的规范嘛? 似乎不需要呢!直接通过 restorecon 来复原即可!很轻松愉快喔!

# 将刚刚的 /www 安全本文类型重置一下!
[root@localhost ~]# restorecon -Rv /www
Relabeled /www from unconfined_u:object_r:default_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0
Relabeled /www/index.html from unconfined_u:object_r:default_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0

加上 -v 之后,连修改的过程都跟你说了!很简单愉快!如果在某个特别的情况下,你想要复原全系统的 SELinux 类型, 不必进入内核功能,直接使用 restorecon 也是办得到的!

[root@localhost ~]# restorecon -Rv /
老实说,在救援 root 密码时,鸟哥觉得最后一动 touch /.autorelabel 之后重新开机,然后又要等好久!真的是很慢!如果是我要处理的话, 救援时,鸟哥很可能会这样做:
  • 先将 /etc/selinux/config 设置为 permissive,然后 reboot
  • 重新开机完成之后,运行 restorecon -Rv /,速度应该是比较快!
  • 然后再次将 /etc/selinux/config 修改成 enforcing
  • 之后 setenforce 1
这样就不用反复重新开机了!也能够避免因为忘记 touch /.autorelabel,导致还得要重新救援一次的困扰!
  • SELinux 服务对应端口口查找与修改 -- 使用 semanage port 指令

如同图 3.1-1 里面提到的,除了主体进程要访问目标文件需要通过 SELinux 管理之外, 程序要触发成为进程时,可能也会经过 SELinux 的修改。系统很可能因为被值入木马,或者是用户不小心安装了有问题的服务, 而这些服务很可能会打开不明的网络端口口!因此,SELinux 确实有针对某些服务来管理缺省端口口, 如果想要启动非正规端口口,还需要这个端口口对应的功能修改正确才行。

虽然我们还没有谈到网络基础,不过,一般常识来说,你可能会知道,网页服务器一般启动的端口口会是 port 80, port 443, 因此,SELinux 可能会管制你的服务器端口口,限制 httpd 这个网络服务程序只能开放在 port 80, 443 而已。 让我们来查找一下:

[root@localhost ~]# semanage port --list | grep http
http_cache_port_t  tcp  8080, 8118, 8123, 10001-10010
http_cache_port_t  udp  3130
http_port_t        tcp  80, 81, 443, 488, 8008, 8009, 8443, 9000

就是 http_port_t 那个项目!基本上使用的端口口好几个!那如果你想要增加一个 port 98 怎么办? 同样使用 semanage port 来处理!指令方式也不算太困难:

[root@localhost ~]# semanage port -a -t TYPE -p [tcp|udp] port_range
-a   添加一笔纪录
-t   修改的端口口名称,例如 httpd_port_t
-p   使用 tcp 或 udp 协定
port_range 使用的端口口号码

# 加入 port 98 的支持到 http 当中
[root@localhost ~]# semanage port -a -t http_port_t -p tcp 98
[root@localhost ~]# semanage port --list | grep http
http_port_t    tcp     98, 80, 81, 443, 488, 8008, 8009, 8443, 9000

很快可以看到 port 98 也加入可让 httpd 服务启动的端口口了!

3.6、利用 SELinux trouble shoot 服务

基本上,通过了解 SELinux 的三种模式 (disabled, permissive, enforcing)、功能规范开放与否 (getsebool, setsebool)、 安全本文的修改 (chcon, restorecon, semanage fcontext),以及端口口规范 (semanage port) 的方法, 对于 SELinux 的管理,大概就不会差太多了!不过,有没有更简单的方法呢?是有的喔!

事实上,如果你的 SELinux 运作错误时,我们可以通过 setroubleshoot 这个软件的功能, 它会自动分析可能的错误,并且将可能的解决方案直接纪录到 /var/log/messages 里面! 如此一来,你只要重复犯错的动作,然后查阅 messages 文件内容,就可以知道如何解决了!相当愉快!

  • 确认 setroubleshoot 与 rsyslog 是有安装的

要使用 SELinux 自动错误克服的功能,就得要安装 setroubleshoot 软件才行!而且, 初次安装完毕时,可能得要重新开机才会有作用。另外,RockyLinux 8 缺省似乎没有启动 rsyslog,不过,RockyLinux 9 倒是缺省安装的。 如果你无法确认 rsyslog 有没有启动以及 setroubleshoot 有没有安装,没关系,就让我们来手动测试看看即可。

[root@localhost ~]# yum -y install setroubleshoot*
[root@localhost ~]# rpm -qa | grep setrouble
setroubleshoot-plugins-3.3.14-4.el9.noarch
setroubleshoot-server-3.3.31-2.el9_2.x86_64
setroubleshoot-3.3.31-2.el9_2.x86_64

[root@localhost ~]# systemctl status rsyslog
● rsyslog.service - System Logging Service
     Loaded: loaded (/usr/lib/systemd/system/rsyslog.service; enabled; preset: enabled)
     Active: active (running) since Fri 2023-08-04 16:02:11 CST; 6h ago
       Docs: man:rsyslogd(8)
             https://www.rsyslog.com/doc/
   Main PID: 609 (rsyslogd)
      Tasks: 3 (limit: 12243)
     Memory: 3.0M
        CPU: 521ms
     CGroup: /system.slice/rsyslog.service
             └─609 /usr/sbin/rsyslogd -n

你可能会觉得很怪异,上面安装的软件名称当中有 setroubleshoot-server 这个关键字,但是,使用 systemctl 去检查相关的服务时,却找不到任何 setrouble 相关的服务名称!这是因为 setrouble 已经集成到稽核模块 auditd 服务中! 因此, setroubleshoot 的运作方式是这样的:

  • 先由 auditd 去调用 audispd 服务
  • 然后 audispd 服务去启动 sedispatch 程序
  • sedispatch 再将原本的 auditd 消息转成 setroubleshootd 的消息,进一步保存下来的

总之,鸟哥这种老人家,还是比较习惯查找 /var/log/messages 内的数据,而不是让日志直接写入 systemd-journald 当中! 因为只写入 systemd-journald 时,当系统重新开机,日志可能是会遗失的呢!

  • 1. 仿真状况,当 port 出问题时:让 httpd 打开在非正规端口口

基本上,http, https 的端口口分别是 port 80, port 443 的 tcp 端口口。那么当我将这个端口口打开到非正规的 377 端口口呢? 很可能会无法启动喔!先来测试看看。我们依序可以这样做:

  • 1. 网页服务器的软件所需名称为 httpd,请安装好这个软件
  • 2. 软件主设置档为 /etc/httpd/conf/httpd.conf ,内部的 Listen 设置,请改为 377
  • 3. 启动名为 httpd 的服务,并且查看有没有出问题?
  • 4. 若出问题,将 SELinux 模式由 enforcing 改为 permissive 测试一下
  • 5. 再次重新启动 httpd 服务,是否能正常启动?若可以,代表就是 SELinux 的问题。
  • 6. 前往 /var/log/messages 查找是否有 setrouble 的关键字?若有,取出查阅
  • 7. 根据 sealert 的解释,将问题克服

我们就实际在虚拟机上面恶搞一下啰!

# 1. 先安装软件
[root@localhost ~]# yum -y install httpd

# 2. 修改设置档,大约在 47 行处,修改端口口号码
[root@localhost ~]# vim /etc/httpd/conf/httpd.conf
Listen 377

# 3. 尝试启动 httpd 服务
[root@localhost ~]# systemctl start httpd
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
# 如上所示,系统会提示出现错误了!

# 4. 尝试将 SELinux 模式改为 permissive
[root@localhost ~]# getenforce
Enforcing
[root@localhost ~]# setenforce 0
[root@localhost ~]# getenforce
Permissive

# 5. 确认一下能不能顺利启动?用来判断问题是否出在 SELinux 的情况
[root@localhost ~]# systemctl start httpd
[root@localhost ~]# netstat -tlunp | grep httpd
tcp6    0      0 :::377    :::*    LISTEN      8324/httpd
# 出现 LISTEN 关键字!代表服务有正常启动了!所以,问题一定是 SELinux 造成的!

# 6. 确认 /var/log/messages 有没有因为启动 httpd 而记载错误解决方案
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
Aug  4 22:13:38 localhost setroubleshoot[3553]: SELinux is preventing /usr/sbin/httpd
  from name_bind access on the tcp_socket port 377. For complete SELinux messages
  run: sealert -l 525fe157-a16b-47c4-ad8d-11bad86c9e9a
# 重点是找到 sealert 这个关键字!后续的指令直接运行就是答案!

# 7. 将找到的 sealert 指令运行,并依据提示处理问题
[root@localhost ~]# sealert -l 525fe157-a16b-47c4-ad8d-11bad86c9e9a
SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 377.

*****  Plugin bind_ports (99.5 confidence) suggests   ************************

If you want to allow /usr/sbin/httpd to bind to network port 377
Then you need to modify the port type.
Do
# semanage port -a -t PORT_TYPE -p tcp 377
    where PORT_TYPE is one of the following: http_cache_port_t, http_port_t, ...

*****  Plugin catchall (1.49 confidence) suggests   **************************
....

其实解决问题的方案不止一种,因此,上述的 sealert 提供的方式中,会有好几个解决方案,不过,解决方案总是有轻重缓急! 所以,最好选择信赖度最高的方案来解决较佳!所以,当然是选上面 99.5% 信赖度的啊!然后,又看到底下的指令, 就是『 semanage port -a -t PORT_TYPE -p tcp 377 』这一段,你应该会觉得很开心!因为刚刚才学过啊! 只是, PORT_TYPE 必须要选择正确的项目才行!因为我们是在处理 http 的端口口,当然最终选择 http_port_t! 所以,整个解决方案的处理会是这样:

[root@localhost ~]# semanage port -a -t http_port_t -p tcp 377
[root@localhost ~]# setenforce 1
[root@localhost ~]# getenforce
Enforcing
[root@localhost ~]# systemctl restart httpd
# 最终,在 Enforcing 的模式中,再次重新启动服务!可正常启动才会是正确的!
范例的思考也是很重要的!上面的范例中,原本鸟哥属意的端口口是 538 (台语谐音:有三八), 但是,这个端口口竟然已经被 semanage port 所管理了!因此无法顺利找到正确的解决方案!差点崩溃! 所以,未来想要找某个非正规端口口来练习时,还是得要先用底下的方法确认,不要使用已记载的端口口为佳! 『semanage port -l | grep 538』(结果没出现任何消息,该端口口才好应用!)

最后,让我们使用文本型浏览器来看看我们的本机 (https://127.0.0.1) 有没有顺利提供服务呢?

[root@localhost ~]# curl https://127.0.0.1:377 2> /dev/null | head
<!doctype html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>HTTP Server Test Page powered by: Rocky Linux</title>
    <style type="text/css">
.....
# 有看到数据输出,就是正确的显示啦!文本型浏览器只能作到这样解析!
  • 2. 仿真状况,当主体进程与目标资源的安全本文类型不符时

进行这个仿真时,先有个观念,那就是,整个网页服务器的数据缺省是放置到 /var/www/html 里面的! 所以,如果想要读取 https://127.0.0.1/test.txt 时,该文件需要放置成为 /var/www/html/test.txt 才对! 现在,让我们仿真一个错误!那就是,用户在自己家目录创建好网页数据,然后使用 cp -a 的方式拷贝到网页服务器上! 那个 -a 很厉害啊!可能会连同 SELinux type 都拷贝过去~如此一来,会变怎样呢?

  • 1. 先以 root 的身份,去自己家目录创建名为 test.txt 的文件
  • 2. 使用 cp -a 的方式,将该文件拷贝到 /var/www/html 网页主目录下
  • 3. 使用 curl 去浏览,看看浏览的结果为何,再来判断!
  • 4. 搜查 /var/log/messages 看看有没有最近的 setrouble 输出结果?
  • 5. 运行 sealert 之后,依据建议修改错误。
# 1. 先创建名为 test.txt 的文件
[root@localhost ~]# vim ~/test.txt
I am VBird
Today: 2023/08/04

# 2. 用 cp -a,注意,记得加上 -a 喔!在这个练习底下!不要用 -r
[root@localhost ~]# cp -a ~/test.txt /var/www/html

# 3. 使用 curl 去浏览,记得我们的端口口在非正规喔!
[root@localhost ~]# curl https://127.0.0.1:377/test.txt
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>
# 注意喔,错误消息是『没有权限』而不是『找不到文件』
# 意思是,有这个文件存在,但是你没有权限读取的意思!重要重要!

[root@localhost ~]# ll /var/www/html/test.txt
-rw-r--r--. 1 root root 29 Aug  4 22:20 /var/www/html/test.txt
# 但是权限是合理的!所有人都可以读取(r)!所以,直接怀疑是 SELinux 啰!

# 4. 检查有没有 SELinux 的 log 错误!?
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
Aug  4 22:20:44 localhost setroubleshoot[3942]: SELinux is preventing /usr/sbin/httpd
  from getattr access on the file /var/www/html/test.txt. For complete SELinux messages
  run: sealert -l 95e5f3a3-e946-4f45-9560-5787ff5083f9
# 果然在最接近的时间就有一个 SELinux 的警告消息出现了!

# 5. 运行看看 sealert 之后,设法解决问题!
[root@localhost ~]# sealert -l 95e5f3a3-e946-4f45-9560-5787ff5083f9
SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/html/test.txt.

*****  Plugin restorecon (99.5 confidence) suggests   ************************

If you want to fix the label.
/var/www/html/test.txt default label should be httpd_sys_content_t.
Then you can run restorecon. The access attempt may have been stopped due to insufficient permissions
  to access a parent directory in which case try to change the following command accordingly.
Do
# /sbin/restorecon -v /var/www/html/test.txt
....

[root@localhost ~]# /sbin/restorecon -v /var/www/html/test.txt
Relabeled /var/www/html/test.txt from unconfined_u:object_r:admin_home_t:s0 
  to unconfined_u:object_r:httpd_sys_content_t:s0

# 6. 最后,让我们测试一下,到底能不能成功浏览到信息了?
[root@localhost ~]# curl https://127.0.0.1:377/test.txt
I am VBird
Today: 2023/08/04
# 果然就正常啦!

这边的两个练习都是常见的问题!请大家务必实做一次以上啊!会很有帮助喔!加油加油!

总之,你要记住,无法启动某个服务或者是某服务无法访问某个文件资源,先查看一下 rwx 是否正确?若正确, 先将 SELinux 变更为 permissive,然后『重复一次刚刚发生错误的动作』,看看有没有正常?若有正常, 那表示问题一定来自 SELinux 了!再将 SELinux 变更为 Enforcing,之后依据 sealert 提供的方式处理错误, 最终,一定要『再次的重复一次刚刚发生错误的动作』,确定在 Enforcing 的状态下也是正常启动处理的! 那就没问题了!

3.7、修改 demo1.img 系统内容

从前一章的后半段开始,我们都用 test1.img 做测试,但其实许多的软件与服务,似乎应该都要在 demo1.img 里面存在比较好! 否则,未来我们都会以为好像系统都安装妥当了...结果却是在测试环境中进行的...那就比较伤脑筋!呵呵! 所以,现在请将 test1 系统的 history 倒出来备份好,将 test1 关机之后,再启动 demo1 看看!

# 确认 test1 关闭之后,就启动 demo1 吧!
[root@cloud ~]# virsh create /kvm/xml/demo1.xml
Domain 'demo1' created from /kvm/xml/demo1.xml

[root@cloud ~]# arp -n -i templan
Address         HWtype  HWaddress           Flags Mask  Iface
192.168.10.53   ether   52:54:00:ed:63:1f   C           templan

# 登录 192.168.10.53,取得控制权之后,就开始将 SELinux 以及前一章需要的软件装上吧!
[root@cloud ~]# ssh vbird@192.168.10.53

[vbird@localhost ~]$ sudo su -

[root@localhost ~]# yum -y install fio iperf3 policycoreutils-python-utils setroubleshoot*

[root@localhost ~]# poweroff

很简单!这样就好了!未来使用这个 demo1.img 作为 backing_file 所设计出来的 overlay, 里头就拥有 SELinux 运作所需要的 debug 软件了!喔耶!

修改历史:
  • 2022/05/15:原本要将 SELinux 跟前一章节放在一起,因为篇幅太小的缘故。后来想想,篇幅小也没关系,各自独立单元,介绍会更清楚!
  • 2022/07/12:根据讨论区网友的提示,发现 Red Hat 提供的文档中,SELinux 应该是在权限判断之后才进行的!
  • 2022/07/22:重新润稿一次,也在 RockyLinux 9 上面重现一次!数据处理挺伤脑筋!哈哈!
  • 2023/08/04:主要是检查看看有没有其他错误!然后增加修改 demo1.img 的状态而已。
  • 2024/10/29:记得修改时,要参考 eZioPan 的建议 https://phorum.vbird.org/viewtopic.php?p=162860#p162860
2022/05/15以来统计人数
计数器
其他链接
环境工程模式篇
鸟园讨论区
鸟哥旧站

今日 人数统计
昨日 人数统计
本月 人数统计
上月 人数统计