第 3 章 SELinux 策略结构:策略文件类型、策略层次、模块化设计
聊到 SELinux 策略,很多新人第一反应就是「那一大堆 .te 文件到底是干啥的?」确实,策略文件乍一看挺吓人,但拆开来看其实就几类东西。我个人习惯把策略结构比作「乐高积木」—— 你有一堆基础砖块(类型、属性、规则),然后按层搭起来,最终组成完整的安全策略。
3.1 策略文件类型:三大基石
SELinux 策略由三种核心文件构成,每类文件承担不同角色。我用一个表格总结它们的区别:
| 文件类型 | 扩展名 | 核心作用 |
|---|---|---|
| 类型强制文件 | .te | 定义类型、属性、类型转换、关联规则 |
| 接口文件 | .if | 封装可复用的策略接口(类似函数库) |
| 文件上下文文件 | .fc | 指定文件系统路径与类型的映射关系 |
3.2 .te 文件:策略的骨架
.te 文件是策略的主体。里面定义的东西包括:
- 类型声明:
type my_app_t; - 属性声明:
attribute my_domain;,然后给类型贴属性 - allow 规则:
allow my_app_t my_file_t:file read; - 类型转换:
type_transition my_app_t exec_t:process my_app_t;
我在项目中遇到过有人把所有规则都写在一个超级大的 .te 文件里,结果维护起来简直噩梦。所以我建议你按模块拆,一个模块一个 .te 文件,比如 network.te、filesystem.te。这样后续做回归测试时,定位问题也会快很多。
3.3 .if 文件:策略的接口层
.if 文件有点像 C 语言的函数声明。它定义可被其他模块调用的策略接口。例如:
interface(`my_app_domtrans',`
gen_require(`
type my_app_t;
')
domtrans_pattern($1, my_app_exec_t, my_app_t)
')
为什么要有 .if 文件?说白了就是为了复用。你想想看,假如多个模块都需要允许某个域转换,每个模块都写一遍相同的规则,不仅啰嗦,还容易出错。用接口封装后,其他模块只需一句:my_app_domtrans(unconfined_t) 就搞定了。
interface 和 template 宏一定要写清楚参数说明。我见过一个团队因为接口文档缺失,同一段逻辑用了三种不同写法,调试时互相甩锅。嗯,血泪教训。
3.4 .fc 文件:文件类型绑定
.fc 文件用于将文件系统路径与安全上下文绑定。最常见的写法:
/opt/myapp(/.*)? system_u:object_r:my_app_file_t:s0
这里要注意的是路径匹配的先后顺序。策略加载时,.fc 文件中的条目按顺序匹配,一旦匹配成功就停止。所以我个人习惯把更具体的路径放在前面,通配符放在后面,避免被粗匹配吃掉。
3.5 策略层次:从低层到高层的栈
SELinux 策略是分层构建的。从最底层开始:
- 基础策略(base):提供最核心的类型和权限,比如
kernel_t、unconfined_t,以及基本的安全类。 - 系统服务层(system):为 systemd、dbus、udev 等系统服务定义规则。
- 应用模块层(modules):为具体应用(如 Nginx、MySQL)定制的策略模块。
- 用户自定义层(custom policy):我们自己写的私有策略,就是绝大多数回归测试需要适配的地方。
为什么要分层?我举个例子你就懂了。假设基础策略升级了,如果你把所有规则都写在一个层级里,升级时可能会冲突;分成模块后,只需要重新编译有改动的模块,再加载就行了。回归测试也是同理,只测受影响的模块,能省下大量时间。
3.6 模块化设计:从单体到积木
SELinux 策略的模块化设计主要体现在几个方面:
- 按功能拆分:每个守护进程或一组相关进程一个模块。
- 显式依赖:模块通过
require声明需要的外部类型/属性/接口。 - 分层引用:低层模块为高层模块提供基础接口,高层模块不会反向依赖低层。
来看一个典型的模块目录结构:
policy/modules/
├── system/
│ ├── init.te
│ ├── init.if
│ └── init.fc
├── apps/
│ ├── nginx.te
│ ├── nginx.if
│ └── nginx.fc
└── custom/
├── myapp.te
├── myapp.if
└── myapp.fc
我建议你把自己的回归用例策略也按这种模块方式组织。比如 regression_test_app1 一个模块,regression_test_app2 一个模块。这样每次覆盖时只需单独编译和加载,不用动整个策略源。
3.7 知识结构图:SELinux 策略的三层架构
3.8 模块化带来的回归测试优势
讲真的,我刚入行时也不太明白为什么要分模块,觉得「麻烦,直接写在一起不就行了」。后来一次项目上线前,基础策略打了补丁,结果整个 .te 文件需要重新 review 一遍,那个酸爽……从那以后,我老老实实按模块拆分。
模块化对回归测试的好处很明显:
- 隔离影响:修改一个模块的规则,其他模块不受影响。回归测试可以只验证该模块的用例。
- 快速迭代:模块编译比全量编译快得多。举个例子,一个包含 50 个模块的策略,改一个模块的全量编译需要 3 分钟,而单独编译这个模块只要 10 秒。
- 避免命名冲突:类型名在模块内是局部作用域,不同模块里定义同名类型也不会冲突。
3.9 实践建议:如何组织回归测试策略模块
最后分享一个我在团队里推广的目录结构:
regression_policy/
├── test_module_a/
│ ├── test_module_a.te
│ ├── test_module_a.if
│ └── test_module_a.fc
├── test_module_b/
│ ├── test_module_b.te
│ ├── test_module_b.if
│ └── test_module_b.fc
└── Makefile
每个测试模块对应一次回归测试场景。Makefile 里写好编译目标,比如 make test_module_a.pp。这样做的好处是,当某个场景回归失败时,你直接定位到对应的模块,不用大海捞针。
嗯,玩 SELinux 策略,模块化不仅仅是架构的追求,更是后期运维和回归测试的救命稻草。记住它。