Linux服务器SELinux是什么?运行模式、权限策略与配置方法

几乎每个用 RHEL、CentOS、Rocky 的人,都被 SELinux 坑过一次:明明文件权限都给了 777,服务就是读不了;明明端口开着,nginx 就是起不来报 403;改了配置重启还是不行。最后在论坛上看到一句"把 SELinux 关了试试",setenforce 0 一敲,问题没了,于是从此见它就关。

但"关掉 SELinux"是一个代价很高的习惯。SELinux 是内核级的强制访问控制,能在进程已经被攻破的情况下,限制它进一步读取系统里的其他文件、限制它监听危险端口。关掉它,等于拆掉了最后一道防线。这篇文章讲清楚 SELinux 到底是什么、三种运行模式怎么切换、安全上下文(context)怎么工作、布尔值怎么用、出问题怎么排查(ausearch、audit2allow),以及为什么不该一关了之,还有它和 AppArmor 的区别。看完你应该能在不关闭 SELinux 的前提下,把绝大多数"权限被拒"的问题解决好。

SELinux 是什么,和普通权限有何不同

Linux 传统的权限模型叫 DAC(自主访问控制):文件有属主、属组、rwx 权限位,进程以某个用户身份运行,只要用户对文件有权限就能访问。DAC 的软肋是"太自主"——一旦进程以 root 运行,或某个服务被攻破拿到高权限,它就能访问系统里几乎所有东西。

SELinux 带来的是 MAC(强制访问控制):除了传统权限,还有一层由安全策略统一规定的访问规则,进程和文件都带标签,能不能访问由策略说了算,root 也不能随意绕过。它回答的是"这个进程(域)能不能操作这个资源(类型)"。

理解 SELinux 的三个核心概念:

  • 主体(Subject):进程,在 SELinux 里属于某个"域"(domain),比如 httpd_t 是 nginx/httpd 的域。
  • 客体(Object):文件、目录、端口、进程等被访问的资源,带"类型"(type),比如网页文件是 httpd_sys_content_t,Web 目录是 httpd_sys_content_t。
  • 策略(Policy):规定哪个域能操作哪种类型。默认策略里,httpd_t 允许读 httpd_sys_content_t,但不允许读用户家目录的 user_home_t。

所以你会遇到这种"反直觉"的情况:文件权限 777,传统视角看谁都能读,但 SELinux 策略说 httpd_t 不能碰 user_home_t,于是 nginx 访问用户家目录里的网页就报 403。这不是权限位的问题,是标签和策略的问题。

# 查看 SELinux 是否启用
getenforce
sestatus

sestatus 会给出完整信息:当前模式、配置文件模式、加载的策略类型、进程和文件的标签统计等。

三种运行模式

SELinux 有三种模式:

模式 说明 行为 适用场景
enforcing 强制 拦截违规访问并拒绝,记录 AVC 日志 生产环境推荐
permissive 宽容 只记录 AVC 日志,不拦截 排障、调试策略
disabled 禁用 完全不加载策略 不推荐,重启用需重打标签

查看与临时切换:

getenforce                # 当前模式

# 临时切换(重启后恢复配置文件的值)
sudo setenforce 0         # 切到 permissive(等价于 setenforce Permissive)
sudo setenforce 1         # 切到 enforcing

sestatus -v               # 查看详细状态与标签

永久切换要改配置文件 /etc/selinux/config:

# 查看
grep '^SELINUX=' /etc/selinux/config

# 修改 /etc/selinux/config 中的 SELINUX= 行
SELINUX=enforcing

重要风险提示:从 disabled 切回 enforcing/permissive 有坑。 系统在 disabled 模式下运行期间创建的文件不会被正确打标签,重新启用 SELinux 后这些文件标签可能是默认的 unlabeled_t,导致大量服务起不来。正确做法是重新启用前触发一次全盘重打标签:

# 在 /etc/selinux/config 改回 enforcing 后,创建这个标记文件
sudo touch /.autorelabel
sudo reboot
# 重启时会全盘重新打标签,耗时较长(视文件数量,可能几十分钟),期间系统会很慢,务必在维护窗口做

或者用 fixfiles 手动重打标签,sudo fixfiles -F onboot。从 disabled 切回 enforcing 不要在业务高峰期操作。

安全上下文:ls -Z、chcon、restorecon

SELinux 给每个文件、进程、端口都打上安全上下文(security context),格式是四段:

system_u:object_r:httpd_sys_content_t:s0
  用户      角色    类型            级别

日常最关心的是第三段"类型"(type),SELinux 的策略主要基于类型来判定。查看方式是在常规命令上加 -Z:

ls -Z /var/www/html/
ls -Zd /var/www/html/
ps -eZ | grep httpd
ss -Ztnlp
id -Z                        # 当前用户的上下文

典型输出:

system_u:object_r:httpd_sys_content_t:s0 index.html
unconfined_u:object_r:user_home_t:s0     mypage.html

如果网页文件在用户家目录下,它的类型是 user_home_t,而 httpd_t 域默认不允许读它,于是报 403。解决办法不是关 SELinux,而是把文件的类型改成 Web 目录应有的类型。

chcon:临时改标签

# 临时修改文件类型(重启或重打标签后可能丢失)
sudo chcon -t httpd_sys_content_t /var/www/html/index.html
sudo chcon -R -t httpd_sys_content_t /var/www/html/

restorecon:恢复默认标签

当文件标签被改乱、或复制后标签不对,用 restorecon 按策略定义的默认值恢复:

sudo restorecon -Rv /var/www/html/

chcon 是"改这一次",restorecon 是"恢复成策略规定的默认值"。优先用 restorecon,因为它符合策略预期。

semanage fcontext:永久自定义标签规则

如果某个目录本来就该有特殊类型(比如网站放在 /data/www 而不是默认的 /var/www),用 chcon 会在下次重打标签时丢失。正确做法是加一条永久规则,再 restorecon 应用:

# 需要安装 policycoreutils-python-utils(RHEL 系)或 policycoreutils-python(Debian 系)
sudo dnf install -y policycoreutils-python-utils

# 加规则:把 /data/www(及其子目录) 默认标为 httpd_sys_content_t
sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"

# 查看规则
sudo semanage fcontext -l | grep '/data/www'

# 应用规则
sudo restorecon -Rv /data/www

这样即使全盘重打标签,/data/www 也会被正确标记。这是处理"网站目录不在默认位置"的标准姿势。

布尔值:开关式的策略微调

SELinux 策略里有很多布尔值(boolean),用来在预设的规则间开关。比如"是否允许 httpd 访问用户家目录""是否允许 httpd 联网"都有对应的布尔开关。

# 查看所有布尔值
getsebool -a

# 查看特定服务的布尔值
getsebool -a | grep httpd

# 永久开启某个开关
sudo setsebool -P httpd_enable_homedirs on

-P 表示持久化,会写进策略,重启仍生效;不加 -P 只临时生效。常见的实用场景:

# 允许 httpd 通过 CGI、脚本联网(做反向代理到后端时需要)
sudo setsebool -P httpd_can_network_connect on

# 允许 httpd 连接数据库
sudo setsebool -P httpd_can_network_connect_db on

# 允许访问用户家目录
sudo setsebool -P httpd_enable_homedirs on

# 查看某开关的状态和说明
sudo semanage boolean -l | grep httpd_can_network_connect

先看布尔值再看自定义策略:很多"服务连不上数据库""反向代理失败"其实只是开关没打开,用 setsebool -P 就能解决,根本不用动策略。但要注意 -P 的副作用是整个系统范围生效,确认需求合理再开。

端口标签

SELinux 不只管文件,还管端口。一个服务想监听某端口,该端口必须有对应的类型标签。比如 nginx 想监听 8080,而 8080 默认没被标记为 http_port_t,就会报 Permission denied 起不来。

# 查看 http 相关端口
sudo semanage port -l | grep http_port_t

# 添加端口标签,允许 nginx 监听 8080
sudo semanage port -a -t http_port_t -p tcp 8080

# 修改已有端口标签
sudo semanage port -m -t http_port_t -p tcp 8080

# 删除
sudo semanage port -d -t http_port_t -p tcp 8080

这是"改端口后服务起不来"的常见原因——很多人改了 Tomcat、nginx 的端口,防火墙也放了行,就是起不来,其实是 SELinux 的端口标签没加。比如 Spring Boot 用 8080、Tomcat 用 8009,都需要对应的标签。

排障:ausearch 与 audit2allow

SELinux 拦截访问时会记录 AVC(Access Vector Cache)拒绝日志。排查思路是:找到 AVC 记录 → 确认是不是误拦 → 用正确方式放行。

查看 AVC 日志

# 需要 auditd(RHEL 系默认装好)
sudo ausearch -m avc -ts recent
sudo ausearch -m avc -ts today

# 或直接从日志里翻
sudo grep 'type=AVC' /var/log/audit/audit.log | tail

一条典型的 AVC 记录:

type=AVC msg=audit(1698888888.123:456): avc:  denied  { read } for  pid=1234 comm="nginx"
name="index.html" dev="sda2" ino=12345 scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

解读:进程 nginx(域 httpd_t)想 read 文件 index.html(类型 user_home_t)被拒绝。tclass=file 说明是文件访问,tclass=tcp_socket 说明是网络,tclass=dir 是目录。

临时放宽:permissive 定位

不确定是不是 SELinux 拦的,先切 permissive 观察:

sudo setenforce 0
# 复现问题
# 如果问题消失,说明是 SELinux;再查看这期间产生的 AVC 日志
sudo ausearch -m avc -ts recent
sudo setenforce 1

注意:permissive 只是诊断手段,定位完要及时切回 enforcing,别长期挂在 permissive(它不拦截,等于没有防护)。

用 audit2allow 生成策略

如果确认是误拦,最"正规"的做法是给自有应用生成一条最小的允许策略,而不是全局关闭 SELinux:

# 从最近的 AVC 日志生成策略模块
sudo ausearch -m avc -ts recent | audit2allow -M mypolicy

# 查看会生成什么规则(先看再装,避免放行过宽)
sudo ausearch -m avc -ts recent | audit2allow

# 安装生成的模块
sudo semodule -i mypolicy.pp

# 查看已安装的模块
sudo semodule -l | grep mypolicy

# 需要时卸载
sudo semodule -r mypolicy

使用 audit2allow 的纪律:audit2allow 会基于它看到的拒绝记录生成"放行"规则,如果日志里混入了可疑的拒绝(比如某个进程想读 /etc/shadow),它也会一并放行。所以生成前一定要先看 audit2allow 的原始输出是否合理,不要闭眼 -M 安装。优先尝试 semanage fcontext 修标签、setsebool 开开关、semanage port 加端口,实在不行才自定义策略。

常见 AVC 场景与对应处理:

现象 AVC 里的关键信息 处理方式
网页 403 httpd_t 读 user_home_t semanage fcontext + restorecon,或开 httpd_enable_homedirs
服务起不来 tclass=tcp_socket,端口不对 semanage port -a 加端口标签
反向代理失败 httpd_t 发起网络连接被拒 setsebool -P httpd_can_network_connect on
数据库连不上 httpd_t 连数据库端口被拒 setsebool -P httpd_can_network_connect_db on
自定义应用被拦 自有域/类型 audit2allow 生成最小策略

为什么不建议直接关掉 SELinux

把 SELinux 设成 disabled,短期看省事,长期看是隐患:

  • 失去纵深防御:SELinux 的价值在于"即便服务已被攻破,也限制它横向移动"。Web 服务被利用后,攻击者想读 /etc/shadow、想连内网数据库,SELinux 会拦住。关掉它,只剩传统权限,root 提权后畅通无阻。
  • 再开起来很痛苦:disabled 期间创建的文件标签是乱的,重新启用要全盘重打标签,耗时且可能引发新问题,很多团队因此"永久关闭",等于把安全债越欠越多。
  • 策略生态的默认值:主流发行版的 SELinux 策略已经调得比较友好,绝大多数常见业务(nginx、MySQL、PostgreSQL、Docker)都能在 enforcing 下正常运行,问题基本都能通过标签、布尔值、端口规则解决。

替代方案:如果确实在某些场景下难以适配,优先用 permissive 模式代替 disabled——它不拦截但记录 AVC 日志,既能保证业务运行,又保留了排查线索,日后想切回 enforcing 也更平滑。真正要关闭的场景已经很少了。

SELinux 与 AppArmor 的区别

两者都是 MAC,目标类似,但实现和生态不同:

维度 SELinux AppArmor
主流发行版 RHEL / CentOS / Rocky / Fedora Ubuntu / Debian / SUSE
策略模型 基于标签(类型强制) 基于路径
粒度 细,标签覆盖文件/进程/端口/网络 相对粗,主要按文件路径
学习曲线 陡峭 平缓
配置位置 /etc/selinux/、semanage /etc/apparmor.d/
默认策略 完整策略集,开箱即用 按应用提供 profile

简单说:SELinux 更强大也更复杂,AppArmor 更简单直观。两者不要同时在一台机器上启用到互相干扰,按发行版用默认的那个即可。Ubuntu 默认用 AppArmor,可以用 sudo aa-status 查看状态。

常见问题

setenforce 0 和改配置文件有什么区别?

setenforce 0 只改当前运行时的模式,重启后按 /etc/selinux/config 的值恢复;改配置文件是永久的。临时排障用 setenforce,别改配置,避免忘了改回来。

从 disabled 改成 enforcing 后一堆服务起不来?

因为 disabled 期间的文件没有正确标签。先切到 permissive(改配置或 setenforce 0),执行 sudo touch /.autorelabel 后重启做全盘重打标签,观察 AVC 日志逐个处理,稳定后再切 enforcing。不要在业务高峰期做。

semanage 命令不存在?

需要安装策略管理工具包:RHEL 系 sudo dnf install -y policycoreutils-python-utils,Debian 系 sudo apt install -y policycoreutils-python-utils。老版本包名可能是 policycoreutils-python。

我的应用是被 SELinux 拦的,怎么最快确认?

先 sudo setenforce 0 复现,若正常则基本是 SELinux;再 sudo ausearch -m avc -ts recent 看拒绝记录;处理完 sudo setenforce 1 切回。别用关闭 SELinux 作为最终方案。

只把文件权限设成 777 为什么还是不行?

因为 SELinux 是独立于传统权限的第二道关卡,看的是标签和策略,不看 rwx。文件权限再大,类型不对照样被拦。要改的是标签(semanage fcontext + restorecon)或布尔值,而不是权限位。

总结

  • SELinux 是内核级强制访问控制,在传统权限之外加了一层基于标签和策略的判定,核心概念是主体域、客体类型和策略规则。
  • 三种模式:enforcing(拦截并记录)、permissive(只记录)、disabled(不加载);生产优先 enforcing,排障用 permissive,尽量不用 disabled。
  • 打标签用 ls -Z 看、chcon 临时改、restorecon 恢复默认、semanage fcontext 加永久规则;端口用 semanage port,功能开关用 setsebool -P。
  • 排障流程是"找 AVC(ausearch -m avc)→ 判断是否误拦 → 优先用标签/布尔/端口解决 → 实在不行 audit2allow 生成最小策略",生成策略前务必人工核对。
  • 不要为了省事关掉 SELinux,它是有价值的纵深防线;实在要放宽就用 permissive 而非 disabled,并且从 disabled 切回 enforcing 必须先全盘重打标签。