Skip to content

feat: allow privileged role to self-grant ADMIN on roles - #272

Open
mansueli wants to merge 2 commits into
masterfrom
feat/privileged-role-self-grant-admin
Open

mansueli wants to merge 2 commits into
masterfrom
feat/privileged-role-self-grant-admin

Conversation

@mansueli

Copy link
Copy Markdown
Member

Problem

Starting from PostgreSQL 16, the CREATEROLE attribute is no longer enough to manage a role: the ADMIN option on it is required. A role gets ADMIN implicitly on the roles it creates, but not on the ones that existed before.

So after a major version upgrade (e.g. PG15 to PG17), the privileged role has no ADMIN option on the roles the user created on the old version, and it cannot alter, rename, drop or grant them:

drop role legacy_role;
ERROR:  permission denied to drop role
DETAIL:  Only roles with the CREATEROLE attribute and the ADMIN option on role "legacy_role" may drop this role.

grant legacy_role to postgres with admin option;
ERROR:  permission denied to grant role "legacy_role"
DETAIL:  Only roles with the ADMIN option on role "legacy_role" may grant this role.

The user cannot fix this on their own. Each role has to be patched by a superuser.

Solution

On PG16+, when the privileged role runs

grant legacy_role to privileged_role with admin option;

and it lacks the ADMIN option on legacy_role, supautils runs the grant elevated. After that the privileged role can manage the role as usual.

The grant is recorded as done by the bootstrap superuser, which is the same state a role created on PG16+ ends up in.

When it applies

  • The current role has the privileges of supautils.privileged_role.
  • The statement is a GRANT with WITH ADMIN OPTION / WITH ADMIN TRUE and no GRANTED BY.
  • Every grantee is the privileged role or a role that has its privileges. CURRENT_USER, CURRENT_ROLE and SESSION_USER are resolved before elevating, so they don't end up pointing to the superuser.
  • The privileged role lacks ADMIN on at least one of the granted roles. Otherwise core does the grant as before and the privileged role stays as the grantor.

What is excluded

Having ADMIN on a role is as good as being that role, so the grant is not elevated when the granted role is:

  • A superuser.
  • A reserved role.
  • A reserved membership.
  • The privileged role.
  • A member of a superuser, a reserved membership or the privileged role, directly or through other roles, regardless of the INHERIT and SET options.
  • A predefined role (pg_*).

Members of reserved roles are allowed, so a custom role that was granted e.g. anon can still be recovered.

In all the excluded cases the statement is left to core and fails with the usual permission error. One excluded role in the statement excludes the whole statement.

There is no new setting, this is enabled whenever supautils.privileged_role is configured. The privileged role can already alter non-reserved roles through the elevated ALTER ROLE path, so this does not give it power over roles it could not already manage.

Notes

  • Nothing changes on PG15 and below, where CREATEROLE is enough.
  • Since the grantor is the bootstrap superuser, the privileged role cannot revoke that membership from itself. This is also the case for the implicit grant core does on CREATE ROLE.

Testing

Added test/sql/ge16_self_grant_admin.sql, covering the allowed case, each exclusion, the grantee restrictions, a grant done inside a plpgsql function and a member of the privileged role.

Run locally on PG18 only, together with reserved_roles, reserved_memberships and ge16_privileged_role. Compilation was also checked on PG15. The full suite on the other versions is left to CI.

Starting from PG16, CREATEROLE is not enough to manage a role, the ADMIN
option on it is required. Roles created before a major version upgrade
to PG16+ don't have it granted to the privileged role, so it cannot
alter, drop or grant them until a superuser patches each one.

On PG16+, `GRANT <role> TO <privileged_role> WITH ADMIN OPTION` now runs
elevated when the privileged role lacks the ADMIN option on the role.

It's not allowed on superusers, reserved roles, reserved memberships,
the privileged role, members of those (except members of reserved
roles) and predefined roles.
@mansueli
mansueli requested a review from a team as a code owner November 11, 2026 21:34

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant