
pgaudit v19beta3
PostgreSQL 审计扩展
pgAudit
开源 PostgreSQL 审计日志
简介
PostgreSQL 审计扩展(pgAudit)通过标准的 PostgreSQL 日志设施提供详细的会话和/或对象审计日志。
pgAudit 的目标是为 PostgreSQL 用户提供生成审计日志的能力,这些日志通常是满足政府、金融或 ISO 认证要求所必需的。
审计是通常由独立机构对个人或组织账目进行的官方检查。pgAudit 收集的信息应恰当地称为审计跟踪或审计日志。本文档中使用术语“审计日志”。
为什么选择 pgAudit?
基本的语句日志可以通过标准日志设施中的 log_statement = all 提供。这对于监控和其他用途是可以接受的,但无法提供审计通常所需的详细程度。仅仅拥有针对数据库执行的所有操作的列表是不够的。还必须能够找到审计员感兴趣的特定语句。标准日志设施显示的是用户请求的内容,而 pgAudit 则专注于数据库满足请求期间所发生事件的细节。
例如,审计员可能希望验证某个特定表是在记录在案的维护窗口内创建的。这看起来可能是 grep 的简单工作,但如果看到如下(故意混淆的)示例呢:
DO $$
BEGIN
EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
END $$;
标准日志会给出:
LOG: statement: DO $$
BEGIN
EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
END $$;
可见,在表是动态创建的情况下,找到感兴趣的表可能需要一些代码知识。这并不理想,因为最好只按表名进行搜索。这正是 pgAudit 发挥作用的地方。对于相同的输入,它会在日志中产生如下输出:
AUDIT: SESSION,33,1,FUNCTION,DO,,,"DO $$
BEGIN
EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
END $$;"
AUDIT: SESSION,33,2,DDL,CREATE TABLE,TABLE,public.important_table,CREATE TABLE important_table (id INT)
不仅 DO 块被记录,而且子语句 2 包含 CREATE TABLE 的完整文本以及语句类型、对象类型和全限定名称,便于搜索。
在记录 SELECT 和 DML 语句时,可以将 pgAudit 配置为对语句中引用的每个关系单独记录一条条目。无需解析即可找到所有涉及特定表的语句。事实上,目标是将语句文本主要提供给深层取证使用,而审计不应依赖它。
使用注意事项
根据设置,pgAudit 可能会产生海量的日志。请务必确定环境中到底需要审计记录哪些内容,以避免记录过多。
例如,在 OLAP 环境中,对大型事实表的插入操作进行审计日志记录可能不是明智之举。日志文件的大小很可能是插入数据实际大小的许多倍,因为日志文件以文本形式表示。由于日志通常存储在操作系统中,这可能导致磁盘空间很快耗尽。在无法将审计日志限制到某些表的情况下,请务必在测试时评估性能影响,并为日志卷分配充足的空间。这在 OLTP 环境中也可能如此。即使插入量没有那么高,审计日志记录的性能影响仍可能显著影响延迟。
要限制 SELECT 和 DML 语句中被审计记录的关系数量,请考虑使用对象审计日志(请参阅对象审计)。对象审计日志允许选择要记录的关系,从而减少总体日志量。但是,当添加新关系时,必须将其显式添加到对象审计日志中。在这种情况下,一种可编程的解决方案——将指定表排除在日志记录之外,而包含所有其他表——可能是一个不错的选择。
PostgreSQL 版本兼容性
pgAudit 支持 PostgreSQL 14 或更高版本。
为了支持每个 PostgreSQL 版本中引入的新功能,pgAudit 为每个 PostgreSQL 主版本(目前为 PostgreSQL 14 - 19)维护一个单独的分支,其维护方式将与 PostgreSQL 项目类似。
除错误修复外,稳定分支不允许进行进一步开发。任何新开发(如果有)将严格针对下一个未发布的 PostgreSQL 主版本。
pgAudit 版本与 PostgreSQL 主版本的对应关系如下:
-
pgAudit v19.X 旨在支持 PostgreSQL 19。
-
pgAudit v18.X 旨在支持 PostgreSQL 18。
-
pgAudit v17.X 旨在支持 PostgreSQL 17。
-
pgAudit v16.X 旨在支持 PostgreSQL 16。
-
pgAudit v1.7.X 旨在支持 PostgreSQL 15。
-
pgAudit v1.6.X 旨在支持 PostgreSQL 14。
编译与安装
pgAudit 可以使用 PGXS 针对已安装且带有开发包的 PostgreSQL 进行编译。以下说明应适用于大多数类 Unix 操作系统。
克隆 pgAudit 扩展:
git clone https://github.com/pgaudit/pgaudit.git
切换到 pgAudit 目录:
cd pgaudit
检出 REL_19_STABLE 分支(请注意,对于未发布的 PostgreSQL 版本,稳定分支可能不存在):
git checkout REL_19_STABLE
构建并安装 pgAudit:
make install USE_PGXS=1 PG_CONFIG=/usr/pgsql-19/bin/pg_config
测试和开发说明可在 test 中找到。
设置
只有超级用户才能修改设置。允许普通用户更改其设置将违背审计日志的意义。
设置可以在全局范围(在 postgresql.conf 中或使用 ALTER SYSTEM ... SET)、数据库级别(使用 ALTER DATABASE ... SET)或角色级别(使用 ALTER ROLE ... SET)中指定。请注意,设置不会通过正常的角色继承进行继承,并且 SET ROLE 不会更改用户的 pgAudit 设置。这是角色系统的限制,并非 pgAudit 固有的限制。
pgAudit 扩展必须在 shared_preload_libraries 中加载。否则,加载时会引发错误,并且不会进行任何审计日志记录。
此外,必须在设置 pgaudit.log 之前调用 CREATE EXTENSION pgaudit,以确保 pgaudit 功能正常。该扩展会安装事件触发器,为 DDL 添加额外的审计。没有安装该扩展,pgAudit 也能工作,但 DDL 语句将不包含对象类型和名称的信息。
如果 pgaudit 扩展被删除并需要重新创建,则必须首先取消设置 pgaudit.log,否则将引发错误。
pgaudit.log
指定会话审计日志将记录哪些类别的语句。可能的值为:
-
READ:当源是关系或查询时的
SELECT和COPY。 -
WRITE:当目标是关系时的
INSERT、UPDATE、DELETE、TRUNCATE和COPY。 -
FUNCTION:函数调用和
DO块。 -
ROLE:与角色和权限相关的语句:
GRANT、REVOKE、CREATE/ALTER/DROP ROLE。 -
DDL:未包含在
ROLE类别中的所有DDL。 -
MISC:其他杂项命令,例如
DISCARD、FETCH、CHECKPOINT、VACUUM、SET。 -
MISC_SET:其他杂项
SET命令,例如SET ROLE。 -
ALL:包含以上所有类别。
可以使用逗号分隔的列表提供多个类别,并且可以通过在类别前加上 - 号来减去类别(请参阅会话审计日志)。
默认值为 none。
pgaudit.log_catalog
指定当语句中的所有关系都在 pg_catalog 中时,应启用会话日志记录。禁用此设置将减少 psql 和 PgAdmin 等大量查询目录的工具在日志中产生的噪音。
默认值为 on。
pgaudit.log_client
指定日志消息是否对客户端进程(如 psql)可见。此设置通常应保持禁用,但可能对调试或其他目的有用。
请注意,仅当 pgaudit.log_client 为 on 时,pgaudit.log_level 才会启用。
默认值为 off。
pgaudit.log_level
指定将用于日志条目的日志级别(有关有效级别,请参阅消息严重性级别),但请注意,不允许使用 ERROR、FATAL 和 PANIC)。此设置用于回归测试,也可能对最终用户的测试或其他目的有用。
请注意,仅当 pgaudit.log_client 为 on 时,pgaudit.log_level 才会启用;否则将使用默认值。
默认值为 log。
pgaudit.log_parameter
指定审计日志应包含随语句传递的参数。当存在参数时,它们将以 CSV 格式包含在语句文本之后。
默认值为 off。
pgaudit.log_parameter_max_size
指定长度超过此设置(以字节为单位)的参数值不应记录,而应替换为 <long param suppressed>。此设置以字节而非字符为单位,因此不考虑文本参数编码中的多字节字符。如果 log_parameter 为 off,此设置无效。如果此设置为 0(默认值),则无论长度如何,所有参数都会被记录。
默认值为 0。
pgaudit.log_relation
指定会话审计日志是否应为 SELECT 或 DML 语句中引用的每个关系(TABLE、VIEW 等)创建单独的日志条目。这是在不使用对象审计日志的情况下进行详尽日志记录的有用快捷方式。
默认值为 off。
pgaudit.log_rows
指定审计日志是否应包含语句检索或影响的行数。启用后,行数字段将包含在参数字段之后。
默认值为 off。
pgaudit.log_statement
指定日志记录是否包含语句文本和参数(如果已启用)。根据需求,审计日志可能不需要这些内容,而且这会使日志更简洁。
默认值为 on。
pgaudit.log_statement_once
指定日志记录是仅将语句文本和参数包含在某个语句/子语句组合的第一条日志条目中,还是包含在每条条目中。启用此设置将减少日志的冗长程度,但可能更难确定生成某条日志条目的语句,尽管语句/子语句对连同进程 ID 应足以识别先前条目中记录的语句文本。
默认值为 off。
pgaudit.role
指定用于对象审计日志的主角色。可以通过将多个审计角色授予主角色来定义它们。这允许多个组负责审计日志的不同方面。
没有默认值。
会话审计日志
会话审计日志提供用户在后台执行的所有语句的详细日志。
配置
会话日志通过 pgaudit.log 设置启用。
为所有 DML 和 DDL 启用会话日志,并记录 DML 语句中的所有关系:
set pgaudit.log = 'write, ddl';
set pgaudit.log_relation = on;
为除 MISC 之外的所有命令启用会话日志,并将审计日志消息提升为 NOTICE:
set pgaudit.log = 'all, -misc';
set pgaudit.log_level = notice;
示例
在此示例中,会话审计日志用于记录 DDL 和 SELECT 语句。请注意,由于未启用 WRITE 类别,因此插入语句未被记录。
SQL:
set pgaudit.log = 'read, ddl';
create table account
(
id int,
name text,
password text,
description text
);
insert into account (id, name, password, description)
values (1, 'user1', 'HASH1', 'blah, blah');
select *
from account;
日志输出:
AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,create table account
(
id int,
name text,
password text,
description text
);,<not logged>
AUDIT: SESSION,2,1,READ,SELECT,,,select *
from account,,<not logged>
对象审计日志
对象审计日志记录影响特定关系的语句。仅支持 SELECT、INSERT、UPDATE 和 DELETE 命令。TRUNCATE 不包含在对象审计日志中。
对象审计日志旨在作为 pgaudit.log = 'read, write' 的更细粒度的替代方案。因此,同时使用它们可能没有意义,但一种可能的场景是使用会话日志捕获每条语句,然后辅以对象日志来获取有关特定关系的更多详细信息。
配置
对象级审计日志通过角色系统实现。pgaudit.role 设置定义了将用于审计日志的角色。当审计角色对执行的命令拥有权限或继承了另一个角色的权限时,某个关系(TABLE、VIEW 等)将被审计记录。这使您可以有效地拥有多个审计角色,即使在任何上下文中都只有一个主角色。
将 pgaudit.role 设置为 auditor,并授予对 account 表的 SELECT 和 DELETE 权限。现在,对 account 表的任何 SELECT 或 DELETE 语句都将被记录:
set pgaudit.role = 'auditor';
grant select, delete
on public.account
to auditor;
示例
在此示例中,对象审计日志用于说明如何以细粒度的方式记录 SELECT 和 DML 语句。请注意,对 account 表的日志记录由列级权限控制,而对 account_role_map 表的日志记录是表级的。
SQL:
set pgaudit.role = 'auditor';
create table account
(
id int,
name text,
password text,
description text
);
grant select (password)
on public.account
to auditor;
select id, name
from account;
select password
from account;
grant update (name, password)
on public.account
to auditor;
update account
set description = 'yada, yada';
update account
set password = 'HASH2';
create table account_role_map
(
account_id int,
role_id int
);
grant select
on public.account_role_map
to auditor;
select account.password,
account_role_map.role_id
from account
inner join account_role_map
on account.id = account_role_map.account_id
日志输出:
AUDIT: OBJECT,1,1,READ,SELECT,TABLE,public.account,select password
from account,<not logged>
AUDIT: OBJECT,2,1,WRITE,UPDATE,TABLE,public.account,update account
set password = 'HASH2',<not logged>
AUDIT: OBJECT,3,1,READ,SELECT,TABLE,public.account,select account.password,
account_role_map.role_id
from account
inner join account_role_map
on account.id = account_role_map.account_id,<not logged>
AUDIT: OBJECT,3,1,READ,SELECT,TABLE,public.account_role_map,select account.password,
account_role_map.role_id
from account
inner join account_role_map
on account.id = account_role_map.account_id,<not logged>
格式
审计条目写入标准日志设施,并以逗号分隔格式包含以下列。仅当移除每条日志条目中的日志行前缀部分时,输出才是符合 CSV 格式的。
-
AUDIT_TYPE -
SESSION或OBJECT。 -
STATEMENT_ID - 本次会话中唯一的语句 ID。每个语句 ID 代表一次后台调用。即使某些语句未被记录,语句 ID 也是连续的。当记录多个关系时,一个语句 ID 可能对应多个条目。
-
SUBSTATEMENT_ID - 主语句中每个子语句的顺序 ID。例如,从查询中调用函数。即使某些子语句未被记录,子语句 ID 也是连续的。当记录多个关系时,一个子语句 ID 可能对应多个条目。
-
CLASS - 例如
READ、ROLE(请参阅 pgaudit.log)。 -
COMMAND - 例如
ALTER TABLE、SELECT。 -
OBJECT_TYPE -
TABLE、INDEX、VIEW等。可用于SELECT、DML和大多数DDL语句。 -
OBJECT_NAME - 全限定对象名称(例如 public.account)。可用于
SELECT、DML和大多数DDL语句。 -
STATEMENT - 在后台执行的语句。
-
PARAMETER - 如果设置了
pgaudit.log_parameter,则此字段将包含带引号 CSV 格式的语句参数;如果没有参数,则为<none>。否则,该字段为<not logged>。
使用 log_line_prefix 添加满足审计日志要求所需的任何其他字段。一个典型的日志行前缀可能是 '%m %u %d [%p]: ',它将为每条审计日志提供日期/时间、用户名、数据库名和进程 ID。
注意事项
审计日志记录是尽力而为的,并且不是事务性的。pgAudit 通过标准的 PostgreSQL 日志设施写入审计条目,该设施不会将每条条目与其产生的事务同步刷新到磁盘,也不会将写入错误传播回会话。无法确保已提交的事务一定会有对应的审计日志条目。如果服务器崩溃或断电,或者日志目标不可用(例如日志卷已满),并且这些都发生在事务提交之后、其审计条目被持久化写入之前,则这些条目可能会丢失。相反,语句在执行时即被记录,因此即使其事务后续回滚,也可能会写入一条条目。
对象重命名将以重命名后的名称记录。例如,重命名表将产生以下结果:
ALTER TABLE test RENAME TO test2;
AUDIT: SESSION,36,1,DDL,ALTER TABLE,TABLE,public.test2,ALTER TABLE test RENAME TO test2,<not logged>
一条命令可能被记录多次。例如,当创建表时指定了主键,该主键的索引将被独立记录,并且在创建条目下还会为该索引生成另一条审计日志。不过,这些多个条目将包含在同一个语句 ID 内。
Autovacuum 和 Autoanalyze 不会被记录。
在事务进入中止状态后执行的语句不会被审计记录。但是,导致错误的语句以及在中止事务中执行的任何后续语句都将由标准日志设施记录为 ERROR。
无法使用 pgAudit 可靠地审计超级用户。一种解决方案是限制对超级用户帐户的访问,并在需要时使用 set_user 扩展来提升权限。
作者
PostgreSQL 审计扩展基于由 Simon Riggs、Abhijit Menon-Sen 和 Ian Barwick 编写并作为扩展提交给 PostgreSQL 核心的 2ndQuadrant pgaudit 项目。Crunchy Data 的 David Steele 进行了额外的开发。