引言
一个 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:为什么需要强制访问控制
- 2. SELinux 基础:上下文与运行模式
- 3. SELinux 策略、类型强制与 booleans
- 4. SELinux 实战:chcon、semanage 与 restorecon
- 5. audit2allow 排错与自定义策略
- 6. AppArmor:profile 语法与实践
- 7. Ubuntu 与 RHEL 的差异与选型
- 8. 容器场景下的强制访问控制
- 9. 生产落地与排错清单
- 10. 速查表
- 延伸阅读
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 段 | user | SELinux 用户,非 Linux 用户 | system_u、unconfined_u |
| 第 2 段 | role | 角色,用于 RBAC | object_r、system_r |
| 第 3 段 | type | 类型,策略的核心 | httpd_t、httpd_sys_content_t |
| 第 4 段 | level | MLS/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 强在易用与低门槛——选型往往由发行版生态决定。
| 维度 | SELinux | AppArmor |
|---|---|---|
| 默认发行版 | RHEL/CentOS/Fedora/Rocky | Ubuntu/Debian/SUSE |
| 模型 | 标签(inode 属性) | 路径 |
| 粒度 | 类型/角色/级别,表达力强 | 路径 + 操作,直观 |
| 学习曲线 | 陡峭 | 平缓 |
| 策略来源 | 发行版预置 + 模块 | 发行版预置 + 少量手写 |
| 文件改名影响 | 标签随 inode 保留 | 路径变化即失效 |
| 容器生态 | 与 OpenShift/CRI-O 深度集成 | 与 Docker/K8s 默认配合 |
| 排错工具 | audit2allow/ausearch | aa-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
| 场景 | SELinux | AppArmor |
|---|---|---|
| Docker 默认 profile | container_t | docker-default |
| 卷标签 | :z / :Z | 不适用(路径模型) |
| 自定义 | --security-opt label=type: | --security-opt apparmor= |
| K8s | seLinuxOptions | annotation 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 |
| 列出 booleans | getsebool -a |
| 永久开 boolean | setsebool -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 转 complain | aa-complain /usr/sbin/app |
| 重载 profile | apparmor_parser -r /etc/apparmor.d/xxx |
| 容器卷标签 | docker run -v /d:/d:Z |
一句话记忆:DAC 管属主、MAC 管全局;SELinux 认标签、AppArmor 认路径;排错先看模式与上下文,改策略前先找 boolean,audit2allow 生成前先用 audit2why 解释。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。