diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/complete.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/complete.sh index 108409463..3aeff263c 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/complete.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/complete.sh @@ -255,7 +255,21 @@ EOF ) run_sql -c "$REENCRYPT_VAULT_SECRETS_QUERY" - run_sql -c "grant pg_read_all_data, pg_signal_backend to postgres" + GRANT_PREDEFINED_ROLES_TO_POSTGRES_QUERY=$(cat <= 16 THEN + GRANT pg_create_subscription TO postgres; + END IF; + GRANT pg_monitor, pg_read_all_data, pg_signal_backend TO postgres; + END + \$\$; +EOF + ) + run_sql -c "$GRANT_PREDEFINED_ROLES_TO_POSTGRES_QUERY" } function complete_pg_upgrade { diff --git a/ansible/vars.yml b/ansible/vars.yml index 0e658202d..4ea6e2191 100644 --- a/ansible/vars.yml +++ b/ansible/vars.yml @@ -9,9 +9,9 @@ postgres_major: # Full version strings for each major version postgres_release: - postgresorioledb-17: "17.0.1.078-orioledb" - postgres17: "17.4.1.028" - postgres15: "15.8.1.085" + postgresorioledb-17: "17.0.1.078-orioledb-sub-1" + postgres17: "17.4.1.028-sub-1" + postgres15: "15.8.1.085-sub-1" # Non Postgres Extensions pgbouncer_release: "1.19.0" diff --git a/migrations/db/migrations/20250402093753_grant_subscription_to_postgres_16_and_above.sql b/migrations/db/migrations/20250402093753_grant_subscription_to_postgres_16_and_above.sql new file mode 100644 index 000000000..9e9d881ae --- /dev/null +++ b/migrations/db/migrations/20250402093753_grant_subscription_to_postgres_16_and_above.sql @@ -0,0 +1,13 @@ +-- migrate:up +DO $$ +DECLARE + major_version INT; +BEGIN + SELECT current_setting('server_version_num')::INT / 10000 INTO major_version; + + IF major_version >= 16 THEN + GRANT pg_create_subscription TO postgres; + END IF; +END $$; + +-- migrate:down diff --git a/nix/tests/expected/roles.out b/nix/tests/expected/roles.out index 1cf984cf1..2d2d5060e 100644 --- a/nix/tests/expected/roles.out +++ b/nix/tests/expected/roles.out @@ -1,3 +1,9 @@ +-- Some predefined roles don't exist in earlier versions of Postgres, so we +-- exclude them in this test file. They're tested in version-specific test files +-- (z__roles.sql). +-- +-- Currently those roles are: +-- pg_create_subscription, pg_maintain, pg_use_reserved_connections -- all roles and attributes select rolname, @@ -11,7 +17,6 @@ select rolbypassrls , rolvaliduntil from pg_roles r --- TODO: this exclusion is to maintain compat with pg17, we should cover it where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by rolname; rolname | rolcreaterole | rolcanlogin | rolsuper | rolinherit | rolcreatedb | rolreplication | rolconnlimit | rolbypassrls | rolvaliduntil @@ -51,7 +56,6 @@ select rolname, rolconfig from pg_roles r --- TODO: this exclusion is to maintain compat with pg17, we should cover it where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by rolname; rolname | rolconfig @@ -98,53 +102,31 @@ left join pg_auth_members m on r.oid = m.member left join pg_roles g on m.roleid = g.oid --- TODO: this exclusion is to maintain compat with pg17, we should cover it where r.rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +and g.rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by r.rolname, g.rolname; - member | member_of (can become) | admin_option -----------------------------+------------------------+-------------- - anon | | - authenticated | | - authenticator | anon | f - authenticator | authenticated | f - authenticator | service_role | f - dashboard_user | | - pg_checkpoint | | - pg_database_owner | | - pg_execute_server_program | | - pg_monitor | pg_read_all_settings | f - pg_monitor | pg_read_all_stats | f - pg_monitor | pg_stat_scan_tables | f - pg_read_all_data | | - pg_read_all_settings | | - pg_read_all_stats | | - pg_read_server_files | | - pg_signal_backend | | - pg_stat_scan_tables | | - pg_write_all_data | | - pg_write_server_files | | - pgbouncer | | - pgsodium_keyholder | pgsodium_keyiduser | f - pgsodium_keyiduser | | - pgsodium_keymaker | pgsodium_keyholder | f - pgsodium_keymaker | pgsodium_keyiduser | f - pgtle_admin | | - postgres | anon | f - postgres | authenticated | f - postgres | pg_monitor | f - postgres | pg_read_all_data | f - postgres | pg_signal_backend | f - postgres | pgtle_admin | f - postgres | service_role | f - service_role | | - supabase_admin | | - supabase_auth_admin | | - supabase_functions_admin | | - supabase_read_only_user | pg_read_all_data | f - supabase_replication_admin | | - supabase_storage_admin | authenticator | f -(40 rows) + member | member_of (can become) | admin_option +-------------------------+------------------------+-------------- + authenticator | anon | f + authenticator | authenticated | f + authenticator | service_role | f + pg_monitor | pg_read_all_settings | f + pg_monitor | pg_read_all_stats | f + pg_monitor | pg_stat_scan_tables | f + pgsodium_keyholder | pgsodium_keyiduser | f + pgsodium_keymaker | pgsodium_keyholder | f + pgsodium_keymaker | pgsodium_keyiduser | f + postgres | anon | f + postgres | authenticated | f + postgres | pg_monitor | f + postgres | pg_read_all_data | f + postgres | pg_signal_backend | f + postgres | pgtle_admin | f + postgres | service_role | f + supabase_read_only_user | pg_read_all_data | f + supabase_storage_admin | authenticator | f +(18 rows) -- Check all privileges of the roles on the schemas select schema_name, privilege_type, grantee, default_for @@ -164,7 +146,8 @@ from ( join pg_roles r on a.grantee = r.oid where - a.privilege_type != 'MAINTAIN' -- TODO: this is to maintain compat with pg17, we should cover it + -- PG17+, handled in version-specific test files + a.privilege_type != 'MAINTAIN' union all -- explicit grant usage and create on the schemas select diff --git a/nix/tests/expected/z_17_roles.out b/nix/tests/expected/z_17_roles.out new file mode 100644 index 000000000..a90a6677d --- /dev/null +++ b/nix/tests/expected/z_17_roles.out @@ -0,0 +1,111 @@ +-- version-specific roles and attributes +select + rolname, + rolcreaterole , + rolcanlogin , + rolsuper , + rolinherit , + rolcreatedb , + rolreplication , + rolconnlimit , + rolbypassrls , + rolvaliduntil +from pg_roles r +where rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by rolname; + rolname | rolcreaterole | rolcanlogin | rolsuper | rolinherit | rolcreatedb | rolreplication | rolconnlimit | rolbypassrls | rolvaliduntil +-----------------------------+---------------+-------------+----------+------------+-------------+----------------+--------------+--------------+--------------- + pg_create_subscription | f | f | f | t | f | f | -1 | f | + pg_maintain | f | f | f | t | f | f | -1 | f | + pg_use_reserved_connections | f | f | f | t | f | f | -1 | f | +(3 rows) + +select + rolname, + rolconfig +from pg_roles r +where rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by rolname; + rolname | rolconfig +-----------------------------+----------- + pg_create_subscription | + pg_maintain | + pg_use_reserved_connections | +(3 rows) + +-- version-specific role memberships +select + r.rolname as member, + g.rolname as "member_of (can become)", + m.admin_option +from + pg_roles r +left join + pg_auth_members m on r.oid = m.member +left join + pg_roles g on m.roleid = g.oid +where r.rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +or g.rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by + r.rolname, g.rolname; + member | member_of (can become) | admin_option +-----------------------------+------------------------+-------------- + pg_create_subscription | | + pg_maintain | | + pg_use_reserved_connections | | + postgres | pg_create_subscription | f +(4 rows) + +-- Check version-specific privileges of the roles on the schemas +select schema_name, privilege_type, grantee, default_for +from ( + -- ALTER DEFAULT privileges on schemas + select + n.nspname as schema_name, + a.privilege_type, + r.rolname as grantee, + d.defaclrole::regrole as default_for, + case when n.nspname = 'public' then 0 else 1 end as schema_order + from + pg_default_acl d + join + pg_namespace n on d.defaclnamespace = n.oid + cross join lateral aclexplode(d.defaclacl) as a + join + pg_roles r on a.grantee = r.oid + where + a.privilege_type = 'MAINTAIN' +) sub +order by schema_order, schema_name, privilege_type, grantee, default_for; + schema_name | privilege_type | grantee | default_for +----------------+----------------+--------------------+--------------------- + public | MAINTAIN | anon | supabase_admin + public | MAINTAIN | anon | postgres + public | MAINTAIN | authenticated | supabase_admin + public | MAINTAIN | authenticated | postgres + public | MAINTAIN | postgres | supabase_admin + public | MAINTAIN | postgres | postgres + public | MAINTAIN | service_role | supabase_admin + public | MAINTAIN | service_role | postgres + auth | MAINTAIN | dashboard_user | supabase_auth_admin + auth | MAINTAIN | postgres | supabase_auth_admin + extensions | MAINTAIN | postgres | supabase_admin + graphql | MAINTAIN | anon | supabase_admin + graphql | MAINTAIN | authenticated | supabase_admin + graphql | MAINTAIN | postgres | supabase_admin + graphql | MAINTAIN | service_role | supabase_admin + graphql_public | MAINTAIN | anon | supabase_admin + graphql_public | MAINTAIN | authenticated | supabase_admin + graphql_public | MAINTAIN | postgres | supabase_admin + graphql_public | MAINTAIN | service_role | supabase_admin + pgsodium | MAINTAIN | pgsodium_keyholder | supabase_admin + pgsodium_masks | MAINTAIN | pgsodium_keyiduser | supabase_admin + realtime | MAINTAIN | dashboard_user | supabase_admin + realtime | MAINTAIN | postgres | supabase_admin + repack | MAINTAIN | postgres | supabase_admin + storage | MAINTAIN | anon | postgres + storage | MAINTAIN | authenticated | postgres + storage | MAINTAIN | postgres | postgres + storage | MAINTAIN | service_role | postgres +(28 rows) + diff --git a/nix/tests/sql/roles.sql b/nix/tests/sql/roles.sql index 9e68a171a..7a582a366 100644 --- a/nix/tests/sql/roles.sql +++ b/nix/tests/sql/roles.sql @@ -1,3 +1,10 @@ +-- Some predefined roles don't exist in earlier versions of Postgres, so we +-- exclude them in this test file. They're tested in version-specific test files +-- (z__roles.sql). +-- +-- Currently those roles are: +-- pg_create_subscription, pg_maintain, pg_use_reserved_connections + -- all roles and attributes select rolname, @@ -11,7 +18,6 @@ select rolbypassrls , rolvaliduntil from pg_roles r --- TODO: this exclusion is to maintain compat with pg17, we should cover it where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by rolname; @@ -19,7 +25,6 @@ select rolname, rolconfig from pg_roles r --- TODO: this exclusion is to maintain compat with pg17, we should cover it where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by rolname; @@ -34,8 +39,8 @@ left join pg_auth_members m on r.oid = m.member left join pg_roles g on m.roleid = g.oid --- TODO: this exclusion is to maintain compat with pg17, we should cover it where r.rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +and g.rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') order by r.rolname, g.rolname; @@ -57,7 +62,8 @@ from ( join pg_roles r on a.grantee = r.oid where - a.privilege_type != 'MAINTAIN' -- TODO: this is to maintain compat with pg17, we should cover it + -- PG17+, handled in version-specific test files + a.privilege_type != 'MAINTAIN' union all -- explicit grant usage and create on the schemas select diff --git a/nix/tests/sql/z_17_roles.sql b/nix/tests/sql/z_17_roles.sql new file mode 100644 index 000000000..ef17fcb77 --- /dev/null +++ b/nix/tests/sql/z_17_roles.sql @@ -0,0 +1,60 @@ +-- version-specific roles and attributes +select + rolname, + rolcreaterole , + rolcanlogin , + rolsuper , + rolinherit , + rolcreatedb , + rolreplication , + rolconnlimit , + rolbypassrls , + rolvaliduntil +from pg_roles r +where rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by rolname; + +select + rolname, + rolconfig +from pg_roles r +where rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by rolname; + +-- version-specific role memberships +select + r.rolname as member, + g.rolname as "member_of (can become)", + m.admin_option +from + pg_roles r +left join + pg_auth_members m on r.oid = m.member +left join + pg_roles g on m.roleid = g.oid +where r.rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +or g.rolname in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections') +order by + r.rolname, g.rolname; + +-- Check version-specific privileges of the roles on the schemas +select schema_name, privilege_type, grantee, default_for +from ( + -- ALTER DEFAULT privileges on schemas + select + n.nspname as schema_name, + a.privilege_type, + r.rolname as grantee, + d.defaclrole::regrole as default_for, + case when n.nspname = 'public' then 0 else 1 end as schema_order + from + pg_default_acl d + join + pg_namespace n on d.defaclnamespace = n.oid + cross join lateral aclexplode(d.defaclacl) as a + join + pg_roles r on a.grantee = r.oid + where + a.privilege_type = 'MAINTAIN' +) sub +order by schema_order, schema_name, privilege_type, grantee, default_for;