「服务改了配置就是起不来,日志也没报错」「明明给了 777 权限还是拒绝访问」——很多人被这类诡异问题坑过,最后发现是 SELinux 或 AppArmor 在「多管闲事」。它们是 Linux 的**强制访问控制(MAC)**机制,比传统的文件权限更底层、更严格。这一篇讲清 SELinux 和 AppArmor 的原理、配置和排错方法,让你不再把「关掉它」当成唯一解法。
一、什么是强制访问控制
传统的 Linux 权限(rwx、owner/group/other)叫DAC(自主访问控制):文件属主可以随意决定谁能访问。问题在于——如果某个进程以 root 运行,它就能访问任何文件,一旦被攻破,危害巨大。
MAC(强制访问控制)在 DAC 之上再加一层:由系统策略统一决定,进程能访问哪些资源,连 root 也要遵守。它就是「纵深防御」里的第 4 层(见第 79 篇)。
| 维度 | DAC(传统权限) | MAC(SELinux/AppArmor) |
|---|---|---|
| 决定者 | 文件属主 | 系统策略 |
| root 能否绕过 | 能 | 不能 |
| 粒度 | 用户/组/其他 | 进程/路径/标签 |
| 目的 | 基本的访问控制 | 限制进程能力,防提权 |
二、两大流派
MAC 实现 ├─ SELinux(红帽系:RHEL/CentOS/Rocky/Fedora)—— 基于「标签」 └─ AppArmor(Debian 系:Ubuntu/Debian/SUSE)—— 基于「路径」| 维度 | SELinux | AppArmor |
|---|---|---|
| 发行版 | RHEL/CentOS/Rocky | Ubuntu/Debian |
| 模型 | 标签(label/context) | 路径(path) |
| 配置 | 复杂,策略语言 | 简单,可读文本 |
| 学习曲线 | 陡 | 缓 |
先确认你的系统用哪个:
# 看 SELinuxgetenforce# 看 AppArmoraa-status三、SELinux 基础
三种模式
getenforce| 模式 | 行为 |
|---|---|
Enforcing | 强制执行策略,违规被拒绝 |
Permissive | 只记录不阻止(排错时用) |
Disabled | 完全关闭 |
临时切换:
sudosetenforce0# 改 Permissivesudosetenforce1# 改 Enforcing配置文件/etc/selinux/config(永久,需重启):
SELINUX=enforcing SELINUXTYPE=targeted⚠️ 排错黄金流程
服务报「Permission denied」时,先别急着关 SELinux,改用 Permissive 模式测试:
# 1. 切换 Permissivesudosetenforce0# 2. 重启服务,如果好了 → 是 SELinux 问题sudosystemctl restart myservice# 3. 切回 Enforcing,查看拒绝日志找真因sudosetenforce1sudoausearch-mavc-tsrecent记住:Permissive 是「排错工具」,不是「解决方案」。找到真因后要么调上下文、要么开布尔值、要么写策略,而不是永久禁用。
查看拒绝日志
# 看 AVC 拒绝记录sudoausearch-mavc-tsrecentsudojournalctl-taudit|grepAVC# 用 sealert 得到人类可读的建议sudodnfinstallsetroubleshoot-server-ysudosealert-a/var/log/audit/audit.logAVC denied就是「SELinux 拒绝了某操作」,日志里会写清楚「哪个进程、对哪个资源、做了什么操作」以及修复建议。
四、SELinux 上下文与布尔值
SELinux 的核心是标签(context)。每个进程和文件都带一个安全上下文,格式:
user:role:type:level system_u:object_r:httpd_sys_content_t:s0排障最关注的是type字段(第三个)。
查看上下文
# 文件上下文ls-Z/var/www/html/index.html# system_u:object_r:httpd_sys_content_t:s0 ...# 进程上下文ps-eZ|grepnginx# system_u:system_r:httpd_t:s0 nginx规则:进程的 type(如httpd_t)必须能访问文件的 type(如httpd_sys_content_t),否则被拒。
修改文件上下文
场景:把网站文件放到/web/而不是默认的/var/www/html/,nginx 访问被拒。
# 查看应该用什么上下文semanage fcontext-l|grephttpd_sys_content# 把 /web 目录标记为 web 内容类型sudosemanage fcontext-a-thttpd_sys_content_t"/web(/.*)?"# 应用规则到实际文件sudorestorecon-Rv/web关键点:semanage fcontext是持久规则,restorecon把规则应用到现有文件。只做前者不生效,只做后者重启可能丢,两者都要。
布尔值(boolean):开关式策略
SELinux 有一些「即开即用」的开关,对应常见需求:
# 列出所有布尔值getsebool-a# 查看 web 相关getsebool-a|grephttpd# 开启「允许 httpd 连接网络」sudosetsebool-Phttpd_can_network_connect on-P表示持久化(重启后仍生效)。
典型场景:
| 需求 | 布尔值 |
|---|---|
| web 服务连数据库 | httpd_can_network_connect_db on |
| web 服务访问外网 | httpd_can_network_connect on |
| ftp 读写家目录 | ftp_home_dir on |
| 允许 http 写目录 | httpd_unified on |
先查布尔值,再改上下文,最后才考虑写策略——这是 SELinux 排错的优先级。
五、SELinux 常见排错
1. 服务起不来(端口问题)
场景:给 nginx 配了 8080 端口,起不来。
nginx: [emerg] bind() to 0.0.0.0:8080 failed (13: Permission denied)原因:SELinux 只允许 httpd 绑定特定端口。
# 查看 httpd 允许的端口semanage port-l|grephttp_port_t# 把 8080 加入 httpd 允许端口sudosemanage port-a-thttp_port_t-ptcp80802. 文件访问被拒
场景:网站文件放自定义目录,403。
# 查看 AVC 日志确认sudoausearch-mavc-tsrecent|grepnginx# 修正上下文(见上文)sudosemanage fcontext-a-thttpd_sys_content_t"/web(/.*)?"sudorestorecon-Rv/web3. 服务重启后上下文丢失
cp/mv/tar有时不带上下文,或文件系统不保留。用restorecon重建:
sudorestorecon-Rv/path4. 实在要用「文档化」的关闭
如果确认某策略不适用于业务,且不想关全局,可以:
# 只对特定服务设白名单(推荐)semanage permissive-ahttpd_t# 完全禁用(不推荐,仅测试)setenforce0permissive只让httpd_t域宽容,其他仍受保护,比全局关闭安全得多。
六、AppArmor 基础
Ubuntu/Debian 默认用 AppArmor,它基于路径而不是标签,profile 是纯文本,易懂。
查看状态
sudoaa-statusapparmor module is loaded. 42 profiles are loaded. 38 profiles are in enforce mode. /usr/sbin/nginx /usr/bin/man ... 4 profiles are in complain mode.两种模式
| 模式 | 行为 |
|---|---|
enforce | 强制执行,违规拒绝 |
complain | 只记录不阻止(排错用) |
切换模式
# 改某 profile 为 complain(排错)sudoaa-complain /usr/sbin/nginx# 改回 enforcesudoaa-enforce /usr/sbin/nginx# 禁用某 profilesudoln-s/etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/disable/sudoapparmor_parser-R/etc/apparmor.d/usr.sbin.nginx排错
# 查看拒绝日志sudodmesg|grep-iapparmorsudojournalctl|grepapparmorsudodmesg|grep"apparmor=\"DENIED\""生成/调整 profile
# 让 AppArmor 学习一个程序的行为sudoaa-genprof /usr/bin/myapp# 从日志自动生成 profilesudoaa-logprof七、MAC 加固的正确心态
| 做法 | 评价 |
|---|---|
出问题就Disabled/disable | ❌ 等于拆掉一层防线 |
| 保持 Enforcing,用 Permissive 定位 | ✅ 正确姿势 |
| 先调上下文/布尔值 | ✅ 优先方案 |
| 为特定服务设 permissive | ✅ 折中 |
| 写自定义策略 | ✅ 终极方案(复杂) |
SELinux/AppArmor 的初衷是「即使服务被攻破,也限制它能造成的破坏」。关掉它,等于放弃了这层保护。正确的做法是理解它为什么拒绝,然后精准地放行必要操作。
八、⚠️ 常见错误
- 遇到问题直接
setenforce 0。这是把安全防线拆了,应该先定位再精准放行。 - 改了
semanage fcontext忘了restorecon。规则是规则,还得应用到实际文件。 getenforce显示 Enforcing 就慌。Enforcing 是正常状态,说明策略在保护你。- 复制文件后上下文丢失。
cp/mv可能不保留上下文,用restorecon修复。 chmod 777也解决不了。因为瓶颈在 MAC 而非 DAC,权限再宽也没用。- 不知道系统用哪个 MAC。先
getenforce/aa-status确认,别对着 AppArmor 找 SELinux 的错。 - 重装系统后策略没迁移。自定义的 SELinux 上下文、AppArmor profile 需要备份和迁移。
- 在生产上直接改配置验证。应在测试环境验证策略改动,避免影响生产。
九、原理探究:安全上下文与策略匹配
SELinux 的标签模型
SELinux 给每个主体(进程)和每个客体(文件、端口、socket)都打一个安全上下文。访问时,内核做一件事:
主体(进程上下文)的操作 → 查策略 → 客体(文件/端口上下文)决策不看你「是谁(root 还是普通用户)」,而看「进程带什么标签、文件带什么标签、策略允不允许这对标签交互」。
举例:
nginx 进程 上下文:system_u:system_r:httpd_t 网站文件 上下文:system_u:object_r:httpd_sys_content_t 策略:允许 httpd_t 读 httpd_sys_content_t → 通过 ✓ /etc/shadow 上下文:system_u:object_r:shadow_t 策略:不允许 httpd_t 读 shadow_t → 拒绝 ✗这就是为什么 nginx(哪怕以 root 运行)也读不了/etc/shadow——不是权限问题,是 SELinux 策略层面根本不允许。这就是「强制访问控制」的威力:连 root 都要服从策略。
为什么按「端口」也要管
不光是文件,端口也有标签。http_port_t是允许 httpd 绑定的端口集合。这样即使攻击者让 nginx 绑定到别的端口,也绑不上——限制的不是「谁能绑」,而是「这个进程能绑哪些」。
AppArmor 的路径模型
AppArmor 更直观,profile 里直接写路径规则:
/usr/sbin/nginx { /etc/nginx/** r, /var/www/** r, /var/log/nginx/** w, network tcp, }意思是「nginx 只能读/etc/nginx、/var/www,写/var/log/nginx,允许 TCP」。基于路径的好处是可读性好,缺点是路径变动(如软链接、mount)容易绕过。
两者对比的本质
- SELinux 基于标签:资源带标签,进程带标签,策略管「标签对标签」。强势但复杂。
- AppArmor 基于路径:策略管「进程对路径」。简单但不够细。
无论哪种,核心思想都是一样的:给进程划定一个「只能碰这些」的笼子,即使它被攻破,也翻不出这个笼子。这就是「沙箱(sandbox)」和「最小权限」在网络访问控制层的落地。
和容器安全的关系
Docker/Kubernetes 的容器隔离,也会用到 SELinux/AppArmor:
# docker 指定 SELinux 标签dockerrun --security-optlabel=type:container_t...# docker 指定 AppArmor profiledockerrun --security-optapparmor=docker-default...K8s 的 Pod Security Context 也能指定seLinuxOptions。理解 MAC,也是理解容器安全的基础。
十、SELinux/AppArmor 速查表
# ===== SELinux =====getenforce# 看模式setenforce0/1# 临时切 Permissive/Enforcingls-Zfile# 看文件上下文ps-eZ|grepproc# 看进程上下文ausearch-mavc-tsrecent# 看拒绝日志semanage fcontext-a-tTYPE"路径"# 加上下文规则restorecon-Rv/path# 应用上下文semanage port-a-thttp_port_t-ptcp8080# 放开端口getsebool-a|grepxxx# 看布尔值setsebool-Pxxx on# 开布尔值semanage permissive-adomain_t# 单域宽容# ===== AppArmor =====aa-status# 看状态aa-complain /path# 切 complainaa-enforce /path# 切 enforcedmesg|grep-iapparmor# 看日志aa-genprof /usr/bin/app# 生成 profile一句话收尾:SELinux 用「标签」、AppArmor 用「路径」,都是给进程划定活动范围,连 root 也管。遇到「权限给了还被拒」,先想它俩;排错用 Permissive/complain 定位,再用semanage/布尔值精准放行——别把关闭当解法,那等于自断一层防线。
至此,第八篇「网络配置、远程与安全篇」10 篇全部完成。下一篇进入第九篇「存储、磁盘、备份与恢复篇」,从磁盘分区开始,把存储这块硬骨头啃下来。