-- pg_command_fw: ProcessUtility hook firewall for DDL/utility commands.
-- To activate for all connections, add to postgresql.conf:
--   shared_preload_libraries = 'pg_command_fw'

CREATE SCHEMA IF NOT EXISTS command_fw;

-- Audit log for all intercepted DDL/utility commands.
--
-- NOTE: for *blocked* commands the INSERT is performed before ERROR is raised,
-- so it will be rolled back when the current transaction aborts.  The server log
-- (LOG: blocked ...) is the authoritative record for blocked events.
-- Allowed commands commit normally and their rows persist here.
CREATE TABLE command_fw.audit_log (
    id                bigserial   NOT NULL,
    -- clock_timestamp() captures the actual wall-clock time; now() would give
    -- the transaction start time, which is the same row for every statement in
    -- a multi-statement transaction.
    ts                timestamptz NOT NULL DEFAULT clock_timestamp(),
    -- Both user fields are recorded because SET ROLE makes them diverge:
    -- session_user_name is who actually authenticated, current_user_name is the
    -- effective role at the time of the command.
    session_user_name text        NOT NULL,
    current_user_name text        NOT NULL,
    query_text        text        NOT NULL,
    -- Command category: 'TRUNCATE' | 'DROP_TABLE' | 'ALTER_SYSTEM' | 'LOAD' | 'COPY_PROGRAM'
    --                   | 'COPY' | 'READ_FILE' | 'STAT_FILE'
    command_type      text        NOT NULL,
    -- For DROP_TABLE: the production schema that triggered the block (NULL otherwise).
    target_schema     text,
    -- For LOAD: the library filename.  NULL for other command types.
    target_object     text,
    -- inet_client_addr() returns NULL for local (Unix-socket) connections.
    client_addr       inet,
    application_name  text,
    blocked           bool        NOT NULL,
    -- NULL when not blocked; one of: 'role_listed', 'truncate_non_superuser',
    -- 'drop_production_table', 'alter_system', 'load', 'copy_program', 'copy',
    -- 'read_file' when blocked.
    block_reason      text,
    PRIMARY KEY (id)
);

-- Index for time-range queries and dashboards.
CREATE INDEX ON command_fw.audit_log (ts);
-- Index for per-user audits.
CREATE INDEX ON command_fw.audit_log (current_user_name);
-- Index for per-command-type queries.
CREATE INDEX ON command_fw.audit_log (command_type);
-- Partial index: fast scan of blocked-only events (typically a small fraction).
CREATE INDEX ON command_fw.audit_log (ts) WHERE blocked;

-- Lock down the schema and table; superusers can explicitly grant SELECT to
-- monitoring roles as needed.
REVOKE ALL ON SCHEMA command_fw FROM PUBLIC;
REVOKE ALL ON command_fw.audit_log FROM PUBLIC;
