F.36. sepgsql #
sepgsql 是一个可加载模块,支持基于
SELinux 安全策略和安全标签的强制访问控制(MAC)。
警告
当前实现存在重大限制,并不会对所有操作实施强制访问控制。详见第 F.36.7 节。
F.36.1. 概述 #
该模块与 SELinux 集成,在 PostgreSQL 通常提供的安全检查之外再增加一层安全检查。从 SELinux 的角度看,该模块使 PostgreSQL 能够充当用户空间对象管理器。由 DML 查询发起的每一次表或函数访问,都会依据系统安全策略进行检查。这种检查是对 PostgreSQL 常规 SQL 权限检查的补充。
SELinux 的访问控制决策通过安全标签作出,安全标签以 system_u:object_r:sepgsql_table_t:s0 这样的字符串表示。每个访问控制决策都涉及两个标签:尝试执行操作的主体标签,以及要执行该操作的对象标签。由于这些标签可以应用到任意类型的对象,数据库中存储对象的访问控制决策也可以与文件等其他类型对象一样,遵循同一套一般性标准;使用该模块时也确实如此。这样的设计旨在让集中式安全策略能够保护信息资产,而不受这些资产具体存储方式的影响。
SECURITY LABEL 语句允许为数据库对象分配安全标签。
F.36.2. 安装 #
sepgsql 只能在启用了
SELinux 的
Linux 2.6.28 或更高版本上使用。它在其他任何平台上都不可用。还需要
libselinux 2.1.10 或更高版本,以及
selinux-policy 3.9.13 或更高版本(尽管某些发行版可能会把所需规则回移植到较旧的策略版本中)。
可以使用 sestatus 命令检查
SELinux 的状态。典型显示如下:
$ sestatus SELinux status: enabled SELinuxfs mount: /selinux Current mode: enforcing Mode from config file: enforcing Policy version: 24 Policy from config file: targeted
如果 SELinux 被禁用或尚未安装,则必须先安装并配置它,之后才能安装本模块。
要构建此模块,请将选项 --with-selinux 加入 PostgreSQL 的 configure 命令中。还要确保构建时已经安装 libselinux-devel RPM。
要使用此模块,必须将 sepgsql 包含在 shared_preload_libraries 参数中,该参数位于
postgresql.conf 内。如果以其他任何方式加载该模块,它都无法正确工作。模块加载后,应在每个数据库中执行 sepgsql.sql。这会安装安全标签管理所需的函数,并分配初始安全标签。
下面的示例展示了如何初始化一个全新的数据库集簇,并安装
sepgsql 函数和安全标签。请根据实际安装情况调整其中的路径:
$ export PGDATA=/path/to/data/directory
$ initdb
$ vi $PGDATA/postgresql.conf
change
#shared_preload_libraries = '' # (change requires restart)
to
shared_preload_libraries = 'sepgsql' # (change requires restart)
$ for DBNAME in template0 template1 postgres; do
postgres --single -F -c exit_on_error=true $DBNAME \
</usr/local/pgsql/share/contrib/sepgsql.sql >/dev/null
done请注意,具体会看到以下通知中的哪些,取决于所使用的 libselinux 和 selinux-policy 版本,可能是部分,也可能是全部:
/etc/selinux/targeted/contexts/sepgsql_contexts: line 33 has invalid object type db_blobs /etc/selinux/targeted/contexts/sepgsql_contexts: line 36 has invalid object type db_language /etc/selinux/targeted/contexts/sepgsql_contexts: line 37 has invalid object type db_language /etc/selinux/targeted/contexts/sepgsql_contexts: line 38 has invalid object type db_language /etc/selinux/targeted/contexts/sepgsql_contexts: line 39 has invalid object type db_language /etc/selinux/targeted/contexts/sepgsql_contexts: line 40 has invalid object type db_language
这些消息没有害处,应予忽略。
如果安装过程无错误完成,此时就可以正常启动服务器。
F.36.3. 回归测试 #
由于 SELinux 的特性,运行 sepgsql 的回归测试需要额外执行若干配置步骤,其中部分步骤必须以 root 身份完成。普通的 make check 或 make installcheck 命令不会运行这些回归测试;必须完成配置,然后手工调用测试脚本。这些测试必须在已配置的 PostgreSQL 构建树的 contrib/sepgsql 目录中运行。虽然需要构建树,但这些测试是为已安装的服务器设计的,也就是说,它们类似于 make installcheck 而不是 make check。
首先,在一个可用数据库中设置 sepgsql,并遵循第 F.36.2 节中的说明。请注意,当前操作系统用户必须能够无需密码认证,以超级用户身份连接到该数据库。
第二,构建并安装回归测试所需的策略包。sepgsql-regtest
策略是一个专用策略包,提供了一组允许在回归测试期间使用的规则。它应从策略源文件 sepgsql-regtest.te 构建,构建过程通过使用
make 和由 SELinux 提供的 Makefile 完成。需要在本机上找到合适的 Makefile;下面展示的路径仅为示例。构建完成后,使用
semodule 命令安装该策略包,该命令会将提供的策略包装载到内核中。如果安装正确, 应该把
semodule -lsepgsql-regtest 列为可用策略包之一:
$ cd .../contrib/sepgsql $ make -f /usr/share/selinux/devel/Makefile $ sudo semodule -u sepgsql-regtest.pp $ sudo semodule -l | grep sepgsql sepgsql-regtest 1.07
第三,打开 sepgsql_regression_test_mode。出于安全原因,sepgsql-regtest 中的规则默认并未启用;sepgsql_regression_test_mode 参数会启用启动回归测试所需的规则。可以使用 setsebool 命令将其打开:
$ sudo setsebool sepgsql_regression_test_mode on $ getsebool sepgsql_regression_test_mode sepgsql_regression_test_mode --> on
第四,确认 shell 正在 unconfined_t 域中运行:
$ id -Z unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
如有需要,请参阅第 F.36.8 节,了解如何调整工作域。
最后,运行回归测试脚本:
$ ./test_sepgsql
该脚本会尝试验证是否已正确完成全部配置步骤,然后运行
sepgsql 模块的回归测试。
完成测试后,建议关闭
sepgsql_regression_test_mode 参数:
$ sudo setsebool sepgsql_regression_test_mode off
也可以选择彻底移除 sepgsql-regtest 策略:
$ sudo semodule -r sepgsql-regtest
F.36.4. GUC 参数 #
-
sepgsql.permissive(boolean) # 该参数使
sepgsql无论系统设置如何都以宽容模式运行。默认值为关闭。该参数只能在postgresql.conf文件中或服务器命令行上设置。当该参数打开时,
sepgsql会以宽容模式运行,即使 SELinux 整体处于强制模式也是如此。该参数主要用于测试。-
sepgsql.debug_audit(boolean) # 该参数会在不考虑系统策略设置的情况下启用审计消息输出。默认值为关闭,这意味着消息将按系统设置输出。
SELinux 的安全策略本身也有规则来控制是否记录特定的访问。默认情况下,访问违例会被记录,而被允许的访问不会被记录。
该参数会在不考虑系统策略的情况下,强制打开所有可能的日志记录。
F.36.5. 特性 #
F.36.5.1. 受控对象类
SELinux 的安全模型把全部访问控制规则描述为主体实体(通常是数据库客户端)与对象实体(例如数据库对象)之间的关系,这两者都由安全标签标识。如果尝试访问一个未带标签的对象,则会把它视为被赋予了 unlabeled_t 标签。
当前,sepgsql 允许为模式、表、列、序列、视图和函数分配安全标签。使用 sepgsql 时,安全标签会在受支持的数据库对象创建时自动分配。这个标签称为默认安全标签,其取值由系统安全策略决定;策略的输入包括创建者的标签、新对象父对象的标签,以及待创建对象的名称(如果需要)。
新数据库对象基本上会继承父对象的安全标签,但如果安全策略中存在称为类型转换规则的特殊规则,则可能会应用不同的标签。对于模式,父对象是当前数据库;对于表、序列、视图和函数,父对象是其所在模式;对于列,父对象是其所在表。
F.36.5.2. DML 权限
对于表,会根据语句类型,对所有被引用的目标表检查
db_table:select、db_table:insert、db_table:update 或 db_table:delete;此外,对于其列在 WHERE 或 RETURNING
子句中被引用的所有表,以及作为 UPDATE 数据源的所有表等,还会检查 db_table:select。
每个被引用的列也都会检查列级权限。db_column:select
不仅会对通过 SELECT 读取的列进行检查,也会对其他 DML
语句中被引用的列进行检查;对于被 UPDATE 或
INSERT 修改的列,还会检查
db_column:update 或
db_column:insert。
例如,考虑:
UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100;
这里会对 t1.x 检查 db_column:update,因为它被更新;会对 t1.y 检查
db_column:{select update},因为它既被更新也被引用;会对 t1.z 检查 db_column:select,因为它只是被引用。表级别还会检查
db_table:{select update}。
对于序列,当使用 SELECT 引用序列对象时,会检查
db_sequence:get_value;但请注意,当前不会检查执行相应函数(如 lastval())的权限。
对于视图,会先检查 db_view:expand,然后对由该视图展开得到的各个对象分别检查所需的其他权限。
对于函数,当用户试图在查询中执行某个函数,或通过快速路径调用执行某个函数时,会检查 db_procedure:{execute}。如果该函数是受信任过程,还会检查 db_procedure:{entrypoint} 权限,以判定它是否可以充当受信任过程的入口点。
要访问任何模式对象,都需要在其所在模式上具有
db_schema:search 权限。当对象在引用时未带模式限定时,不具备该权限的模式不会被搜索(就像用户在该模式上没有
USAGE 权限一样)。如果使用了显式模式限定,而用户在所指定模式上又没有所需权限,则会报错。
客户端必须被允许访问所有被引用的表和列,即使这些表和列最初来自随后被展开的视图也是如此,这样我们才能在不受表内容引用方式影响的情况下,应用一致的访问控制规则。
默认数据库权限系统允许数据库超级用户使用 DML 命令修改系统目录,也允许引用或修改 TOAST 表。启用 sepgsql 后,这些操作都会被禁止。
F.36.5.3. DDL 权限
SELinux 为每种对象类型定义了若干权限,用于控制常见操作,例如创建、修改、删除以及重设安全标签。此外,若干对象类型还具有特殊权限,用于控制它们特有的操作,例如在特定模式中添加或删除名称项。
创建新的数据库对象需要 create 权限。SELinux 会根据客户端的安全标签以及为新对象拟定的安全标签授予或拒绝该权限。在某些情况下,还需要额外权限:
CREATE DATABASE 还需要对源数据库或模板数据库具有
getattr权限。创建模式对象还需要在父模式上具有
add_name权限。创建表还需要有权限创建每个单独的表列,就好像每个表列都是独立的顶层对象一样。
创建被标记为
LEAKPROOF的函数还需要install权限。(对现有函数设置LEAKPROOF时同样会检查该权限。)
执行 DROP 命令时,会对将被移除的对象检查
drop 权限。通过 CASCADE 间接删除的对象同样会检查权限。删除位于特定模式中的对象(表、视图、序列和过程)还需要该模式上的 remove_name 权限。
执行 ALTER 命令时,会针对被修改的对象检查
setattr 权限,但表的索引或触发器等附属对象除外,这类情况改为在父对象上检查权限。在某些情况下,还需要额外权限:
将对象移动到新模式还需要在旧模式上具有
remove_name权限,并在新模式上具有add_name权限。在函数上设置
LEAKPROOF属性需要install权限。在对象上使用 SECURITY LABEL 还需要同时具备与其旧安全标签组合的对象
relabelfrom权限,以及与其新安全标签组合的对象relabelto权限。(在安装了多个标签提供者且用户尝试设置一个不由 SELinux 管理的安全标签时,这里本应只检查setattr。由于实现限制,当前尚未这样做。)
F.36.5.4. 受信任过程
受信任过程类似于安全定义器函数或 setuid 命令。SELinux 提供了一项功能,允许受信任代码以不同于客户端的安全标签运行,通常用于以高度受控的方式访问敏感数据(例如可以省略某些行,或降低已存储值的精度)。函数是否充当受信任过程,由其安全标签和操作系统安全策略控制。例如:
postgres=# CREATE TABLE customer (
cid int primary key,
cname text,
credit text
);
CREATE TABLE
postgres=# SECURITY LABEL ON COLUMN customer.credit
IS 'system_u:object_r:sepgsql_secret_table_t:s0';
SECURITY LABEL
postgres=# CREATE FUNCTION show_credit(int) RETURNS text
AS 'SELECT regexp_replace(credit, ''-[0-9]+$'', ''-xxxx'', ''g'')
FROM customer WHERE cid = $1'
LANGUAGE sql;
CREATE FUNCTION
postgres=# SECURITY LABEL ON FUNCTION show_credit(int)
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0';
SECURITY LABEL上述操作应由管理用户执行。
postgres=# SELECT * FROM customer; ERROR: SELinux: security policy violation postgres=# SELECT cid, cname, show_credit(cid) FROM customer; cid | cname | show_credit -----+--------+--------------------- 1 | taro | 1111-2222-3333-xxxx 2 | hanako | 5555-6666-7777-xxxx (2 rows)
在这种情况下,普通用户不能直接引用 customer.credit,但受信任过程 show_credit 允许用户打印客户信用卡号,并对其中部分数字做掩码处理。
F.36.5.5. 动态域转换
如果安全策略允许,可以利用 SELinux 的动态域转换功能,将客户端进程(即客户端域)的安全标签切换到新的上下文。客户端域需要
setcurrent 权限,以及从旧域切换到新域的
dyntransition 权限。
动态域转换需要谨慎考虑,因为它允许用户按自己的意愿切换标签,因而也能切换其权限,而不是像受信任过程那样由系统强制规定。因此,只有当它被用于切换到一个权限集合少于原始域的新域时,dyntransition 权限才被认为是安全的。例如:
regression=# select sepgsql_getcon();
sepgsql_getcon
-------------------------------------------------------
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
(1 row)
regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c4');
sepgsql_setcon
----------------
t
(1 row)
regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c1023');
ERROR: SELinux: security policy violation 在上面的例子中,允许从较大的 MCS 范围 c1.c1023 切换到较小的范围 c1.c4,但不允许切换回去。
动态域转换与受信任过程的组合支持一种很有意思的用例,它契合连接池软件的典型进程生命周期。即使连接池软件本身不允许运行大多数 SQL 命令,也可以让它在受信任过程中调用 sepgsql_setcon() 函数来切换客户端的安全标签;该过程应要求某种凭据,以授权切换客户端标签的请求。此后,这个会话就将拥有目标用户而非连接池软件的权限。之后,连接池软件还可以再次在具有适当权限检查的受信任过程中,以 NULL 参数调用
sepgsql_setcon(),从而恢复这一安全标签变更。这里的关键在于,真正有权修改生效安全标签的只有受信任过程,而且它只会在获得适当凭据时这样做。当然,为了安全运行,凭据存储(表、过程定义或其他载体)必须防止未授权访问。
F.36.5.6. 杂项
我们一律拒绝 LOAD 命令,因为装入任意模块都可能轻易绕过安全策略的强制执行。
F.36.6. sepgsql 函数 #
表 F.30 展示了可用函数。
表 F.30. sepgsql 函数
sepgsql_getcon() returns text | 返回客户端域,也就是客户端当前的安全标签。 |
sepgsql_setcon(text) returns bool | 如果安全策略允许,将当前会话的客户端域切换到新域。它也接受
NULL 输入,表示请求切换到客户端的原始域。
|
sepgsql_mcstrans_in(text) returns text | 如果 mcstrans 守护进程正在运行,则把给定的限定格式 MLS/MCS 范围转换为原始格式。 |
sepgsql_mcstrans_out(text) returns text | 如果 mcstrans 守护进程正在运行,则把给定的原始 MLS/MCS 范围转换为限定格式。 |
sepgsql_restorecon(text) returns bool | 为当前数据库中的所有对象设置初始安全标签。参数可以为 NULL,也可以是一个 specfile 的名称,用来替代系统默认文件。 |
F.36.7. 限制 #
- 数据定义语言(DDL)权限
由于实现限制,某些 DDL 操作不会检查权限。
- 数据控制语言(DCL)权限
由于实现限制,DCL 操作不会检查权限。
- 行级访问控制
PostgreSQL 支持行级访问,但
sepgsql不支持。- 隐蔽通道
sepgsql不会试图隐藏某个对象的存在,即使用户无权引用它也是如此。例如,即便我们无法取得一个不可见对象的内容,也仍然可以从主键冲突、外键违例等结果中推断出它的存在。绝密表的存在无法隐藏;我们只希望隐藏其内容。
F.36.8. 外部资源 #
- SE-PostgreSQL Introduction
该 wiki 页面提供了简要概述,并介绍了安全设计、体系结构、管理以及未来特性。
- SELinux User's and Administrator's Guide
该文档提供了在系统上管理 SELinux 所需的广泛知识。它主要聚焦于 Red Hat 操作系统,但并不限于此。
- Fedora SELinux FAQ
该文档回答了关于 SELinux 的常见问题。它主要聚焦于 Fedora,但并不限于 Fedora。
F.36.9. 作者 #
KaiGai Kohei <kaigai@ak.jp.nec.com>