Как в PostgreSQL журналировать DDL команды в таблицу БД? (How log DDL queries to table in PostgreSQL?)
DDL команды обычно логируются в общем большом файловом журнале. У этого подхода следующие недостатки:
- Там полно SELECT и DML команд, отыскать там DDL непросто. DDL запросы ещё могут сохраняться в Graylog, но отыскать там DDL тоже непросто.
- Там нет чётко выделенного идентификатора объекта БД и события, только тело DDL команды.
- Файловые журналы обычно хранятся несколько недель и потом автоматически удаляются.
Логирование DDL команд в отдельной таблице БД позволяет:
-
Усилить безопасность БД:
- сохранять легальные изменения схемы БД администраторами БД (DBA), включая ручные (неавтоматические) миграции БД
- выявить нелегальные изменения схемы БД: прямое выполнения DDL команд мимо миграторов БД; SQL injection
-
Сохранять следующие события:
- Создание, удаление, модификация объектов БД (schema, table, view, column, sequence, index, type, function, procedure, domain), включая изменение владельца. При удалении сохраняется информация о всех удалённых зависимых объектах.
- Наделение и отзыв прав доступа к объектам БД
-
Иметь возможность получить и предоставить информацию по запросу коллег при возникновении инцидентов, включая нестабильность работы БД. При этом будет возможность получить точную дату-время начала и окончания, а так же длительность выполнения DDL запросов в транзакции.
-
Знать дату-время создания и обновления объектов БД (к сожалению, в БД такая информация в системных таблицах отсутствует), чтобы потом автоматически их удалять через 1 месяц после создания из схем:
migration
— здесь хранятся таблицы с данными для возможности для отката ранее накаченных миграций БДunused
— здесь временно хранятся неиспользуемые и уже ненужные объекты БД перед их окончательным удалением
-
Выявить DDL запросы в бизнес-логике приложения (такие запросы должны выполняться под отдельным пользователем?).
-
Для отдела информационной безопасности появляется возможность интеграции с SIEM в качестве источника данных.
Не журналируется:
- Создание, удаление, модификация пользователей и ролей, включая изменение атрибутов и пароля. Почему так.
- Событие
ddl_command_start
для запросовDROP INDEX CONCURRENTLY …
. Почему так. - До PostgreSQL 17-й версии не журналируется команда
REINDEX
. См. https://pgpedia.info/e/event-trigger.html
select tag,
event,
count(*) as total
from db_audit.ddl_log
group by 1, 2
order by 1, 2;
tag | event | total |
---|---|---|
ALTER FUNCTION | ddl_command_start | 15 |
ALTER FUNCTION | ddl_command_end | 15 |
ALTER SCHEMA | ddl_command_start | 2 |
ALTER SCHEMA | ddl_command_end | 2 |
ALTER SEQUENCE | ddl_command_start | 4 |
ALTER SEQUENCE | ddl_command_end | 4 |
ALTER TABLE | ddl_command_start | 79 |
ALTER TABLE | ddl_command_end | 79 |
ALTER TABLE | sql_drop | 4 |
ALTER TYPE | ddl_command_start | 6 |
ALTER TYPE | ddl_command_end | 6 |
ALTER VIEW | ddl_command_start | 2 |
ALTER VIEW | ddl_command_end | 2 |
COMMENT | ddl_command_start | 167 |
COMMENT | ddl_command_end | 167 |
CREATE FUNCTION | ddl_command_start | 34 |
CREATE FUNCTION | ddl_command_end | 34 |
CREATE INDEX | ddl_command_start | 53 |
CREATE INDEX | ddl_command_end | 55 |
CREATE PROCEDURE | ddl_command_start | 5 |
CREATE PROCEDURE | ddl_command_end | 5 |
CREATE SCHEMA | ddl_command_start | 2 |
CREATE SCHEMA | ddl_command_end | 2 |
CREATE TABLE | ddl_command_start | 23258 |
CREATE TABLE | ddl_command_end | 13831 |
CREATE TABLE AS | ddl_command_start | 8 |
CREATE TABLE AS | ddl_command_end | 8 |
CREATE TRIGGER | ddl_command_start | 20 |
CREATE TRIGGER | ddl_command_end | 20 |
CREATE TYPE | ddl_command_start | 2 |
CREATE TYPE | ddl_command_end | 2 |
CREATE VIEW | ddl_command_start | 6 |
CREATE VIEW | ddl_command_end | 8 |
DROP FUNCTION | ddl_command_start | 3 |
DROP FUNCTION | sql_drop | 3 |
DROP INDEX | ddl_command_start | 10 |
DROP INDEX | sql_drop | 10 |
DROP PROCEDURE | ddl_command_start | 1 |
DROP PROCEDURE | sql_drop | 1 |
DROP SCHEMA | ddl_command_start | 1 |
DROP SCHEMA | sql_drop | 1 |
DROP TABLE | ddl_command_start | 32167 |
DROP TABLE | sql_drop | 132 |
DROP TRIGGER | ddl_command_start | 16 |
DROP TRIGGER | sql_drop | 1 |
DROP TYPE | ddl_command_start | 2 |
DROP TYPE | sql_drop | 2 |
DROP VIEW | ddl_command_start | 1 |
DROP VIEW | sql_drop | 4 |
GRANT | ddl_command_start | 5 |
GRANT | ddl_command_end | 5 |
REFRESH MATERIALIZED VIEW | ddl_command_start | 17534 |
REFRESH MATERIALIZED VIEW | ddl_command_end | 17534 |
select tag,
lower(object_type) as object_type,
count(*) as total
from db_audit.ddl_log
where event != 'ddl_command_start'
group by 1, 2
order by 1, 2;
tag | object_type | total |
---|---|---|
ALTER FUNCTION | function | 15 |
ALTER SCHEMA | schema | 2 |
ALTER SEQUENCE | sequence | 4 |
ALTER TABLE | table | 77 |
ALTER TABLE | table column | 3 |
ALTER TABLE | table constraint | 3 |
ALTER TYPE | type | 6 |
ALTER VIEW | view | 2 |
COMMENT | function | 5 |
COMMENT | procedure | 3 |
COMMENT | schema | 2 |
COMMENT | table | 13 |
COMMENT | table column | 132 |
COMMENT | type | 7 |
COMMENT | view | 5 |
CREATE FUNCTION | function | 34 |
CREATE INDEX | index | 55 |
CREATE PROCEDURE | procedure | 5 |
CREATE SCHEMA | schema | 2 |
CREATE TABLE | index | 15 |
CREATE TABLE | sequence | 28 |
CREATE TABLE | table | 13989 |
CREATE TABLE AS | table | 8 |
CREATE TRIGGER | trigger | 20 |
CREATE TYPE | type | 2 |
CREATE VIEW | view | 8 |
DROP FUNCTION | function | 3 |
DROP INDEX | index | 10 |
DROP PROCEDURE | procedure | 1 |
DROP SCHEMA | schema | 1 |
DROP TABLE | default value | 6 |
DROP TABLE | index | 34 |
DROP TABLE | sequence | 2 |
DROP TABLE | table | 18 |
DROP TABLE | table constraint | 10 |
DROP TABLE | toast table | 10 |
DROP TABLE | trigger | 4 |
DROP TABLE | type | 48 |
DROP TRIGGER | trigger | 1 |
DROP TYPE | type | 2 |
DROP VIEW | rule | 1 |
DROP VIEW | type | 2 |
DROP VIEW | view | 1 |
GRANT | sequence | 1 |
GRANT | table | 4 |
REFRESH MATERIALIZED VIEW | materialized view | 17536 |