Rocky Linux 9 从入门到精通005 — SELinux配置

SELinux 简介

SELinux(Security Enhanced Linux)中文简称: 安全增强型 Linux,是一种为 Linux 系统提供强制访问控制(Mandatory access control,MAC)的安全子系统。它最初是由美国国家安全局(National Security Agency, NSA)开发,并以开源许可证形式发布,旨在增强 Linux 系统的安全性。SELinux 通过定义一系列安全策略、限制程序和进程能够访问的资源和操作,从而减少潜在的安全漏洞和风险。需要特别说明的是 SELinux 主要用于 RHEL 发行版系列,虽然它在 Debian 发行版系列也可以使用,但并不是那么完美。Debian 发行版系列一般都集成了 AppArmor 系统,能实现 SELinux 类似需求。

使用 SELinux 的好处:

  1. 强制访问控制(MAC): 与传统的基于所有者的自主访问控制(Discretionary access control, DAC)不同,SELinux 的强制访问控制不依赖于对象的所有者或用户的权限。它基于预定义的安全策略来控制进程对文件、网络接口等资源的访问。
  2. 最小权限原则(RBAC): 确保每个进程只能访问执行其功能所必需的资源,限制恶意软件和漏洞的潜在影响。
  3. 安全上下文(Security Context): 管理员可以根据需要定制安全策略,进行灵活配置,以适应不同的应用场景和安全需求。

SELinux 简单运行示意图

SELinux 并不是为了取代传统的读写执行(rwx)权限,而是在文件权限判定通过之后,再增加了一层保护。这层 SELinux 的保护可以对某些程序可读取的文件的安全内容类型进行限制。因此,如果目标文件的安全上下文设计不符合相关程序的要求,那么程序就无法读写该文件。
上面的说法可能有点抽象,我们来通过一幅图来解释一下。如下图所示,要启动 httpd 或 vsftpd 服务,通常需要执行某些脚本或程序。这时 SELinux 就开始发挥作用了,如果您权限设置有误,不符合 SELinux 最初设定的默认规则,那么这些脚本或程序就无法成功加载到内存中了(蓝色箭头部分,可能会被 SELinux 阻挡),进程就无法正常启动。如果通过了默认规则并且程序加载成为网络服务,那么当这个网络服务想要读取系统上的某些文件时,如果网络程序和文件的安全上下文设计不匹配,它也无法读取那个文件(红色箭头部分)。也就是说不管是服务启动、文件访问都需要经过 SELinux 安全上下文验证。

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

SELinux 运行方式

在上图中,存在几个名词,我们逐一进行解释:

主体 (Subject):
通常所说的主体大多指的是进程,尤其是网络方面的进程,在本章节中讨论的主体主要都是进程。

目标 (Object):
主体进程能否访问的“目标资源”,通常目标指的是文件。但也有例外。例如,主体可能想要激活某个端口(port),这时目标就变成了端口。不过,大多数情况下,目标指的还是文件。

SELinux 模式 (Mode):
SELinux 有三种模式:关闭(Disabled)、宽容模式(Permissive)和强制模式(Enforcing)。在 disabled 和 permissive 模式下,SELinux 实际上并不会控制进程的访问行为。只有在 enforcing 模式下,才会真正进行访问控制。

  • 强制模式(Enforcing):默认模式,在这种模式下,SELinux 会强制执行其安全策略,拒绝未授权的访问,并记录所有违反策略的行为,记录在 /var/log/messages 文件中。
  • 宽容模式(Permissive):SELinux 仍然检查所有访问,但不会拒绝未授权的访问,只是记录违反策略的行为,记录在 /var/log/messages 文件中。这对于调试和调整安全策略很有用。
  • 禁用模式(Disabled):SELinux 被完全禁用,不会执行任何安全检查。

策略与规则 (Policy、rules、boolean):.
因为存在大量的(主体)进程和(目标)文件,SELinux 根据不同的服务定义了基本的访问安全策略。这些策略中包含了详细的规则(rules),用以指定不同的服务是否允许访问某些资源,此外,还需控制和理解某些功能是否被允许(SELinux boolean)。在目前的 Rocky Linux 9 中,主要提供了三个策略,其中默认为 targeted 策略。相关策略描述可以参考 /etc/selinux/config 的内容:

  • targeted:默认策略,主要针对网络进程,对本机进程(如 bash)的限制较少。
  • minimum:最小控制策略,是对 targeted 策略的修改,只有少数选定的进程受到管理。
  • mls (Multi Level Security protection):完整的 SELinux 管理,限制非常严格。

安全上下文 (security context):
每个主体进程可以访问的安全上下文并不相同,SELinux 规定了进程可以访问的安全上下文类型。当目标文件的安全上下文与主体进程允许访问的类型一致时,进程才能访问该文件。这个安全上下文有点类似于 SELinux 中的 rwx 概念。如果主体要访问的对象的安全上下文不匹配,在 enforcing 模式下,将会出现权限不符的错误信息。此时,无论权限设置如何开放,都无法使进程读取到文件。

综上所述,重点在于“主体”如何获得“目标”的资源访问权限。可以发现,进程的读写操作首先需要通过 rwx 权限分析,如果 rwx 权限本身就被阻止,则后续的 SELinux 流程不会启动。如果通过了 rwx 权限并且确认可以读写之后,SELinux 在获取主体进程与需要访问的目标资源(目标文件)的安全上下类型后,开始在 SELinux 中搜索两者的规则,若是确认匹配可行,则予以放行,否则就予以阻止。

回过头来,我们再来看 SELinux 的运行方式图,红色线条为 Enforcing 模式,在读取文件没有权限时,会直接禁止。黄色线条为 Permissive 模式,只会写入日志记录,不会阻止。绿色线条为 Disabled 模式,不记录日志,完全放行。

为了让大家更好的理解 SELinux 的运行方式,这里举例说明,在 SELinux 启用的情况下,当用户 user1 需要访问文件 file1 时,大概过程是怎样的?

1、用户 user1 发起一个请求,比如试图读取文件 file1。
2、系统首先会进行常规的 Linux 权限检查。这包括检查用户 user1 对文件 file1 的读、写、执行等权限。
3、如果用户通过了常规的 Linux 权限检查,系统接着会进行 SELinux 权限检查。这是一个额外的安全检查层,它基于 SELinux 策略和安全上下文来决定是否允许访问。
4、SELinux 检查用户 user1 和文件 file1 的 SELinux 上下文。这包括用户、角色和类型信息。例如,用户 user1 可能有一个安全上下文 user_u:user_r:user_t,文件 file1 可能有一个安全上下文 system_u:object_r:file_t
5、SELinux 查看其策略数据库,看看是否存在一个规则允许 user_t 类型的进程访问 file_t 类型的文件。如果存在这样的规则,SELinux 就会允许访问。否则,SELinux 会拒绝访问,并记录一个拒绝访问的审计日志 /var/logs/messages

这个过程提供了一个额外的安全层(MAC),它可以防止即使有适当 Linux 权限的用户或进程访问他们不应该访问的文件或资源。

SELinux 安全上下文

在前面的举例中,我们提到安全上下文 user_u:user_r:user_t,在 SELinux(Security-Enhanced Linux)中,"安全上下文"是指附加在文件、进程或用户上的安全标签。这个标签包含了一些关于该对象的安全信息,SELinux 就是根据这些信息来执行访问控制的。怎么查看了?

# 查看文件安全上下文
[root@localhost ~]# ll -Zd /root/anaconda-ks.cfg
-rw-------. 1 root root system_u:object_r:admin_home_t:s0 1101 Dec 17 11:32 /root/anaconda-ks.cfg

# 查看进程安全上下文
[root@localhost ~]# ps -auxZ | egrep 'bash|chronyd|LABEL'
LABEL                           USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
system_u:system_r:chronyd_t:s0  chrony       804  0.0  0.0  84436  3308 ?        S    Feb05   0:06 /usr/sbin/chronyd -F 2
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 11889 0.0  0.0 5044 4144 tty1 Ss+ Feb16   0:00 -bash

# 查看当前用户安全上下文
[root@localhost ~]# id -Z 
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# 查看指定用户安全上下文(需要root权限)
[root@localhost ~]# runcon -u root -- id -Z
root:unconfined_r:unconfined_t:s0-s0:c0.c1023

# 每个 Linux 用户都通过 SELinux 策略映射到一个 SELinux 用户
[root@localhost ~]# semanage login -l
Login Name           SELinux User         MLS/MCS Range        Service
__default__          unconfined_u         s0-s0:c0.c1023       *
root                 unconfined_u         s0-s0:c0.c1023       *

一个 SELinux 安全上下文通常包括三部分:用户身份识别 User_Identify(身份)、角色 Role 和类型 Type(域)。“域”一般指进程,“类型”一般指对象。一个“用户”被分配一个或多个角色,但每个“角色”只对应一个“域”。比如,一个文件的 SELinux 上下文可能是 user_u:role_r:type_t:s0。这里,“user_u”是用户,“role_r”是角色,“type_t”是类型,“s0”是可选的安全等级。

  • 用户: 表示文件或进程的拥有者。在大多数情况下,这部分对访问控制没有影响,但在某些复杂的 SELinux 策略中,不同的用户可能会有不同的权限。
    • system_u: 表示系统进程方面的识别,大部分指的是受到管理限制的情况。
    • unconfined_u: 表示没有限制的情况。
  • 角色: 对于文件通常是"object_r",对于进程则可能是"sysadm_r"或其他角色。角色主要用于 RBAC(基于角色的访问控制)。
    • object_r: 代表的是文件或目录等文件资源,这应该是最常见的。
    • system_r: 代表的就是受到管理限制的进程。
    • unconfined_r: 表示没有限制的情况。
  • 类型: 在默认的 targeted 政策中,User 与 Role 字段基本上是不重要的,重要的是这个类型 (type) 字段。基本上,一个主体进程能否读取到这个文件资源,都与类型字段有关。而类型字段在文件与进程的定义不太相同,分别是:
    • type: 在文件资源 (Object) 上面称为类型 (Type)。
    • domain: 在主体进程 (Subject) 则称为领域 (domain)。

因此,可以把 SELinux 上下文理解为一种标签,它告诉 SELinux 系统该文件、进程或用户应该有什么样的访问权限。

SELinux 常用命令

前面聊了很多关于 SELinux 的概念,终于到了实操环节。很多同学估计会直接禁用 SELinux,这是不建议的。

启用禁用 SELinux

# 永久禁用 SELinux,需要重启服务器生效。
[root@localhost ~]# sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

# 查看是否禁用成功
[root@localhost ~]# cat /etc/selinux/config | grep SELINUX=disabled
# NOTE: Up to RHEL 8 release included, SELINUX=disabled would also
SELINUX=disabled

# 临时禁用(切换至 Permissive 模式)
[root@localhost ~]# setenforce 0

# 查看是否切换成功
[root@localhost ~]# getenforce
Permissive

# 永久启用 SELinux,需要重启服务器生效。
[root@localhost ~]# sed -i "s/SELINUX=disabled/SELINUX=enforcing/g" /etc/selinux/config

# 临时启用(切换至 Enforcing 模式)
[root@localhost ~]# setenforce 1

# 查看是否切换成功
[root@localhost ~]# getenforce
Enforcing

# 查看 SELinux 详细设置信息
[root@localhost ~]# sestatus
SELinux status:                 enabled # SELinux 启用状态
SELinuxfs mount:                /sys/fs/selinux # SELinux文件系统的挂载点
SELinux root directory:         /etc/selinux # 配置文件目录
Loaded policy name:             targeted # 已加载的策略名称:targeted
Current mode:                   enforcing # 当前工作模式
Mode from config file:          enforcing # 配置文件中的模式
Policy MLS status:              enabled # 策略中启用多级安全(MLS)
Policy deny_unknown status:     allowed # 允许未定义的行为
Memory protection checking:     actual (secure) # 正在实际检查内存保护
Max kernel policy version:      33 # 内核支持的SELinux策略的最大版本

内核级别禁用 SELinux

# 内核层禁用 SELinux
[root@localhost ~]# grubby --update-kernel ALL --args selinux=0

# 此命令禁用,不会变更 /boot/grub2/grub.cfg 配置文件
[root@localhost ~]# cat /boot/grub2/grub.cfg | grep kernelopts | tail -n 1
  set kernelopts="root=/dev/mapper/rl-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap "

# 验证内核参数是否禁用 SELinux,以下命令实际查看的是 /etc/default/grub 文件。
[root@localhost ~]# grubby --info DEFAULT
index=0
kernel="/boot/vmlinuz-5.14.0-362.8.1.el9_3.x86_64"
args="ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap selinux=0"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-5.14.0-362.8.1.el9_3.x86_64.img"
title="Rocky Linux (5.14.0-362.8.1.el9_3.x86_64) 9.3 (Blue Onyx)"
id="263d8af4bb1d454fbf67662961401825-5.14.0-362.8.1.el9_3.x86_64"

# 查看 /etc/default/grub 文件
[root@localhost ~]# cat /etc/default/grub 
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap selinux=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

# 重启生效配置
[root@localhost ~]# reboot

# 回滚配置
[root@localhost ~]# grubby --update-kernel ALL --remove-args selinux

# 验证是否变更成功,同样需要重启服务器生效。
[root@localhost ~]# grubby --info DEFAULT
index=0
kernel="/boot/vmlinuz-5.14.0-362.8.1.el9_3.x86_64"
args="ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-5.14.0-362.8.1.el9_3.x86_64.img"
title="Rocky Linux (5.14.0-362.8.1.el9_3.x86_64) 9.3 (Blue Onyx)"
id="263d8af4bb1d454fbf67662961401825-5.14.0-362.8.1.el9_3.x86_64"
温馨提醒
除非您的服务器在内网,且不会暴露在公网,否则强烈不建议将 SELinux 设置为 disabled 模式,这将带来较大的安全风险。

Disabled 模式切换回 Enforcing 模式

如果您从 Disabled 模式切换回 Enforcing 或 Permissive 模式,需要执行以下操作,来进行安全上下文的修复。 fixfiles -F onboot 命令是用于修复 SELinux 文件上下文的。在 Linux 系统中,SELinux 通过为文件分配安全上下文来提供访问控制。然而,有时候这些文件的安全上下文可能会被错误地更改或者丢失,导致 SELinux 无法正确地执行访问控制。 fixfiles 命令可以检查并修复这些文件的安全上下文。-F 选项会强制 fixfiles 命令修复所有的文件,而不仅仅是那些已经被修改的文件。onboot 选项则会在下次系统启动时执行修复。因此,fixfiles -F onboot 命令的作用是在下次系统启动时强制修复所有文件的 SELinux 安全上下文。这个命令通常在修改了 SELinux 策略,或者在文件的安全上下文被大规模错误地更改或丢失后使用。

当您执行 fixfiles -F onboot 命令时,它会在根目录下创建一个名为 .autorelabel 的文件。这个文件会在下次系统启动时被 SELinux 识别,并触发一个全系统的文件上下文修复过程。文件中的 -F 选项表示强制修复所有文件的上下文,而不仅仅是已经被修改的文件。这需要相当长的时间来进行处理。

# 执行以后会提示系统将在下次重新引导时执行修复
[root@localhost ~]# fixfiles -F onboot
System will relabel on next boot

# 执行上述命令后,会生成一个 .autoreabel 文件
[root@localhost ~]# ll /.autorelabel 
-rw-r--r--. 1 root root 3 Feb 20 20:19 /.autorelabel

# 查看文件内容为 -F
[root@localhost ~]# cat /.autorelabel 
-F 

SELinux 布尔值(Boolean)配置

SELinux Boolean 本质就是系统预设的一组常用软件控制策略,我们可以通过 getsebool、setsebool、sesearch、seinfo、semanage 等命令来进行开关控制。

# 因为策略比较多,我们可以通过 grep 进行过滤,这里过滤常用的ftp、ssh、httpd 进行了解。
[root@localhost ~]# getsebool -a | grep ftpd
ftpd_anon_write --> off
ftpd_connect_all_unreserved --> off
ftpd_connect_db --> off
ftpd_full_access --> off
ftpd_use_cifs --> off
ftpd_use_fusefs --> off
ftpd_use_nfs --> off
ftpd_use_passive_mode --> off
[root@localhost ~]# getsebool -a | grep ssh
fenced_can_ssh --> off
selinuxuser_use_ssh_chroot --> off
ssh_chroot_rw_homedirs --> off
ssh_keysign --> off
ssh_sysadm_login --> off
ssh_use_tcpd --> off
sshd_launch_containers --> off
virt_qemu_ga_manage_ssh --> off
[root@localhost ~]# getsebool -a | grep httpd
httpd_anon_write --> off
httpd_builtin_scripting --> on
httpd_can_check_spam --> off
httpd_can_connect_ftp --> off
httpd_can_connect_ldap --> off
httpd_can_connect_mythtv --> off
httpd_can_connect_zabbix --> off
httpd_can_manage_courier_spool --> off
httpd_can_network_connect --> off
httpd_can_network_connect_cobbler --> off
httpd_can_network_connect_db --> off
httpd_can_network_memcache --> off
httpd_can_network_relay --> off
httpd_can_sendmail --> off
httpd_dbus_avahi --> off
httpd_dbus_sssd --> off
......(略)

这里提到 sesearch 和 seinfo 是两个用于查询和分析 SELinux 策略的工具,不过因为目前 RockyLinux 已经包含 setroubleshoot 问题分析工具,当 SELinux 出现问题会直接将日志写入  /var/log/messages,所以这个工具可以不用过多了解,如果您想安装,可以采用以下命令进行安装。

# sesearch 与 seinfo 包含在工具包 setools-console 中
[root@localhost ~]# dnf install -y setools-console

semanage 需要独立进行安装

# 查询 semanage 工具提供商
[root@localhost ~]# dnf whatprovides '*bin/semanage'
Last metadata expiration check: 1:20:05 ago on Sat Jan 2 10:03:18 2024.
policycoreutils-python-utils-3.5-2.el9.noarch : SELinux policy core python utilities
Repo        : @System
Matched from:
Other       : *bin/semanage

policycoreutils-python-utils-3.5-3.el9_3.noarch : SELinux policy core python utilities
Repo        : appstream
Matched from:
Other       : *bin/semanage

# 安装 semanage 工具
[root@localhost ~]# dnf install  -y policycoreutils-python-utils

# 获取 semanage 帮助信息
[root@localhost ~]# semanage -h
usage: semanage [-h] {import,export,login,user,port,ibpkey,ibendport,interface,module,node,fcontext,boolean,permissive,dontaudit} ...

semanage is used to configure certain elements of SELinux policy with-out requiring modification or recompilation from policy source.

positional arguments:
  {import,export,login,user,port,ibpkey,ibendport,interface,module,node,fcontext,boolean,permissive,dontaudit}
    import              Import local customizations
    export              Output local customizations
    login               Manage login mappings between linux users and SELinux confined users
    user                Manage SELinux confined users (Roles and levels for an SELinux user)
    port                Manage network port type definitions
    ibpkey              Manage infiniband ibpkey type definitions
    ibendport           Manage infiniband end port type definitions
    interface           Manage network interface type definitions
    module              Manage SELinux policy modules
    node                Manage network node type definitions
    fcontext            Manage file context mapping definitions
    boolean             Manage booleans to selectively enable functionality
    permissive          Manage process type enforcement mode
    dontaudit           Disable/Enable dontaudit rules in policy

optional arguments:
  -h, --help            show this help message and exit

# 获取 semanage boolean 帮助
[root@localhost ~]# semanage boolean -h
usage: semanage boolean [-h] [-n] [-N] [-S STORE] [  --extract  | --deleteall  | --list -C | --modify ( --on | --off ) boolean ]

positional arguments:
  boolean               boolean

optional arguments:
  -h, --help            show this help message and exit
  -C, --locallist       List boolean local customizations
  -n, --noheading       Do not print heading when listing boolean object types
  -N, --noreload        Do not reload policy after commit
  -S STORE, --store STORE
                        Select an alternate SELinux Policy Store to manage
  -m, --modify          Modify a record of the boolean object type
  -l, --list            List records of the boolean object type
  -E, --extract         Extract customizable commands, for use within a transaction
  -D, --deleteall       Remove all boolean objects local customizations
  -1, --on              Enable the boolean
  -0, --off             Disable the boolean

# 这里还是以 FTP 服务为例,可以看到 semanage 会有详细的解释各策略的作用,如果您刚刚接触 Linux,可能还没有办法理解所有策略的作用,但随着对于 Linux 各服务的深入学习,慢慢就会熟悉。  
[root@localhost ~]# semanage boolean --list | grep ftpd
ftpd_anon_write                (off  ,  off)  Allow ftpd to anon write
ftpd_connect_all_unreserved    (off  ,  off)  Allow ftpd to connect all unreserved
ftpd_connect_db                (off  ,  off)  Allow ftpd to connect db
ftpd_full_access               (off  ,  off)  Allow ftpd to full access
ftpd_use_cifs                  (off  ,  off)  Allow ftpd to use cifs
ftpd_use_fusefs                (off  ,  off)  Allow ftpd to use fusefs
ftpd_use_nfs                   (off  ,  off)  Allow ftpd to use nfs
ftpd_use_passive_mode          (off  ,  off)  Allow ftpd to use passive mode

在 SELinux 中,布尔值(boolean)用来开启或关闭特定的安全策略规则,以此来调整系统的安全行为,而不需要修改或重新加载整个安全策略。下面我们以 FTP 为例,简单介绍一下 SELinux 中关于 FTP 各安全策略的作用以及怎么进行设置。

  1. ftpd_anon_write --> off

    • 控制是否允许 FTP 守护进程(ftpd)对匿名用户提供写权限。当设置为 off 时,表示匿名用户不允许上传文件到 FTP 服务器。
  2. ftpd_connect_all_unreserved --> off

    • 控制着 FTP 守护进程是否可以绑定到所有未预设的端口(除 21、20 端口外的端口,在被动模式下可以自定义端口范围,主要用于 FTP 位于防火墙及 NAT 模式后面)。当设置为 off 时,ftpd 只能绑定到它被允许的特定端口。
  3. ftpd_connect_db --> off

    • 控制着 FTP 守护进程是否可以连接到数据库。当设置为 off 时,表示 ftpd 不能连接到任何数据库。
  4. ftpd_full_access --> off

    • 当这个规则被设置为 off 时,FTP 守护进程不会获得对所有类型文件和目录的完全访问权限。这意味着它的访问将受到 SELinux 策略的限制。
  5. ftpd_use_cifs --> off

    • 控制着 FTP 守护进程是否可以使用 CIFS(Common Internet File System,一个用于 Windows 系统的网络文件共享协议)。设置为 off 时,ftpd 无法通过 CIFS 协议访问共享文件。
  6. ftpd_use_fusefs --> off

    • 控制着 FTP 守护进程是否可以使用 FUSE(Filesystem in Userspace,用户空间文件系统)文件系统。设置为 off 时,ftpd 无法挂载或使用 FUSE 文件系统。
  7. ftpd_use_nfs --> off

    • 控制着 FTP 守护进程是否可以使用 NFS(Network File System,网络文件系统)。设置为 off 时,ftpd 不能通过 NFS 协议访问网络共享。
  8. ftpd_use_passive_mode --> off

    • 控制着 FTP 守护进程是否可以使用被动模式(passive mode)。在被动模式中,客户端会连接到由服务器指定的任意端口上。设置为 off 时,ftpd 可能被限制仅使用主动模式(active mode)进行连接。

关闭这些规则通常是出于安全考虑,以限制 FTP 服务的某些功能,防止潜在的安全漏洞或不必要的权限扩展。我们可以根据实际需求来调整这些布尔值,以满足不同的安全策略和操作要求。

了解各规则的作用后,我们就可以根据自己的需求进行设置了,操作相对来说比较简单。

# 启用允许 ftpd 服务完全访问控制权限,1 为 on,0 为 off,-P 为永久生效
[root@localhost ~]# setsebool -P ftpd_full_access 1
[root@localhost ~]# getsebool -a | grep ftpd_full_access
ftpd_full_access --> on

# 禁用允许 ftpd 服务完全访问控制权限
[root@localhost ~]# setsebool -P ftpd_full_access 0
[root@localhost ~]# getsebool -a | grep ftpd_full_access
ftpd_full_access --> off

# 使用 semanage 命令进行控制,实际体验下来 semanage 的操作会比 setsebool 慢。  
[root@localhost ~]# semanage boolean ftpd_full_access --modify --on
[root@localhost ~]# getsebool ftpd_full_access
ftpd_full_access --> on

[root@localhost ~]# semanage boolean ftpd_full_access --modify --off
[root@localhost ~]# getsebool ftpd_full_access
ftpd_full_access --> off

SELinux 文件安全上下文管理

使用 chcon 修改文件安全上下文

前面我们提到通过 ll -Zd 查看文件的安全上下文,那怎么修改文件的安全上下文了?我们可以使用 chcon 命令来进行修改。

# 查看 chcon 工具提供商
[root@localhost ~]# dnf whatprovides chcon
Last metadata expiration check: 3:34:30 ago on Sat Jan 2 10:03:18 2024.
coreutils-8.32-34.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
Repo        : @System
Matched from:
Filename    : /usr/bin/chcon

coreutils-8.32-34.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
Repo        : baseos
Matched from:
Filename    : /usr/bin/chcon

coreutils-single-8.32-34.el9.x86_64 : coreutils multicall binary
Repo        : baseos
Matched from:
Filename    : /usr/bin/chcon

# 安装 chcon
[root@localhost ~]# dnf install -y coreutils
Last metadata expiration check: 3:34:44 ago on Sat Jan 2 10:03:18 2024.
Package coreutils-8.32-34.el9.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
[root@localhost ~]# chcon -h
chcon: missing operand
Try 'chcon --help' for more information.

# 通过帮助我们可以看到使用 chcon 可以修改用户、角色、类型等
[root@localhost ~]# chcon --help
Usage: chcon [OPTION]... CONTEXT FILE...
  or:  chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...
  or:  chcon [OPTION]... --reference=RFILE FILE...

# 这里我们以变更 /tmp/resolv.conf 文件权限为例,先复制 /etc/resolv.conf 文件至 /tmp/resolv.conf 
[root@localhost ~]# cp /etc/resolv.conf /tmp/

# 我们会发现类型为 user_tmp_t
[root@localhost tmp]# ls -lZ /tmp/resolv.conf 
-rw-r--r--. 1 root root unconfined_u:object_r:user_tmp_t:s0 78 Jan 2 13:42 resolv.conf

# 再看 /etc/resolv.conf 类型为 net_conf_t
[root@localhost tmp]# ls -lZ /etc/resolv.conf 
-rw-r--r--. 1 root root system_u:object_r:net_conf_t:s0 78 Jan 2 11:02 /etc/resolv.conf

# 现在我们将 /tmp/resolv.conf 类型修改为 net_conf_t
[root@localhost tmp]# chcon -t net_conf_t /tmp/resolv.conf

# 验证成功
[root@localhost tmp]# ls -lZ /tmp/resolv.conf 
-rw-r--r--. 1 root root unconfined_u:object_r:net_conf_t:s0 78 Jan 2 13:42 /tmp/resolv.conf

# 这时候我们会发现 /tmp/resolv.conf 的用户、角色分别为 unconfined_u:object_r,如果我们想让它和 /etc/resolv.conf 一致怎么办了?除了上面的方法,还有一种方法,参考 /etc/resolv.conf 快速修改安全上下文。
[root@localhost tmp]# chcon --reference=/etc/resolv.conf /tmp/resolv.conf 

# 查看安全上下文是否一致
[root@localhost tmp]# ls -lZ /tmp/resolv.conf 
-rw-r--r--. 1 root root system_u:object_r:net_conf_t:s0 78 Jan 2 13:42 /tmp/resolv.conf
[root@localhost tmp]# ls -lZ /etc/resolv.conf 
-rw-r--r--. 1 root root system_u:object_r:net_conf_t:s0 78 Jan 2 11:02 /etc/resolv.conf

使用 semanage fcontext 查看目录文件预设安全上下文

前面提到我们可以在 SELinux 的 disable 与 enforcing 之前进行切换,只是从 disable 切换至 enforcing 的时候,系统需要花费大量的时间对所有文件重新设置安全上下文,那么说明系统本身默认存在一个预设值了?这里我们可能通过 semanage 命令进行查看,当然也可以直接查看 /etc/selinux/targeted/contexts/files/file_contexts 文件。

[root@localhost tmp]# semanage fcontext --list | grep /tmp
/home/[^/]+/tmp                                    directory          unconfined_u:object_r:user_tmp_t:s0 
/tmp                                               all files          system_u:object_r:tmp_t:s0 
......(略)

[root@localhost tmp]# more /etc/selinux/targeted/contexts/files/file_contexts 
/.*     system_u:object_r:default_t:s0
/[^/]+  --      system_u:object_r:etc_runtime_t:s0
/a?quota\.(user|group)  --      system_u:object_r:quota_db_t:s0
/efi(/.*)?      system_u:object_r:boot_t:s0
/nsr(/.*)?      system_u:object_r:var_t:s0
/sys(/.*)?      system_u:object_r:sysfs_t:s0
/xen(/.*)?      system_u:object_r:xen_image_t:s0
/mnt(/[^/]*)?   -d      system_u:object_r:mnt_t:s0
/mnt(/[^/]*)?   -l      system_u:object_r:mnt_t:s0
......(略)

使用 semanage fcontext 自定义目录文件预设安全上下文

一般来说我们的 web 文件都存放在 /var/ www/html 目录下,所以这个目录 SELinux 是存在预设值的,如果我们更改了 web 的默认目录为 /data/ www,并且希望后续在进行 web 文件上传时,不用再去设置 web 文件的安全上下文,这时候我们可以为 /data/ www 目录预设一个安全上下文 ,来解决此问题。

# 查看 /var/www 目录的安全上下文
[root@localhost tmp]# 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

# 创建新的 web 目录
[root@localhost tmp]# mkdir -p /data/www

# 查看对应安全上下文,实际继承的是 /.* 的安全上下文
[root@localhost tmp]# ll -Z /data
total 0
drwxr-xr-x. 2 root root unconfined_u:object_r:default_t:s0 6 Jan 2 14:41 www

# 设置 /data/www 安全上下文
[root@localhost tmp]# semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"

# 验证是否设置成功
[root@localhost tmp]# semanage fcontext -l | grep '/data/www('
/data/www(/.*)?                                    all files          system_u:object_r:httpd_sys_content_t:s0 

# 为了让后面上传的文件可以继承对应安全上下文,需要使用 restorecon 命令来应用这些安全上下文规则
[root@localhost tmp]# restorecon -Rv /data/www  
Relabeled /data/www from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0

# 创建文件
[root@localhost tmp]# echo "hello" > /data/www/index.html

# 可以看到对应的 httpd_sys_content_t 已经继承
[root@localhost tmp]# ll -Z /data/www/index.html
-rw-r--r--. 1 root root unconfined_u:object_r:httpd_sys_content_t:s0 6 Jan 2 14:53 /data/www/index.html

使用 restorecon 恢复目录文件预设安全上下文

如果您觉得对应目录或文件的安全上下文不正确,希望恢复系统预设值,可以使用 restorecon 命令进行设置。

# 删除安全上下文规则
[root@localhost tmp]# semanage fcontext -d "/data/www(/.*)?"

# 验证确保没有返回
[root@localhost tmp]# semanage fcontext -l | grep '/data/www('

# 使用 restorecon 命令递归修复 /data/www 下所有文件的安全上下文
[root@localhost tmp]# restorecon -Rv /data/www
Relabeled /data/www from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:default_t:s0
Relabeled /data/www/index.html from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:default_t:s0

# 使用 restorecon 命令修复单个文件或目录的安全上下文
[root@localhost tmp]# restorecon /data/www/index.html
活学活用
如果您希望全局恢复 SELinux 设置,采用前面提到的 fixfiles -F onboot 重启服务器是很慢的,建议这样操作:
1、先将 selinux 设置为 permissive,并重启 reboot
2、重启完成进入系统,执行 restorecon -Rv
3、再将 selinux 设置为 enforcing
4、再执行 setenforce 1
这样就不需要多次重启,也不需要等待很久。
🔔提示: disable 模式下,是不能够对 selinux 进行操作的。

SELinux 端口管理

前面我们提到 SELinux 不仅仅可以管理文件,还可以对端口进行管理,大家可能知道 FTP 使用的端口为 21、20(ftps 989、990),但如果我们要指定被动模式端口为: 50000-51000 怎么办了?

# 查看端口信息
[root@localhost tmp]# semanage port --list | grep ^ftp
ftp_data_port_t                tcp      20
ftp_port_t                     tcp      21, 989, 990
ftp_port_t                     udp      989, 990

# 修改数据端口
[root@localhost tmp]# semanage port -a -t ftp_data_port_t -p tcp 50000-51000  

# 验证修改是否成功
[root@localhost tmp]# semanage port --list | grep ^ftp
ftp_data_port_t                tcp      50000-51000, 20
ftp_port_t                     tcp      21, 989, 990
ftp_port_t                     udp      989, 990

# 删除修改的配置
[root@localhost tmp]# semanage port -d -t ftp_data_port_t -p tcp 50000-51000  
[root@localhost tmp]# semanage port --list | grep ^ftp
ftp_data_port_t                tcp      20
ftp_port_t                     tcp      21, 989, 990
ftp_port_t                     udp      989, 990

SELinux 诊断工具

setroubleshoot 是一个与 SELinux 配套使用的工具,它帮助系统管理员和用户理解 SELinux 的日志和审计消息,并提供解决方案建议。当 SELinux 阻止某些操作时,例如文件访问,它会生成“拒绝”消息,这些消息记录在系统日志中,但通常很难理解。 setroubleshoot 服务会监控 SELinux 的审计日志,并在检测到拒绝消息时,提供更易于理解的解释,并在可能的情况下,给出了如何解决问题的建议。它可以将消息发送到不同的目的地,包括系统日志、电子邮件或桌面通知。

安装 setroubleshoot

通过了解 SELinux 的三种模式(disabled、permissive,、enforcing)、功能规则的开启与关闭(getsebool、setsebool)、安全上下文的修改(chcon、restorecon、semanage fcontext)以及端口规则(semanage port)修改方法,我们对 SELinux 的管理基本已经掌握。对于 SELinux 异常怎样快速诊断、分析,我们可以使用 setroubleshoot 命令行工具,它会自动分析可能的错误,并且把可能的解决方案直接记录到 /var/log/messages 日志文件中,这样我们配置 SELinux 就更加简单了。

[root@localhost ~]# dnf -y install setroubleshoot* 
Last metadata expiration check: 2:44:44 ago on Mon Jan 2 16:49:09 2024.
Dependencies resolved.
================================================================================================================================================================================================================================================================
 Package                                                                     Architecture                                    Version                                                                   Repository                                          Size
================================================================================================================================================================================================================================================================
Installing:
 setroubleshoot                                                              x86_64                                          3.3.32-1.el9                                                              appstream                                           69 k
 setroubleshoot-plugins                                                      noarch                                          3.3.14-4.el9                                                              appstream                                          324 k
 setroubleshoot-server                                                       x86_64                                          3.3.32-1.el9                                                              appstream                                          323 k
......(略)

# 重启服务器,生效配置
[root@localhost ~]# reboot

# 确保 rsyslog 服务启动正常
[root@localhost ~]# systemctl status rsyslog

您可能会感到奇怪,虽然我们安装了名为 setroubleshoot-server 的软件,但是当我们用 systemctl 去检查相关的服务时,却找不到任何带有 setrouble 的服务名称。这是因为 setrouble 已经被集成到了审计模块 auditd 服务中。因此,setroubleshoot 的工作流程是这样的:

  • 首先由 auditd 服务来调用 audispd 服务。
  • 然后 audispd 服务负责启动 sedispatch 程序。
  • Sedispatch 程序再将原本的 auditd 消息转化为 setroubleshootd 的消息,并将其保存下来。

端口问题排查

我们安装一个 httpd 服务,来对 setroubleshoot 的易用性进行验证。

# 安装 httpd
[root@localhost ~]# dnf -y install httpd

# 修改监听端口,需要注意修改的端口,必须在 SELinux 预设值中不存在
[root@localhost ~]# sed -i "s/Listen 80/Listen 8055/g" /etc/httpd/conf/httpd.conf 

# 验证 SELinux 预设值中是否存在
[root@localhost ~]# semanage port -l | grep  8055

# 启动 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.

# 错误提示绑定 0.0.0.0:8055 权限被拒绝
[root@localhost ~]# journalctl -f -u httpd
Jan 2 19:48:52 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
Jan 2 19:48:52 localhost.localdomain httpd[4822]: AH00558: httpd: Could not reliably determine the servers fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
Jan 2 19:48:52 localhost.localdomain httpd[4822]: (13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:8055
Jan 2 19:48:52 localhost.localdomain httpd[4822]: no listening sockets available, shutting down
Jan 2 19:48:52 localhost.localdomain httpd[4822]: AH00015: Unable to open logs
Jan 2 19:48:52 localhost.localdomain systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Jan 2 19:48:52 localhost.localdomain systemd[1]: httpd.service: Failed with result 'exit-code'.
Jan 2 19:48:52 localhost.localdomain systemd[1]: Failed to start The Apache HTTP Server.

# 查看 /var/log/messages
[root@localhost ~]# tail -f /var/log/messages
Jan 2 19:48:52 localhost systemd[1]: Starting SETroubleshoot daemon for processing new SELinux denial logs...
Jan 2 19:48:53 localhost systemd[1]: Started SETroubleshoot daemon for processing new SELinux denial logs.
Jan 2 19:48:53 localhost systemd[1]: Created slice Slice /system/dbus-:1.1-org.fedoraproject.SetroubleshootPrivileged.
Jan 2 19:48:53 localhost systemd[1]: Started dbus-:[email protected].
# 下面的日志是重点,完整的 SELinux 信息运行 sealert -l e1d45e95-b7fc-4010-a355-dfa2d6adcd33 命令查看
Jan 2 19:49:03 localhost setroubleshoot[4825]: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055. For complete SELinux messages run: sealert -l e1d45e95-b7fc-4010-a355-dfa2d6adcd33
Jan 2 19:49:03 localhost setroubleshoot[4825]: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055.#012#012*****  Plugin bind_ports (92.2 confidence) suggests   ************************#012#012If you want to allow /usr/sbin/httpd to bind to network port 8055#012Then you need to modify the port type.#012Do#012# semanage port -a -t PORT_TYPE -p tcp 8055#012    where PORT_TYPE is one of the following: http_cache_port_t, http_port_t, jboss_management_port_t, jboss_messaging_port_t, ntop_port_t, puppet_port_t.#012#012*****  Plugin catchall_boolean (7.83 confidence) suggests   ******************#012#012If you want to allow nis to enabled#012Then you must tell SELinux about this by enabling the 'nis_enabled' boolean.#012#012Do#012setsebool -P nis_enabled 1#012#012*****  Plugin catchall (1.41 confidence) suggests   **************************#012#012If you believe that httpd should be allowed name_bind access on the port 8055 tcp_socket by default.#012Then you should report this as a bug.#012You can generate a local policy module to allow this access.#012Do#012allow this access for now by executing:#012# ausearch -c 'httpd' --raw | audit2allow -M my-httpd#012# semodule -X 300 -i my-httpd.pp#012
Jan 2 19:49:04 localhost systemd[1]: dbus-:[email protected]: Deactivated successfully.
Jan 2 19:49:04 localhost systemd[1]: dbus-:[email protected]: Consumed 2.756s CPU time.
Jan 2 19:49:13 localhost systemd[1]: setroubleshootd.service: Deactivated successfully.
Jan 2 19:49:13 localhost systemd[1]: setroubleshootd.service: Consumed 5.640s CPU time.

# setroubleshoot 日志排查
[root@localhost ~]# journalctl -t setroubleshoot
Jan 2 19:49:03 localhost.localdomain setroubleshoot[4825]: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055. For complete SELinux messages run: sealert -l e1d45e95-b7fc-4010-a355-dfa2d6adcd33
Jan 2 19:49:03 localhost.localdomain setroubleshoot[4825]: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055.

                                                            *****  Plugin bind_ports (92.2 confidence) suggests   ************************

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

                                                            *****  Plugin catchall_boolean (7.83 confidence) suggests   ******************

                                                            If you want to allow nis to enabled
                                                            Then you must tell SELinux about this by enabling the 'nis_enabled' boolean.

                                                            Do
                                                            setsebool -P nis_enabled 1

                                                            *****  Plugin catchall (1.41 confidence) suggests   **************************

                                                            If you believe that httpd should be allowed name_bind access on the port 8055 tcp_socket by default.
                                                            Then you should report this as a bug.
                                                            You can generate a local policy module to allow this access.
                                                            Do
                                                            allow this access for now by executing:
                                                            # ausearch -c 'httpd' --raw | audit2allow -M my-httpd
                                                            # semodule -X 300 -i my-httpd.pp

# 使用 ausearch 命令搜索
[root@localhost ~]# ausearch -m AVC
time->Tue Jan 2 19:48:52 2024
type=PROCTITLE msg=audit(1709552932.311:110): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1709552932.311:110): arch=c000003e syscall=49 success=no exit=-13 a0=3 a1=56136381b378 a2=10 a3=7fffca98736c items=0 ppid=1 pid=4822 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1709552932.311:110): avc:  denied  { name_bind } for  pid=4822 comm="httpd" src=8055 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0

# 摘要报告
[root@localhost ~]# aureport --avc

AVC Report
===============================================================
# date time comm subj syscall class permission obj result event
===============================================================
1. 03/04/24 19:48:52 httpd system_u:system_r:httpd_t:s0 49 tcp_socket name_bind system_u:object_r:unreserved_port_t:s0 denied 110

# 通过 sealert 命令查看相关信息
[root@localhost ~]# sealert -l e1d45e95-b7fc-4010-a355-dfa2d6adcd33
SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055.

*****  Plugin bind_ports (92.2 confidence) suggests   ************************

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

*****  Plugin catchall_boolean (7.83 confidence) suggests   ******************
.....(略)

其实 sealert 返回的信息中,提供的解决方法不止一种。当然我们选择可性度 92.2% 的解决方案。semanage port -a -t PORT_TYPE -p tcp 8055 就是一个好的解决方案,PORT_TYPE 必须要选择正确的类型才行,因为我们正在处理 HTTP 的端口,所以选择 http_port_t 类型。所以,整个问题的解决过程应该是这样的:

# 放开 8055 端口
[root@localhost ~]# semanage port -a -t http_port_t -p tcp 8055

# 允许端口是否添加成功
[root@localhost ~]# semanage port -l | grep  8055
http_port_t                    tcp      8055, 80, 81, 443, 488, 8008, 8009, 8443, 9000

# 启动 httpd 服务
[root@localhost ~]# systemctl start httpd

# httpd 服务正常
[root@localhost ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: active (running) since Mon 2024-03-04 19:50:48 CST; 4s ago
       Docs: man:httpd.service(8)
   Main PID: 4851 (httpd)
     Status: "Started, listening on: port 8055"
      Tasks: 213 (limit: 48839)
     Memory: 35.4M
        CPU: 339ms
     CGroup: /system.slice/httpd.service
             ├─4851 /usr/sbin/httpd -DFOREGROUND
             ├─4852 /usr/sbin/httpd -DFOREGROUND
             ├─4853 /usr/sbin/httpd -DFOREGROUND
             ├─4854 /usr/sbin/httpd -DFOREGROUND
             └─4855 /usr/sbin/httpd -DFOREGROUND

Jan 2 19:50:48 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
Jan 2 19:50:48 localhost.localdomain httpd[4851]: AH00558: httpd: Could not reliably determine the servers fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
Jan 2 19:50:48 localhost.localdomain httpd[4851]: Server configured, listening on: port 8055
Jan 2 19:50:48 localhost.localdomain systemd[1]: Started The Apache HTTP Server.

# 端口开启成功
[root@localhost ~]# ss -antlp | grep 8055
LISTEN 0      511          0.0.0.0:8055      0.0.0.0:*    users:(("httpd",pid=4855,fd=3),("httpd",pid=4854,fd=3),("httpd",pid=4853,fd=3),("httpd",pid=4851,fd=3))

# 命令行验证 httpd 服务请求是否正常
[root@localhost ~]# curl http://localhost:8055 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">
      /*<![CDATA[*/

      html {

目标资源(文件)安全上下文问题排查

前面我们提到所有的目录、资源,在 SELinux 中默认都有一个预设值,对应值不正常,访问也会报错。这里我们模拟一个故障,来进行验证与修复。

在进行这个模拟之前,先要明确一个概念,那就是整个网页服务器的文件默认是放在 /var/ www/html 里面的,所以如果您想要访问 http://localhost:8055/hello.html 这个地址,相应的文件应该放在 /var/ www/html/hello.html 才对。现在我们来模拟一个错误情况,就是用户在自己的家目录里创建了网页文件,然后使用 cp -a 命令把文件复制到 /var/www/html/hello.html-a 参数很强大,它会连同 SELinux 的类型属性也一并复制过去(带权限复制)。这样会发生什么情况呢?

  1. 首先以 root 用户的身份,在自己的家目录里创建一个名为 hello.html 的文件。
  2. 使用 cp -a 命令,把这个文件复制到 /var/ www/html httpd 服务主目录。
  3. 使用 curl 命令去访问这个文件,看看访问结果如何,然后再来判断问题。
  4. 查看 /var/log/messages 文件,看看有没有最近的 setroubleshoot 输出结果。
  5. 根据 sealert 给出的建议,执行相应的修改来纠正错误。
# 创建 hello.html 文件
[root@localhost ~]# echo "Hello,Rocky Linux 中文社区" > ~/hello.html
[root@localhost ~]# cat ~/hello.html 
Hello,Rocky Linux 中文社区

# 复制至 /var/www/html/ 目录
[root@localhost ~]# cp -a  ~/hello.html /var/www/html/

# 这时候我们查看 SELinux 相关信息,type 为 admin_home_t
[root@localhost ~]# ll -Z /var/www/html/hello.html 
-rw-r--r--. 1 root root unconfined_u:object_r:admin_home_t:s0 31 Jan 2 20:10 /var/www/html/hello.html

# 请求对应页面
[root@localhost ~]# curl http://localhost:8055/hello.html
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You dont have permission to access this resource.</p> # 重点,这是会提示没有权限访问对应资源,说明文件是存在的,但是您没有权限访问。
</body></html>

# 查看对应日志
[root@localhost ~]# grep setrouble /var/log/messages | grep sealert
Jan 2 19:49:03 localhost setroubleshoot[4825]: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 8055. For complete SELinux messages run: sealert -l e1d45e95-b7fc-4010-a355-dfa2d6adcd33
Jan 2 20:11:35 localhost setroubleshoot[5106]: SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/html/hello.html. For complete SELinux messages run: sealert -l 2ee1ffbe-b904-4318-af80-21613da46b78
Jan 2 20:11:35 localhost setroubleshoot[5106]: SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/html/hello.html. For complete SELinux messages run: sealert -l 2ee1ffbe-b904-4318-af80-21613da46b78

# 因为 SELinux 会记录到 audit 中,所以其实也可以直接过滤 audit 审计日志。
[root@localhost ~]# grep "avc: .denied" /var/log/audit/audit.log
type=AVC msg=audit(1709552932.311:110): avc:  denied  { name_bind } for  pid=4822 comm="httpd" src=8055 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
type=AVC msg=audit(1709554291.769:123): avc:  denied  { getattr } for  pid=4853 comm="httpd" path="/var/www/html/hello.html" dev="dm-0" ino=50615424 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1709554291.769:124): avc:  denied  { getattr } for  pid=4853 comm="httpd" path="/var/www/html/hello.html" dev="dm-0" ino=50615424 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0

# 获取解决方案
[root@localhost ~]# sealert -l 2ee1ffbe-b904-4318-af80-21613da46b78
SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/html/hello.html.

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

If you want to fix the label. 
/var/www/html/hello.html 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/hello.html

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

# 99.5% 解决方案就是重置权限
[root@localhost ~]# /sbin/restorecon -v /var/www/html/hello.html
Relabeled /var/www/html/hello.html from unconfined_u:object_r:admin_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0

# 这时候 type 为 httpd_sys_content_t
[root@localhost ~]# ll -Z /var/www/html/hello.html 
-rw-r--r--. 1 root root unconfined_u:object_r:httpd_sys_content_t:s0 31 Jan 2 20:10 /var/www/html/hello.html

# 再请求页面,返回正常
[root@localhost ~]# curl http://localhost:8055/hello.html
Hello,Rocky Linux 中文社区
技巧
总之,您要记住,如果无法启动某个服务或者服务无法访问某个文件资源,首先检查一下读写执行权限(rwx)是否设置正确。如果权限没问题,那么就先把 SELinux 设置为许可模式(permissive),然后重复刚刚出现错误的操作,看看操作是否能正常进行。如果这时候能正常运行,那就说明问题肯定是由 SELinux 引起的。接下来,再把 SELinux 设置回强制模式(enforcing),然后根据 sealert 提供的方法来处理错误。最后,一定要再次重复刚刚出现错误的操作,确保在强制模式下操作也能正常进行。如果可以的话,那就没问题了。

写在最后

SELinux 配置相对比较复杂,但不用担心,我们将在接下来的教程中逐步深入其使用方法。除了已经提及的命令行工具,SELinux 还支持利用 audit2allow、matchpathcon、sesearch 等命令进行全面分析和故障诊断。这里就不再展开详述。

Avatar photo

关于 木子

Founder of the Rocky Linux Chinese community, MVP、VMware vExpert、TVP, advocate for cloud native technologies, with over ten years of experience in site reliability engineering (SRE) and the DevOps field. Passionate about Cloud Computing、Microservices、CI&CD、DevOps、Kubernetes, currently dedicated to promoting and implementing Rocky Linux in Chinese-speaking regions.
用一杯咖啡支持我们,每一篇 [文档] 都经过我们实操,并非从网上一味的copy,期间花费了大量的心思,希望能够帮忙到您。
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇