Linux 基础体系 · 第 73/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
SELinux 完整基础:Label、Type Enforcement、Policy、布尔值和排障
SELinux(Security-Enhanced Linux)是 Linux 内核中的强制访问控制(Mandatory Access Control,MAC)机制。它不替代传统的 Unix 权限,而是在传统权限检查之外,再增加一层由安全策略决定的访问控制。
一个进程即使以 root 身份运行,也不一定能够读取、修改或执行任意文件;反过来,文件的 Unix 权限允许访问,也不代表 SELinux 一定允许访问。
SELinux 的核心可以概括为:
其中:
- DAC(Discretionary Access Control)是传统的用户、组、文件权限和 ACL;
- SELinux 策略主要通过 Label、Type Enforcement 和规则决定;
- 在启用 MLS/MCS 等机制时,还可能检查安全级别约束;
- 内核会在系统调用执行期间实施这些检查。
因此,排查“服务明明有权限却访问失败”时,不能只看 chmod 和 chown。
一、SELinux 解决什么问题
传统 Unix 权限通常围绕文件所有者、所属组和其他用户展开。例如:
-rw-r----- 1 app app 1024 /srv/app/config.yml
这表示:
- 文件所有者
app可以读写; app组成员可以读;- 其他用户没有权限。
但是,如果某个 Web 服务进程本身以 app 用户运行,那么它可能同时拥有:
- 读取应用配置的能力;
- 读取数据库凭据的能力;
- 修改应用目录的能力;
- 访问用户上传文件的能力。
传统权限很难表达“这个服务可以读取网站静态文件,但不能读取 SSH 私钥”这样的进程级限制。
SELinux 的基本思路是:不仅看访问者是谁,还看访问者运行在哪个安全域,以及目标对象具有什么安全类型。
例如:
nginx 进程 -> httpd_t
网站静态文件 -> httpd_sys_content_t
应用运行时目录 -> httpd_sys_rw_content_t
SSH 私钥 -> ssh_home_t 或 user_home_t
策略可以规定:
httpd_t 可以读取 httpd_sys_content_t
httpd_t 不可以读取 ssh_home_t
httpd_t 只有在特定条件下可以写入 httpd_sys_rw_content_t
这样,即使 Web 服务进程被入侵,攻击者获得的也只是 httpd_t 这个受限域,而不是整个系统的完整访问能力。
二、SELinux 的组件和访问路径
现代发行版中的 SELinux 通常由以下部分共同组成:
-
Linux 内核安全模块
- 执行安全钩子;
- 检查文件、进程、套接字等对象的访问;
- 记录被拒绝的访问。
-
SELinux 用户空间工具
- 查询和修改运行状态;
- 管理文件 Label;
- 加载策略模块;
- 管理布尔值;
- 查询审计日志。
-
SELinux Policy
- 描述哪些域可以访问哪些类型;
- 描述对象创建、域转换、继承等行为;
- 可能包含基于布尔值的条件规则。
-
审计系统
- 通常通过
auditd或内核审计接口接收 AVC(Access Vector Cache)拒绝事件; - 日志常见于
/var/log/audit/audit.log; - systemd 环境下也可能通过
journalctl查询。
- 通常通过
一次典型访问可以抽象为:
进程发起系统调用
|
v
传统 DAC 检查
|
| 失败 -> 返回 EACCES/EPERM
v
SELinux 安全检查
|
| 失败 -> 记录 AVC,返回拒绝
v
文件系统或内核对象执行实际操作
SELinux 不一定是唯一的拒绝来源。还可能存在:
- 文件 Unix 权限不足;
- POSIX ACL 拒绝;
- 只读挂载;
- 文件系统不支持所需操作;
- seccomp、容器隔离或 systemd sandbox 限制;
- AppArmor 或其他 LSM 限制。
因此,不能看到服务报错就直接运行 audit2allow。
三、Label:SELinux 的安全上下文
3.1 Label 是什么
SELinux 为进程、文件、目录、端口、设备和其他对象关联安全上下文(security context),通常写成:
user:role:type:level
例如:
system_u:system_r:httpd_t:s0
system_u:object_r:httpd_sys_content_t:s0
四个字段分别表示:
| 字段 | 含义 |
|---|---|
| SELinux user | SELinux 用户身份,不等同于 Linux 用户 |
| role | SELinux 角色,主要用于 RBAC |
| type | 类型,是 Type Enforcement 的核心 |
| level | 安全级别,主要用于 MLS/MCS |
在大多数启用默认 targeted policy 的服务器上,最需要关注的是 type。
例如:
system_u:system_r:sshd_t:s0
这是一个进程上下文,其中:
system_u是 SELinux 用户;system_r是角色;sshd_t是进程类型,也称为域(domain);s0是安全级别。
目录或普通文件通常使用:
system_u:object_r:var_t:s0
其中 object_r 表示对象角色。
3.2 查看进程和文件 Label
查看进程:
ps -eZ | grep -E 'sshd|nginx|httpd'
可能得到:
system_u:system_r:sshd_t:s0 812 ? 00:00:00 sshd
system_u:system_r:httpd_t:s0 1250 ? 00:00:00 nginx
查看文件:
ls -lZ /etc/ssh/sshd_config
ls -ldZ /var/www/html
示例输出:
-rw-------. root root system_u:object_r:sshd_config_t:s0 /etc/ssh/sshd_config
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html
查看当前登录 Shell 的上下文:
id -Z
查看当前 SELinux 状态:
getenforce
sestatus
典型状态是:
Enforcing
三种模式含义如下:
Enforcing:执行策略,违规访问会被拒绝并通常记录日志;Permissive:不实际阻止访问,但记录可能的拒绝;Disabled:内核不加载 SELinux。
setenforce 0 和 setenforce 1 通常只改变当前运行状态:
sudo setenforce 0
sudo setenforce 1
它们不会自动永久修改启动配置。生产环境不应把 Permissive 当作长期解决方案;它可以用于短时诊断,但会让被攻击的进程获得策略本应阻止的访问能力。
3.3 Label 不等于 Linux 文件权限
下面两条命令操作的是不同层次:
chown nginx:nginx /srv/site/index.html
chmod 0644 /srv/site/index.html
它们修改传统 DAC 属性。
chcon -t httpd_sys_content_t /srv/site/index.html
它修改 SELinux Label。
即使文件最终显示为:
-rw-r--r--. nginx nginx system_u:object_r:httpd_sys_content_t:s0
也只能说明两层属性分别满足某些条件,不能直接推断任意进程都能访问。
四、Type Enforcement:SELinux 的主要授权模型
4.1 域、类型和访问向量
Type Enforcement(TE,类型强制)把主体和客体都抽象为类型:
- 进程类型通常称为域,例如
sshd_t、httpd_t; - 文件类型例如
httpd_sys_content_t、etc_t; - 网络套接字、目录、进程等对象也具有类型。
一条典型 TE 规则可以抽象为:
allow <source_type> <target_type>:<object_class> <permissions>;
例如:
allow httpd_t httpd_sys_content_t:file { open read getattr };
含义是:
类型为
httpd_t的进程,可以对类型为httpd_sys_content_t的普通文件执行open、read和getattr。
目录访问还需要目录本身的权限,例如:
allow httpd_t httpd_sys_content_t:dir {
search
getattr
open
read
};
实际策略通常通过宏和接口生成,不一定直接出现这样的底层规则,但这个形式有助于理解访问决策。
4.2 访问不只是“读文件”
一个程序读取:
/var/www/html/index.html
至少可能涉及:
- 对
/执行search; - 对
/var执行search; - 对
/var/www执行search; - 对
/var/www/html执行search、getattr; - 对
index.html执行open、read、getattr。
因此,文件本身 Label 正确,并不保证访问成功。父目录缺少搜索权限、传统权限不足或挂载参数异常,都可能导致失败。
4.3 允许条件的形式化表达
对一次访问,可以使用下面的抽象条件表示:
变量含义:
- :主体,例如一个进程;
- :目标对象,例如一个文件;
- :对象类别,例如
file、dir、tcp_socket; - :具体权限,例如
read、write、connect; - :传统权限、ACL 等是否允许;
- :SELinux TE 规则是否允许;
- :角色、级别或其他约束是否允许。
假设:
进程类型 = httpd_t
文件类型 = httpd_sys_content_t
对象类别 = file
权限 = read
如果策略存在:
allow httpd_t httpd_sys_content_t:file read;
并且 DAC 也允许该进程读取路径,那么访问才可能成功。
反例是:
文件类型 = default_t
即使文件权限是:
-rw-r--r--. root root
也不代表 httpd_t 能读取。默认策略通常不会允许 Web 服务读取任意 default_t 文件。
4.4 为什么改成 777 仍然失败
下面的操作经常被误用:
chmod -R 777 /srv/site
它最多改变 DAC:
所有本地用户都可能通过传统权限访问
但如果 SELinux 规则没有允许:
httpd_t -> 目标类型:file read
访问仍然会被 SELinux 拒绝。
更糟的是,chmod -R 777 会扩大所有本地用户和被入侵服务的写权限,可能造成:
- Web Shell 或恶意文件被植入;
- 配置文件被篡改;
- 密钥和凭据泄露;
- 攻击者通过其他服务继续横向利用。
正确做法是先判断访问者进程域、目标路径 Label 和实际操作,再决定是否需要调整 Label、DAC 或策略。
五、文件 Label 的来源、继承和持久化
5.1 默认 Label 与文件上下文规则
SELinux 不仅保存当前文件的 Label,还可以根据路径匹配规则推导“这个路径应该具有什么 Label”。
查看文件上下文规则:
matchpathcon /var/www/html/index.html
semanage fcontext -l | grep -E '/var/www|httpd_sys_content_t'
matchpathcon 查询的是策略对某路径的默认判断;ls -Z 查看的是文件当前实际 Label。
二者可能不同:
matchpathcon: system_u:object_r:httpd_sys_content_t:s0
ls -Z: system_u:object_r:default_t:s0
这通常表示文件曾被复制、解压、移动或手工修改,当前 Label 已经偏离默认规则。
5.2 restorecon:根据规则恢复 Label
如果路径本来就属于系统默认目录,可以执行:
sudo restorecon -Rv /var/www/html
参数含义:
-R:递归处理;-v:显示修改过程。
预期可能看到:
Relabeled /var/www/html/index.html from ...:default_t to ...:httpd_sys_content_t
restorecon 的关键特点是:它根据已有的文件上下文规则恢复 Label,而不是任意猜测一个类型。
5.3 semanage fcontext:为自定义路径建立持久规则
如果网站目录放在 /srv/site,直接运行:
sudo chcon -R -t httpd_sys_content_t /srv/site
可能暂时有效,但后续执行 restorecon、重新打包恢复或某些系统维护操作时,Label 可能恢复为默认值。
更持久的做法是:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site
这里:
-a添加规则;-t指定目标类型;'/srv/site(/.*)?'匹配目录本身及其所有后代;restorecon把规则应用到已有文件。
检查规则:
sudo semanage fcontext -l | grep '/srv/site'
删除错误规则:
sudo semanage fcontext -d -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site
实际生产环境中,semanage 可能来自发行版的额外软件包,例如某些 Red Hat 系发行版需要安装 policycoreutils-python-utils。包名在发行版之间不同,应使用本发行版的软件包管理器查询。
5.4 内容类型和可写类型必须区分
以 Web 服务为例,常见策略会区分:
httpd_sys_content_t 可由 Web 服务读取的内容
httpd_sys_rw_content_t 允许 Web 服务读写的内容
不要为了让上传功能工作,就把整个站点目录改成可写类型:
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site(/.*)?'
这会扩大 Web 服务被入侵后的写入范围。更合理的模型是:
/srv/site/public -> httpd_sys_content_t
/srv/site/uploads -> httpd_sys_rw_content_t
例如:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site/public(/.*)?'
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site/uploads(/.*)?'
sudo restorecon -Rv /srv/site
这里的安全边界不是“能否访问整个站点”,而是“Web 服务在哪些最小目录中拥有写权限”。
5.5 创建、复制和移动的差异
Label 处理与文件操作方式有关:
- 在已正确标记的目录中创建文件,通常会继承目录的类型;
cp创建目标文件时,可能依据目标目录或复制选项产生不同结果;mv在同一文件系统内移动文件时通常保留原有 inode 的 Label;- 从其他文件系统移动过来、解压归档或使用特殊工具恢复文件时,Label 可能不符合目标路径规则。
因此,部署后执行:
sudo restorecon -Rv /srv/site
往往比单独依赖 cp 或 mv 的行为更可靠。
六、Label 不只用于普通文件
6.1 进程域转换
服务启动时,进程可能从一个初始域转换到服务专用域。例如:
init_t -> sshd_t
init_t -> httpd_t
子进程也可能通过执行特定程序发生域转换。域转换规则决定了:
- 哪个进程可以执行某个程序;
- 执行后进入哪个域;
- 新域具有什么访问能力。
如果一个服务错误地在通用域运行,或者没有正确发生域转换,排障方向就不能只看目标文件 Label,还要检查:
ps -eZ
systemctl status <service>
systemctl cat <service>
6.2 端口 Label
SELinux 也可以限制服务绑定哪些端口。查看端口类型:
sudo semanage port -l | grep http_port_t
典型结果可能包括:
http_port_t tcp 80, 443, 8080
假设 Web 服务要绑定 8443,而该端口没有标记为 http_port_t,即使程序拥有普通 Unix 权限,也可能无法绑定。
添加端口:
sudo semanage port -a -t http_port_t -p tcp 8443
如果端口已经存在但类型不同,应使用修改:
sudo semanage port -m -t http_port_t -p tcp 8443
检查:
sudo semanage port -l | grep 8443
这与开放防火墙端口是两件事:
semanage port解决 SELinux 对服务端口类型的判断;firewall-cmd或其他防火墙工具解决网络包是否到达主机。
两者都满足,服务才可能从外部正常访问。
6.3 网络连接和文件访问是不同权限
Web 服务读取文件成功,不表示它可以连接数据库或外部 API。网络访问可能受到:
- SELinux 网络类权限;
- 防火墙;
- 路由和 DNS;
- 远端服务 ACL;
- 应用配置。
SELinux 策略通常通过布尔值控制一部分可选的网络能力,例如:
getsebool httpd_can_network_connect
如果业务确实需要 Web 服务主动连接其他服务,可以考虑:
sudo setsebool -P httpd_can_network_connect on
但是,这个布尔值往往允许更广泛的网络连接,而不是只允许某一个 IP 和端口。生产上应先确认发行版策略提供的更细粒度类型或专用布尔值,不能把“能联网”理解为“只连接数据库”。
七、Policy:策略由什么组成
7.1 Policy 是规则集合,不是单个配置文件
SELinux Policy 是一组描述安全关系的规则,通常包括:
- 类型和域定义;
allow授权规则;- 域转换规则;
- 文件上下文规则;
- 端口和设备类型;
- 条件规则;
- 角色和安全级别约束;
dontaudit规则;- 策略模块及其依赖。
现代发行版通常使用模块化策略。管理员安装或启用某个服务时,相关策略模块可能随软件包提供。
查看已加载模块:
sudo semodule -l
查看某个模块的状态:
sudo semodule -lfull | grep <module-name>
不同发行版的输出格式和模块名称可能不同,因此不应依赖某个固定列表。
7.2 allow 不是完整策略语义
策略中的 allow 规则说明某种访问可以被允许,但实际决策还可能受到:
- DAC;
neverallow编译约束;- 角色约束;
- MLS/MCS 约束;
- 条件布尔值;
dontaudit对日志的抑制;- 文件和进程实际 Label;
- 对象类别和具体权限。
因此,“我查到了一个 allow 规则”不能直接推出“访问一定成功”。
7.3 查看某个访问是否有策略支持
可以使用:
sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read
参数含义:
-A:查询 allow 规则;-s:源类型;-t:目标类型;-c:对象类别;-p:权限。
可能得到类似:
allow httpd_t httpd_sys_content_t:file { getattr map open read };
如果要查看是否受布尔值条件控制,可以使用更完整的策略查询方式;具体输出会随 setools 版本和发行版策略不同而变化。
安装 sesearch 的软件包也因发行版不同而不同,常见来源是 setools-console。
7.4 自定义策略模块的正确边界
当默认策略确实不覆盖某个合法业务行为时,可以开发自定义策略模块,而不是直接关闭 SELinux。
一个常见流程是:
- 在测试环境重现最小失败操作;
- 收集 AVC;
- 判断拒绝是否来自错误 Label、DAC、端口类型、布尔值或配置错误;
- 只有确认是合法且默认策略未覆盖时,才设计新规则;
- 在最小范围内编写和测试模块;
- 验证没有扩大无关访问能力;
- 记录模块来源、版本和回滚方法。
简单的自定义模块可能通过 audit2allow 生成初稿:
sudo ausearch -m AVC -ts recent | audit2allow -M local_service
安装前先查看:
cat local_service.te
cat local_service.fc 2>/dev/null
安装:
sudo semodule -i local_service.pp
删除:
sudo semodule -r local_service
但 audit2allow 只是把观察到的拒绝转换为可能的 allow 规则,它不知道业务意图。下面这种做法风险很高:
sudo ausearch -m AVC -ts recent | audit2allow -M fix
sudo semodule -i fix.pp
因为日志中的拒绝可能是:
- 攻击者正在尝试越权;
- 文件 Label 配错;
- 服务启动参数错误;
- 被错误配置触发的无效路径;
- 本来就应该被拒绝的行为。
生成规则前应先理解每个 AVC 的主体、目标、对象类别、权限和调用路径。
八、布尔值:条件化启用策略能力
8.1 布尔值是什么
SELinux 布尔值是策略中的条件开关。策略可以表达:
如果 boolean_X 为 on:
允许 httpd_t 访问某类网络对象
否则:
不允许
查询当前值:
getsebool httpd_can_network_connect
示例:
httpd_can_network_connect --> off
查看所有布尔值:
getsebool -a
查询布尔值说明:
semanage boolean -l | grep httpd
说明通常会同时显示:
- 当前运行时状态;
- 永久配置状态;
- 布尔值的文字描述。
8.2 临时与永久修改
临时修改:
sudo setsebool httpd_can_network_connect on
系统重启后通常会恢复原值。
永久修改:
sudo setsebool -P httpd_can_network_connect on
-P 会更新持久策略配置,并可能触发策略重新生成或加载,因此在生产系统上应安排变更窗口并验证服务状态。
回滚:
sudo setsebool -P httpd_can_network_connect off
8.3 布尔值不是任意权限开关
布尔值名称和含义取决于发行版策略。不能因为在一台系统上看到:
httpd_can_network_connect_db
就假设所有发行版都提供同名且同义的开关。
正确流程是:
getsebool -a | grep -E 'httpd|ssh|named|container'
semanage boolean -l
然后结合当前服务的实际需求选择。
布尔值的安全取舍是:
它通常比关闭 SELinux 更精确,但未必是最小权限。比如允许 Web 服务联网,并不一定能限制为“只访问数据库的 5432 端口”。如果业务需要更细粒度隔离,可能需要专门的服务域、端口类型、代理架构或网络层控制。
九、AVC:理解拒绝日志
9.1 AVC 日志包含哪些信息
典型 AVC 事件可以抽象为:
avc: denied { read } for
pid=1234 comm="nginx"
name="config.yml"
dev="vda1" ino=123456
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:default_t:s0
tclass=file
关键字段:
| 字段 | 含义 |
|---|---|
denied { read } |
被拒绝的权限 |
pid |
进程 PID |
comm |
进程名 |
name |
目标名称 |
scontext |
主体上下文 |
tcontext |
目标上下文 |
tclass |
目标对象类别 |
dev、ino |
文件系统设备和 inode 信息 |
把这条记录翻译成自然语言:
类型为
httpd_t的进程,尝试对类型为default_t的普通文件执行read,该访问被拒绝。
这比单纯看到“权限 denied”提供了更多因果信息。
9.2 查询审计日志
优先使用结构化查询:
sudo ausearch -m AVC -ts recent
查看最近十分钟:
sudo ausearch -m AVC -ts recent -i
-i 会把部分数值字段转换为更易读的形式。
systemd 环境还可以:
sudo journalctl -k | grep -i avc
sudo journalctl --since "10 minutes ago" | grep -i 'avc.*denied'
如果查询不到日志,不能直接得出“没有 SELinux 拒绝”。需要检查:
getenforce
systemctl is-active auditd
ls -l /var/log/audit/audit.log
还可能存在:
- 审计服务未运行;
- 日志已轮转;
- 拒绝发生在更早时间;
dontaudit抑制了某些日志;- 访问实际被 DAC、应用或其他组件拒绝。
9.3 audit2why 的作用和局限
可以使用:
sudo ausearch -m AVC -ts recent | audit2why
它会尝试解释:
- 是否可能由布尔值控制;
- 是否存在策略规则;
- 是否可能是 Label 问题;
- 是否建议创建本地策略。
但它是辅助解释工具,不是自动修复工具。输出“建议允许”不等于这就是正确的生产修复。
十、完整排障流程
下面以“Web 服务无法读取自定义目录”为例。
假设:
Web 根目录:/srv/site
服务进程:nginx
现象:访问页面返回 403
第一步:确认实际运行域
ps -eZ | grep nginx
预期类似:
system_u:system_r:httpd_t:s0 2100 ? 00:00:00 nginx
如果进程不在预期域中,先检查服务启动方式、二进制路径和域转换,而不是急于修改文件 Label。
第二步:检查当前强制状态
getenforce
如果是:
Enforcing
SELinux 会实际阻止访问。
如果是:
Permissive
服务可能暂时成功,但仍会产生 AVC。这种状态适合短时间收集证据,不适合作为生产修复。
第三步:检查完整路径的 Label
ls -ldZ /srv /srv/site
find /srv/site -maxdepth 2 -printf '%p\n' -exec ls -Zd {} \;
假设发现:
/srv/site/index.html system_u:object_r:default_t:s0
这说明文件类型不符合 Web 服务通常使用的内容类型。
还需要检查父目录,因为 Web 服务必须对每一级目录拥有 search 能力。
第四步:检查路径预期类型
matchpathcon /srv/site/index.html
如果系统没有为 /srv/site 建立自定义规则,matchpathcon 可能不会返回 httpd_sys_content_t。这时应先建立规则:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site
第五步:再次确认结果
ls -lZ /srv/site/index.html
matchpathcon /srv/site/index.html
两者的类型应一致或至少符合预期。
第六步:检查传统权限
namei -l /srv/site/index.html
getfacl /srv/site/index.html
namei -l 可以逐级显示路径权限,适合发现父目录缺少执行权限的问题。
例如:
drwx------ root root /srv
即使文件 Label 完全正确,httpd_t 对应的 Linux 用户仍可能无法穿过 /srv。此时需要修复 DAC 或路径设计,而不是添加 SELinux 规则。
第七步:复现并查询新 AVC
sudo ausearch -m AVC -ts recent -i
如果仍有拒绝,重点比较:
scontext是否为预期服务域;tcontext是否为预期文件类型;tclass是否为file、dir或其他类别;- 被拒绝的权限是否为
read、search、write、execute; - 发生时间是否对应刚才的复现操作。
第八步:检查策略和布尔值
如果问题是网络连接:
getsebool httpd_can_network_connect
semanage boolean -l | grep httpd
如果问题是端口绑定:
sudo semanage port -l | grep http_port_t
如果问题是文件读取:
sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read
第九步:验证应用行为,而不是只验证命令
修复后测试:
curl -I http://127.0.0.1/
systemctl status nginx
journalctl -u nginx --since "5 minutes ago"
同时观察:
sudo ausearch -m AVC -ts recent -i
“页面返回 200”说明这一条请求成功,但仍应确认没有把整个目录错误地标记为可写、没有启用过宽的布尔值,也没有产生新的拒绝。
十一、常见失败方式及其原因
11.1 只修改 chmod
chmod -R 755 /srv/site
如果问题是 default_t 或其他不匹配的类型,DAC 修改不会解决 SELinux 拒绝,还可能改变大量无关文件的权限。
11.2 直接使用 chcon
chcon -R -t httpd_sys_content_t /srv/site
chcon 修改当前 Label,但通常不增加持久的路径映射规则。后续 restorecon 可能覆盖它。
适合:
- 临时验证假设;
- 实验环境快速测试。
持久配置应优先使用:
semanage fcontext
restorecon
11.3 把全部目录标记为可写
semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site(/.*)?'
这会使整个站点成为 Web 服务潜在的写入区域。更小的写入范围通常更容易审计,也能降低服务被入侵后的持久化风险。
11.4 一看到 AVC 就运行 audit2allow
AVC 说明“某次访问被策略拒绝”,不说明“应该允许这次访问”。
如果攻击者正在尝试:
httpd_t -> ssh_home_t:file read
生成 allow 规则会把防护失败伪装成修复成功。
11.5 误把 dontaudit 当成没有拒绝
某些策略会使用 dontaudit 抑制预期噪声。日志中没有 AVC 并不等于所有访问都被允许,也不等于没有 SELinux 参与。
在测试环境中,可以使用策略调试工具检查被抑制的规则;具体命令和影响取决于发行版策略版本。不要在生产环境长期关闭审计抑制,因为日志量可能显著增加。
11.6 只检查文件,不检查进程和端口
以下三类问题都可能表现为“服务启动失败”:
进程域不正确
配置文件类型错误
监听端口类型错误
排查时应同时查看:
ps -eZ
ls -lZ <配置文件>
sudo semanage port -l
十二、SELinux 与 SSH 的关系
OpenSSH 本身负责 SSH 协议、认证和会话管理;SELinux 负责限制 sshd 及其子进程访问本机对象。
查看 SSH 进程:
ps -eZ | grep sshd
查看 SSH 配置文件:
ls -lZ /etc/ssh/sshd_config
检查 SSH 服务状态:
systemctl status sshd
journalctl -u sshd
发行版服务名可能是 sshd 或 ssh,例如:
systemctl status sshd
systemctl status ssh
不要把 OpenSSH 配置错误和 SELinux 拒绝混为一谈:
sshd_config语法错误通常会出现在sshd -t或服务日志中;- 端口冲突会表现为 bind 失败;
- SELinux 端口类型错误可能伴随 AVC 或服务启动拒绝;
- 文件权限不安全可能被 OpenSSH 直接拒绝;
- 用户登录后的目录访问还可能受到 SELinux 用户、角色和目录类型影响。
验证 OpenSSH 配置:
sudo sshd -t
这个命令只验证配置语法和基本合法性,不会替代 SELinux 排查。
十三、域转换与“执行”权限
SELinux 对“读取脚本”和“执行程序”可以进行不同控制。
假设某服务读取一个脚本文件:
httpd_t -> script_t:file read
不等于它一定可以执行:
httpd_t -> script_t:file execute
即便允许执行,执行后还可能发生:
httpd_t -> cgi_t
或其他域转换。
这使策略能够表达:
Web 服务可以读取静态内容;
Web 服务只能执行经过策略定义的 CGI;
执行 CGI 后进入更受限的域;
CGI 域不能直接读取 SSH 私钥。
因此,看到进程访问某文件时,应区分:
read:读取内容;execute:执行文件;entrypoint:作为进入目标域的程序;transition:执行后改变域;open、getattr、map等辅助权限。
不要简单地把某个程序文件改成可执行权限,就认为 SELinux 会允许它运行。
十四、挂载、网络文件系统和容器边界
14.1 NFS 和 CIFS
网络文件系统可能无法像本地文件系统一样保存 SELinux 扩展属性,或者使用特殊的 Label 映射方式。
遇到 NFS/CIFS 上的访问问题,应检查:
mount
findmnt -T /path/to/file
ls -Zd /path/to/file
不同发行版和挂载方案可能使用:
- NFS 服务端提供的 SELinux Label;
context=挂载选项;nfs_t、cifs_t等策略类型;- 多级安全标签映射。
不要把本地 ext4 上的 chcon 操作直接套到网络文件系统上。先确认文件系统是否支持扩展属性以及当前挂载策略。
14.2 容器
容器环境中,SELinux 还可能限制:
- 容器进程域;
- 容器访问宿主机路径;
- 容器之间的数据目录隔离;
- Podman、CRI-O 或 Kubernetes 的挂载 Label。
常见容器 Label 包括:
container_t
container_file_t
在支持的工具中,挂载卷时可能使用自动重新标记选项,例如 Podman 的 :Z 或 :z。它们的语义和风险必须结合具体容器工具确认:
:Z通常用于私有、专属容器卷;:z通常用于多个容器共享的卷;- 错误使用可能改变宿主机路径的 Label,影响其他服务。
容器排障不能只在容器内执行 ls -Z,还要在宿主机检查:
ps -eZ
ls -ldZ /host/path
podman inspect <container>
十五、Enforcing、Permissive 和 Disabled 的生产取舍
15.1 Enforcing
这是实际执行强制策略的状态。优点是:
- 违规访问被阻止;
- 可以限制服务入侵后的横向能力;
- 策略错误能够尽早暴露。
风险是:
- 未充分测试的自定义服务可能启动失败;
- 部署流程遗漏 Label 时会产生运行故障;
- 需要建立审计和变更流程。
15.2 Permissive
Permissive 会记录潜在拒绝,但不阻止访问。它适合:
- 迁移或首次启用 SELinux 时收集行为;
- 测试新服务;
- 短时定位是否存在 SELinux 相关问题。
它不适合:
- 长期生产运行;
- 作为“解决问题”的替代方案;
- 在不理解日志的情况下批量生成策略。
15.3 Disabled
Disabled 会让 SELinux 完全不参与。重新启用时,文件系统可能需要重新标记;如果系统长期处于 Disabled,直接切换到 Enforcing 可能造成大量服务故障。
在支持的发行版中,永久状态通常通过启动配置管理,例如检查:
grep -E '^SELINUX=' /etc/selinux/config
具体配置文件、引导参数和重新标记流程存在发行版差异,修改前应准备控制台访问、备份和回滚方案。不能仅执行一次 setenforce 1 就认为重启后仍是 Enforcing。
十六、一个最小实验:观察 Label 如何影响访问
以下实验应在测试虚拟机或专用环境执行。
创建测试目录:
sudo mkdir -p /srv/selinux-demo
echo 'selinux demo' | sudo tee /srv/selinux-demo/index.html
先查看 Label:
ls -lZ /srv/selinux-demo/index.html
matchpathcon /srv/selinux-demo/index.html
为目录建立 Web 内容规则:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/selinux-demo(/.*)?'
sudo restorecon -Rv /srv/selinux-demo
再次查看:
ls -lZ /srv/selinux-demo/index.html
预期类型变为:
httpd_sys_content_t
现在用一个服务域进行实际测试比直接用当前用户更有意义。例如,系统安装并运行 Web 服务后,可以把该目录配置为站点根目录,重新加载服务,再通过本机请求验证。
测试写入时,不要只看是否能创建文件,还要观察目录和文件的 Label:
ls -ldZ /srv/selinux-demo
find /srv/selinux-demo -maxdepth 1 -printf '%p\n' -exec ls -Zd {} \;
如果服务需要上传,应该只把上传子目录标记为:
sudo mkdir -p /srv/selinux-demo/uploads
sudo semanage fcontext -a -t httpd_sys_rw_content_t \
'/srv/selinux-demo/uploads(/.*)?'
sudo restorecon -Rv /srv/selinux-demo/uploads
这样可以验证一个重要边界:
静态内容目录:读取
上传目录:读取和写入
其他目录:不自动获得写入权限
十七、排障决策树
可以把一次 SELinux 访问失败简化为以下路径:
flowchart TD
A[服务访问失败] --> B{SELinux 是否启用?}
B -- Disabled --> C[检查 DAC、ACL、应用和系统调用错误]
B -- Enforcing/Permissive --> D[确认实际进程域]
D --> E[检查目标对象和父目录 Label]
E --> F{Label 是否符合预期?}
F -- 否 --> G[使用 semanage fcontext 和 restorecon]
F -- 是 --> H[检查 DAC、ACL、挂载和路径权限]
G --> H
H --> I{是否有 AVC?}
I -- 是 --> J[分析 scontext、tcontext、tclass、权限]
I -- 否 --> K[检查审计服务、日志时间和其他拒绝来源]
J --> L{是否已有布尔值或端口类型?}
L -- 是 --> M[按最小范围调整并验证]
L -- 否 --> N[确认业务合法后设计最小自定义策略]
M --> O[回归测试并观察新日志]
N --> O
K --> O
关键路径是:
- 先确认 SELinux 是否真的参与;
- 再确认进程域;
- 再确认目标 Label 和父目录;
- 再区分 DAC 与 SELinux;
- 最后才考虑布尔值或自定义策略。
这个顺序可以避免把路径错误、文件权限错误和策略错误混在一起。
十八、如何区分“策略问题”和“业务问题”
一个访问失败至少可以按以下证据分类:
SELinux Label 问题
特征:
tcontext=...:default_t:...
或者:
matchpathcon <path>
ls -Z <path>
二者不一致。
优先动作:
semanage fcontext
restorecon
DAC 或 ACL 问题
特征:
- 没有对应 AVC;
namei -l显示父目录无搜索权限;getfacl显示 ACL 拒绝;- 用相同 Linux 用户复现也失败。
优先检查:
namei -l /path/to/object
getfacl /path/to/object
SELinux 许可缺失
特征:
scontext和tcontext都符合预期;- 仍有明确 AVC;
- 访问行为确实属于合法业务需求;
- 没有可用布尔值、端口类型或现有策略接口。
这时才考虑自定义策略。
应用自身错误
特征:
- SELinux 无拒绝;
- DAC 和 Label 正确;
- 应用日志显示配置解析失败、协议错误、超时或认证失败。
例如数据库连接失败可能是:
数据库凭据错误
DNS 失败
防火墙阻断
数据库拒绝来源地址
SELinux 禁止网络连接
不能只因为服务日志出现 Permission denied,就断言是 SELinux。
十九、Label、Policy 和布尔值之间的关系
三者承担不同职责:
Label 回答“对象属于哪一类”
例如:
/var/www/html/index.html
-> httpd_sys_content_t
Policy 回答“哪些类型之间允许哪些操作”
例如:
httpd_t 可以读取 httpd_sys_content_t
Boolean 回答“某组预定义规则是否启用”
例如:
httpd_can_network_connect = on
它们的关系不是:
改 Label = 修改策略
也不是:
打开 Boolean = 任意访问
更准确的推导是:
实际进程域 + 实际目标类型 + 对象类别 + 权限
|
v
策略中的无条件规则或条件规则
|
v
访问是否得到 SELinux 允许
如果对象类型错误,正确策略可能无法匹配;如果类型正确但策略没有允许,修改 Label 也不会凭空创造访问权限;如果策略规则受布尔值控制,布尔值可能改变结果,但其范围由策略定义。
二十、生产环境中的变更、验证和恢复
20.1 变更前记录状态
修改前记录:
getenforce
sestatus
ps -eZ > /tmp/selinux-process-contexts.txt
sudo semanage fcontext -l > /tmp/selinux-fcontexts.txt
sudo semanage boolean -l > /tmp/selinux-booleans.txt
sudo semanage port -l > /tmp/selinux-ports.txt
sudo semodule -lfull > /tmp/selinux-modules.txt
这些信息用于:
- 变更审计;
- 故障对比;
- 回滚判断;
- 确认修改是否真的生效。
20.2 变更后验证三层结果
至少验证:
-
配置层
semanage fcontext -l | grep /srv/site getsebool httpd_can_network_connect semanage port -l | grep 8443 -
运行层
ps -eZ | grep nginx ls -lZ /srv/site/index.html systemctl status nginx -
业务层
curl -I http://127.0.0.1/
同时查询:
sudo ausearch -m AVC -ts recent -i
20.3 回滚要区分不同对象
回滚文件上下文规则:
sudo semanage fcontext -d '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site
回滚布尔值:
sudo setsebool -P httpd_can_network_connect off
回滚端口类型:
sudo semanage port -d -t http_port_t -p tcp 8443
回滚策略模块:
sudo semodule -r local_service
如果规则由配置管理系统、软件包或自动化平台下发,还必须从源头回滚,否则下一次部署可能再次覆盖手工修改。
二十一、与 AppArmor 的概念差异
SELinux 和 AppArmor 都属于 Linux 强制访问控制方案,但模型不同。
SELinux 主要围绕:
进程域 + 对象类型 + 策略规则
AppArmor 主要围绕:
程序路径对应的 Profile + 路径规则
因此:
- SELinux 排障重点通常是
ps -eZ、ls -Z、semanage fcontext、AVC、布尔值和类型规则; - AppArmor 排障重点通常是 Profile、模式、路径规则和内核审计日志;
- 两者不能直接把命令、规则语法和排障方法互换;
- 某些发行版默认启用 SELinux,另一些发行版默认使用 AppArmor,实际状态必须通过系统命令确认。
无论采用哪一种机制,核心目标都是让服务获得最小必要能力,而不是让服务拥有“能工作就行”的广泛权限。
二十二、核心判断原则
理解 SELinux 后,可以用以下因果链分析问题:
谁在访问?
-> 查看进程域和 Linux 用户
访问什么?
-> 查看目标对象类型、路径和父目录
进行什么操作?
-> 区分 read、write、execute、search、connect、bind 等权限
传统权限是否允许?
-> 检查 owner/group/mode/ACL/挂载
SELinux 策略是否允许?
-> 检查 AVC、sesearch、布尔值和端口类型
访问是否属于合法业务?
-> 不是所有拒绝都应该被放行
修复是否持久且最小?
-> 优先正确 Label,再考虑布尔值,最后才设计自定义策略
最重要的不是记住某个命令,而是保持对象模型:
进程运行在域中;
文件和其他对象拥有类型;
策略决定域对类型执行哪些操作;
布尔值条件化启用一组预定义规则;
AVC 记录实际被拒绝的访问;
restorecon 和 semanage 管理持久 Label;
自定义策略只应覆盖确认合法且确实缺失的最小行为。
当 Label、Type Enforcement、Policy、布尔值和审计证据能够对应起来时,SELinux 就不再是“随机挡住服务的黑盒”,而是一套可以观察、推导、验证和回滚的主机安全控制机制。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux Keepalived 与 VRRP:虚拟 IP、健康检查、切换和脑裂边界
- 下一篇:AppArmor 完整基础:Profile、Mode、规则、日志和服务加固
- 延伸:Linux 主机安全加固:最小化、SELinux/AppArmor、审计与漏洞治理
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论