85 lines
3.7 KiB
SQL
85 lines
3.7 KiB
SQL
\set ON_ERROR_STOP on
|
|
|
|
-- Run during the documented v1.2.1 downtime while connected as the installation's existing
|
|
-- modelforge bootstrap superuser. This creates a separate retained administrator before demoting
|
|
-- the schema owner. Secrets come from the psql process environment and are never stored here.
|
|
\getenv admin_role MODELFORGE_POSTGRES_ADMIN_USER
|
|
\getenv admin_password MODELFORGE_POSTGRES_ADMIN_PASSWORD
|
|
\getenv owner_password MODELFORGE_MIGRATION_DB_PASSWORD
|
|
\getenv runtime_password MODELFORGE_RUNTIME_DB_PASSWORD
|
|
\getenv database_name MODELFORGE_POSTGRES_DB
|
|
|
|
-- PostgreSQL permanently protects the cluster bootstrap superuser (OID 10): even another
|
|
-- superuser cannot remove that role's SUPERUSER attribute. v1.2.1 installations initialized with
|
|
-- POSTGRES_USER=modelforge therefore need a fresh cluster plus logical restore. Refuse before
|
|
-- creating roles or changing privileges; direct pg_authid mutation is not a supported shortcut.
|
|
do $$
|
|
begin
|
|
if exists (
|
|
select 1
|
|
from pg_catalog.pg_roles
|
|
where rolname = 'modelforge' and oid = 10
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'modelforge is the protected PostgreSQL bootstrap superuser (OID 10)',
|
|
detail = 'PostgreSQL cannot demote the bootstrap superuser.',
|
|
hint = 'Follow docs/UPGRADE.md: create a fresh cluster under the retained admin, logically restore with --no-owner --no-privileges, then run migration 0024.';
|
|
end if;
|
|
end
|
|
$$;
|
|
|
|
select pg_catalog.format(
|
|
'create role %I login password %L superuser createdb createrole inherit replication bypassrls',
|
|
:'admin_role', :'admin_password'
|
|
)
|
|
where not exists (
|
|
select 1 from pg_catalog.pg_roles where rolname = :'admin_role'
|
|
)
|
|
\gexec
|
|
select pg_catalog.format(
|
|
'alter role %I with login password %L superuser createdb createrole inherit replication bypassrls',
|
|
:'admin_role', :'admin_password'
|
|
)
|
|
\gexec
|
|
|
|
select pg_catalog.format(
|
|
'create role modelforge_runtime login password %L nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls',
|
|
:'runtime_password'
|
|
)
|
|
where not exists (select 1 from pg_catalog.pg_roles where rolname = 'modelforge_runtime')
|
|
\gexec
|
|
select pg_catalog.format(
|
|
'alter role modelforge_runtime with login password %L nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls',
|
|
:'runtime_password'
|
|
)
|
|
\gexec
|
|
|
|
-- NOINHERIT still permits SET ROLE. Purge stale memberships while this session still has the
|
|
-- bootstrap superuser authority; after the next statement the retained owner is deliberately
|
|
-- unable to grant or revoke roles.
|
|
select pg_catalog.format('revoke %I from %I', granted.rolname, member.rolname)
|
|
from pg_catalog.pg_auth_members as membership
|
|
join pg_catalog.pg_roles as granted on granted.oid = membership.roleid
|
|
join pg_catalog.pg_roles as member on member.oid = membership.member
|
|
where member.rolname in ('modelforge', 'modelforge_runtime')
|
|
\gexec
|
|
|
|
-- Keep the existing role OID so every v1.2.1 table remains owned by the migration role.
|
|
select pg_catalog.format(
|
|
'alter role modelforge with login password %L nosuperuser nocreatedb nocreaterole inherit noreplication nobypassrls',
|
|
:'owner_password'
|
|
)
|
|
\gexec
|
|
select pg_catalog.format('revoke create, temporary on database %I from public', :'database_name')
|
|
\gexec
|
|
select pg_catalog.format('grant connect, create, temporary on database %I to modelforge', :'database_name')
|
|
\gexec
|
|
select pg_catalog.format('revoke all on database %I from modelforge_runtime', :'database_name')
|
|
\gexec
|
|
select pg_catalog.format('grant connect on database %I to modelforge_runtime', :'database_name')
|
|
\gexec
|
|
revoke create on schema public from public;
|
|
grant usage, create on schema public to modelforge;
|
|
grant usage on schema public to modelforge_runtime;
|