
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;