《SELinux 与 AppArmor:强制访问控制从原理到实战》

系统讲解 Linux 强制访问控制(MAC):DAC 与 MAC 的本质区别、SELinux 的上下文(user/role/type/level)与策略模型、enforcing/permissive 模式与 booleans 开关、chcon/semanage/restorecon 与 audit2allow 排错、AppArmor profile 语法与 Ubuntu 实践、两大发行版差异对比,以及容器与 Kubernetes 场景下的 MAC 落地经验。

引言

一个 chmod 777 的文件,被任意进程读写似乎理所当然——这正是 DAC(自主访问控制)的局限:权限由文件属主决定,属主一旦被攻破,防线就形同虚设。MAC(强制访问控制)把「谁能访问谁」交给系统级策略统一裁决,进程即便以 root 运行也无法越权。Linux 上两大实现是 SELinux(RHEL/CentOS/Fedora 默认)与 AppArmor(Ubuntu/SUSE 默认),二者都建立在 LSM(Linux Security Modules)框架之上。

本文从 DAC 与 MAC 的本质差异讲起,拆解 SELinux 的上下文、策略与 booleans,给出 chcon/semanage/restorecon 与 audit2allow 的完整排错链路;再介绍 AppArmor profile 的写法与 Ubuntu 实践,对比两大发行版的差异与选型,最后落到容器与 Kubernetes 场景下的强制访问控制落地。

前置:用户、权限与访问控制基础。系统加固整体思路见 Linux 安全加固与基线。


目录


1. DAC 与 MAC:为什么需要强制访问控制

DAC 把访问决策权交给资源属主,MAC 把决策权交给系统策略——前者灵活但脆弱,后者刚性但可靠。

传统 Unix 权限模型(rwx + owner/group/other)属于 DAC:文件属主可以随意 chmod,root 可以绕过一切检查。攻击者只要拿到某个服务进程的控制权,就能以该进程的身份访问它有权访问的一切。MAC 在 DAC 检查通过之后再叠一层策略检查,两层都通过才放行:

进程访问文件
   ↓
DAC 检查(rwx 位、UID/GID)——传统权限
   ↓ 通过
MAC 检查(SELinux 策略 / AppArmor profile)——强制策略
   ↓ 通过
内核放行

LSM 是内核提供的钩子框架,在每个关键系统调用(打开文件、创建 socket、执行程序、加载模块)处插入检查点。SELinux 与 AppArmor 都是 LSM 的具体实现,同一内核通常只启用其中一种(也可叠加,但极少这么做)。

维度DAC(传统权限)MAC(SELinux/AppArmor)
决策者资源属主系统策略
root 可否绕过可以不可以
粒度文件级 rwx类型/路径/操作级
典型场景多用户主机服务器、容器、合规环境
出错表现Permission denied同样是 Permission denied

一句话:DAC 防「不小心」,MAC 防「被攻破」——root 被拿下的那一刻,只有 MAC 还能守住最后一道门。


2. SELinux 基础:上下文与运行模式

SELinux 给每个对象(文件、进程、端口、socket)贴上「安全上下文」标签,策略规定「哪种标签的进程能对哪种标签的对象做什么操作」。

安全上下文形如 user:role:type:level,四段各有含义:

段名称含义示例
第 1 段userSELinux 用户,非 Linux 用户system_u、unconfined_u
第 2 段role角色,用于 RBACobject_r、system_r
第 3 段type类型,策略的核心httpd_t、httpd_sys_content_t
第 4 段levelMLS/MCS 敏感级别s0、s0:c1,c2

查看上下文用 -Z 系列选项:

# 查看文件/进程的上下文
ls -Z /var/www/html/index.html
ps -eZ | grep httpd
# 查看当前模式
getenforce

运行模式有三种,可全局或按域切换:

# 查看与切换全局模式
getenforce
setenforce 0        # 切到 Permissive
setenforce 1        # 切回 Enforcing
# 永久修改(配置文件)
# /etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted
模式行为用途
enforcing拦截并记录拒绝生产常态
permissive只记录不拦截排错、策略调试
disabled完全关闭不推荐,切换需重启

一句话:排错时先 setenforce 0 验证「是不是 SELinux 干的」,但生产绝不要用 disabled 关掉它——permissive 保留日志,disabled 丢掉证据。


3. SELinux 策略、类型强制与 booleans

SELinux 的核心是类型强制(Type Enforcement):策略里绝大多数规则都是 allow 源类型 目标类型:类别 权限 的形式。

一条典型规则读作「允许 httpd_t 对 httpd_sys_content_t 类型的文件执行 read」:

allow httpd_t httpd_sys_content_t:file { read getattr open };

策略来源分三层:

策略类型说明位置
targeted只针对特定服务,其余 unconfined/etc/selinux/targeted
内置模块随策略包安装semodule -l
自定义模块管理员用 audit2allow 生成自行编译加载

booleans 是策略的「运行时开关」,把常见变体打包成可切换的布尔值,避免直接改策略:

# 列出所有布尔值
getsebool -a | grep httpd
# 查看某个布尔值
getsebool httpd_can_network_connect
# 临时开启
setsebool httpd_can_network_connect on
# 永久开启(-P)
setsebool -P httpd_can_network_connect on
# 常用布尔值示例
setsebool -P httpd_can_network_connect on        # 允许 httpd 发起网络连接
setsebool -P httpd_enable_homedirs on            # 允许访问用户家目录
setsebool -P samba_export_all_ro on              # Samba 只读导出
setsebool -P ftpd_full_access on                 # FTP 完整访问

一句话:改策略之前先找 boolean——90% 的「服务起不来」都能用一个 setsebool -P 解决,只有找不到对应开关时才需要动策略模块。


4. SELinux 实战:chcon、semanage 与 restorecon

给文件贴标签用 chcon,管理标签规则用 semanage fcontext,让规则生效用 restorecon——三者配合才完整。

chcon 直接改当前文件的上下文,但重启或 relabel 后会丢失:

# 临时修改(不推荐长期使用)
chcon -t httpd_sys_content_t /web/site/index.html
chcon -R -t httpd_sys_content_t /web/site/
# 参照某文件设置
chcon --reference=/var/www/html /web/site

正确做法是用 semanage fcontext 登记规则,再用 restorecon 应用:

# 登记目录规则(-a 添加,-t 类型,-e 等价路径)
semanage fcontext -a -t httpd_sys_content_t "/web/site(/.*)?"
# 查看已登记规则
semanage fcontext -l | grep /web/site
# 应用规则到现有文件(-R 递归,-v 显示变化)
restorecon -Rv /web/site

自定义端口也要登记,否则服务绑定会被拒:

# 允许 httpd 监听 8080
semanage port -a -t http_port_t -p tcp 8080
# 修改已有端口类型
semanage port -m -t http_port_t -p tcp 8080
# 查看某类型可用的端口
semanage port -l | grep http_port_t

一句话:chcon 是「临时贴纸」,semanage fcontext + restorecon 才是「永久档案」——生产环境只应使用后者,前者会被下一次 relabel 抹掉。


5. audit2allow 排错与自定义策略

SELinux 拒绝访问时会写审计日志,audit2allow 把「拒绝记录」翻译成「策略规则」——这是自定义策略的标准姿势。

排查链路:

# 1. 确认是不是 SELinux 拦的
dmesg | grep -i 'avc: denied'
# 或查审计日志
ausearch -m avc -ts recent
# 2. 把最近的拒绝翻译成可读建议
ausearch -m avc -ts today | audit2allow
# 3. 生成策略模块
ausearch -m avc -ts today | audit2allow -M mypolicy
# 4. 编译并加载
semodule -i mypolicy.pp

audit2allow -M 会生成 .te(策略源)与 .pp(编译产物)两个文件。生成的 .te 形如:

module mypolicy 1.0;

require {
    type httpd_t;
    type var_log_t;
    class file { read open };
}

#============= httpd_t ==============
allow httpd_t var_log_t:file { read open };
工具作用
ausearch -m avc查询 AVC 拒绝事件
audit2allow从日志生成规则建议
audit2why解释某条拒绝的原因
semodule -i安装策略模块
semodule -l列出已加载模块
semodule -r移除模块
# 只看解释(判断是否该用 boolean 而非新模块)
ausearch -m avc -ts today | audit2why

一句话:先 audit2why 判断,再 audit2allow 生成——如果解释指向某个 boolean,改开关远比新增允许规则更安全,因为 audit2allow 会无差别放行,可能把攻击面一起打开。


6. AppArmor:profile 语法与实践

AppArmor 走「路径」路线:profile 用文件路径而非 inode 标签来描述访问规则,配置直观、上手快。

查看状态与模式:

# 查看 AppArmor 状态
aa-status

profile 存放在 /etc/apparmor.d/,文件名通常是程序的路径(斜杠换成点),例如 /usr/sbin/nginx 对应 usr.sbin.nginx:

# /etc/apparmor.d/usr.sbin.mysqld (节选)
/usr/sbin/mysqld {
  #include <abstractions/base>
  #include <abstractions/mysql>

  /usr/sbin/mysqld mr,
  /var/lib/mysql/ r,
  /var/lib/mysql/** rwk,
  /var/log/mysql/* rw,
  /run/mysqld/mysqld.sock rw,

  # 网络
  network inet stream,
  network inet6 stream,
  capability setuid,
  capability setgid,
}

权限字符含义:

字符含义字符含义
r读w写
k锁定m可执行映射
ix继承当前 profile 执行px切换到目标 profile
ux无限制执行cx切换并继承子 profile

加载与切换模式:

# 加载/重载 profile
apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
# 切换到 complain 模式(只记录不拦截,等价 permissive)
aa-complain /usr/sbin/mysqld
# 切回 enforce
aa-enforce /usr/sbin/mysqld
# 查看日志中的拒绝
dmesg | grep -i apparmor
journalctl -k | grep apparmor

一句话:AppArmor 的 complain 就是 SELinux 的 permissive——先在 complain 模式下跑业务收集日志,用 aa-logprof 交互式生成规则,再切 enforce。


7. Ubuntu 与 RHEL 的差异与选型

SELinux 强在表达力与标签模型,AppArmor 强在易用与低门槛——选型往往由发行版生态决定。

维度SELinuxAppArmor
默认发行版RHEL/CentOS/Fedora/RockyUbuntu/Debian/SUSE
模型标签(inode 属性)路径
粒度类型/角色/级别,表达力强路径 + 操作,直观
学习曲线陡峭平缓
策略来源发行版预置 + 模块发行版预置 + 少量手写
文件改名影响标签随 inode 保留路径变化即失效
容器生态与 OpenShift/CRI-O 深度集成与 Docker/K8s 默认配合
排错工具audit2allow/ausearchaa-logprof/aa-complain

SELinux 的标签随 inode 走,mv 不改标签、cp 会新建标签;AppArmor 绑路径,文件一改名规则就失效。理解这一点能解释大量「同名文件行为不同」的怪象。

# 关闭某一发行版的 MAC(仅演示,生产慎用)
# RHEL:
grubby --update-kernel ALL --args selinux=0
# Ubuntu:
aa-teardown
systemctl disable apparmor

一句话:选型看生态,不看好坏——在 RHEL 系上用好 SELinux,在 Ubuntu 上用好 AppArmor,硬要在 Ubuntu 上装 SELinux 只会徒增维护成本。


8. 容器场景下的强制访问控制

容器默认运行在受限的 MAC 域里:Docker 用 SELinux 的 container_t 或 AppArmor 的 docker-default,K8s 还提供 seccomp 与 AppArmor annotation。

Docker 与 SELinux 的 :z/:Z 卷标签:

# :z 共享标签(多容器可读),:Z 私有标签(仅本容器)
docker run -v /data:/data:z nginx
docker run -v /data:/data:Z nginx
# 指定自定义 SELinux 标签
docker run --security-opt label=type:my_container_t nginx

Kubernetes 中通过 securityContext 控制:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  containers:
    - name: app
      image: nginx
      securityContext:
        seLinuxOptions:
          level: "s0:c123,c456"
        seccompProfile:
          type: RuntimeDefault
场景SELinuxAppArmor
Docker 默认 profilecontainer_tdocker-default
卷标签:z / :Z不适用(路径模型)
自定义--security-opt label=type:--security-opt apparmor=
K8sseLinuxOptionsannotation container.apparmor.security.beta.kubernetes.io
排错看宿主机 AVC 日志`dmesg

一句话:容器安全 = namespace 隔离 + cgroup 限制 + MAC 域 + seccomp——少了 MAC 这一层,容器逃逸的代价会低很多;--privileged 会同时打开几乎所有闸门。


9. 生产落地与排错清单

落地原则:能开就开、能窄就窄、改策略有据可查、每一次放行都要问「是否必要」。

服务起不来时的排查顺序:

# 1. 先看是不是 MAC 拦的
getenforce                          # SELinux
aa-status | head                    # AppArmor
dmesg | grep -iE 'avc: denied|apparmor' | tail
# 2. SELinux:查上下文是否匹配
ls -Z /path/to/resource
restorecon -Rv /path/to/resource
# 3. 查是否需要 boolean
ausearch -m avc -ts recent | audit2why
# 4. 查端口是否登记
semanage port -l | grep <port>

加固与审计要点:

# 生成当前策略的差异报告
sepolicy generate --init /usr/local/bin/myapp
# 列出所有本地自定义模块
semodule -l | grep -v '^\(abrt\|accountsd\)'
# 审计:统计被拒绝最多的域
ausearch -m avc -ts this-week | audit2allow -M weekly
风险缓解
直接 setenforce 0 长期运行仅限排错窗口,生产必须回 enforcing
用 audit2allow 无脑放行先 audit2why,优先 boolean
chcon 后忘记 semanage fcontext统一用 semanage + restorecon
容器 --privileged按需最小化 capability + label
关闭 MAC 换「省事」合规红线,禁止

一句话:排错三问:模式对吗?上下文对吗?规则登记了吗?——按这个顺序走,绝大多数 SELinux/AppArmor 问题都能在五分钟内定位。


10. 速查表

需求命令
查看 SELinux 模式getenforce / sestatus
切换模式setenforce 0|1
查看文件上下文ls -Z
查看进程上下文ps -eZ
临时改标签chcon -t type file
登记目录规则semanage fcontext -a -t type "path(/.*)?"
应用标签规则restorecon -Rv path
登记端口semanage port -a -t http_port_t -p tcp 8080
列出 booleansgetsebool -a
永久开 booleansetsebool -P name on
查拒绝日志ausearch -m avc -ts recent
解释拒绝原因ausearch -m avc | audit2why
生成策略模块ausearch -m avc | audit2allow -M name
安装策略semodule -i name.pp
AppArmor 状态aa-status
AppArmor 转 complainaa-complain /usr/sbin/app
重载 profileapparmor_parser -r /etc/apparmor.d/xxx
容器卷标签docker run -v /d:/d:Z

一句话记忆:DAC 管属主、MAC 管全局;SELinux 认标签、AppArmor 认路径;排错先看模式与上下文,改策略前先找 boolean,audit2allow 生成前先用 audit2why 解释。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「linux」更多文章

  1. 《Linux 故障排查工具箱:从 strace 到火焰图》
  2. 《磁盘加密与 LUKS 密钥管理实战》
  3. 《文本处理三剑客:grep、sed、awk 与 jq 实战》