Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor bootstrap user switch script #1282

Draft
wants to merge 1 commit into
base: release/15.6
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 125 additions & 111 deletions ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,17 @@ set session authorization supabase_tmp;
-- to handle snowflakes that happened in the past
revoke supabase_admin from authenticator;

-- #incident-2024-09-12-project-upgrades-are-temporarily-disabled
do $$
begin
if exists (select from pg_extension where extname = 'timescaledb') then
execute(format('select %s.timescaledb_pre_restore()', (select pronamespace::regnamespace from pg_proc where proname = 'timescaledb_pre_restore')));
if exists (select from pg_authid where rolname = 'pg_read_all_data') then
execute('grant pg_read_all_data to postgres');
end if;
end
$$;
grant pg_signal_backend to postgres;

-- Swap postgres & supabase_admin on global objects (roles, role settings, etc.).
do $$
declare
postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres');
Expand All @@ -128,7 +131,84 @@ declare
join pg_authid a on a.oid = s.setrole
where a.rolname in ('postgres', 'supabase_admin')
);
event_triggers jsonb[] := (select coalesce(array_agg(jsonb_build_object('name', evtname)), '{}') from pg_event_trigger where evtowner = 'postgres'::regrole);
rec record;
obj jsonb;
begin
set local search_path = '';

alter role postgres rename to supabase_admin_;
alter role supabase_admin rename to postgres;
alter role supabase_admin_ rename to supabase_admin;

-- role grants
for rec in
select * from pg_auth_members
loop
execute(format('revoke %s from %s;', rec.roleid::regrole, rec.member::regrole));
execute(format(
'grant %s to %s %s granted by %s;',
case
when rec.roleid = 'postgres'::regrole then 'supabase_admin'
when rec.roleid = 'supabase_admin'::regrole then 'postgres'
else rec.roleid::regrole
end,
case
when rec.member = 'postgres'::regrole then 'supabase_admin'
when rec.member = 'supabase_admin'::regrole then 'postgres'
else rec.member::regrole
end,
case
when rec.admin_option then 'with admin option'
else ''
end,
case
when rec.grantor = 'postgres'::regrole then 'supabase_admin'
when rec.grantor = 'supabase_admin'::regrole then 'postgres'
else rec.grantor::regrole
end
));
end loop;

-- role passwords
execute(format('alter role postgres password %L;', postgres_rolpassword));
execute(format('alter role supabase_admin password %L;', supabase_admin_rolpassword));

-- role settings
foreach obj in array role_settings
loop
execute(format('alter role %I %s reset all',
case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end,
case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end
));
end loop;
foreach obj in array role_settings
loop
for rec in
select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value
from jsonb_array_elements_text(obj->'configs')
loop
execute(format('alter role %I %s set %I to %s',
obj->>'role',
case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end,
rec.key,
-- https://github.com/postgres/postgres/blob/70d1c664f4376fd3499e3b0c6888cf39b65d722b/src/bin/pg_dump/dumputils.c#L861
case
when rec.key in ('local_preload_libraries', 'search_path', 'session_preload_libraries', 'shared_preload_libraries', 'temp_tablespaces', 'unix_socket_directories')
then rec.value
else quote_literal(rec.value)
end
));
end loop;
end loop;

reassign owned by postgres to supabase_admin;
end
$$;

-- Swap postgres & supabase_admin on in-database objects (schemas, tables, functions, etc.).
do $$
declare
event_triggers jsonb[] := (select coalesce(array_agg(jsonb_build_object('name', evtname)), '{}') from pg_event_trigger where evtowner = 'supabase_admin'::regrole);
user_mappings jsonb[] := (
select coalesce(array_agg(jsonb_build_object('oid', um.oid, 'role', a.rolname, 'server', s.srvname, 'options', um.umoptions)), '{}')
from pg_user_mapping um
Expand Down Expand Up @@ -219,72 +299,9 @@ begin
alter event trigger pgsodium_trg_mask_update disable;
end if;

alter role postgres rename to supabase_admin_;
alter role supabase_admin rename to postgres;
alter role supabase_admin_ rename to supabase_admin;

-- role grants
for rec in
select * from pg_auth_members
loop
execute(format('revoke %s from %s;', rec.roleid::regrole, rec.member::regrole));
execute(format(
'grant %s to %s %s granted by %s;',
case
when rec.roleid = 'postgres'::regrole then 'supabase_admin'
when rec.roleid = 'supabase_admin'::regrole then 'postgres'
else rec.roleid::regrole
end,
case
when rec.member = 'postgres'::regrole then 'supabase_admin'
when rec.member = 'supabase_admin'::regrole then 'postgres'
else rec.member::regrole
end,
case
when rec.admin_option then 'with admin option'
else ''
end,
case
when rec.grantor = 'postgres'::regrole then 'supabase_admin'
when rec.grantor = 'supabase_admin'::regrole then 'postgres'
else rec.grantor::regrole
end
));
end loop;

-- role passwords
execute(format('alter role postgres password %L;', postgres_rolpassword));
execute(format('alter role supabase_admin password %L;', supabase_admin_rolpassword));

-- role settings
foreach obj in array role_settings
loop
execute(format('alter role %I %s reset all',
case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end,
case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end
));
end loop;
foreach obj in array role_settings
loop
for rec in
select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value
from jsonb_array_elements_text(obj->'configs')
loop
execute(format('alter role %I %s set %I to %s',
obj->>'role',
case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end,
rec.key,
-- https://github.com/postgres/postgres/blob/70d1c664f4376fd3499e3b0c6888cf39b65d722b/src/bin/pg_dump/dumputils.c#L861
case
when rec.key in ('local_preload_libraries', 'search_path', 'session_preload_libraries', 'shared_preload_libraries', 'temp_tablespaces', 'unix_socket_directories')
then rec.value
else quote_literal(rec.value)
end
));
end loop;
end loop;

reassign owned by postgres to supabase_admin;
if exists (select from pg_extension where extname = 'timescaledb') then
execute(format('select %s.timescaledb_pre_restore()', (select pronamespace::regnamespace from pg_proc where proname = 'timescaledb_pre_restore')));
end if;

-- databases
for rec in
Expand Down Expand Up @@ -352,10 +369,7 @@ begin
loop
if obj->>'role' in ('postgres', 'supabase_admin') or rec.grantee::regrole in ('postgres', 'supabase_admin') then
execute(format('alter default privileges for role %I %s revoke %s on %s from %s'
, case when obj->>'role' = 'postgres' then 'supabase_admin'
when obj->>'role' = 'supabase_admin' then 'postgres'
else obj->>'role'
end
, obj->>'role'
, case when obj->>'schema' is null then ''
else format('in schema %I', obj->>'schema')
end
Expand All @@ -366,16 +380,11 @@ begin
when obj->>'objtype' = 'T' then 'types'
when obj->>'objtype' = 'n' then 'schemas'
end
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin'
when rec.grantee = 'supabase_admin'::regrole then 'postgres'
when rec.grantee = 0 then 'public'
else rec.grantee::regrole::text
end
, case when rec.grantee = 0 then 'public' else rec.grantee::regrole::text end
));
end if;
end loop;
end loop;

foreach obj in array default_acls
loop
for rec in
Expand All @@ -384,7 +393,10 @@ begin
loop
if obj->>'role' in ('postgres', 'supabase_admin') or rec.grantee::regrole in ('postgres', 'supabase_admin') then
execute(format('alter default privileges for role %I %s grant %s on %s to %s %s'
, obj->>'role'
, case when obj->>'role' = 'postgres' then 'supabase_admin'
when obj->>'role' = 'supabase_admin' then 'postgres'
else obj->>'role'
end
, case when obj->>'schema' is null then ''
else format('in schema %I', obj->>'schema')
end
Expand All @@ -395,7 +407,11 @@ begin
when obj->>'objtype' = 'T' then 'types'
when obj->>'objtype' = 'n' then 'schemas'
end
, case when rec.grantee = 0 then 'public' else rec.grantee::regrole::text end
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin'
when rec.grantee = 'supabase_admin'::regrole then 'postgres'
when rec.grantee = 0 then 'public'
else rec.grantee::regrole::text
end
, case when rec.is_grantable then 'with grant option' else '' end
));
end if;
Expand All @@ -405,15 +421,15 @@ begin
-- schemas
foreach obj in array schemas
loop
if obj->>'owner' = 'postgres' then
if obj->>'owner' = 'supabase_admin' then
execute(format('alter schema %s owner to postgres;', (obj->>'oid')::regnamespace));
end if;
for rec in
select grantor, grantee, privilege_type, is_grantable
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('revoke %s on schema %s from %I', rec.privilege_type, (obj->>'oid')::regnamespace, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end));
execute(format('revoke %s on schema %s from %I', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole));
end loop;
end loop;
foreach obj in array schemas
Expand All @@ -423,22 +439,26 @@ begin
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('grant %s on schema %s to %s %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end));
execute(format('grant %s on schema %s to %s %s'
, rec.privilege_type
, (obj->>'oid')::regnamespace
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end
, case when rec.is_grantable then 'with grant option' else '' end));
end loop;
end loop;

-- types
foreach obj in array types
loop
if obj->>'owner' = 'postgres' then
if obj->>'owner' = 'supabase_admin' then
execute(format('alter type %s owner to postgres;', (obj->>'oid')::regtype));
end if;
for rec in
select grantor, grantee, privilege_type, is_grantable
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('revoke %s on type %s from %I', rec.privilege_type, (obj->>'oid')::regtype, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end));
execute(format('revoke %s on type %s from %I', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole));
end loop;
end loop;
foreach obj in array types
Expand All @@ -448,14 +468,18 @@ begin
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('grant %s on type %s to %s %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end));
execute(format('grant %s on type %s to %s %s'
, rec.privilege_type
, (obj->>'oid')::regtype
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end
, case when rec.is_grantable then 'with grant option' else '' end));
end loop;
end loop;

-- functions
foreach obj in array functions
loop
if obj->>'owner' = 'postgres' then
if obj->>'owner' = 'supabase_admin' then
execute(format('alter routine %s(%s) owner to postgres;', (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc)));
end if;
for rec in
Expand All @@ -471,7 +495,7 @@ begin
end
, (obj->>'oid')::regproc
, pg_get_function_identity_arguments((obj->>'oid')::regproc)
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end
, rec.grantee::regrole
));
end loop;
end loop;
Expand All @@ -490,7 +514,7 @@ begin
end
, (obj->>'oid')::regproc
, pg_get_function_identity_arguments((obj->>'oid')::regproc)
, rec.grantee::regrole
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end
, case when rec.is_grantable then 'with grant option' else '' end
));
end loop;
Expand All @@ -501,15 +525,15 @@ begin
loop
-- obj->>'oid' (text) needs to be casted to oid first for some reason

if obj->>'owner' = 'postgres' then
if obj->>'owner' = 'supabase_admin' then
execute(format('alter table %s owner to postgres;', (obj->>'oid')::oid::regclass));
end if;
for rec in
select grantor, grantee, privilege_type, is_grantable
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('revoke %s on table %s from %I', rec.privilege_type, (obj->>'oid')::oid::regclass, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end));
execute(format('revoke %s on table %s from %I', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole));
end loop;
end loop;
foreach obj in array relations
Expand All @@ -521,35 +545,25 @@ begin
from aclexplode((obj->>'acl')::aclitem[])
where grantee::regrole in ('postgres', 'supabase_admin')
loop
execute(format('grant %s on table %s to %s %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end));
execute(format('grant %s on table %s to %s %s'
, rec.privilege_type
, (obj->>'oid')::oid::regclass
, case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end
, case when rec.is_grantable then 'with grant option' else '' end));
end loop;
end loop;

if exists (select from pg_event_trigger where evtname = 'pgsodium_trg_mask_update') then
alter event trigger pgsodium_trg_mask_update enable;
end if;
end
$$;

do $$
begin
if exists (select from pg_extension where extname = 'timescaledb') then
execute(format('select %s.timescaledb_post_restore()', (select pronamespace::regnamespace from pg_proc where proname = 'timescaledb_post_restore')));
end if;
end
$$;

alter database postgres connection limit -1;

-- #incident-2024-09-12-project-upgrades-are-temporarily-disabled
do $$
begin
if exists (select from pg_authid where rolname = 'pg_read_all_data') then
execute('grant pg_read_all_data to postgres');
if exists (select from pg_event_trigger where evtname = 'pgsodium_trg_mask_update') then
alter event trigger pgsodium_trg_mask_update enable;
end if;
end
$$;
grant pg_signal_backend to postgres;

alter database postgres connection limit -1;

set session authorization supabase_admin;
drop role supabase_tmp;
Expand Down
Loading