DB Internals AtlasPostgreSQL 18.6 Home About

Connections and security

Roles and privileges

Every user and group in PostgreSQL is a role. A query is allowed only if the role, directly or through roles it belongs to, holds the right privileges on each object it touches, and row-level security may then hide rows.

In plain words

PostgreSQL has one concept for both users and groups: the role. A role that can log in is what we call a user; a role that others are members of works as a group. Members normally inherit the group's privileges.

Every object (database, schema, table, function…) has an owner. Ownership carries powerful object-management rights, while other roles need privileges handed out with GRANT and removed with REVOKE. Some operations still require superuser or another special role/privilege. To read a table through normal name resolution you typically need database CONNECT, schema USAGE, and table SELECT.

On top of object privileges, row-level security (RLS) policies can restrict which rows a role may read or modify. Policy expressions act like additional row-security qualifications, with important owner, BYPASSRLS and superuser rules. Pick a user below and follow the checks.

For DBAs and operators

Roles and membership

CREATE ROLE analyst NOLOGIN;
CREATE ROLE alice LOGIN PASSWORD '…';
GRANT analyst TO alice;                      -- INHERIT TRUE by default
GRANT analyst TO dave WITH INHERIT FALSE;    -- must SET ROLE analyst

Since PostgreSQL 16 membership has per-grant options INHERIT, SET and ADMIN, and CREATEROLE no longer lets a role manage roles it was not granted ADMIN on. Superusers (and the bootstrap role) skip all privilege checks, including RLS; BYPASSRLS skips only RLS.

Privileges

Table: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, MAINTAIN (MAINTAIN is new in PostgreSQL 17: VACUUM, ANALYZE, REINDEX, CLUSTER, REFRESH MATERIALIZED VIEW, LOCK). Schema: USAGE, CREATE. Database: CONNECT, CREATE, TEMP. \dp shows ACLs, e.g. postgres=arwdDxtm/postgres (letters for each privilege, m = MAINTAIN) and analyst=r/postgres.

Defaults that surprise people

  • PUBLIC (everyone) gets CONNECT and TEMP on new databases and EXECUTE on new functions.
  • Since PostgreSQL 15 PUBLIC no longer has CREATE on the public schema, and the schema is owned by pg_database_owner. Apps that silently created tables there now fail with ERROR: permission denied for schema public.
  • GRANT … ON ALL TABLES IN SCHEMA only covers existing tables. For future ones use ALTER DEFAULT PRIVILEGES [FOR ROLE creator] IN SCHEMA sales GRANT SELECT ON TABLES TO analyst; it applies to objects created by that creator role only.
  • Only the owner (or a member of the owning role) can ALTER/DROP: ERROR: must be owner of table orders. Use a group role as owner in shared environments.

Predefined roles

pg_read_all_data / pg_write_all_data (14+), pg_monitor, pg_read_all_stats, pg_read_all_settings, pg_signal_backend, pg_checkpoint (15+), pg_use_reserved_connections and pg_create_subscription (16+), pg_maintain (17+: MAINTAIN on all relations), pg_signal_autovacuum_worker (18), pg_read_server_files, pg_write_server_files, pg_execute_server_program. Granting these is much safer than handing out superuser.

Row-level security

ALTER TABLE sales.orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY own_rows ON sales.orders
  USING (sales_rep = current_user);

With RLS enabled and no matching policy, a role sees no rows (no error). Owners are exempt unless FORCE ROW LEVEL SECURITY. pg_read_all_data does not bypass RLS. Inserting a row the policy would hide fails with ERROR: new row violates row-level security policy for table "orders". pg_dump sets row_security = off and errors out rather than dump partial data.

SELECT r.rolname, m.rolname AS member_of, am.inherit_option
  FROM pg_auth_members am
  JOIN pg_roles r ON r.oid = am.member
  JOIN pg_roles m ON m.oid = am.roleid;
SELECT has_table_privilege('alice', 'sales.orders', 'SELECT');

For developers: inside the source

Where each check happens

login     InitializeSessionUserId()   utils/init/miscinit.c   rolcanlogin
          CheckMyDatabase()           utils/init/postinit.c   ACL_CONNECT
parse     RangeVarGetRelidExtended()
            LookupExplicitNamespace() catalog/namespace.c     ACL_USAGE
rewrite   fireRIRrules()
            get_row_security_policies()  rewrite/rowsecurity.c
execute   InitPlan() → ExecCheckPermissions()   executor/execMain.c
            ExecCheckOneRelPerms() → pg_class_aclmask()
              pg_class_aclmask_ext() → aclmask()

Unqualified names never fail on schema USAGE: the search_path lookup simply skips schemas the role cannot use. Row-level security is decided by check_enable_rls() (rls.c): tables without RLS give RLS_NONE; superusers and BYPASSRLS roles (has_bypassrls_privilege()) and the owner without FORCE ROW LEVEL SECURITY give RLS_NONE_ENV (no policies, but the plan depends on the user); otherwise RLS_ENABLED and the policy expressions are prepended to the range table entry's securityQuals.

Privilege bits and ACLs

  • AclMode is a uint64 bitmask (ACL_SELECT, ACL_INSERT … ACL_MAINTAIN in nodes/parsenodes.h); grant options live in the upper half. An ACL is an array of AclItem {ai_grantee, ai_grantor, ai_privs}, printed with the letters of ACL_ALL_RIGHTS_STR = "arwdDxtXUCTcsAm" (utils/acl.h).
  • A NULL relacl means "default": acldefault() gives the owner all privileges. The owner can revoke them from itself; only grant options are implicit for the owner (see the comment in aclmask()).
  • pg_class_aclmask_ext() short-circuits for superuser_arg() and adds the implicit rights of pg_read_all_data, pg_write_all_data and pg_maintain before calling aclmask(). Role membership is resolved by has_privs_of_role() → roles_is_member_of() with ROLERECURSE_PRIVS (follows only grants with INHERIT), cached per backend and invalidated by pg_auth_members changes. SET ROLE uses ROLERECURSE_SETROLE instead.
  • The executor gets the required bits per table from RTEPermissionInfo (requiredPerms, checkAsUser, selectedCols); column privileges are consulted only when the table-level check fails.
  • GRANT/REVOKE: ExecuteGrantStmt() → ExecGrant_Relation() → merge_acl_with_grant() / aclupdate(); ALTER DEFAULT PRIVILEGES is ExecAlterDefaultPrivilegesStmt() storing rows in pg_default_acl.

Hooks and tests

  • ExecutorCheckPerms_hook (called from ExecCheckPermissions()), object_access_hook (used by contrib/sepgsql), and row_security_policy_hook_permissive/_restrictive to inject policies (example: src/test/modules/test_rls_hooks).
  • Regression tests: src/test/regress/sql/privileges.sql, rowsecurity.sql, create_role.sql. Try SELECT acldefault('r', 'alice'::regrole) and aclexplode(relacl) to decode ACLs.

Parameters that shape this

row_security password_encryption search_path

Related topics

Client authentication SSL/TLS encryption Logical structure Life of a query