From 63ff187ecddcbb35f72645e1342b9915336e6628 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Mon, 22 Jul 2024 17:07:27 +0800 Subject: [PATCH 1/8] feat: change bootstrap user to supabase_admin upon upgrade --- .github/workflows/test.yml | 6 +- Dockerfile-156 | 3 +- .../pg_upgrade_scripts/common.sh | 395 ++++++++++++++++++ .../pg_upgrade_scripts/initiate.sh | 24 +- ansible/playbook.yml | 7 + ansible/tasks/setup-postgres.yml | 4 +- docker/all-in-one/postgres-entrypoint.sh | 4 +- flake.nix | 11 +- .../00000000000000-initial-schema.sql | 1 - migrations/db/migrate.sh | 14 + migrations/docker-compose.yaml | 9 +- nix/docker/init.sh.in | 2 +- nix/init.sh | 2 +- nix/tools/migrate-tool.sh.in | 4 +- nix/tools/run-client-migrate.sh.in | 10 +- nix/tools/run-client.sh.in | 3 +- 16 files changed, 462 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index be9c9a72d..2be061941 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,7 @@ jobs: target: production build-args: | ${{ steps.args.outputs.result }} - tags: supabase/postgres:${{ steps.settings.outputs.postgres-version }} + tags: supabase/postgres:${{ steps.settings.outputs.postgres-version }},supabase_postgres cache-from: | type=gha,scope=${{ github.ref_name }}-${{ steps.settings.outputs.postgres-version }}-${{ matrix.arch }} type=gha,scope=${{ github.base_ref }}-${{ steps.settings.outputs.postgres-version }}-${{ matrix.arch }} @@ -107,10 +107,6 @@ jobs: PGUSER: supabase_admin PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} - schema: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - name: verify schema.sql is committed run: | docker compose -f migrations/docker-compose.yaml up db dbmate --abort-on-container-exit diff --git a/Dockerfile-156 b/Dockerfile-156 index 162c7fff2..ce83237f2 100644 --- a/Dockerfile-156 +++ b/Dockerfile-156 @@ -206,6 +206,8 @@ STOPSIGNAL SIGINT EXPOSE 5432 ENV POSTGRES_HOST=/var/run/postgresql +ENV POSTGRES_USER=supabase_admin +ENV POSTGRES_DB=postgres RUN apt-get update && apt-get install -y --no-install-recommends \ locales \ && rm -rf /var/lib/apt/lists/* && \ @@ -219,4 +221,3 @@ ENV LC_CTYPE=C.UTF-8 ENV LC_COLLATE=C.UTF-8 ENV LOCALE_ARCHIVE /usr/lib/locale/locale-archive CMD ["postgres", "-D", "/etc/postgresql"] - diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 02ce2c9a6..8ed9100e9 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -10,6 +10,8 @@ if [ -f "$REPORTING_CREDENTIALS_FILE" ]; then REPORTING_ANON_KEY=$(cat "$REPORTING_CREDENTIALS_FILE") fi +# shellcheck disable=SC2120 +# Arguments are passed in other files function run_sql { psql -h localhost -U supabase_admin -d postgres "$@" } @@ -83,3 +85,396 @@ CI_start_postgres() { su postgres -c "$BINDIR/pg_ctl start -o '-c config_file=/etc/postgresql/postgresql.conf' -l /tmp/postgres.log" } + +swap_postgres_and_supabase_admin() { + run_sql <<'EOSQL' +begin; +create role supabase_tmp superuser; +set session authorization supabase_tmp; + +do $$ +declare + postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres'); + supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin'); + role_settings jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}') + from pg_db_role_setting s + left join pg_database d on d.oid = s.setdatabase + 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); + 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 + join pg_authid a on a.oid = um.umuser + join pg_foreign_server s on s.oid = um.umserver + where a.rolname in ('postgres', 'supabase_admin') + ); + -- Objects can have initial privileges either by having those privileges set + -- when the system is initialized (by initdb) or when the object is created + -- during a CREATE EXTENSION and the extension script sets initial + -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html) + -- We only care about swapping init_privs for extensions. + init_privs jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}') + from pg_init_privs + where privtype = 'e' + ); + default_acls jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}') + from pg_default_acl d + join pg_authid a on a.oid = d.defaclrole + left join pg_namespace n on n.oid = d.defaclnamespace + ); + schemas jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}') + from pg_namespace n + join pg_authid a on a.oid = n.nspowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + types jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}') + from pg_type t + join pg_namespace n on n.oid = t.typnamespace + join pg_authid a on a.oid = t.typowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and ( + t.typrelid = 0 + or ( + select + c.relkind = 'c' + from + pg_class c + where + c.oid = t.typrelid + ) + ) + and not exists ( + select + from + pg_type el + where + el.oid = t.typelem + and el.typarray = t.oid + ) + ); + functions jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'acl', p.proacl::text)), '{}') + from pg_proc p + join pg_namespace n on n.oid = p.pronamespace + join pg_authid a on a.oid = p.proowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + relations jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}') + from ( + -- Sequences must appear after tables, so we order by relkind + select * from pg_class order by relkind desc + ) c + join pg_namespace n on n.oid = c.relnamespace + join pg_authid a on a.oid = c.relowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and c.relkind not in ('c', 'i') + ); + 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 %I from %I;', rec.roleid::regrole, rec.member::regrole)); + execute(format( + 'grant %I to %I %s granted by %I;', + 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, + rec.value + )); + end loop; + end loop; + + reassign owned by postgres to supabase_admin; + + -- databases + for rec in + select * from pg_database where datname not in ('template0') + loop + execute(format('alter database %I owner to postgres;', rec.datname)); + end loop; + + -- event triggers + foreach obj in array event_triggers + loop + execute(format('alter event trigger %I owner to postgres;', obj->>'name')); + end loop; + + -- publications + for rec in + select * from pg_publication + loop + execute(format('alter publication %I owner to postgres;', rec.pubname)); + end loop; + + -- FDWs + for rec in + select * from pg_foreign_data_wrapper + loop + execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname)); + end loop; + + -- foreign servers + for rec in + select * from pg_foreign_server + loop + execute(format('alter server %I owner to postgres;', rec.srvname)); + end loop; + + -- user mappings + foreach obj in array user_mappings + loop + execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server')); + end loop; + foreach obj in array user_mappings + loop + execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server')); + for rec in + select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value + from jsonb_array_elements_text(obj->'options') + loop + execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value)); + end loop; + end loop; + + -- init privs + foreach obj in array init_privs + loop + -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES. + update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid; + end loop; + + -- default acls + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I' + , 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')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + 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' + else rec.grantee::regrole + end + )); + end if; + end loop; + end loop; + + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I %s' + , obj->>'role' + , case when obj->>'schema' is null then '' + else format('in schema %I', (obj->>'schema')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + when obj->>'objtype' = 'T' then 'types' + when obj->>'objtype' = 'n' then 'schemas' + end + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); + end if; + end loop; + end loop; + + -- schemas + foreach obj in array schemas + loop + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array schemas + loop + 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('grant %s on schema %s to %I %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, 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 + 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)); + end loop; + end loop; + foreach obj in array types + loop + 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('grant %s on type %s to %I %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, 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 + 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 + 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 function %s(%s) from %I', rec.privilege_type, (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc), case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end)); + end loop; + end loop; + foreach obj in array functions + loop + 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('grant %s on function %s(%s) to %I %s', rec.privilege_type, (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc), rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); + end loop; + end loop; + + -- relations + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + 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('grant %s on table %s to %I %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); + end loop; + end loop; +end +$$; + +set session authorization supabase_admin; +drop role supabase_tmp; +commit; +EOSQL +} diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 21fec9ed9..692c97a78 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -73,6 +73,8 @@ if [ -n "$IS_CI" ]; then echo "PGVERSION: $PGVERSION" fi +OLD_BOOTSTRAP_USER=$(run_sql -A -t -c "select rolname from pg_authid where oid = 10;") + cleanup() { UPGRADE_STATUS=${1:-"failed"} EXIT_CODE=${?:-0} @@ -367,10 +369,15 @@ function initiate_upgrade { echo "7. Disabling extensions and generating post-upgrade script" handle_extensions - - echo "8. Granting SUPERUSER to postgres user" + + echo "8.1. Granting SUPERUSER to postgres user" run_sql -c "ALTER USER postgres WITH SUPERUSER;" + echo "8.2. Swap postgres & supabase_admin roles if upgrading from a project with postgres as bootstrap user" + if [ "$OLD_BOOTSTRAP_USER" = "postgres" ]; then + swap_postgres_and_supabase_admin + fi + if [ -z "$IS_NIX_UPGRADE" ]; then if [ -d "/usr/share/postgresql/${PGVERSION}" ]; then mv "/usr/share/postgresql/${PGVERSION}" "/usr/share/postgresql/${PGVERSION}.bak" @@ -390,17 +397,26 @@ function initiate_upgrade { rm -rf "${PGDATANEW:?}/" if [ "$IS_NIX_UPGRADE" = "true" ]; then - LC_ALL=en_US.UTF-8 LC_CTYPE=$SERVER_LC_CTYPE LC_COLLATE=$SERVER_LC_COLLATE LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --encoding=$SERVER_ENCODING --lc-collate=$SERVER_LC_COLLATE --lc-ctype=$SERVER_LC_CTYPE -L $PGSHARENEW -D $PGDATANEW/" -s "$SHELL" postgres + LC_ALL=en_US.UTF-8 LC_CTYPE=$SERVER_LC_CTYPE LC_COLLATE=$SERVER_LC_COLLATE LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --encoding=$SERVER_ENCODING --lc-collate=$SERVER_LC_COLLATE --lc-ctype=$SERVER_LC_CTYPE -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres else - su -c "$PGBINNEW/initdb -L $PGSHARENEW -D $PGDATANEW/" -s "$SHELL" postgres + su -c "$PGBINNEW/initdb -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres fi + # This line avoids the need to supply the supabase_admin password on the old + # instance, since pg_upgrade connects to the db as supabase_admin using unix + # sockets, which is gated behind scram-sha-256 per pg_hba.conf.j2. The new + # instance is unaffected. + echo "local all supabase_admin trust +$(cat /etc/postgresql/pg_hba.conf)" > /etc/postgresql/pg_hba.conf + run_sql -c "select pg_reload_conf();" + UPGRADE_COMMAND=$(cat < $TMPDIR/getkey.sh echo 'echo $PGSODIUM_KEY' >> $TMPDIR/getkey.sh chmod +x $TMPDIR/getkey.sh - initdb --locale=C + initdb --locale=C --username=supabase_admin substitute ${./nix/tests/postgresql.conf.in} $PGDATA/postgresql.conf \ --subst-var-by PGSODIUM_GETKEY_SCRIPT "$TMPDIR/getkey.sh" echo "listen_addresses = '*'" >> $PGDATA/postgresql.conf @@ -459,14 +459,14 @@ exit 1 fi done - createdb -p 5432 -h localhost testing - if ! psql -p 5432 -h localhost -d testing -v ON_ERROR_STOP=1 -Xaf ${./nix/tests/prime.sql}; then + createdb -p 5432 -h localhost --username=supabase_admin testing + if ! psql -p 5432 -h localhost --username=supabase_admin -d testing -v ON_ERROR_STOP=1 -Xaf ${./nix/tests/prime.sql}; then echo "Error executing SQL file. PostgreSQL log content:" cat $TMPDIR/logfile/postgresql.log pg_ctl -D "$PGDATA" stop exit 1 fi - pg_prove -p 5432 -h localhost -d testing ${sqlTests}/*.sql + pg_prove -p 5432 -h localhost --username=supabase_admin -d testing ${sqlTests}/*.sql mkdir -p $out/regression_output pg_regress \ @@ -476,6 +476,7 @@ --outputdir=$out/regression_output \ --host=localhost \ --port=5432 \ + --user=supabase_admin \ $(ls ${./nix/tests/sql} | sed -e 's/\..*$//' | sort ) pg_ctl -D "$PGDATA" stop diff --git a/migrations/db/init-scripts/00000000000000-initial-schema.sql b/migrations/db/init-scripts/00000000000000-initial-schema.sql index a98f0144d..ecce79a3d 100644 --- a/migrations/db/init-scripts/00000000000000-initial-schema.sql +++ b/migrations/db/init-scripts/00000000000000-initial-schema.sql @@ -5,7 +5,6 @@ create publication supabase_realtime; -- Supabase super admin -create user supabase_admin; alter user supabase_admin with superuser createdb createrole replication bypassrls; -- Supabase replication user diff --git a/migrations/db/migrate.sh b/migrations/db/migrate.sh index 1882978ed..0a84d1e6c 100755 --- a/migrations/db/migrate.sh +++ b/migrations/db/migrate.sh @@ -28,6 +28,16 @@ fi db=$( cd -- "$( dirname -- "$0" )" > /dev/null 2>&1 && pwd ) if [ -z "${USE_DBMATE:-}" ]; then + psql -v ON_ERROR_STOP=1 --no-password --no-psqlrc -U supabase_admin < Date: Thu, 15 Aug 2024 22:26:47 +0800 Subject: [PATCH 2/8] chore: bump version --- common-nix.vars.pkr.hcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-nix.vars.pkr.hcl b/common-nix.vars.pkr.hcl index 17d242f77..87ec91ad5 100644 --- a/common-nix.vars.pkr.hcl +++ b/common-nix.vars.pkr.hcl @@ -1 +1 @@ -postgres-version = "15.6.1.118" +postgres-version = "15.6.1.119" From 0775be0a8bf9664069bb4a582d54bf6aa1873f95 Mon Sep 17 00:00:00 2001 From: Paul Cioanca Date: Fri, 23 Aug 2024 13:23:02 +0300 Subject: [PATCH 3/8] fix: account for procedures --- .../pg_upgrade_scripts/common.sh | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 8ed9100e9..5ed134c42 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -164,7 +164,7 @@ declare ) ); functions jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'acl', p.proacl::text)), '{}') + select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'kind', p.prokind, 'acl', p.proacl::text)), '{}') from pg_proc p join pg_namespace n on n.oid = p.pronamespace join pg_authid a on a.oid = p.proowner @@ -428,7 +428,16 @@ begin from aclexplode((obj->>'acl')::aclitem[]) where grantee::regrole in ('postgres', 'supabase_admin') loop - execute(format('revoke %s on function %s(%s) from %I', rec.privilege_type, (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc), case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end)); + execute(format('revoke %s on %s %s(%s) from %I' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end + )); end loop; end loop; foreach obj in array functions @@ -438,7 +447,17 @@ begin from aclexplode((obj->>'acl')::aclitem[]) where grantee::regrole in ('postgres', 'supabase_admin') loop - execute(format('grant %s on function %s(%s) to %I %s', rec.privilege_type, (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc), rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); + execute(format('grant %s on %s %s(%s) to %I %s' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); end loop; end loop; From 8ade21a5a387bbc3773d6160e9d99c476c33e515 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Tue, 27 Aug 2024 17:53:39 +0800 Subject: [PATCH 4/8] fix: only update pg_hba.conf if needed --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 692c97a78..a27398bdd 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -406,9 +406,11 @@ function initiate_upgrade { # instance, since pg_upgrade connects to the db as supabase_admin using unix # sockets, which is gated behind scram-sha-256 per pg_hba.conf.j2. The new # instance is unaffected. - echo "local all supabase_admin trust + if ! grep -q "local all supabase_admin trust" /etc/postgresql/pg_hba.conf; then + echo "local all supabase_admin trust $(cat /etc/postgresql/pg_hba.conf)" > /etc/postgresql/pg_hba.conf - run_sql -c "select pg_reload_conf();" + run_sql -c "select pg_reload_conf();" + fi UPGRADE_COMMAND=$(cat < Date: Wed, 28 Aug 2024 17:51:35 +0300 Subject: [PATCH 5/8] chore: persist file in pg_upgrade_scripts directory; run only when targeting PG16 --- .../pg_upgrade_scripts/common.sh | 453 +----------------- .../pg_upgrade_scripts/initiate.sh | 4 +- .../migrate_bootstrap_user.sql | 407 ++++++++++++++++ 3 files changed, 435 insertions(+), 429 deletions(-) create mode 100644 ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 5ed134c42..853535be6 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -39,29 +39,29 @@ function ship_logs { printf -v BODY '{ "ref": "%s", "step": "%s", "content": %s }' "$DERIVED_REF" "completion" "$(cat "$LOG_FILE" | jq -Rs '.')" curl -sf -X POST "https://$REPORTING_PROJECT_REF.supabase.co/rest/v1/error_logs" \ - -H "apikey: ${REPORTING_ANON_KEY}" \ - -H 'Content-type: application/json' \ - -d "$BODY" + -H "apikey: ${REPORTING_ANON_KEY}" \ + -H 'Content-type: application/json' \ + -d "$BODY" } function retry { - local retries=$1 - shift - - local count=0 - until "$@"; do - exit=$? - wait=$((2 ** (count + 1))) - count=$((count + 1)) - if [ $count -lt "$retries" ]; then - echo "Command $* exited with code $exit, retrying..." - sleep $wait - else - echo "Command $* exited with code $exit, no more retries left." - return $exit - fi - done - return 0 + local retries=$1 + shift + + local count=0 + until "$@"; do + exit=$? + wait=$((2 ** (count + 1))) + count=$((count + 1)) + if [ $count -lt "$retries" ]; then + echo "Command $* exited with code $exit, retrying..." + sleep $wait + else + echo "Command $* exited with code $exit, no more retries left." + return $exit + fi + done + return 0 } CI_stop_postgres() { @@ -87,413 +87,12 @@ CI_start_postgres() { } swap_postgres_and_supabase_admin() { - run_sql <<'EOSQL' -begin; -create role supabase_tmp superuser; -set session authorization supabase_tmp; - -do $$ -declare - postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres'); - supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin'); - role_settings jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}') - from pg_db_role_setting s - left join pg_database d on d.oid = s.setdatabase - 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); - 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 - join pg_authid a on a.oid = um.umuser - join pg_foreign_server s on s.oid = um.umserver - where a.rolname in ('postgres', 'supabase_admin') - ); - -- Objects can have initial privileges either by having those privileges set - -- when the system is initialized (by initdb) or when the object is created - -- during a CREATE EXTENSION and the extension script sets initial - -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html) - -- We only care about swapping init_privs for extensions. - init_privs jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}') - from pg_init_privs - where privtype = 'e' - ); - default_acls jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}') - from pg_default_acl d - join pg_authid a on a.oid = d.defaclrole - left join pg_namespace n on n.oid = d.defaclnamespace - ); - schemas jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}') - from pg_namespace n - join pg_authid a on a.oid = n.nspowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - ); - types jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}') - from pg_type t - join pg_namespace n on n.oid = t.typnamespace - join pg_authid a on a.oid = t.typowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - and ( - t.typrelid = 0 - or ( - select - c.relkind = 'c' - from - pg_class c - where - c.oid = t.typrelid - ) - ) - and not exists ( - select - from - pg_type el - where - el.oid = t.typelem - and el.typarray = t.oid - ) - ); - functions jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'kind', p.prokind, 'acl', p.proacl::text)), '{}') - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - join pg_authid a on a.oid = p.proowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - ); - relations jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}') - from ( - -- Sequences must appear after tables, so we order by relkind - select * from pg_class order by relkind desc - ) c - join pg_namespace n on n.oid = c.relnamespace - join pg_authid a on a.oid = c.relowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - and c.relkind not in ('c', 'i') - ); - 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 %I from %I;', rec.roleid::regrole, rec.member::regrole)); - execute(format( - 'grant %I to %I %s granted by %I;', - 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, - rec.value - )); - end loop; - end loop; - - reassign owned by postgres to supabase_admin; - - -- databases - for rec in - select * from pg_database where datname not in ('template0') - loop - execute(format('alter database %I owner to postgres;', rec.datname)); - end loop; - - -- event triggers - foreach obj in array event_triggers - loop - execute(format('alter event trigger %I owner to postgres;', obj->>'name')); - end loop; - - -- publications - for rec in - select * from pg_publication - loop - execute(format('alter publication %I owner to postgres;', rec.pubname)); - end loop; - - -- FDWs - for rec in - select * from pg_foreign_data_wrapper - loop - execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname)); - end loop; + SCRIPT_DIR=$(dirname -- "$0") - -- foreign servers - for rec in - select * from pg_foreign_server - loop - execute(format('alter server %I owner to postgres;', rec.srvname)); - end loop; - - -- user mappings - foreach obj in array user_mappings - loop - execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server')); - end loop; - foreach obj in array user_mappings - loop - execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server')); - for rec in - select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value - from jsonb_array_elements_text(obj->'options') - loop - execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value)); - end loop; - end loop; - - -- init privs - foreach obj in array init_privs - loop - -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES. - update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid; - end loop; - - -- default acls - foreach obj in array default_acls - loop - for rec in - select grantor, grantee, privilege_type, is_grantable - from aclexplode((obj->>'acl')::aclitem[]) - 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 %I' - , 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')::regnamespace) - end - , rec.privilege_type - , case when obj->>'objtype' = 'r' then 'tables' - when obj->>'objtype' = 'S' then 'sequences' - when obj->>'objtype' = 'f' then 'functions' - 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' - else rec.grantee::regrole - end - )); - end if; - end loop; - end loop; - - foreach obj in array default_acls - loop - for rec in - select grantor, grantee, privilege_type, is_grantable - from aclexplode((obj->>'acl')::aclitem[]) - 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 %I %s' - , obj->>'role' - , case when obj->>'schema' is null then '' - else format('in schema %I', (obj->>'schema')::regnamespace) - end - , rec.privilege_type - , case when obj->>'objtype' = 'r' then 'tables' - when obj->>'objtype' = 'S' then 'sequences' - when obj->>'objtype' = 'f' then 'functions' - when obj->>'objtype' = 'T' then 'types' - when obj->>'objtype' = 'n' then 'schemas' - end - , rec.grantee::regrole - , case when rec.is_grantable then 'with grant option' else '' end - )); - end if; - end loop; - end loop; - - -- schemas - foreach obj in array schemas - loop - if obj->>'owner' = 'postgres' 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)); - end loop; - end loop; - foreach obj in array schemas - loop - 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('grant %s on schema %s to %I %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, 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 - 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)); - end loop; - end loop; - foreach obj in array types - loop - 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('grant %s on type %s to %I %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, 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 - 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 - 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 %s %s(%s) from %I' - , rec.privilege_type - , case - when obj->>'kind' = 'p' then 'procedure' - else 'function' - end - , (obj->>'oid')::regproc - , pg_get_function_identity_arguments((obj->>'oid')::regproc) - , case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end - )); - end loop; - end loop; - foreach obj in array functions - loop - 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('grant %s on %s %s(%s) to %I %s' - , rec.privilege_type - , case - when obj->>'kind' = 'p' then 'procedure' - else 'function' - end - , (obj->>'oid')::regproc - , pg_get_function_identity_arguments((obj->>'oid')::regproc) - , rec.grantee::regrole - , case when rec.is_grantable then 'with grant option' else '' end - )); - end loop; - end loop; - - -- relations - foreach obj in array relations - loop - -- obj->>'oid' (text) needs to be casted to oid first for some reason - - if obj->>'owner' = 'postgres' 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)); - end loop; - end loop; - foreach obj in array relations - loop - -- obj->>'oid' (text) needs to be casted to oid first for some reason - - 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('grant %s on table %s to %I %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); - end loop; - end loop; -end -$$; + if [ -f "$SCRIPT_DIR/migrate_bootstrap_user.sql" ]; then + run_sql -f "$SCRIPT_DIR/migrate_bootstrap_user.sql" + else + echo "Bootstrap user migration script not found. Skipping." + fi -set session authorization supabase_admin; -drop role supabase_tmp; -commit; -EOSQL } diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index a27398bdd..a549afe9f 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -373,8 +373,8 @@ function initiate_upgrade { echo "8.1. Granting SUPERUSER to postgres user" run_sql -c "ALTER USER postgres WITH SUPERUSER;" - echo "8.2. Swap postgres & supabase_admin roles if upgrading from a project with postgres as bootstrap user" - if [ "$OLD_BOOTSTRAP_USER" = "postgres" ]; then + if [ "$OLD_BOOTSTRAP_USER" = "postgres" ] && [ "$PGVERSION" != "15" ]; then + echo "8.2. Swap postgres & supabase_admin roles as we're upgrading a project with postgres as bootstrap user" swap_postgres_and_supabase_admin fi diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql b/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql new file mode 100644 index 000000000..ac7cde1f4 --- /dev/null +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql @@ -0,0 +1,407 @@ +begin; +create role supabase_tmp superuser; +set session authorization supabase_tmp; + +do $$ +declare + postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres'); + supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin'); + role_settings jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}') + from pg_db_role_setting s + left join pg_database d on d.oid = s.setdatabase + 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); + 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 + join pg_authid a on a.oid = um.umuser + join pg_foreign_server s on s.oid = um.umserver + where a.rolname in ('postgres', 'supabase_admin') + ); + -- Objects can have initial privileges either by having those privileges set + -- when the system is initialized (by initdb) or when the object is created + -- during a CREATE EXTENSION and the extension script sets initial + -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html) + -- We only care about swapping init_privs for extensions. + init_privs jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}') + from pg_init_privs + where privtype = 'e' + ); + default_acls jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}') + from pg_default_acl d + join pg_authid a on a.oid = d.defaclrole + left join pg_namespace n on n.oid = d.defaclnamespace + ); + schemas jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}') + from pg_namespace n + join pg_authid a on a.oid = n.nspowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + types jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}') + from pg_type t + join pg_namespace n on n.oid = t.typnamespace + join pg_authid a on a.oid = t.typowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and ( + t.typrelid = 0 + or ( + select + c.relkind = 'c' + from + pg_class c + where + c.oid = t.typrelid + ) + ) + and not exists ( + select + from + pg_type el + where + el.oid = t.typelem + and el.typarray = t.oid + ) + ); + functions jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'kind', p.prokind, 'acl', p.proacl::text)), '{}') + from pg_proc p + join pg_namespace n on n.oid = p.pronamespace + join pg_authid a on a.oid = p.proowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + relations jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}') + from ( + -- Sequences must appear after tables, so we order by relkind + select * from pg_class order by relkind desc + ) c + join pg_namespace n on n.oid = c.relnamespace + join pg_authid a on a.oid = c.relowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and c.relkind not in ('c', 'i') + ); + 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 %I from %I;', rec.roleid::regrole, rec.member::regrole)); + execute(format( + 'grant %I to %I %s granted by %I;', + 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, + rec.value + )); + end loop; + end loop; + + reassign owned by postgres to supabase_admin; + + -- databases + for rec in + select * from pg_database where datname not in ('template0') + loop + execute(format('alter database %I owner to postgres;', rec.datname)); + end loop; + + -- event triggers + foreach obj in array event_triggers + loop + execute(format('alter event trigger %I owner to postgres;', obj->>'name')); + end loop; + + -- publications + for rec in + select * from pg_publication + loop + execute(format('alter publication %I owner to postgres;', rec.pubname)); + end loop; + + -- FDWs + for rec in + select * from pg_foreign_data_wrapper + loop + execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname)); + end loop; + + -- foreign servers + for rec in + select * from pg_foreign_server + loop + execute(format('alter server %I owner to postgres;', rec.srvname)); + end loop; + + -- user mappings + foreach obj in array user_mappings + loop + execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server')); + end loop; + foreach obj in array user_mappings + loop + execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server')); + for rec in + select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value + from jsonb_array_elements_text(obj->'options') + loop + execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value)); + end loop; + end loop; + + -- init privs + foreach obj in array init_privs + loop + -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES. + update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid; + end loop; + + -- default acls + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I' + , 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')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + 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' + else rec.grantee::regrole + end + )); + end if; + end loop; + end loop; + + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I %s' + , obj->>'role' + , case when obj->>'schema' is null then '' + else format('in schema %I', (obj->>'schema')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + when obj->>'objtype' = 'T' then 'types' + when obj->>'objtype' = 'n' then 'schemas' + end + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); + end if; + end loop; + end loop; + + -- schemas + foreach obj in array schemas + loop + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array schemas + loop + 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('grant %s on schema %s to %I %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, 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 + 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)); + end loop; + end loop; + foreach obj in array types + loop + 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('grant %s on type %s to %I %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, 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 + 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 + 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 %s %s(%s) from %I' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end + )); + end loop; + end loop; + foreach obj in array functions + loop + 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('grant %s on %s %s(%s) to %I %s' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); + end loop; + end loop; + + -- relations + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + 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('grant %s on table %s to %I %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); + end loop; + end loop; +end +$$; + +set session authorization supabase_admin; +drop role supabase_tmp; +commit; \ No newline at end of file From 4b21113887ff159cdd005e018e719185d77630b6 Mon Sep 17 00:00:00 2001 From: Paul Cioanca Date: Wed, 28 Aug 2024 18:17:33 +0300 Subject: [PATCH 6/8] chore: revert PG16 gating --- ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index a549afe9f..7c5abfdae 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -373,7 +373,7 @@ function initiate_upgrade { echo "8.1. Granting SUPERUSER to postgres user" run_sql -c "ALTER USER postgres WITH SUPERUSER;" - if [ "$OLD_BOOTSTRAP_USER" = "postgres" ] && [ "$PGVERSION" != "15" ]; then + if [ "$OLD_BOOTSTRAP_USER" = "postgres" ]; then echo "8.2. Swap postgres & supabase_admin roles as we're upgrading a project with postgres as bootstrap user" swap_postgres_and_supabase_admin fi From 310fc0b2525855e8f472c4479cd705b5b4035846 Mon Sep 17 00:00:00 2001 From: Paul Cioanca Date: Thu, 29 Aug 2024 17:02:14 +0300 Subject: [PATCH 7/8] Revert "chore: persist file in pg_upgrade_scripts directory; run only when targeting PG16" This reverts commit 376a9346cae816a03077fcfa8c2c6b0c6f1a1890. --- .../pg_upgrade_scripts/common.sh | 453 +++++++++++++++++- .../migrate_bootstrap_user.sql | 407 ---------------- 2 files changed, 427 insertions(+), 433 deletions(-) delete mode 100644 ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 853535be6..5ed134c42 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -39,29 +39,29 @@ function ship_logs { printf -v BODY '{ "ref": "%s", "step": "%s", "content": %s }' "$DERIVED_REF" "completion" "$(cat "$LOG_FILE" | jq -Rs '.')" curl -sf -X POST "https://$REPORTING_PROJECT_REF.supabase.co/rest/v1/error_logs" \ - -H "apikey: ${REPORTING_ANON_KEY}" \ - -H 'Content-type: application/json' \ - -d "$BODY" + -H "apikey: ${REPORTING_ANON_KEY}" \ + -H 'Content-type: application/json' \ + -d "$BODY" } function retry { - local retries=$1 - shift - - local count=0 - until "$@"; do - exit=$? - wait=$((2 ** (count + 1))) - count=$((count + 1)) - if [ $count -lt "$retries" ]; then - echo "Command $* exited with code $exit, retrying..." - sleep $wait - else - echo "Command $* exited with code $exit, no more retries left." - return $exit - fi - done - return 0 + local retries=$1 + shift + + local count=0 + until "$@"; do + exit=$? + wait=$((2 ** (count + 1))) + count=$((count + 1)) + if [ $count -lt "$retries" ]; then + echo "Command $* exited with code $exit, retrying..." + sleep $wait + else + echo "Command $* exited with code $exit, no more retries left." + return $exit + fi + done + return 0 } CI_stop_postgres() { @@ -87,12 +87,413 @@ CI_start_postgres() { } swap_postgres_and_supabase_admin() { - SCRIPT_DIR=$(dirname -- "$0") + run_sql <<'EOSQL' +begin; +create role supabase_tmp superuser; +set session authorization supabase_tmp; - if [ -f "$SCRIPT_DIR/migrate_bootstrap_user.sql" ]; then - run_sql -f "$SCRIPT_DIR/migrate_bootstrap_user.sql" - else - echo "Bootstrap user migration script not found. Skipping." - fi +do $$ +declare + postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres'); + supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin'); + role_settings jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}') + from pg_db_role_setting s + left join pg_database d on d.oid = s.setdatabase + 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); + 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 + join pg_authid a on a.oid = um.umuser + join pg_foreign_server s on s.oid = um.umserver + where a.rolname in ('postgres', 'supabase_admin') + ); + -- Objects can have initial privileges either by having those privileges set + -- when the system is initialized (by initdb) or when the object is created + -- during a CREATE EXTENSION and the extension script sets initial + -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html) + -- We only care about swapping init_privs for extensions. + init_privs jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}') + from pg_init_privs + where privtype = 'e' + ); + default_acls jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}') + from pg_default_acl d + join pg_authid a on a.oid = d.defaclrole + left join pg_namespace n on n.oid = d.defaclnamespace + ); + schemas jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}') + from pg_namespace n + join pg_authid a on a.oid = n.nspowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + types jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}') + from pg_type t + join pg_namespace n on n.oid = t.typnamespace + join pg_authid a on a.oid = t.typowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and ( + t.typrelid = 0 + or ( + select + c.relkind = 'c' + from + pg_class c + where + c.oid = t.typrelid + ) + ) + and not exists ( + select + from + pg_type el + where + el.oid = t.typelem + and el.typarray = t.oid + ) + ); + functions jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'kind', p.prokind, 'acl', p.proacl::text)), '{}') + from pg_proc p + join pg_namespace n on n.oid = p.pronamespace + join pg_authid a on a.oid = p.proowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + ); + relations jsonb[] := ( + select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}') + from ( + -- Sequences must appear after tables, so we order by relkind + select * from pg_class order by relkind desc + ) c + join pg_namespace n on n.oid = c.relnamespace + join pg_authid a on a.oid = c.relowner + where true + and n.nspname != 'information_schema' + and not starts_with(n.nspname, 'pg_') + and c.relkind not in ('c', 'i') + ); + 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 %I from %I;', rec.roleid::regrole, rec.member::regrole)); + execute(format( + 'grant %I to %I %s granted by %I;', + 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, + rec.value + )); + end loop; + end loop; + + reassign owned by postgres to supabase_admin; + + -- databases + for rec in + select * from pg_database where datname not in ('template0') + loop + execute(format('alter database %I owner to postgres;', rec.datname)); + end loop; + + -- event triggers + foreach obj in array event_triggers + loop + execute(format('alter event trigger %I owner to postgres;', obj->>'name')); + end loop; + + -- publications + for rec in + select * from pg_publication + loop + execute(format('alter publication %I owner to postgres;', rec.pubname)); + end loop; + + -- FDWs + for rec in + select * from pg_foreign_data_wrapper + loop + execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname)); + end loop; + + -- foreign servers + for rec in + select * from pg_foreign_server + loop + execute(format('alter server %I owner to postgres;', rec.srvname)); + end loop; + + -- user mappings + foreach obj in array user_mappings + loop + execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server')); + end loop; + foreach obj in array user_mappings + loop + execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server')); + for rec in + select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value + from jsonb_array_elements_text(obj->'options') + loop + execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value)); + end loop; + end loop; + + -- init privs + foreach obj in array init_privs + loop + -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES. + update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid; + end loop; + + -- default acls + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I' + , 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')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + 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' + else rec.grantee::regrole + end + )); + end if; + end loop; + end loop; + + foreach obj in array default_acls + loop + for rec in + select grantor, grantee, privilege_type, is_grantable + from aclexplode((obj->>'acl')::aclitem[]) + 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 %I %s' + , obj->>'role' + , case when obj->>'schema' is null then '' + else format('in schema %I', (obj->>'schema')::regnamespace) + end + , rec.privilege_type + , case when obj->>'objtype' = 'r' then 'tables' + when obj->>'objtype' = 'S' then 'sequences' + when obj->>'objtype' = 'f' then 'functions' + when obj->>'objtype' = 'T' then 'types' + when obj->>'objtype' = 'n' then 'schemas' + end + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); + end if; + end loop; + end loop; + + -- schemas + foreach obj in array schemas + loop + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array schemas + loop + 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('grant %s on schema %s to %I %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, 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 + 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)); + end loop; + end loop; + foreach obj in array types + loop + 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('grant %s on type %s to %I %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, 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 + 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 + 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 %s %s(%s) from %I' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end + )); + end loop; + end loop; + foreach obj in array functions + loop + 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('grant %s on %s %s(%s) to %I %s' + , rec.privilege_type + , case + when obj->>'kind' = 'p' then 'procedure' + else 'function' + end + , (obj->>'oid')::regproc + , pg_get_function_identity_arguments((obj->>'oid')::regproc) + , rec.grantee::regrole + , case when rec.is_grantable then 'with grant option' else '' end + )); + end loop; + end loop; + + -- relations + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + if obj->>'owner' = 'postgres' 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)); + end loop; + end loop; + foreach obj in array relations + loop + -- obj->>'oid' (text) needs to be casted to oid first for some reason + + 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('grant %s on table %s to %I %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); + end loop; + end loop; +end +$$; +set session authorization supabase_admin; +drop role supabase_tmp; +commit; +EOSQL } diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql b/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql deleted file mode 100644 index ac7cde1f4..000000000 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/migrate_bootstrap_user.sql +++ /dev/null @@ -1,407 +0,0 @@ -begin; -create role supabase_tmp superuser; -set session authorization supabase_tmp; - -do $$ -declare - postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres'); - supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin'); - role_settings jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}') - from pg_db_role_setting s - left join pg_database d on d.oid = s.setdatabase - 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); - 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 - join pg_authid a on a.oid = um.umuser - join pg_foreign_server s on s.oid = um.umserver - where a.rolname in ('postgres', 'supabase_admin') - ); - -- Objects can have initial privileges either by having those privileges set - -- when the system is initialized (by initdb) or when the object is created - -- during a CREATE EXTENSION and the extension script sets initial - -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html) - -- We only care about swapping init_privs for extensions. - init_privs jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}') - from pg_init_privs - where privtype = 'e' - ); - default_acls jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}') - from pg_default_acl d - join pg_authid a on a.oid = d.defaclrole - left join pg_namespace n on n.oid = d.defaclnamespace - ); - schemas jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}') - from pg_namespace n - join pg_authid a on a.oid = n.nspowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - ); - types jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}') - from pg_type t - join pg_namespace n on n.oid = t.typnamespace - join pg_authid a on a.oid = t.typowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - and ( - t.typrelid = 0 - or ( - select - c.relkind = 'c' - from - pg_class c - where - c.oid = t.typrelid - ) - ) - and not exists ( - select - from - pg_type el - where - el.oid = t.typelem - and el.typarray = t.oid - ) - ); - functions jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'kind', p.prokind, 'acl', p.proacl::text)), '{}') - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - join pg_authid a on a.oid = p.proowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - ); - relations jsonb[] := ( - select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}') - from ( - -- Sequences must appear after tables, so we order by relkind - select * from pg_class order by relkind desc - ) c - join pg_namespace n on n.oid = c.relnamespace - join pg_authid a on a.oid = c.relowner - where true - and n.nspname != 'information_schema' - and not starts_with(n.nspname, 'pg_') - and c.relkind not in ('c', 'i') - ); - 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 %I from %I;', rec.roleid::regrole, rec.member::regrole)); - execute(format( - 'grant %I to %I %s granted by %I;', - 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, - rec.value - )); - end loop; - end loop; - - reassign owned by postgres to supabase_admin; - - -- databases - for rec in - select * from pg_database where datname not in ('template0') - loop - execute(format('alter database %I owner to postgres;', rec.datname)); - end loop; - - -- event triggers - foreach obj in array event_triggers - loop - execute(format('alter event trigger %I owner to postgres;', obj->>'name')); - end loop; - - -- publications - for rec in - select * from pg_publication - loop - execute(format('alter publication %I owner to postgres;', rec.pubname)); - end loop; - - -- FDWs - for rec in - select * from pg_foreign_data_wrapper - loop - execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname)); - end loop; - - -- foreign servers - for rec in - select * from pg_foreign_server - loop - execute(format('alter server %I owner to postgres;', rec.srvname)); - end loop; - - -- user mappings - foreach obj in array user_mappings - loop - execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server')); - end loop; - foreach obj in array user_mappings - loop - execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server')); - for rec in - select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value - from jsonb_array_elements_text(obj->'options') - loop - execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value)); - end loop; - end loop; - - -- init privs - foreach obj in array init_privs - loop - -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES. - update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid; - end loop; - - -- default acls - foreach obj in array default_acls - loop - for rec in - select grantor, grantee, privilege_type, is_grantable - from aclexplode((obj->>'acl')::aclitem[]) - 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 %I' - , 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')::regnamespace) - end - , rec.privilege_type - , case when obj->>'objtype' = 'r' then 'tables' - when obj->>'objtype' = 'S' then 'sequences' - when obj->>'objtype' = 'f' then 'functions' - 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' - else rec.grantee::regrole - end - )); - end if; - end loop; - end loop; - - foreach obj in array default_acls - loop - for rec in - select grantor, grantee, privilege_type, is_grantable - from aclexplode((obj->>'acl')::aclitem[]) - 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 %I %s' - , obj->>'role' - , case when obj->>'schema' is null then '' - else format('in schema %I', (obj->>'schema')::regnamespace) - end - , rec.privilege_type - , case when obj->>'objtype' = 'r' then 'tables' - when obj->>'objtype' = 'S' then 'sequences' - when obj->>'objtype' = 'f' then 'functions' - when obj->>'objtype' = 'T' then 'types' - when obj->>'objtype' = 'n' then 'schemas' - end - , rec.grantee::regrole - , case when rec.is_grantable then 'with grant option' else '' end - )); - end if; - end loop; - end loop; - - -- schemas - foreach obj in array schemas - loop - if obj->>'owner' = 'postgres' 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)); - end loop; - end loop; - foreach obj in array schemas - loop - 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('grant %s on schema %s to %I %s', rec.privilege_type, (obj->>'oid')::regnamespace, rec.grantee::regrole, 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 - 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)); - end loop; - end loop; - foreach obj in array types - loop - 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('grant %s on type %s to %I %s', rec.privilege_type, (obj->>'oid')::regtype, rec.grantee::regrole, 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 - 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 - 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 %s %s(%s) from %I' - , rec.privilege_type - , case - when obj->>'kind' = 'p' then 'procedure' - else 'function' - end - , (obj->>'oid')::regproc - , pg_get_function_identity_arguments((obj->>'oid')::regproc) - , case when rec.grantee = 'postgres'::regrole then 'supabase_admin' else 'postgres' end - )); - end loop; - end loop; - foreach obj in array functions - loop - 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('grant %s on %s %s(%s) to %I %s' - , rec.privilege_type - , case - when obj->>'kind' = 'p' then 'procedure' - else 'function' - end - , (obj->>'oid')::regproc - , pg_get_function_identity_arguments((obj->>'oid')::regproc) - , rec.grantee::regrole - , case when rec.is_grantable then 'with grant option' else '' end - )); - end loop; - end loop; - - -- relations - foreach obj in array relations - loop - -- obj->>'oid' (text) needs to be casted to oid first for some reason - - if obj->>'owner' = 'postgres' 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)); - end loop; - end loop; - foreach obj in array relations - loop - -- obj->>'oid' (text) needs to be casted to oid first for some reason - - 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('grant %s on table %s to %I %s', rec.privilege_type, (obj->>'oid')::oid::regclass, rec.grantee::regrole, case when rec.is_grantable then 'with grant option' else '' end)); - end loop; - end loop; -end -$$; - -set session authorization supabase_admin; -drop role supabase_tmp; -commit; \ No newline at end of file From c4f0f9affd099fecd62107b5471f8c040853adb5 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo <31685197+soedirgo@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:15:16 +0100 Subject: [PATCH 8/8] Update common-nix.vars.pkr.hcl --- common-nix.vars.pkr.hcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-nix.vars.pkr.hcl b/common-nix.vars.pkr.hcl index 87ec91ad5..e5af7f254 100644 --- a/common-nix.vars.pkr.hcl +++ b/common-nix.vars.pkr.hcl @@ -1 +1 @@ -postgres-version = "15.6.1.119" +postgres-version = "15.6.1.120"