Files
Mattia Eleuteri 21fa1fcdb1 feat(postgres): add per-user createRole and bypassRls attributes
Without these, a tenant that needs to manage its own roles has no option
but the postgres superuser, which also hands over filesystem access and
command execution on the instance. Role attributes are not inherited
through role membership, so the generated <db>_admin group role cannot
carry them: they have to be set on the login user itself.

users.<name>.createRole and users.<name>.bypassRls set CREATEROLE and
BYPASSRLS on a managed login user. Both are reconciled on every
init-script run, so setting one back to false emits the NO... form and
revokes the attribute.

createRole is refused on PostgreSQL 15 and older. Before PostgreSQL 16,
CREATEROLE allowed granting membership in any non-superuser role, so a
user holding it could grant itself pg_execute_server_program and reach
command execution on the instance, which is exactly what not exposing
the superuser prevents. PostgreSQL 16 restricted CREATEROLE to roles the
grantee administers. bypassRls carries no such gate: BYPASSRLS only
waives row-level security inside the tenant's own databases, whose owner
already reads every row, so it adds no reach.

The two attributes are needed together, not separately. PostgreSQL only
lets a role create other roles carrying BYPASSRLS if it holds the
attribute itself, so provisioning a privileged service role fails with
"Only roles with the BYPASSRLS attribute may create roles with the
BYPASSRLS attribute" when only createRole is granted.

createRole also sets createrole_self_grant as a role-level default.
PostgreSQL 16 grants a CREATEROLE holder only ADMIN OPTION on the roles
it creates, governed by that parameter, whose default is empty. ADMIN
OPTION allows granting the role onward but not SET ROLE to it, and
creating an object owned by another role requires SET. The attribute
therefore looks functional -- CREATE ROLE succeeds -- while the next
statement a tenant writes, CREATE SCHEMA ... AUTHORIZATION <new role>,
fails with "must be able to SET ROLE". The default is reset when the
attribute is withdrawn.

The init job runs as the superuser, and before this change it trusted
two things a CREATEROLE holder controls for the roles it creates: role
comments and role names. It picked the users and roles to drop by their
comment, and a PostgreSQL 16 CREATEROLE holder may comment every role it
created, then hand it to the chart for deletion. It adopted any role
that already existed under a declared name, so a role created under a
name an administrator declares later stayed under its creator's control.
Neither can be caught from the catalog: creating a role through an
intermediate role and then dropping that role leaves the squatted role
with no member at all, while the objects, settings and default
privileges planted in it stay.

The chart therefore records the roles it manages as members of
"cozystack:managed", a role only the superuser can grant, and the delete
passes act only on those. Its first run marks every role the chart had
commented before, when no role could yet hold CREATEROLE through it.
Once any user has held createRole, recorded as a setting of the postgres
database, the chart refuses to adopt a role it did not mark and fails
with the role's name, rather than hand a squatted role REPLICATION or
the ownership of a database.

Verified against live PostgreSQL instances. On 17 a migration checklist
replayed as the login user, with no superuser: role creation with
attributes, CREATE SCHEMA ... AUTHORIZATION with the requested owner,
extensions in a named schema, per-role search_path and timeouts, RLS
including FORCE ROW LEVEL SECURITY, and SECURITY DEFINER functions all
succeed. On 17 the forged-comment and squatting paths are refused or
ignored, the upgrade from the previous chart marks the roles it already
managed, and a fresh install still adopts the role CNPG bootstraps for
users.app. On 13, 15 and 16 the init script runs and re-runs cleanly.

Assisted-by: LLM
Signed-off-by: Mattia Eleuteri <mattia@hidora.io>
2026-10-06 16:26:59 +03:00
..
2025-06-11 22:48:09 +03:00
2024-02-08 12:04:32 +01:00
2024-02-08 12:04:32 +01:00