Linux 基础 16:SELinux——当权限都对,服务还是被拒绝

发布于 2022-04-24 20:30 更新于 2022-04-24 22:10 1639 字 9 min read ... 访问量 手写 每个字都是我自己敲的。

Linux 基础 25:收尾——自查清单与继续深入的方向Linux 基础 24:启动流程与救援——UEFI、GRUB、initramfs 与 emergency modeLinux 基础 23:容器与内核边界——namespace、cgroup 与 docker exec 背后Linux 基础 22:日志与故障排查方法论——从现象到根因的六步Linux 基础 21:定时任务——crontab 的坑与 systemd timerLinux 基础 20:Shell 脚本(下)——trap、日志、幂等与并发锁Linux 基础 19:Shell 脚本(上)——引号、条件与退出码Linux 基础 18:性能观测与 strace——机器到底卡在哪Linux 基础 17:防火墙——iptables、nftables、firewalld 与 ufw 的关系Linux 基础 16:SELinux——当权限都对,服务还是被拒绝Linux 基础 15:权限进阶——sudoers 的通配陷阱、ACL 与 capabilitiesLinux 基础 14:SSH 与远程运维——密钥、权限与三种隧道Linux 基础 13:网络基础——自下而上的排错顺序Linux 基础 12:磁盘与文件系统——从块设备到挂载点Linux 基础 11:systemd——unit、systemctl 与 journalctl 的正确用法Linux 基础 10:包管理——apt 到底做了什么,以及四个高频报错Linux 基础 09:用户与组——passwd、shadow、sudoers 三件套Linux 基础 08:环境变量与 shell 启动文件——为什么 ssh 跑命令找不到你的 PATHLinux 基础 07:进程与信号——TERM 和 KILL 到底差在哪Linux 基础 06:管道与重定向——三条流、一个顺序问题Linux 基础 05:文本处理——把 grep/sort/uniq 变成你的分析工具Linux 基础 04:权限与属主——rwx 在文件和目录上不是一回事Linux 基础 03:ls、cp、mv、rm 的真实语义,以及那个由 shell 展开的 *Linux 基础 02:/ 底下为什么是这些目录,inode 里到底存了什么Linux 基础 01:终端、Shell,和命令是怎么被找到的
三种模式与 enforcing 的真正含义、ls -Z / ps -Z 里的安全上下文怎么读、chcon 与 restorecon 的区别、semanage fcontext 才是正确做法、booleans 开关,以及"服务读不到自己目录"的标准定位流程。

这一节要解决什么

有一种故障特别费解:文件权限、属主、路径全都对,namei -l 一路畅通,服务还是被拒绝——日志里只有一句 Permission denied。如果你在 RHEL/CentOS/Rocky 上,八成撞上了 SELinux。

它检查的不是"你是谁",而是"你这个进程带着什么标签,要去碰什么标签的东西"。这是完全另一套模型。

一、三种模式(先搞清楚在不在拦你)

getenforce                     # Enforcing / Permissive / Disabled
sestatus                       # 完整状态:策略类型、加载的策略名、当前模式
sestatus -b | head             # booleans 概览
sudo setenforce 0              # 临时切成 permissive(重启后失效,仅用于验证假设)
模式行为
enforcing真的拦截,并记录 AVC 拒绝日志
permissive不拦截,但照样记录 AVC(排障黄金档位:能验证"是不是 SELinux 干的")
disabled完全不加载策略

排障的第一步永远是"切 permissive 复现一次":如果切到 permissive 问题就消失了,那 100% 是 SELinux;如果照样失败,就该回去看权限/路径,而不是在 SELinux 上耗时间。 setenforce 0 只是临时验证,别当成修复手段长期开着。

二、安全上下文:ls -Z 在说什么

ls -Z /var/www/html/index.html       # 文件的 context: user:role:type:level
ps -efZ | grep nginx | head          # 进程的 context(进程带的是什么标签)
id -Z                                # 当前 shell 的 context
matchpathcon /var/www/html/index.html  # 该路径“按策略应该”是什么标签 ⭐
system_u:object_r:httpd_sys_content_t:s0  /var/www/html/index.html
                     ↑ 这一段(type)才是日常最常打交道的

日常真正起作用的是第三段 type:

type典型含义
httpd_sys_content_t允许 httpd 读的网站内容
httpd_sys_rw_content_t允许 httpd 读写(上传目录用)
var_log_t日志目录
ssh_port_t / http_port_t允许对应服务监听的端口类型
unconfined_t未受限制(默认用户态)

批注:matchpathcon(新系统用 matchpathcon -n 或 selabel_lookup)能回答"这个路径应该是什么标签"——这是判断"标签是否被弄脏"的最快方式。

三、修标签:chcon 是临时的,restorecon 是恢复,semanage fcontext 才是正解

# ① 临时改(重启/relabel 后会丢,只适合验证)
sudo chcon -t httpd_sys_content_t /srv/www/index.html

# ② 恢复成“策略规定的默认值”(最常用)
sudo restorecon -Rv /srv/www            # -R 递归,-v 显示改了什么

# ③ ⭐ 正确做法:先写进策略,再恢复
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'
sudo restorecon -Rv /srv/www
sudo semanage fcontext -l | grep '/srv/www'

为什么 chcon 不够:它是"一次性打补丁"。下次系统做全盘 relabel(touch /.autorelabel 后重启、或 restorecon -R /)时,所有手工 chcon 的标签都会被重置回策略默认值——你的服务又坏了。semanage fcontext 把规则写进策略数据库,才是持久解法。

四、booleans:策略提供的"开关"

很多 "SELinux 不允许" 的场景,官方早就准备了开关,只是默认关着:

getsebool -a | grep -i httpd | head          # 看 httpd 相关开关
sudo setsebool -P httpd_can_network_connect on   # ⭐ -P 持久化(重启仍在)
getsebool httpd_can_network_connect
# 其他高频开关:
#   httpd_can_sendmail            允许 httpd 发邮件
#   httpd_read_user_content       允许读家目录内容(网站放家目录时)
#   container_manage_cgroup       容器相关

批注:setsebool 不加 -P 就只是临时生效——这事我在第一次配反代时踩过:本机 on 好了,重启后又变回去,服务连不上后端。

五、端口与网络:semanage port

"非标准端口起服务"会被拦,因为策略里没有声明这个端口:

sudo semanage port -l | grep -w http_port_t      # 看 http 允许哪些端口
sudo semanage port -a -t http_port_t -p tcp 8080  # 新增
sudo semanage port -d -t http_port_t -p tcp 8080  # 删除

六、标准定位流程(照这个顺序走)

# 1) 真的拦了吗?切 permissive 复现
sudo setenforce 0     # 问题消失 → 就是它;记得切回 setenforce 1

# 2) 找 AVC 拒绝记录
sudo ausearch -m avc -ts recent                 # 最近的拒绝
sudo ausearch -m avc -ts today | tail -20       # 今天的
sudo journalctl -t audit | grep -i denied | tail -10

# 3) 人类可读的建议(装 setroubleshoot 后才有)
sudo sealert -a /var/log/audit/audit.log | head -40

# 4) 对比标签
ls -Z 目标文件 | head -1; matchpathcon 目标文件

# 5) 按类型修:标签 → semanage fcontext + restorecon;开关 → setsebool -P;端口 → semanage port -a

一条真实 AVC 长这样(关注 scontext/tcontext/tclass):

type=AVC msg=audit(…): avc:  denied  { read } for  pid=1234 comm="nginx"
  name="index.html" scontext=system_u:system_r:httpd_t:s0
  tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file

读法:scontext 是进程(httpd_t),tcontext 是被访问对象的标签(user_home_t)——这里的问题很典型:网站文件放在家目录里,标签是 user_home_t,httpd 读不了。解法就是 semanage fcontext + restorecon,或开 httpd_read_user_content。

七、和 AppArmor 的关系(Ubuntu 用户必看)

SELinuxAppArmor
模型标签(不管路径,只管类型)路径(针对具体程序与路径)
默认发行版RHEL / CentOS / Rocky / FedoraUbuntu / Debian / SUSE
状态命令getenforce、sestatusaa-status
配置目录/etc/selinux//etc/apparmor.d/

批注:所以"Ubuntu 上没 SELinux"是正常的——它默认用 AppArmor。看到 Ubuntu 上的 Permission denied 却查不到 SELinux,就该去 aa-status 看看是不是 AppArmor 在拦。

八、动手清单

getenforce; sestatus | head -8                    # 1. 模式与策略
ls -Z /var/www/html/ | head                       # 2. 文件标签
ps -efZ | grep -E 'nginx|httpd' | head            # 3. 进程标签
sudo ausearch -m avc -ts today | tail -20         # 4. 今天的 AVC
getsebool -a | grep -i httpd | head               # 5. booleans
sudo semanage port -l | grep -w http_port_t       # 6. 端口类型

小结

  • SELinux 检查的是标签,不是传统权限;enforcing 才拦,permissive 只记日志——排障第一步是"切 permissive 复现"。
  • 日常打交道的是 context 的第三段 type;matchpathcon 能告诉你"策略里应该是哪个 type"。
  • chcon 是临时的(relabel 后失效),持久修法永远是 semanage fcontext + restorecon。
  • booleans 用 setsebool -P 才持久;自定义端口用 semanage port -a。
  • 定位流程:切 permissive → ausearch -m avc → sealert 读建议 → 修标签/开关/端口。
  • Ubuntu 系默认是 AppArmor,别拿 SELinux 的思路硬套。

下一节预告

下一节:防火墙——netfilter 与 iptables/nftables 的关系、firewalld 的 zone 模型、ufw 的简化用法、docker 插入的 DOCKER-USER 链为什么会"绕过 ufw",以及"机器放行了但外面还是不通"时的检查顺序(云安全组在更外层)。

执笔 手写

喜欢的话,留下你的评论吧~

加载中...