一. 核心概念理解
在开始编写 .te
文件之前,必须理解几个核心概念:
- 主体 (Subject): 通常是进程。在 SELinux 中,进程是在某个域 (Domain) 中运行的。例如,
httpd_t
是 Apache Web 服务器的域 - 客体 (Object): 被进程访问的资源,例如文件、目录、端口、套接字等
- 类型 (Type): SELinux 为所有客体(有时也包括主体)分配的一个标签(Label)。策略规则基于这些类型来定义访问控制
○ 文件的类型通常称为 类型 (Type)
○ 进程的域本质上也是一种特殊的类型 - 策略规则 (Policy Rule): 定义了"哪个域(主体)可以对哪个类型(客体)执行哪些操作"。规则的基本结构是:
allow <domain> <type> : <class> <permission>;
○domain
: 进程的域(如my_app_t
)
○type
: 客体的类型(如my_app_log_t
)
○class
: 客体的类别(如file
,dir
,tcp_socket
)
○permission
: 允许的操作(如read
,write
,create
,listen
) - .te 文件 (Type Enforcement File): 这是定义策略模块的主要文件,其中包含了类型声明、域转换规则以及主要的
allow
规则
二. 编写 .te 文件的基本步骤
假设我们有一个自定义的应用程序 my_app
,它需要:
- 在自己的目录(
/opt/my_app/
)中读写文件 - 绑定到 TCP 端口 9999
我们的目标是创建一个策略模块来允许这些行为
步骤 1:创建 .te 文件
首先,为模块创建一个文件,通常命名为 <module_name>.te
。模块名应具有描述性,例如 my_app.te
步骤 2:定义策略模块和所需类型
在 my_app.te
文件中,我们首先声明模块并定义新的类型
bash
# 声明策略模块名称
policy_module(my_app, 1.0)
# 定义进程域类型
# 你的应用程序进程将运行在这个域中
type my_app_t;
# 初始域:当系统启动时,由 init 启动的进程的初始上下文
init_daemon_domain(my_app_t)
# 定义用于 /opt/my_app 目录及其文件的类型
type my_app_etc_t;
# 定义用于日志文件的类型
type my_app_log_t;
说明:
policy_module(my_app, 1.0)
:声明模块名为my_app
,版本为1.0
type my_app_t;
:定义一个新类型my_app_t
,用于我们的应用程序进程域init_daemon_domain(my_app_t)
:这是一个宏,它做了很多事情:
○ 它告诉 SELinux,由init
系统启动的、类型为my_app_exec_t
的可执行文件,应该转换到my_app_t
域运行
○ 它自动为my_app_t
域设置了允许与终端、消息队列等基本资源交互的规则type my_app_etc_t;
和type my_app_log_t;
:定义用于配置文件和日志文件的新类型
步骤 3:定义文件上下文(File Contexts)
我们需要告诉 SELinux 哪些路径应该用我们新定义的类型来标记。这通常在另一个文件 my_app.fc
中完成,但为了教程连贯性,我们先在 .te
文件中声明依赖
.te
文件需要知道这些类型将应用于文件系统对象。我们稍后会创建 .fc
文件
步骤 4:编写访问规则 (Allow Rules)
这是策略的核心,我们明确授予必要的权限
bash
# 1. 允许 my_app_t 域对 my_app_etc_t 类型的文件和目录进行基本读写
files_search_etc(my_app_t) # 宏:允许搜索 /etc 目录
allow my_app_t my_app_etc_t : dir { read search open getattr };
allow my_app_t my_app_etc_t : file { read open getattr };
# 2. 允许 my_app_t 域对 my_app_log_t 类型的文件进行读写和创建
logging_log_file(my_app_t) # 宏:允许使用日志相关的权限
allow my_app_t my_app_log_t : file { create read write open append getattr setattr };
allow my_app_t my_app_log_t : dir { create read write open add_name remove_name getattr setattr };
# 3. 允许 my_app_t 绑定到 TCP 端口 9999
# 首先,需要定义我们自己的端口类型
type my_app_port_t;
# 然后,声明该类型用于 tcp 套接字,并且端口号是 9999
# (注意:这行通常也在另一个 .if 文件中,但可以写在这里)
corenet_port(my_app_port_t, tcp, 9999)
# 最后,允许我们的域绑定到该类型的端口
allow my_app_t my_app_port_t : tcp_socket { name_bind };
说明:
files_search_etc
,logging_log_file
:这些是宏 ,它们展开后是一组常用的、复杂的allow
规则。强烈建议优先使用宏而不是手动编写allow
规则 ,因为它们更安全、更简洁。你可以通过sepolicy generate
命令或查阅现有策略来找到可用的宏allow ... : dir { read search ... };
:详细列出了对目录 (dir
类) 允许的操作corenet_port(my_app_port_t, tcp, 9999)
:这是一个宏,它:
○ 定义规则允许my_app_port_t
类型与 TCP 端口 9999 关联
○ 为其他域(如内核netif_t
、node_t
)访问该端口提供必要的规则
步骤 5:处理其他依赖
你的应用可能需要其他权限,例如访问 /proc
文件系统、与 Unix 套接字通信等。一个很好的起点是使用 audit2allow
工具
-
将 SELinux 设置为
permissive
模式:setenforce Permissive
-
运行你的应用程序,执行所有操作
-
查看
/var/log/audit/audit.log
或使用ausearch
来收集 AVC(Access Vector Cache)拒绝消息 -
使用
audit2allow -a
或audit2allow -w -a
来生成建议的规则
○-w
选项会告诉你哪些规则是因为哪个拒绝消息而需要的
○ 不要盲目添加所有audit2allow
建议的规则! 只添加与你应用程序功能相关的、你理解的那些规则。audit2allow
可能会生成过于宽松的规则
三. 完整的示例文件
my_app.te
bash
policy_module(my_app, 1.0)
###########################################
# Type declarations
###########################################
type my_app_t;
type my_app_etc_t;
type my_app_log_t;
type my_app_port_t;
###########################################
# Role and user domain transition
# init_daemon_domain 宏已经处理了大部分内容
###########################################
init_daemon_domain(my_app_t)
###########################################
# File context rules (referenced from .fc)
# 这里只是声明类型,实际路径映射在 .fc 文件
###########################################
###########################################
# Access Vector Rules
###########################################
# Allow basic directory operations for etc files
files_search_etc(my_app_t)
allow my_app_t my_app_etc_t : dir { read search open getattr };
allow my_app_t my_app_etc_t : file { read open getattr };
# Allow read/write/create for log files
logging_log_file(my_app_t)
allow my_app_t my_app_log_t : file { create read write open append getattr setattr };
allow my_app_t my_app_log_t : dir { create read write open add_name remove_name getattr setattr };
# Allow binding to TCP port 9999
corenet_port(my_app_port_t, tcp, 9999)
allow my_app_t my_app_port_t : tcp_socket { name_bind };
# Additional rules based on audit2allow analysis
# Example: if your app needs to read /proc files
# allow my_app_t self : proc { read };
my_app.fc
bash
# 这个文件将类型映射到文件系统路径
# Format: <path_regex> <<type>
# Configuration files in /opt/my_app/etc
/opt/my_app/etc(/.*)? gen_context(system_u:object_r:my_app_etc_t, s0)
# Log files in /var/log/my_app
/var/log/my_app(/.*)? gen_context(system_u:object_r:my_app_log_t, s0)
# The application binary itself
/opt/my_app/bin/my_app gen_context(system_u:object_r:my_app_exec_t, s0)
注意: .fc
文件中的 my_app_exec_t
类型是由 init_daemon_domain(my_app_t)
宏自动创建和使用的,它用于可执行文件,以实现域转换
四. 编译、安装和启用策略模块
-
所需软件包: 确保已安装
selinux-policy-devel
、policycoreutils
等包
○ (RHEL/CentOS/Fedora): sudo dnf install selinux-policy-devel policycoreutils -
编译和安装:
bash# 1. 将 .te 和 .fc 文件放在一个目录下 # 2. 运行 make -f /usr/share/selinux/devel/Makefile # 这会将 .te 文件编译成 .pp 策略模块包文件 sudo make -f /usr/share/selinux/devel/Makefile my_app.pp # 3. 安装编译好的模块 sudo semodule -i my_app.pp
-
应用文件上下文:
bash# 在创建文件之前,先应用文件上下文规则 sudo restorecon -Rv /opt/my_app/ /var/log/my_app/ # 如果文件已经存在,同样使用此命令重新标记它们
-
重启应用程序: 重启你的应用程序,以便它在新策略下以正确的域 (
my_app_t
) 启动 -
验证:
○ 检查进程域:ps -efZ | grep my_app
○ 检查文件上下文:ls -lZ /opt/my_app/ /var/log/my_app/
五. 调试技巧
permissive
模式是你的朋友: 始终先在permissive
模式下开发和测试你的策略。在此模式下,SELinux 会记录拒绝访问的日志但不会真正阻止操作。这让你可以收集所有必要的 AVC 消息,而不会使应用程序崩溃- 使用
audit2why
:audit2why -a
可以将 AVC 消息翻译成更易读的英文,解释为什么访问被拒绝 sepolicy generate
: 这是一个非常强大的工具。你可以尝试:
sepolicy generate --init /path/to/my_app/binary
它会尝试为你生成一个初步的.te
、.fc
文件,这是一个极好的起点。你可以在其基础上进行修改和细化
总结
步骤 | 关键动作 | 常用命令/宏 |
---|---|---|
1. 分析 | 在 Permissive 模式下运行应用,收集 AVC 拒绝消息 | setenforce 0 , ausearch -m avc , audit2allow -a |
2. 编写 | 创建 .te 和 .fc 文件,定义类型,使用宏和规则 |
type , init_daemon_domain() , allow , files_search_etc() , corenet_port() |
3. 编译 | 将 .te 文件编译成 .pp 模块 |
make -f /usr/share/selinux/devel/Makefile |
4. 安装 | 将模块加载到内核 | semodule -i |
5. 标记 | 将安全上下文应用到文件系统 | restorecon -Rv |
6. 测试 | 在 Enforcing 模式下重启应用并验证 | setenforce 1 , ps -efZ , ls -lZ |