5.6 SELinux 与 AppArmor 强制访问控制
预计阅读时间:16 分钟
📖 目录
学习目标
- 理解 DAC 与 MAC 的根本区别,以及为什么需要 MAC
- 掌握 SELinux 三种模式切换与安全上下文查看
- 学会用 semanage / restorecon 管理标签,用 audit2allow 生成策略
- 理解 AppArmor path-based profile 编写与 aa-enforce / aa-complain 切换
- 熟练使用 AVC 日志和 aa-logprof 排查 MAC 拒绝问题
核心知识
MAC vs DAC
| 维度 | DAC(传统 rwx) | MAC(强制访问控制) |
|---|---|---|
| 控制主体 | 文件 owner 决定谁可以访问 | 系统策略决定(root 也必须遵守) |
| 绕过方式 | chown / chmod 即可 | 必须修改策略或标签 |
| root 豁免 | root 绕过所有权限检查 | root 也被限制 |
| 攻击面 | Web 应用提权后无限制 | 即使提权也只能访问策略允许的资源 |
SELinux vs AppArmor
| 维度 | SELinux | AppArmor |
|---|---|---|
| 策略模型 | 基于 Label(安全上下文标签) | 基于 Path(程序路径+文件路径白名单) |
| 配置复杂度 | 高(TE 语言,标签体系庞大) | 中(类白名单语法,直观) |
| 策略语言 | Type Enforcement(TE) | Profile 文件(ruleset per program) |
| 排错工具 | Permissive + audit2allow | Complain + aa-logprof |
| 默认发行版 | RHEL/CentOS/Fedora | Ubuntu/Debian/openSUSE |
知识关联
- 前置知识:1.7:用户与权限管理 用户与权限管理(理解 DAC 模型的局限),5.1:Linux 安全加固 Linux 安全加固(纵深防御理念)
- 后续影响:5.13:auditd 审计 auditd 审计(auditd 记录 SELinux AVC 事件,是排查拒绝的日志基础),6.18:内核管理 内核管理(LSM 框架加载 SELinux/AppArmor)
- 配套技术:安全加固链路——防火墙(nftables)→ MAC(SELinux/AppArmor)→ 审计(auditd)→ 入侵检测
原理讲解
为什么需要强制访问控制(MAC)
Linux 传统 DAC 模型的核心缺陷在于"root 无所不能"。当一个 Web 应用(如 Nginx)以 root 运行时,攻击者利用文件上传漏洞拿到 shell 后立即获得完全控制权——可以读取 /etc/shadow、修改 SSH 配置、安装后门。即使不以 root 运行,DAC 也无力阻止进程访问同一用户下的其他文件(如 Nginx 进程被入侵后可以读取同用户的其他应用代码)。MAC 通过在 DAC 之上叠加系统级策略层解决了这个问题:即使进程是 root,也必须满足 MAC 规则才能访问资源。这就像公司门禁系统——DAC 只检查你的工牌(用户身份),MAC 还要检查你是否有权进入特定房间(资源标签)。
SELinux 类型强制(TE)为什么有效
类型强制是 SELinux 的核心机制,其设计哲学是"默认拒绝,显式授权"。策略由无数条 allow 规则组成,每条规则精确描述"哪个类型的进程可以对哪个类型的资源执行什么操作"。例如 allow httpd_t httpd_sys_content_t : file { read getattr }; 只允许 Nginx 进程读取 Web 内容文件,不能写入、不能执行。这种精细度是传统 rwx 位无法实现的——你无法用 DAC 让 Nginx 只读 /var/www 而不能读 /etc/shadow,但 MAC 可以轻松做到。TE 的另一个关键特性是域迁移(domain transition):当 execve() 执行一个可执行文件时,进程类型会从父进程类型切换到目标类型,这确保了每个程序运行在与其职责匹配的最小权限域中。
SELinux vs AppArmor:选型哲学差异
SELinux 和 AppArmor 代表了两种不同的 MAC 实现哲学。SELinux 采用基于标签(Label)的模型:每个进程和文件都有安全上下文标签,策略定义标签之间的关系。这种模型的优势是策略与文件系统路径解耦——即使文件被移动或重命名,标签仍保持不变,策略继续生效。缺点是标签管理复杂,需要理解 type、role、user、level 四层概念。AppArmor 采用基于路径(Path)的模型:每个程序的 profile 直接列出可访问的文件路径,直观但与文件路径绑定——文件移动后策略失效。选型建议:RHEL/CentOS 系默认 SELinux 且生态成熟,适合需要细粒度控制的企业环境;Ubuntu/Debian 系默认 AppArmor,学习曲线更平滑,适合快速部署。
DAC 的局限
Linux 传统权限模型基于用户身份和文件 rwx 位。root 用户不受任何限制——一旦进程以 root 运行,它可读/写任何文件、打开任何端口、加载任何内核模块。Web 应用漏洞(如文件上传、命令注入)可直接导致完全控制服务器。DAC 模型无法做到"最小权限":你无法让 Nginx 只读 /var/www 而无法读 /etc/shadow——只要文件权限允许、或进程以 root 运行,就无约束。
MAC 模型
强制访问控制(Mandatory Access Control)在 DAC 之上叠加一层系统级策略层:即使进程是 root,也必须满足 MAC 规则才能访问资源。策略由系统管理员(而非资源 owner)统一制定,进程无法绕过。
SELinux:基于标签的 MAC
SELinux 为每个进程和文件分配一个安全上下文(Security Context):user:role:type:mls_level。其中 type 是最关键的字段——SELinux 策略定义了一系列类型强制规则(Type Enforcement):进程的 type 能否访问目标的 type。
# 安全上下文示例
ps -Z
# 输出:
# system_u:system_r:httpd_t:s0 /usr/sbin/httpd
# ^用户 ^角色 ^类型 ^级别
# system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
# ^文件类型
# 三种模式
# Enforcing — 强制执行策略,拒绝违规操作并记录 AVC 日志
# Permissive — 不阻止,仅记录 AVC 日志(排错用)
# Disabled — 完全关闭 SELinux(不推荐)
getenforce
# 输出: Enforcing
SELinux 策略文件位于 /etc/selinux/,核心策略二进制在 /etc/selinux/targeted/policy/policy.*。管理开销主要来自标签正确性和策略规则密度。
AppArmor:基于路径的 MAC
AppArmor 为每个程序关联一个 profile,以白名单方式列出该程序可访问的文件路径、网络能力、Linux capabilities。相比 SELinux,AppArmor 没有"标签"概念,直接用文件系统路径做规则,学习曲线更平滑。
# 简化的 AppArmor profile 结构
# 路径规则:文件路径 + 权限字母
# r=读(read) w=写(write) k=锁定(lock) m=内存映射(mmap) a=追加写(append) l=链接(link)
# 网络规则:network 协议 类型
# 能力规则:capability 名称(如 net_bind_service 允许绑定 1024 以下端口)
profile nginx /usr/sbin/nginx {
#include <abstractions/base>
/etc/nginx/** r,
/var/log/nginx/*.log rw,
/usr/share/nginx/html/** r,
network inet tcp,
capability net_bind_service,
}
AppArmor 的两种模式:enforce(强制阻止违规)和 complain(仅记录不阻止,用于学习新规则)。
SELinux 类型强制(TE)深入
类型强制是 SELinux 的核心机制。策略由无数条"allow 规则"组成,形如:allow httpd_t httpd_sys_content_t : file { read getattr };——允许类型为 httpd_t 的进程对类型为 httpd_sys_content_t 的文件执行读操作。规则按"主体类型 : 客体类型 : 客体类别 { 权限 }"四元组组织,客体类别(class)包括 file、dir、tcp_socket、process 等。
另一个重要概念是域迁移(domain transition):执行一个可执行文件时,进程类型可能从父进程类型切换到目标类型(如从 init_t 启动 httpd 变成 httpd_t)。这要求该可执行文件有 entrypoint 权限。日常排错中遇到的"服务起不来",很大比例是标签错误导致域迁移失败——ausearch -m avc 会给出 denied { execute } 字样。
# 查看策略中与 httpd 相关的规则数量
sesearch --allow -s httpd_t | wc -l
# 查看 httpd_t 能访问的文件类型
sesearch --allow -s httpd_t -c file | head -10
# 查看域迁移规则(哪个程序能把进程变成 httpd_t)
sesearch --allow -d -t httpd_t -c process
sVirt 是 SELinux 对虚拟化的扩展:每个虚拟机进程自动分配 svirt_t 类型和唯一的 MCS 级别(如 s0:c123,c456),不同虚拟机之间即使配置出错也无法互访磁盘镜像——这就是 ch37 KVM 章节"不要关闭 SELinux"的底层原因。
AppArmor 规则语言进阶
AppArmor 的 profile 支持变量、抽象层和更细的路径匹配。变量(@{var})让同一 profile 适配多台机器:
# 使用变量定义路径前缀(/etc/apparmor.d/tunables/local)
@{WEBROOT}=/var/www
@{LOGDIR}=/var/log/nginx
profile nginx /usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/openssl>
@{WEBROOT}/** r,
@{LOGDIR}/*.log rw,
# 文件所有者修改(owner 限定,防止读他人文件)
owner @{HOME}/.cache/** rw,
}
# 全局路径规则以 / 结尾匹配目录及内容
/var/www/ r, # 仅目录本身
/var/www/** r, # 目录下所有层级
/var/www/*.html r, # 单层通配
# 子进程限制(子进程继承主 profile 限制)
profile sshd /usr/sbin/sshd {
...
/usr/bin/ssh r,
}
AppArmor 没有 SELinux 的"域迁移"概念,子进程默认继承父进程 profile;需要独立限制时用 profile child /path/to/bin {...} 声明子 profile。规则中漏掉共享库路径(如 /usr/lib/x86_64-linux-gnu/**)是程序启动失败的头号原因,先用 complain 模式收集访问足迹再 enforce。
示例代码
SELinux 模式与状态
# 查看当前模式
getenforce
# 输出: Enforcing
# 查看完整状态
sestatus
# 输出:
# SELinux status: enabled
# Current mode: enforcing
# Policy version: 33
# 临时切换模式(排错)
setenforce 0 # Permissive
setenforce 1 # Enforcing
# 永久修改配置
sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
安全上下文管理
# 查看文件/进程上下文
ls -Z /var/www/html/
# 输出: system_u:object_r:httpd_sys_content_t:s0 index.html
ps -Z $(pidof nginx)
# 输出: system_u:system_r:httpd_t:s0 ...
# 修改文件类型标签
semanage fcontext -a -t httpd_sys_content_t '/var/www(/.*)?'
restorecon -Rv /var/www
# 输出: Relabeled /var/www/html from unconfined_u:object_r:default_t to ...
# 查看端口标签
semanage port -l | grep http
# 输出: http_port_t tcp 80, 443, 488, 8008, 8009, 8443
SELinux 布尔值
# 查看所有布尔值
getsebool -a | grep http
# 输出:
# httpd_can_network_connect --> off
# httpd_enable_homedirs --> off
# 开启布尔值(-P 持久化)
setsebool -P httpd_can_network_connect on
setsebool -P httpd_enable_homedirs on
# 查看布尔值描述
semanage boolean -l | grep httpd_can_network_connect
# 输出: httpd_can_network_connect (off) : Allow HTTPD to connect to network
audit2allow 排错
# 场景:Nginx 被 SELinux 阻止写入日志
# 1. 查看 AVC 拒绝
ausearch -m avc -ts recent
# 输出:
# type=AVC msg=audit(1690000000.123:456): avc: denied { write }
# for pid=1234 comm="nginx" name="access.log"
# scontext=system_u:system_r:httpd_t:s0
# tcontext=system_u:object_r:var_log_t:s0 tclass=file
# 2. 生成规则模块
ausearch -m avc -ts recent | audit2allow -M mynginx
# 输出: Generating type enforcement module: mynginx.pp
# 3. 安装模块
semodule -i mynginx.pp
# 4. 查看人类可读建议
ausearch -m avc -ts recent | audit2allow -w
# 输出: Was caused by: Missing type enforcement rule.
# You can use "audit2allow -M mynginx" to generate the rule.
# 5. 验证
setenforce 0 # 临时放行测试
# ... 复现操作 ...
setenforce 1
AppArmor 状态与模式切换
# 查看所有 profile 状态
aa-status
# 输出:
# 10 profiles are loaded.
# 8 profiles are in enforce mode.
# /usr/sbin/nginx
# /usr/sbin/mysqld
# 2 profiles are in complain mode.
# 切换 profile 模式
sudo aa-complain /usr/sbin/nginx # 只记录不阻止
sudo aa-enforce /usr/sbin/nginx # 强制执行
# 查看 profile 文件
ls /etc/apparmor.d/
# 输出: usr.sbin.nginx usr.sbin.mysqld sbin.dhclient
创建 AppArmor Profile
# 新建 profile /etc/apparmor.d/usr.local.bin.myapp
#include <tunables/global>
profile myapp /usr/local/bin/myapp {
#include <abstractions/base>
#include <abstractions/openssl>
/usr/local/bin/myapp rm,
/usr/lib/x86_64-linux-gnu/** rm,
/etc/myapp/** r,
/var/log/myapp/** rw,
/var/run/myapp.pid rwk,
/var/lib/myapp/** rwk,
network inet tcp,
network inet6 tcp,
capability setgid,
capability setuid,
capability net_bind_service,
}
# 加载 profile
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapp
# 学习模式:aa-complain + aa-logprof
sudo aa-complain /usr/local/bin/myapp
sudo aa-logprof
sudo aa-enforce /usr/local/bin/myapp
# 查看 AppArmor 日志
journalctl -t apparmor -f -n 20
容器与 MAC 的联动
# SELinux 下运行容器:容器进程被自动打上 container_t 类型
ps -eZ | grep containerd
# 输出: system_u:system_r:container_t:s0:c123,c456 ...
# 容器挂载目录必须打上 container_file_t 标签,否则 Permission denied
sudo semanage fcontext -a -t container_file_t '/opt/appdata(/.*)?'
sudo restorecon -Rv /opt/appdata
# 查看容器可用的布尔值(sVirt 相关)
getsebool -a | grep container
# 输出: container_connect_any --> off
# Ubuntu/Docker + AppArmor:Docker 默认加载 docker-default profile
# 查看容器安全配置
docker inspect --format '{{.AppArmorProfile}}' myapp
# 输出: docker-default
# 卸载某个容器的 AppArmor 限制(仅测试)
docker run --rm --security-opt apparmor=unconfined alpine id
SELinux 端口与网络标签
# 查看各服务的端口标签
semanage port -l | grep -E 'ssh|mysql|redis'
# 输出:
# ssh_port_t tcp 22
# mysqld_port_t tcp 3306, 1186, 33060
# 场景:把 Nginx 监听端口改到 8088,SELinux 会拒绝
# 给 8088 端口添加 http_port_t 标签
semanage port -a -t http_port_t -p tcp 8088
# 查看域名标签与本地策略
semanage fcontext -l | grep -E '/var/www|/home/.*/www'
# 输出: /var/www(/.*)? all files system_u:object_r:httpd_sys_content_t:s0
常见错误
| 错误 | 现象 | 根因 | 正确做法 |
|---|---|---|---|
| 遇到 Permission denied 直接 setenforce 0 | 排错无记录,下次重复出现 | 未定位真正的策略缺失 | 先 ausearch -m avc,再用 audit2allow 生成规则 |
| 修改文件上下文后不执行 restorecon | 新标签未生效,仍然被拒绝 | semanage 只改策略库,不直接影响文件 | semanage + restorecon 两步缺一不可 |
| AppArmor 切换 complain 后未运行 aa-logprof | 日志被记录但 profile 未更新 | aa-logprof 是交互式生成工具 | 执行 aa-logprof 或手动对照日志编写规则 |
| 永久关闭 SELinux(disabled) | 重启后 SELinux 完全关闭,需重新标记文件系统才能恢复 | /etc/selinux/config 设为 disabled | 永不用 disabled,改用 permissive 排错后恢复 enforcing |
| 忽略布尔值直接写策略模块 | 策略模块膨胀,维护困难 | 有些功能开关已封装为布尔值 | 先 getsebool -a 确认是否有现成布尔值 |
| Profile 路径写错或未覆盖依赖库 | 程序启动报错 | AppArmor 白名单模式遗漏必要路径 | 用 complain 模式收集访问足迹后再 enforce |
| 容器挂载目录报 Permission denied | 主机目录标签不是 container_file_t,容器进程(container_t)无权访问 | semanage fcontext + restorecon 给挂载目录打标签 | |
| 改 Nginx 端口后服务无法启动 | 新端口没有 http_port_t 标签 | semanage port -a -t http_port_t -p tcp 端口号 | |
| AppArmor 规则过多导致性能下降 | 每条文件访问都要匹配 profile 规则 | 只对面向公网的服务启用 profile,内部工具用 complain | |
| audit2allow 生成模块后问题依旧 | AVC 记录被 rate limit 丢弃,或模块未正确安装 | auditctl -s 检查 lost 计数;semodule -l | grep 模块名 确认加载 |
最佳实践
| 实践 | 具体方案 | 验收标准 |
|---|---|---|
| 永不用 disabled | 排错时用 permissive/complain,修复后切换回 enforcing | getenforce 返回 Enforcing |
| 先排错再写策略 | 遇到拒绝先用 ausearch/aa-logprof 定位,再用 audit2allow 或 profile | AVC 日志或 AppArmor 日志确认问题已解决 |
| 善用布尔值 | 检查 semanage boolean -l 是否有现成开关 | 减少自定义策略模块数量 |
| 代码管理中包含策略 | SELinux 的 .pp 模块和 AppArmor profile 纳入版本控制 | git log 可追溯每次策略变更 |
| CI/CD 中验证 MAC | 部署流水线中加入 getenforce 和 ausearch 检查 | 不符合 enforcing 的构建标记失败 |
| 定期审计 AVC 日志 | 每周自动化扫描 audit.log 中的 AVC 拒绝记录 | 无未处理的 AVC 警告 |
| 先查布尔值和端口标签 | 80% 的"Permission denied"是端口标签或布尔值问题,而非策略缺失 | 排错顺序:getsebool → semanage port → 才考虑 audit2allow |
| AppArmor 抽象层复用 | abstractions 提供通用规则集(base/openssl/nameservice),避免重复编写 | profile 首行 #include <abstractions/base> |
| 监控 auditd lost 计数 | AVC 事件丢失会让排错误判为"没有拒绝" | 定期 auditctl -s 查看 lost,必要时调大 backlog |
练习题
- 在 RHEL/CentOS 上执行
getenforce和sestatus,记录你的 SELinux 模式和策略版本。切换到 permissive 后启动一个 Nginx 容器,观察是否仍被 SELinux 拒绝。 - 模拟 Nginx 被 SELinux 阻止写入
/var/log/mycustom/:创建自定义目录,修改 Nginx 配置日志路径,观察 AVC 拒绝日志,用 audit2allow 生成并加载策略模块。 - 在 Ubuntu 上为
/usr/local/bin/myapp编写一个 AppArmor profile(只允许读/etc/myapp/、写/var/log/myapp/、绑定 8080 端口),加载后确认 enforce 生效。 - 列出 5 个常见的 SELinux 布尔值并解释其作用(如 httpd_can_network_connect、httpd_enable_homedirs、ftp_home_dir 等)。打开其中两个并用
getsebool -a验证更改。 - 用
aa-complain将 Nginx 或 MySQL 切换为 complain 模式,执行完整的访问操作,然后运行aa-logprof收集规则。对比 aa-logprof 生成的规则与手动编写的差异。
点击查看答案
getenforce输出 Enforcing/Permissive/Disabled。sestatus显示当前模式和策略路径。Permissive 模式下 SELinux 只记录不阻止,Nginx 应能正常启动但 audit.log 中产生 AVC 记录。- AVC 拒绝日志形如
type=AVC msg=audit(...): avc: denied { write } ...。用audit2allow -a -M mynginx生成策略模块,semodule -i mynginx.pp加载后拒绝消失。 - 用
aa-genprof /usr/local/bin/myapp引导生成 profile,或手动写/etc/apparmor.d/usr.local.bin.myapp。加载后用aa-enforce切换,在日志中验证访问被正确放行或拒绝。 - 常见布尔值:httpd_can_network_connect(允许 Apache 连网络)、httpd_enable_homedirs(允许访问用户家目录)、ftp_home_dir(允许 FTP 读家目录)、httpd_graceful_shutdown(优雅关闭)、httpd_builtin_scripting(内嵌脚本)。
getsebool -a | grep httpd列出当前值。 - Complain 模式只记不拦,
aa-logprof交互式扫描日志并生成规则。手动编写更精确但繁琐,aa-logprof 作为起点再微调效率最高。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 SELinux 和 AppArmor 的区别及工作模式 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 SELinux 状态查看、策略管理、上下文修改 | 在终端实际执行 |
| 原理掌握 | 能说出 MAC(强制访问控制)与 DAC(自主访问控制)的区别 | 画出流程图 |
| 故障排查 | 能独立排查 SELinux 阻止服务正常运行的问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么生产环境应保持 SELinux enforcing 模式 | 对比不同方案 |
本章总结
速查表
SELinux
| 任务 | 命令 |
|---|---|
| 查看模式 | getenforce |
| 查看完整状态 | sestatus |
| 临时切换模式 | setenforce 0|1 |
| 查看 AVC 拒绝 | ausearch -m avc -ts recent |
| 生成策略模块 | ausearch -m avc -ts recent | audit2allow -M mymod |
| 安装策略模块 | semodule -i mymod.pp |
| 查看布尔值 | getsebool -a 或 semanage boolean -l |
| 修改布尔值 | setsebool -P name on|off |
| 修改文件类型 | semanage fcontext -a -t type '/path(/.*)?' |
| 应用标签 | restorecon -Rv /path |
| 查看端口标签 | semanage port -l | grep service |
AppArmor
| 任务 | 命令 |
|---|---|
| 查看状态 | aa-status 或 apparmor_status |
| 切换 complain | aa-complain /path/to/bin |
| 切换 enforce | aa-enforce /path/to/bin |
| 学习模式 | aa-logprof |
| 加载 profile | apparmor_parser -r /etc/apparmor.d/profile |
| 查看日志 | journalctl -t apparmor -f |
| profile 文件目录 | /etc/apparmor.d/ |
学习路径
学完本章后,建议依次阅读: 5.5:防火墙实战 iptables/nftables 防火墙实战(网络层访问控制)→ 5.13:auditd 审计 Linux 审计——auditd(审计日志体系)→ 5.1:Linux 安全加固 Linux 服务器安全加固指南(完整安全加固清单)→ 5.12:eBPF 基础 eBPF 基础(内核级可观测性与安全检测)。
延伸阅读
- Red Hat SELinux 官方指南 — SELinux 完整策略编写与排错
- Ubuntu AppArmor 官方文档 — Profile 语法详解与社区维护的抽象层
- SELinux Project Wiki — TE 语言参考与策略模块开发
- AppArmor Wiki — 高级规则(命名空间、网络规则、policy cache)
- audit2allow(8) — 策略生成工具完整参考
- semanage(8) — SELinux 策略管理工具手册