forked from wangziqi/gongxue-base
feat: establish production SaaS foundation
This commit is contained in:
188
scripts/deploy/sql/bootstrap-backend-runtime-roles.sql
Normal file
188
scripts/deploy/sql/bootstrap-backend-runtime-roles.sql
Normal file
@@ -0,0 +1,188 @@
|
||||
do $$
|
||||
begin
|
||||
if not exists (select 1 from pg_roles where rolname = 'tiku_api') then
|
||||
create role tiku_api login;
|
||||
end if;
|
||||
if not exists (select 1 from pg_roles where rolname = 'tiku_worker') then
|
||||
create role tiku_worker login;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
alter role tiku_api
|
||||
login nosuperuser noinherit nocreatedb nocreaterole noreplication bypassrls;
|
||||
|
||||
alter role tiku_worker
|
||||
login nosuperuser noinherit nocreatedb nocreaterole noreplication bypassrls;
|
||||
|
||||
create schema if not exists extensions;
|
||||
revoke create on schema extensions from public, tiku_api, tiku_worker;
|
||||
grant usage on schema extensions to tiku_api, tiku_worker;
|
||||
|
||||
-- Install or relocate required extensions before normal migrations. Otherwise
|
||||
-- citext/ltree can be created later by supabase_admin with PostgreSQL's default
|
||||
-- PUBLIC EXECUTE and the migration role cannot close that RPC surface.
|
||||
do $$
|
||||
declare
|
||||
extension_name name;
|
||||
extension_schema name;
|
||||
extension_relocatable boolean;
|
||||
begin
|
||||
foreach extension_name in array array[
|
||||
'pgcrypto'::name,
|
||||
'citext'::name,
|
||||
'ltree'::name,
|
||||
'pg_trgm'::name
|
||||
]
|
||||
loop
|
||||
select namespace.nspname, extension.extrelocatable
|
||||
into extension_schema, extension_relocatable
|
||||
from pg_extension extension
|
||||
join pg_namespace namespace on namespace.oid = extension.extnamespace
|
||||
where extension.extname = extension_name;
|
||||
|
||||
if not found then
|
||||
execute format('create extension %I with schema extensions', extension_name);
|
||||
elsif extension_schema <> 'extensions' then
|
||||
if not extension_relocatable then
|
||||
raise exception 'Required extension % cannot be relocated from schema %', extension_name, extension_schema;
|
||||
end if;
|
||||
execute format('alter extension %I set schema extensions', extension_name);
|
||||
end if;
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
alter role tiku_api set search_path = pg_catalog, public, extensions;
|
||||
alter role tiku_worker set search_path = pg_catalog, public, extensions;
|
||||
|
||||
-- Official Supabase images can preinstall relocatable extensions such as
|
||||
-- citext and ltree in public. Their functions are owned by supabase_admin, so
|
||||
-- the normal migration role cannot remove PostgreSQL's default PUBLIC EXECUTE.
|
||||
-- Seal that inherited RPC surface while a real superuser is available.
|
||||
do $$
|
||||
declare
|
||||
required_role name;
|
||||
begin
|
||||
foreach required_role in array array['anon'::name, 'authenticated'::name]
|
||||
loop
|
||||
if not exists (select 1 from pg_roles where rolname = required_role) then
|
||||
raise exception 'Required Supabase Data API role % is missing', required_role;
|
||||
end if;
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
revoke execute on all functions in schema public
|
||||
from public, anon, authenticated, tiku_api, tiku_worker;
|
||||
revoke execute on all functions in schema extensions
|
||||
from public, anon, authenticated, tiku_api, tiku_worker;
|
||||
|
||||
-- Supabase's internal services use dedicated trusted database roles. Preserve
|
||||
-- their extension execution when those roles exist, without reopening the
|
||||
-- surface to the client-facing anon/authenticated roles.
|
||||
do $$
|
||||
declare
|
||||
trusted_role name;
|
||||
begin
|
||||
foreach trusted_role in array array[
|
||||
'postgres'::name,
|
||||
'service_role'::name,
|
||||
'dashboard_user'::name,
|
||||
'supabase_auth_admin'::name,
|
||||
'supabase_storage_admin'::name,
|
||||
'supabase_realtime_admin'::name,
|
||||
'supabase_functions_admin'::name
|
||||
]
|
||||
loop
|
||||
if exists (select 1 from pg_roles where rolname = trusted_role) then
|
||||
execute format('grant execute on all functions in schema extensions to %I', trusted_role);
|
||||
end if;
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- citext and ltree operators, casts and tree helpers call extension-owned
|
||||
-- functions. pg_trgm index support is similarly needed by backend search.
|
||||
-- Backend roles receive only those pure type/search helpers; pgcrypto stays
|
||||
-- unavailable and Data API client roles receive no extension execution.
|
||||
do $$
|
||||
declare
|
||||
extension_function record;
|
||||
begin
|
||||
for extension_function in
|
||||
select procedure_row.oid::regprocedure::text as signature
|
||||
from pg_proc procedure_row
|
||||
join pg_depend dependency
|
||||
on dependency.classid = 'pg_proc'::regclass
|
||||
and dependency.objid = procedure_row.oid
|
||||
and dependency.refclassid = 'pg_extension'::regclass
|
||||
and dependency.deptype = 'e'
|
||||
join pg_extension extension on extension.oid = dependency.refobjid
|
||||
where extension.extname in ('citext', 'ltree', 'pg_trgm')
|
||||
loop
|
||||
execute format(
|
||||
'revoke execute on function %s from public, anon, authenticated, tiku_api, tiku_worker',
|
||||
extension_function.signature
|
||||
);
|
||||
execute format(
|
||||
'grant execute on function %s to tiku_api, tiku_worker',
|
||||
extension_function.signature
|
||||
);
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- Extension upgrades may add more public functions under the extension owner.
|
||||
-- PostgreSQL's function default ACL is global, so this must not use IN SCHEMA.
|
||||
do $$
|
||||
declare
|
||||
owner_name name;
|
||||
begin
|
||||
for owner_name in
|
||||
select bootstrap_owner.owner_name
|
||||
from (
|
||||
select current_user::name as owner_name
|
||||
union
|
||||
select distinct owner_role.rolname
|
||||
from pg_proc function_row
|
||||
join pg_namespace namespace on namespace.oid = function_row.pronamespace
|
||||
join pg_roles owner_role on owner_role.oid = function_row.proowner
|
||||
where namespace.nspname in ('public', 'extensions')
|
||||
) bootstrap_owner
|
||||
loop
|
||||
execute format(
|
||||
'alter default privileges for role %I revoke execute on functions from public, anon, authenticated, tiku_api, tiku_worker',
|
||||
owner_name
|
||||
);
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- Remove all inherited or SET ROLE paths. Runtime roles receive object ACLs
|
||||
-- directly from the normal migration and never inherit another database role.
|
||||
do $$
|
||||
declare
|
||||
runtime_role name;
|
||||
parent_role name;
|
||||
begin
|
||||
foreach runtime_role in array array['tiku_api'::name, 'tiku_worker'::name]
|
||||
loop
|
||||
for parent_role in
|
||||
select parent.rolname
|
||||
from pg_auth_members membership
|
||||
join pg_roles member on member.oid = membership.member
|
||||
join pg_roles parent on parent.oid = membership.roleid
|
||||
where member.rolname = runtime_role
|
||||
loop
|
||||
execute format('revoke %I from %I', parent_role, runtime_role);
|
||||
end loop;
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
comment on role tiku_api is
|
||||
'Trusted API-only runtime. Cluster attributes are provisioned by the database administrator; object ACLs are managed by migration 202607120013.';
|
||||
|
||||
comment on role tiku_worker is
|
||||
'Trusted background-worker runtime. Cluster attributes are provisioned by the database administrator; object ACLs are managed by migration 202607120013.';
|
||||
Reference in New Issue
Block a user