A convenience feature committed for PostgreSQL 14

PostgreSQL has long offered no built-in way to grant a user read (or write) access to every table at once. The pg_read_all_data role, now committed to the repository, removes that gap. The need for it is not theoretical: where a data model cannot be fully anticipated and access requirements change faster than the privilege design keeps up, operators have often solved the problem by handing out superusers.

1

2

3

4

5

6

7

8

9

10

commit 6c3ffd697e2242f5497ea4b40fffc8f6f922ff60

Author: Stephen Frost <[email protected]>

Date:   Mon Apr 5 13:42:52 2021 -0400

    Add pg_read_all_data and pg_write_all_data roles

    ….

    Reviewed-by: Georgios Kokolatos

    Discussion: https://postgr.es/m/[email protected]

Why avoiding superusers matters

Superuser access is not merely a data-exposure question. A superuser can execute commands at the OS level, so the impact of a single overly privileged account can reach the database server, and from there the wider company network. Backups or low data sensitivity do not change that risk profile.

Granting it

Usage is a single GRANT to a trustworthy user of your choice, with the parallel pg_write_all_data covering the write case. The issuing session must itself still be a superuser.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

## NB! Assuming logged in as superuser on a devel build

# Let’s first create a test table

CREATE TABLE data (data jsonb);

# And a plain mortal login user called “bob”

CREATE USER bob;

# Let’s verify that Bob has no privileges to the “data” table.

# NB! This is a “psql” command, not SQL...

dp+ data

                            Access privileges

Schema | Name | Type  | Access privileges | Column privileges | Policies

--------+------+-------+-------------------+-------------------+----------

public | data | table |                   |                   |

(1 row)

# Let’s a test row

INSERT INTO data SELECT '{'hello': 'world'}';

# And try to look at it as Bob

SET ROLE TO bob; /* acting as Bob from now on … */

SELECT * FROM data;

ERROR:  permission denied for table data

# Time to check out that new functionality…

RESET ROLE; /* back to superuser again */

GRANT pg_read_all_data TO bob;

# Let’s try again as Bob

SET ROLE TO bob;

# Voila...

SELECT * FROM data;

        data        

--------------------

{'hello': 'world'}

(1 row)

Options until v14 ships

The commit exists, but release is still many months out. Until then, the requirement can be met with existing means.

  • The cleanest approach is a role hierarchy plus ALTER DEFAULT PRIVILEGES. A meta-role such as appdb_reader is automatically granted SELECT on every table created afterwards, and that role is then assigned to the login user that needs it.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

CREATE ROLE appdb_reader;

CREATE ROLE data_analyst_role IN ROLE appdb_reader;

CREATE USER alice;

GRANT data_analyst_role TO alice;

-- NB! You need to do this for all schemas!

ALTER DEFAULT PRIVILEGES IN SCHEMA public

  GRANT SELECT ON TABLES TO appdb_reader;

CREATE TABLE data(id INT);

SET ROLE TO alice;

/* Should work automagically... */

TABLE data;

  • A quick fix suits static schemas where a hierarchy seems too much work, with the caveat that tables created later still need their own grant.

1

GRANT SELECT ON ALL TABLES IN SCHEMA public TO appdb_reader;

  • For the more daring: confine superuser access to a replica. A separate replication machine is set up, and access to the real HA nodes is blocked at the pg_hba.conf level, so a compromised user cannot damage operations. Heavy read queries may require configuration tuning to avoid replication conflicts.

Limits and design considerations

Because the grant is coarse, it can encourage a less granular database design. Understanding what it implies matters before adopting it, and for more complex projects the established role system remains the better fit. In an enterprise setting, fine-grained access can be the difference between a simple GRANT and an expensive redesign of the whole schema.

pg_read_all_data: The new built-in PostgreSQL roles next to their older colleagues
The new built-in PostgreSQL roles next to their older colleagues.