@@ -83,3 +83,393 @@ CI_start_postgres() {
83
83
84
84
su postgres -c " $BINDIR /pg_ctl start -o '-c config_file=/etc/postgresql/postgresql.conf' -l /tmp/postgres.log"
85
85
}
86
+
87
+ swap_postgres_and_supabase_admin () {
88
+ run_sql << 'EOSQL '
89
+ begin;
90
+ create role supabase_tmp superuser;
91
+ set session authorization supabase_tmp;
92
+
93
+ do $$
94
+ declare
95
+ postgres_rolpassword text := (select rolpassword from pg_authid where rolname = 'postgres');
96
+ supabase_admin_rolpassword text := (select rolpassword from pg_authid where rolname = 'supabase_admin');
97
+ role_settings jsonb[] := (
98
+ select coalesce(array_agg(jsonb_build_object('database', d.datname, 'role', a.rolname, 'configs', s.setconfig)), '{}')
99
+ from pg_db_role_setting s
100
+ left join pg_database d on d.oid = s.setdatabase
101
+ join pg_authid a on a.oid = s.setrole
102
+ where a.rolname in ('postgres', 'supabase_admin')
103
+ );
104
+ event_triggers jsonb[] := (select coalesce(array_agg(jsonb_build_object('name', evtname)), '{}') from pg_event_trigger where evtowner = 'postgres'::regrole);
105
+ user_mappings jsonb[] := (
106
+ select coalesce(array_agg(jsonb_build_object('oid', um.oid, 'role', a.rolname, 'server', s.srvname, 'options', um.umoptions)), '{}')
107
+ from pg_user_mapping um
108
+ join pg_authid a on a.oid = um.umuser
109
+ join pg_foreign_server s on s.oid = um.umserver
110
+ where a.rolname in ('postgres', 'supabase_admin')
111
+ );
112
+ -- Objects can have initial privileges either by having those privileges set
113
+ -- when the system is initialized (by initdb) or when the object is created
114
+ -- during a CREATE EXTENSION and the extension script sets initial
115
+ -- privileges using the GRANT system. (https://www.postgresql.org/docs/current/catalog-pg-init-privs.html)
116
+ -- We only care about swapping init_privs for extensions.
117
+ init_privs jsonb[] := (
118
+ select coalesce(array_agg(jsonb_build_object('objoid', objoid, 'classoid', classoid, 'initprivs', initprivs::text)), '{}')
119
+ from pg_init_privs
120
+ where privtype = 'e'
121
+ );
122
+ default_acls jsonb[] := (
123
+ select coalesce(array_agg(jsonb_build_object('oid', d.oid, 'role', a.rolname, 'schema', n.nspname, 'objtype', d.defaclobjtype, 'acl', defaclacl::text)), '{}')
124
+ from pg_default_acl d
125
+ join pg_authid a on a.oid = d.defaclrole
126
+ left join pg_namespace n on n.oid = d.defaclnamespace
127
+ );
128
+ schemas jsonb[] := (
129
+ select coalesce(array_agg(jsonb_build_object('oid', n.oid, 'owner', a.rolname, 'acl', nspacl::text)), '{}')
130
+ from pg_namespace n
131
+ join pg_authid a on a.oid = n.nspowner
132
+ where true
133
+ and n.nspname != 'information_schema'
134
+ and not starts_with(n.nspname, 'pg_')
135
+ );
136
+ types jsonb[] := (
137
+ select coalesce(array_agg(jsonb_build_object('oid', t.oid, 'owner', a.rolname, 'acl', t.typacl::text)), '{}')
138
+ from pg_type t
139
+ join pg_namespace n on n.oid = t.typnamespace
140
+ join pg_authid a on a.oid = t.typowner
141
+ where true
142
+ and n.nspname != 'information_schema'
143
+ and not starts_with(n.nspname, 'pg_')
144
+ and (
145
+ t.typrelid = 0
146
+ or (
147
+ select
148
+ c.relkind = 'c'
149
+ from
150
+ pg_class c
151
+ where
152
+ c.oid = t.typrelid
153
+ )
154
+ )
155
+ and not exists (
156
+ select
157
+ from
158
+ pg_type el
159
+ where
160
+ el.oid = t.typelem
161
+ and el.typarray = t.oid
162
+ )
163
+ );
164
+ functions jsonb[] := (
165
+ select coalesce(array_agg(jsonb_build_object('oid', p.oid, 'owner', a.rolname, 'acl', p.proacl::text)), '{}')
166
+ from pg_proc p
167
+ join pg_namespace n on n.oid = p.pronamespace
168
+ join pg_authid a on a.oid = p.proowner
169
+ where true
170
+ and n.nspname != 'information_schema'
171
+ and not starts_with(n.nspname, 'pg_')
172
+ );
173
+ relations jsonb[] := (
174
+ select coalesce(array_agg(jsonb_build_object('oid', c.oid, 'owner', a.rolname, 'acl', c.relacl::text)), '{}')
175
+ from pg_class c
176
+ join pg_namespace n on n.oid = c.relnamespace
177
+ join pg_authid a on a.oid = c.relowner
178
+ where true
179
+ and n.nspname != 'information_schema'
180
+ and not starts_with(n.nspname, 'pg_')
181
+ and c.relkind not in ('c', 'i')
182
+ );
183
+ rec record;
184
+ obj jsonb;
185
+ begin
186
+ set local search_path = '';
187
+
188
+ alter role postgres rename to supabase_admin_;
189
+ alter role supabase_admin rename to postgres;
190
+ alter role supabase_admin_ rename to supabase_admin;
191
+
192
+ -- role grants
193
+ for rec in
194
+ select * from pg_auth_members
195
+ loop
196
+ execute(format('revoke %I from %I;', rec.roleid::regrole, rec.member::regrole));
197
+ execute(format(
198
+ 'grant %I to %I %s granted by %I;',
199
+ case
200
+ when rec.roleid = 'postgres'::regrole then 'supabase_admin'
201
+ when rec.roleid = 'supabase_admin'::regrole then 'postgres'
202
+ else rec.roleid::regrole
203
+ end,
204
+ case
205
+ when rec.member = 'postgres'::regrole then 'supabase_admin'
206
+ when rec.member = 'supabase_admin'::regrole then 'postgres'
207
+ else rec.member::regrole
208
+ end,
209
+ case
210
+ when rec.admin_option then 'with admin option'
211
+ else ''
212
+ end,
213
+ case
214
+ when rec.grantor = 'postgres'::regrole then 'supabase_admin'
215
+ when rec.grantor = 'supabase_admin'::regrole then 'postgres'
216
+ else rec.grantor::regrole
217
+ end
218
+ ));
219
+ end loop;
220
+
221
+ -- role passwords
222
+ execute(format('alter role postgres password %L;', postgres_rolpassword));
223
+ execute(format('alter role supabase_admin password %L;', supabase_admin_rolpassword));
224
+
225
+ -- role settings
226
+ foreach obj in array role_settings
227
+ loop
228
+ execute(format('alter role %I %s reset all',
229
+ case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end,
230
+ case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end
231
+ ));
232
+ end loop;
233
+ foreach obj in array role_settings
234
+ loop
235
+ for rec in
236
+ select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value
237
+ from jsonb_array_elements_text(obj->'configs')
238
+ loop
239
+ execute(format('alter role %I %s set %I to %s',
240
+ obj->>'role',
241
+ case when obj->>'database' is null then '' else format('in database %I', obj->>'database') end,
242
+ rec.key,
243
+ rec.value
244
+ ));
245
+ end loop;
246
+ end loop;
247
+
248
+ reassign owned by postgres to supabase_admin;
249
+
250
+ -- databases
251
+ for rec in
252
+ select * from pg_database where datname not in ('template0')
253
+ loop
254
+ execute(format('alter database %I owner to postgres;', rec.datname));
255
+ end loop;
256
+
257
+ -- event triggers
258
+ foreach obj in array event_triggers
259
+ loop
260
+ execute(format('alter event trigger %I owner to postgres;', obj->>'name'));
261
+ end loop;
262
+
263
+ -- publications
264
+ for rec in
265
+ select * from pg_publication
266
+ loop
267
+ execute(format('alter publication %I owner to postgres;', rec.pubname));
268
+ end loop;
269
+
270
+ -- FDWs
271
+ for rec in
272
+ select * from pg_foreign_data_wrapper
273
+ loop
274
+ execute(format('alter foreign data wrapper %I owner to postgres;', rec.fdwname));
275
+ end loop;
276
+
277
+ -- foreign servers
278
+ for rec in
279
+ select * from pg_foreign_server
280
+ loop
281
+ execute(format('alter server %I owner to postgres;', rec.srvname));
282
+ end loop;
283
+
284
+ -- user mappings
285
+ foreach obj in array user_mappings
286
+ loop
287
+ execute(format('drop user mapping for %I server %I', case when obj->>'role' = 'postgres' then 'supabase_admin' else 'postgres' end, obj->>'server'));
288
+ end loop;
289
+ foreach obj in array user_mappings
290
+ loop
291
+ execute(format('create user mapping for %I server %I', obj->>'role', obj->>'server'));
292
+ for rec in
293
+ select split_part(value, '=', 1) as key, substr(value, strpos(value, '=') + 1) as value
294
+ from jsonb_array_elements_text(obj->'options')
295
+ loop
296
+ execute(format('alter user mapping for %I server %I options (%I %L)', obj->>'role', obj->>'server', rec.key, rec.value));
297
+ end loop;
298
+ end loop;
299
+
300
+ -- init privs
301
+ foreach obj in array init_privs
302
+ loop
303
+ -- We need to modify system catalog directly here because there's no ALTER INIT PRIVILEGES.
304
+ update pg_init_privs set initprivs = (obj->>'initprivs')::aclitem[] where objoid = (obj->>'objoid')::oid and classoid = (obj->>'classoid')::oid;
305
+ end loop;
306
+
307
+ -- default acls
308
+ foreach obj in array default_acls
309
+ loop
310
+ for rec in
311
+ select grantor, grantee, privilege_type, is_grantable
312
+ from aclexplode((obj->>'acl')::aclitem[])
313
+ loop
314
+ if obj->>'role' in ('postgres', 'supabase_admin') or rec.grantee::regrole in ('postgres', 'supabase_admin') then
315
+ execute(format('alter default privileges for role %I %s revoke %s on %s from %I'
316
+ , case when obj->>'role' = 'postgres' then 'supabase_admin'
317
+ when obj->>'role' = 'supabase_admin' then 'postgres'
318
+ else obj->>'role'
319
+ end
320
+ , case when obj->>'schema' is null then ''
321
+ else format('in schema %I', (obj->>'schema')::regnamespace)
322
+ end
323
+ , rec.privilege_type
324
+ , case when obj->>'objtype' = 'r' then 'tables'
325
+ when obj->>'objtype' = 'S' then 'sequences'
326
+ when obj->>'objtype' = 'f' then 'functions'
327
+ when obj->>'objtype' = 'T' then 'types'
328
+ when obj->>'objtype' = 'n' then 'schemas'
329
+ end
330
+ , case when rec.grantee = 'postgres'::regrole then 'supabase_admin'
331
+ when rec.grantee = 'supabase_admin'::regrole then 'postgres'
332
+ else rec.grantee::regrole
333
+ end
334
+ ));
335
+ end if;
336
+ end loop;
337
+ end loop;
338
+
339
+ foreach obj in array default_acls
340
+ loop
341
+ for rec in
342
+ select grantor, grantee, privilege_type, is_grantable
343
+ from aclexplode((obj->>'acl')::aclitem[])
344
+ loop
345
+ if obj->>'role' in ('postgres', 'supabase_admin') or rec.grantee::regrole in ('postgres', 'supabase_admin') then
346
+ execute(format('alter default privileges for role %I %s grant %s on %s to %I %s'
347
+ , obj->>'role'
348
+ , case when obj->>'schema' is null then ''
349
+ else format('in schema %I', (obj->>'schema')::regnamespace)
350
+ end
351
+ , rec.privilege_type
352
+ , case when obj->>'objtype' = 'r' then 'tables'
353
+ when obj->>'objtype' = 'S' then 'sequences'
354
+ when obj->>'objtype' = 'f' then 'functions'
355
+ when obj->>'objtype' = 'T' then 'types'
356
+ when obj->>'objtype' = 'n' then 'schemas'
357
+ end
358
+ , rec.grantee::regrole
359
+ , case when rec.is_grantable then 'with grant option' else '' end
360
+ ));
361
+ end if;
362
+ end loop;
363
+ end loop;
364
+
365
+ -- schemas
366
+ foreach obj in array schemas
367
+ loop
368
+ if obj->>'owner' = 'postgres' then
369
+ execute(format('alter schema %s owner to postgres;', (obj->>'oid')::regnamespace));
370
+ end if;
371
+ for rec in
372
+ select grantor, grantee, privilege_type, is_grantable
373
+ from aclexplode((obj->>'acl')::aclitem[])
374
+ where grantee::regrole in ('postgres', 'supabase_admin')
375
+ loop
376
+ 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));
377
+ end loop;
378
+ end loop;
379
+ foreach obj in array schemas
380
+ loop
381
+ for rec in
382
+ select grantor, grantee, privilege_type, is_grantable
383
+ from aclexplode((obj->>'acl')::aclitem[])
384
+ where grantee::regrole in ('postgres', 'supabase_admin')
385
+ loop
386
+ 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));
387
+ end loop;
388
+ end loop;
389
+
390
+ -- types
391
+ foreach obj in array types
392
+ loop
393
+ if obj->>'owner' = 'postgres' then
394
+ execute(format('alter type %s owner to postgres;', (obj->>'oid')::regtype));
395
+ end if;
396
+ for rec in
397
+ select grantor, grantee, privilege_type, is_grantable
398
+ from aclexplode((obj->>'acl')::aclitem[])
399
+ where grantee::regrole in ('postgres', 'supabase_admin')
400
+ loop
401
+ 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));
402
+ end loop;
403
+ end loop;
404
+ foreach obj in array types
405
+ loop
406
+ for rec in
407
+ select grantor, grantee, privilege_type, is_grantable
408
+ from aclexplode((obj->>'acl')::aclitem[])
409
+ where grantee::regrole in ('postgres', 'supabase_admin')
410
+ loop
411
+ 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));
412
+ end loop;
413
+ end loop;
414
+
415
+ -- functions
416
+ foreach obj in array functions
417
+ loop
418
+ if obj->>'owner' = 'postgres' then
419
+ execute(format('alter routine %s(%s) owner to postgres;', (obj->>'oid')::regproc, pg_get_function_identity_arguments((obj->>'oid')::regproc)));
420
+ end if;
421
+ for rec in
422
+ select grantor, grantee, privilege_type, is_grantable
423
+ from aclexplode((obj->>'acl')::aclitem[])
424
+ where grantee::regrole in ('postgres', 'supabase_admin')
425
+ loop
426
+ 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));
427
+ end loop;
428
+ end loop;
429
+ foreach obj in array functions
430
+ loop
431
+ for rec in
432
+ select grantor, grantee, privilege_type, is_grantable
433
+ from aclexplode((obj->>'acl')::aclitem[])
434
+ where grantee::regrole in ('postgres', 'supabase_admin')
435
+ loop
436
+ 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));
437
+ end loop;
438
+ end loop;
439
+
440
+ -- relations
441
+ foreach obj in array relations
442
+ loop
443
+ -- obj->>'oid' (text) needs to be casted to oid first for some reason
444
+
445
+ if obj->>'owner' = 'postgres' then
446
+ execute(format('alter table %s owner to postgres;', (obj->>'oid')::oid::regclass));
447
+ end if;
448
+ for rec in
449
+ select grantor, grantee, privilege_type, is_grantable
450
+ from aclexplode((obj->>'acl')::aclitem[])
451
+ where grantee::regrole in ('postgres', 'supabase_admin')
452
+ loop
453
+ 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));
454
+ end loop;
455
+ end loop;
456
+ foreach obj in array relations
457
+ loop
458
+ -- obj->>'oid' (text) needs to be casted to oid first for some reason
459
+
460
+ for rec in
461
+ select grantor, grantee, privilege_type, is_grantable
462
+ from aclexplode((obj->>'acl')::aclitem[])
463
+ where grantee::regrole in ('postgres', 'supabase_admin')
464
+ loop
465
+ 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));
466
+ end loop;
467
+ end loop;
468
+ end
469
+ $$;
470
+
471
+ set session authorization supabase_admin;
472
+ drop role supabase_tmp;
473
+ commit;
474
+ EOSQL
475
+ }
0 commit comments