Object-level audit logging in YSQL

Object audit logging logs statements that affect a particular relation. Only SELECT, INSERT, UPDATE, and DELETE commands are supported. TRUNCATE is not included in object audit logging.

Object audit logging is intended to be a finer-grained replacement for pgaudit.log = 'read, write'. As such, it may not make sense to use them in conjunction but one possible scenario would be to use session logging to capture each statement and then supplement that with object logging to get more detail about specific relations.

In YugabyteDB, object-level audit logging is implemented by reusing the PG role system. The pgaudit.role setting defines the role that will be used for audit logging. A relation ( TABLE, VIEW, etc.) will be audit logged when the audit role has permissions for the command executed or inherits the permissions from another role. This allows you to effectively have multiple audit roles even though there is a single master role in any context.

Object-level example

In this example, object audit logging is used to illustrate how a granular approach may be taken towards logging of SELECT and DML statements.

Setup

Before you start

The examples will run on any YugabyteDB universe.
To create a universe, see Set up YugabyteDB universe.

Using ysqlsh, connect to the database and enable the pgaudit extension on the YugabyteDB cluster as follows:

\c yugabyte yugabyte;
CREATE EXTENSION IF NOT EXISTS pgaudit;

Enable object auditing

Set pgaudit.role to auditor and grant SELECT and UPDATE privileges on the account table. Any SELECT or UPDATE statements on the account table will now be logged. Note that logging on the account table is controlled by column-level permissions, while logging on the account_role_map table is table-level.

CREATE ROLE auditor;

SET pgaudit.role = 'auditor';

Create a table

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;

Verify output

You should see the following output in the logs:

2020-11-09 19:46:42.633 UTC [3944] LOG:  AUDIT: OBJECT,1,1,READ,SELECT,TABLE,public.account,"select password
          from account;",<not logged>
2020-11-09 19:47:02.531 UTC [3944] LOG:  AUDIT: OBJECT,2,1,WRITE,UPDATE,TABLE,public.account,"update account
           set password = 'HASH2';",<not logged>
I1109 19:47:09.418772  3944 ybccmds.c:453] Creating Table yugabyte.public.account_role_map
I1109 19:47:09.418812  3944 pg_ddl.cc:310] PgCreateTable: creating a transactional table: yugabyte.account_role_map
I1109 19:47:09.538868  3944 table_creator.cc:307] Created table yugabyte.account_role_map of type PGSQL_TABLE_TYPE
2020-11-09 19:47:22.752 UTC [3944] LOG:  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>
2020-11-09 19:47:22.752 UTC [3944] LOG:  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>