SEPostgreSQL 架构

来自 PostgreSQL wiki
跳转到导航跳转到搜索

本章介绍 SE-PostgreSQL 的架构

  • 章节列表
  1. 介绍
  2. 架构
  3. 规格
  4. SELinux 概述
  5. 管理
  6. 参考文献
  7. 开发

SE-PostgreSQL 如何与 SELinux 协同工作

SELinux 安全架构

SELinux 在 Linux 内核中充当参考监视器。根据定义,参考监视器是一个足够小且防篡改的模块,它检查所有访问并做出决定,当用户调用对系统管理的数据对象的请求时。在操作系统中,用户必须调用系统调用来访问由操作系统管理的数据对象,例如文件、套接字等。SELinux 通过部署在战略点的安全钩子获取任何系统调用调用,并根据其安全模型和安全策略做出决定。

SELinux 的安全模型非常简单。它为操作系统管理的所有对象分配一个称为安全上下文的安全标识符,表示为与对象类型无关的格式化字符串,例如文件系统对象、网络套接字、进程等。请注意,任何进程也都有其安全上下文。这意味着系统调用的调用者有一个安全上下文,而不仅仅是系统调用的目标。

SELinux 在安全策略中查找条目,安全策略是一组针对安全上下文对的访问控制规则,如果安全上下文对未明确允许,则会阻止所需的访问。这种机制类似于客户端-服务器模型,因此它通常被称为安全服务器,它可以为给定的安全上下文对返回其决定。

与 SELinux 的交互

SELinux 还为用户空间提供接口,充当安全服务器。它提供一个名为 selinuxfs 的伪文件系统(通常安装在 /selinux 上),为内核中的 SELinux 提供一些原始接口。libselinux 为用户空间应用程序提供了一组抽象级别的 API。它使用户空间应用程序能够确认 SELinux 是否允许安全策略对安全上下文的对执行所需的操作,或者不允许。

SE-PostgreSQL 充当安全服务器的客户端。它检查来自客户端的任何给定查询,就像 SELinux 检查任何系统调用调用一样,并询问内核中的 SELinux 是否应该允许,或者不应该。然后,如果违反,SE-PostgreSQL 可以阻止客户端访问所需数据库对象。一些用户空间应用程序充当安全服务器的客户端,比 SE-PostgreSQL 更常见,例如 XACE/SELinux(带有 SELinux 增强的 X.org)、nscd 守护进程、密码实用程序等等。

这种设计为我们提供了一个名为系统范围一致性访问控制的特性,因为所有访问控制决定都是由基于其共同标准的 SELinux 安全服务器做出的。例如,它将阻止没有权限的用户读取标记为 `credential` 的信息,即使它是作为文件还是数据库记录存储的。

这是 libselinux 提供的 SELinux 和用户空间应用程序之间通信的协议。它要求所有出现在访问控制中的实体都被抽象为安全上下文,因此这意味着 SE-PostgreSQL 需要管理客户端(作为主体)的安全上下文和数据库对象(作为字面上的对象)的安全上下文。libselinux 提供一个有趣的 API,用于获取给定套接字描述符的对等进程的安全上下文。SE-PostgreSQL 将其结果应用为客户端的安全上下文。对于数据库对象,它需要为每个数据库对象尽可能简单和紧凑地分配一个单独的安全上下文。有关更多详细信息,请参阅 安全上下文的管理

战略点上的钩子

在实现级别,SE-PostgreSQL 在原始 PostgreSQL 上放置各种钩子来获取控制权并做出决定。这些点具有重要意义,因此我们称之为战略点。

以下是战略点的一部分。

  • 从 ExecCheckRTPerms() 调用 sepgsqlCheckRTEPerms(),在 ExecCheckRTPerms() 成功之后立即调用。
  • 从 superuser_arg() 调用 sepgsqlCheckDatabaseSuperuse(),如果它允许客户端充当超级用户。
  • 从 LargeObjectCreate() 调用 sepgsqlCheckBlobCreate(),在创建新的大对象之前立即调用。

当 SE-PostgreSQL 在运行时或构建时被禁用时,所有钩子都不会影响任何内容。换句话说,它完全像一个正常的 PostgreSQL 一样执行。

所有 SE-PostgreSQL 逻辑都封装在钩子后面,它可以最大限度地减少对原始 PostgreSQL 的影响并提供良好的可维护性。

用户空间访问向量缓存

当 SE-PostgreSQL 与内核中的 SELinux 通信时,它需要由于系统调用而进行上下文切换,但是,这基本上是一个繁重的操作,因此有必要减少系统调用次数,以最大限度地减少由于额外权限检查而导致的性能损失。特别是,一个查询可以在单个查询中获取大量元组,因此如果它对每个检查调用一个系统调用,它可能是不可接受的。

用户空间 avc(访问向量缓存)的想法可以最大限度地减少它。在 SELinux 安全模型中,大量对象往往会共享有限数量的安全上下文,并且相同的组合将对安全上下文和操作的相同组合返回相同的结果,因此我们可以在用户空间中缓存最近询问的模式。

SepgsqlDocFigure02.png

当客户端给出 SQL 查询时,SE-PostgreSQL 子系统被调用,通过在战略点上的钩子做出决定。首先,它检查用户空间 avc。如果找到,它可以立即返回结果。如果未找到给定的组合,则需要根据通信协议调用内核中的 SELinux。内核中的 SELinux 也具有类似的结构。它可以以很小的成本查找内核 avc,但从安全策略中查找条目是一个繁重的步骤(与 avc 查找相比)。

用户空间 avc 也具有一个很好的特性。它允许通过安全标识符查找哈希表,而不需要安全上下文的任何文本表示。正如我们稍后提到的,安全上下文的文本表示有一个整数标识符,称为安全 ID,它可以使用一个简单的宏从 HeadTuple 数据结构中获取。这意味着当 SE-PostgreSQL 在最频繁的路径上做出决定时,我们不需要将安全 ID 转换为相应的文本表示形式的安全上下文。

该协议要求以文本形式传递安全上下文,因此当用户空间 avc 未命中时,有必要转换安全 ID。但是,它的频率非常小。一般来说,超过 99% 的检查命中用户空间 avc。

安全上下文的管理

什么是安全上下文

安全上下文是一个简短的格式化文本,它抽象了在 SELinux 访问控制中标记的实体的所有属性。在启用了 SELinux 的系统上,操作系统管理的任何数据对象都用特定安全上下文标记。

例如, ls -Z显示文件安全上下文,如下所示。主要文件系统具有将单个文件与扩展属性 (xattr) 关联的功能,SELinux 利用此功能为每个文件分配特定的安全上下文。

[kaigai@saba ~]$ <b>ls -Z /var/</b>
drwxr-xr-x. root root system_u:object_r:acct_data_t:s0   account/
drwxr-xr-x. root root system_u:object_r:var_t:s0         cache/
drwxr-xr-x. root root system_u:object_r:cvs_data_t:s0    cvs/
drwxr-xr-x. root root system_u:object_r:var_t:s0         db/
drwxr-xr-x. root root system_u:object_r:var_t:s0         empty/
drwxr-xr-x. root root system_u:object_r:games_data_t:s0  games/
drwxrwx--T. root gdm  system_u:object_r:xserver_log_t:s0 gdm/
        - (snip) -

文件不仅仅是 SELinux 在其上分配安全上下文的对象。 ps -Zpstree -Z显示进程的安全上下文。Linux 内核为当前可用的安全内容提供了一个私有字段,SELinux 利用它来存储进程的安全上下文。

[kaigai@saba ~]$ <b>pstree -Z</b>
init(`system_u:system_r:init_t:s0')
 ├─auditd(`unconfined_u:system_r:auditd_t:s0')
 │  ├─audispd(`unconfined_u:system_r:audisp_t:s0')
 │  │  ├─sedispatch(`unconfined_u:system_r:audisp_t:s0')
 │  │  └─{audispd}(`unconfined_u:system_r:audisp_t:s0')
 │  └─{auditd}(`unconfined_u:system_r:auditd_t:s0')
 ├─bash(`system_u:system_r:initrc_t:s0')
 ├─httpd(`system_u:system_r:httpd_t:s0')
 │  ├─httpd(`system_u:system_r:httpd_t:s0')
 │  ├─httpd(`system_u:system_r:httpd_t:s0')
 │  └─httpd(`system_u:system_r:httpd_t:s0')
 ├─postgres(`system_u:system_r:postgresql_t:s0')
 │  ├─postgres(`system_u:system_r:postgresql_t:s0')
 │  ├─postgres(`system_u:system_r:postgresql_t:s0')
 │  └─postgres(`system_u:system_r:postgresql_t:s0')
 ├─smbd(`system_u:system_r:smbd_t:s0')
 │  ├─smbd(`system_u:system_r:smbd_t:s0')
 │  └─smbd(`system_u:system_r:smbd_t:s0')
        - (snip) -

安全上下文由四个用冒号字符分隔的字段组成。

安全上下文的示例

system_u:system_r:postgresql_t:s0               for PostgreSQL server process
system_u:object_r:shadow_t:s0                   for /etc/shadow file
unconfined_u:object_r:user_home_t:s0            for user home directory

第一个字段是 selinux 用户,第二个是角色,第三个是类型,当安全上下文分配给进程时也称为域,最后一个字段是范围。

SELinux 有几个安全模型(TE、MLS 和 RBAC),每个安全模型都会选择一个特定的字段并做出决定。只有当所有安全模型都允许时,SELinux 安全服务器才会回答给定的访问应该被允许。有关更多详细信息,请参阅 SELinux 概述

关键点是,安全上下文抽象了标记为特定安全上下文的对象的所有属性,SELinux 仅根据安全上下文做出决定,与任何其他因素无关。

这意味着 SE-PostgreSQL 也需要提供一种管理数据库对象安全上下文的机制,就像主要文件系统提供 xattr 功能一样。本节介绍 SE-PostgreSQL 如何管理数据库对象的安全上下文,以及为用户提供了哪些接口。

pg_security 系统目录之间的交互

典型的安全上下文是一个长度为几十字节的简短字符串。我们需要提供一种在数据库对象上分配特定安全上下文的机制,用于访问控制。PostgreSQL 将数据库对象管理为系统目录中的元组,因此我们可以将问题视为将元组与安全上下文关联起来的一种方式。

安全上下文有一个特点,大量对象往往会共享有限数量的安全上下文,因为它抽象了对象的全部安全属性,因此一组具有统一规则的对象可以共享相同的安全上下文。

安全上下文的大小并不大,但是元组的数量非常多,因此我们需要考虑尽可能紧凑地存储安全上下文的方式。

SepgsqlDocFigure03.png

pg_security系统目录提供一种功能,用于存储文本表示和对象标识符对。每个数据库对象都有一个安全标识符,它是该对象的标识符。 pg_security系统目录,而不是它的文本表示。该 HeapTupleHeaderData结构允许可变填充字段。安全标识符存储在该字段中,类似于存储在其中的对象标识符。这种设计可以减少存储安全属性的空间,并允许在没有字符串比较的情况下查找 用户空间 AVC

在导入/导出安全上下文时,安全标识符会自动从其文本表示形式转换/为其文本表示形式,就像它被用户视为文本一样。如果给定的文本表示形式不在该 pg_security上,它会隐式地插入到 pg_security作为一个新记录,其对象标识符被应用到新安全上下文的安全标识符。

security_label 系统列

元组的安全标识符不存储为常规字段,因此需要提供一种替代方式来访问它,例如oid系统列为用户提供了一种引用元组对象标识符的方式。

添加了一个名为security_label的新系统列。系统列是为每个关系(除了oid)隐式创建的,并且不会通过SELECT * FROM ...语句扩展。这些特性有助于与嵌入在应用程序软件中的现有查询兼容。

用户可以在security_label系统列作为SELECT语句中的字段,如下所示。它被声明为TEXT类型,客户端可以获得元组安全上下文的文本表示。它在内部从元组的安全标识符转换为pg_security系统目录上的对应安全上下文。

postgres=# SELECT security_label, * FROM drink ORDER BY id;
                 security_label                  | id | name  | price
-------------------------------------------------+----+-------+-------
 system_u:object_r:sepgsql_table_t               |  1 | water |   100
 system_u:object_r:sepgsql_ro_table_t:Classified |  2 | coke  |   120
 system_u:object_r:sepgsql_table_t:Classified    |  3 | juice |   130
 system_u:object_r:sepgsql_ro_table_t            |  4 | cofee |   180
(4 rows)

security_label系统列的一个特点是可写,虽然现有的系统列是只读的。它允许用户使用UPDATE语句更改元组的安全上下文,只要他们有足够的权限。

postgres=# UPDATE drink SET security_label =
               'system_u:object_r:sepgsql_secret_table_t' WHERE id in (1,4);
UPDATE 2
postgres=# SELECT security_label, * FROM drink ORDER BY id;
                 security_label                  | id | name  | price
-------------------------------------------------+----+-------+-------
 system_u:object_r:sepgsql_secret_table_t        |  1 | water |   100
 system_u:object_r:sepgsql_ro_table_t:Classified |  2 | coke  |   120
 system_u:object_r:sepgsql_table_t:Classified    |  3 | juice |   130
 system_u:object_r:sepgsql_secret_table_t        |  4 | cofee |   180
(4 rows)

我们还可以使用显式安全上下文插入新元组security_label系统列。当我们不提供任何安全上下文时,SE-PostgreSQL 会分配一个默认安全上下文。有关详细信息,请参见 默认安全上下文 部分。

postgres=# INSERT INTO drink (security_label, id, name, price)
               VALUES ('system_u:object_r:sepgsql_table_t:Secret', 5, 'beer', 280);
INSERT 16493 1
postgres=# SELECT security_label, * FROM drink ORDER BY id;
                 security_label                  | id | name  | price
-------------------------------------------------+----+-------+-------
 system_u:object_r:sepgsql_secret_table_t        |  1 | water |   100
 system_u:object_r:sepgsql_ro_table_t:Classified |  2 | coke  |   120
 system_u:object_r:sepgsql_table_t:Classified    |  3 | juice |   130
 system_u:object_r:sepgsql_secret_table_t        |  4 | cofee |   180
 system_u:object_r:sepgsql_table_t:Secret        |  5 | beer  |   280
(5 rows)

COPY语句也支持security_label系统目录。当用户显式指定列时,它允许导出/导入安全上下文。如果COPY FROM语句没有包含security_label的列列表,则所有新元组都将被标记为默认安全上下文。

SELECT INTOCREATE TABLE AS语句也支持可写系统列。当字段列表包含security_label时,其值将用于显式指定的安全上下文。

postgres=# SELECT 'system_u:object_r:sepgsql_table_t:s0:c' || id AS security_label, *
               INTO t FROM drink WHERE id in (2,4,6);
SELECT
postgres=# SELECT security_label, * FROM t;
             security_label              | id | name  | price
-----------------------------------------+----+-------+-------
 system_u:object_r:sepgsql_table_t:s0:c2 |  2 | coke  |   120
 system_u:object_r:sepgsql_table_t:s0:c4 |  4 | cofee |   180
 system_u:object_r:sepgsql_table_t:s0:c6 |  6 | sake  |   320
(3 rows)