Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ansible/files/postgresql_config/supautils.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ supautils.restrict_extension_versions = 'warn'
supautils.privileged_extensions_superuser = 'supabase_admin'
supautils.privileged_role = 'supabase_privileged_role'
supautils.privileged_role_allowed_configs = 'auto_explain.*, deadlock_timeout, log_duration, log_lock_waits, log_min_duration_statement, log_min_error_statement, log_min_messages, log_parameter_max_length, log_replication_commands, log_statement, log_temp_files, pg_net.batch_size, pg_net.ttl, pg_stat_statements.*, pgaudit.log, pgaudit.log_catalog, pgaudit.log_client, pgaudit.log_level, pgaudit.log_relation, pgaudit.log_rows, pgaudit.log_statement, pgaudit.log_statement_once, pgaudit.role, pgrst.*, plan_filter.*, safeupdate.enabled, session_replication_role, track_functions, track_io_timing, wal_compression'
supautils.reserved_memberships = 'pg_read_server_files, pg_write_server_files, pg_execute_server_program, supabase_admin, supabase_auth_admin, supabase_storage_admin, supabase_read_only_user, supabase_realtime_admin, supabase_replication_admin, supabase_etl_admin, dashboard_user, pgbouncer, authenticator, pg_write_all_data'
supautils.reserved_roles = 'supabase_admin, supabase_auth_admin, supabase_storage_admin, supabase_read_only_user, supabase_realtime_admin, supabase_replication_admin, supabase_etl_admin, dashboard_user, pgbouncer, service_role*, authenticator*, authenticated*, anon*, supabase_privileged_role'
supautils.reserved_memberships = 'pg_read_server_files, pg_write_server_files, pg_execute_server_program, supabase_admin, supabase_auth_admin, supabase_storage_admin, supabase_read_only_user, supabase_realtime_admin, supabase_replication_admin, supabase_etl_admin, supabase_monitor_admin, dashboard_user, pgbouncer, authenticator, pg_write_all_data'
supautils.reserved_roles = 'supabase_admin, supabase_auth_admin, supabase_storage_admin, supabase_read_only_user, supabase_realtime_admin, supabase_replication_admin, supabase_etl_admin, supabase_monitor_admin, dashboard_user, pgbouncer, service_role*, authenticator*, authenticated*, anon*, supabase_privileged_role'
supautils.hint_roles = 'anon, authenticated, service_role'
13 changes: 13 additions & 0 deletions migrations/db/init-scripts/00000000000000-initial-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ grant create on database postgres to supabase_etl_admin;
create role supabase_read_only_user with login bypassrls;
grant pg_read_all_data to supabase_read_only_user;

-- Supabase monitoring user: no password and can only connect via localhost,
-- reads every table
-- bypassing RLS, read-only, owns nothing.
-- so the name can never be pre-empted: a plain CREATE fails loudly if it exists.
create role supabase_monitor_admin with login bypassrls connection limit 2;
grant pg_read_all_data, pg_monitor to supabase_monitor_admin;
alter role supabase_monitor_admin set default_transaction_read_only = on;
alter role supabase_monitor_admin set transaction_read_only = on;
alter role supabase_monitor_admin set search_path = '';
alter role supabase_monitor_admin set statement_timeout = '3s';
alter role supabase_monitor_admin set lock_timeout = '3s';
alter role supabase_monitor_admin set idle_in_transaction_session_timeout = '3s';

Comment on lines +22 to +34

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to docs this should be in its own migration

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking over the PR, @mmlb! I was following the example set in the request:

It created the supabase_etl_admin user.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we don't have CI catching this atm and if its not a test then it doesn't get enforced :(, but I'd appreciate doing as the docs say

-- Extension namespacing
create schema if not exists extensions;
create extension if not exists "uuid-ossp" with schema extensions;
Expand Down
7 changes: 7 additions & 0 deletions migrations/tests/database/privs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ SELECT schema_privs_are('extensions', 'service_role', array['USAGE']);
-- Role memberships
SELECT is_member_of('pg_read_all_data', 'postgres');
SELECT is_member_of('pg_signal_backend', 'postgres');

-- read-only monitoring role.
SELECT isnt_superuser('supabase_monitor_admin');
SELECT is_member_of('pg_read_all_data', 'supabase_monitor_admin');
SELECT is_member_of('pg_monitor', 'supabase_monitor_admin');
SELECT database_privs_are(current_database(), 'supabase_monitor_admin', ARRAY['CONNECT', 'TEMPORARY']);
SELECT schema_privs_are('public', 'supabase_monitor_admin', array['USAGE']);
10 changes: 6 additions & 4 deletions nix/tests/expected/roles.out
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,21 @@ order by rolname;
supabase_auth_admin | t | t | f | f | f | f | -1 | f |
supabase_etl_admin | f | t | f | t | f | t | -1 | t |
supabase_functions_admin | t | t | f | f | f | f | -1 | f |
supabase_monitor_admin | f | t | f | t | f | f | 2 | t |
supabase_privileged_role | f | f | f | t | f | f | -1 | f |
supabase_read_only_user | f | t | f | t | f | f | -1 | t |
supabase_replication_admin | f | t | f | t | f | t | -1 | f |
supabase_storage_admin | t | t | f | f | f | f | -1 | f |
(31 rows)
(32 rows)

select
rolname,
rolconfig
from pg_roles r
where rolname not in ('pg_create_subscription', 'pg_maintain', 'pg_use_reserved_connections')
order by rolname;
rolname | rolconfig
----------------------------+------------------------------------------------------------------------------------------
rolname | rolconfig
----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------
anon | {statement_timeout=3s}
authenticated | {statement_timeout=8s}
authenticator | {"session_preload_libraries=supautils, safeupdate",statement_timeout=8s,lock_timeout=8s}
Expand Down Expand Up @@ -89,11 +90,12 @@ order by rolname;
supabase_auth_admin | {search_path=auth,idle_in_transaction_session_timeout=60000,log_statement=none}
supabase_etl_admin |
supabase_functions_admin |
supabase_monitor_admin | {default_transaction_read_only=on,transaction_read_only=on,"search_path=\"\"",statement_timeout=3s,lock_timeout=3s,idle_in_transaction_session_timeout=3s}
supabase_privileged_role |
supabase_read_only_user | {default_transaction_read_only=on}
supabase_replication_admin |
supabase_storage_admin | {search_path=storage,log_statement=none}
(31 rows)
(32 rows)

-- Check all privileges of the roles on the schemas
select schema_name, privilege_type, grantee, default_for
Expand Down
4 changes: 3 additions & 1 deletion nix/tests/expected/z_15_roles.out
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ order by
supabase_etl_admin | pg_monitor | f
supabase_etl_admin | pg_read_all_data | f
supabase_etl_admin | supabase_privileged_role | f
supabase_monitor_admin | pg_monitor | f
supabase_monitor_admin | pg_read_all_data | f
supabase_read_only_user | pg_monitor | f
supabase_read_only_user | pg_read_all_data | f
supabase_storage_admin | authenticator | f
(23 rows)
(25 rows)

-- Check all privileges of non-superuser roles on functions
select
Expand Down
100 changes: 100 additions & 0 deletions nix/tests/expected/z_15_supabase_monitor_admin.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
-- supabase_monitor_admin: passwordless, read-only
-- monitoring role (
-- migrations/db/init-scripts/00000000000000-initial-schema.sql).
-- attributes: login, no password, bypassrls, nothing else
select rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin,
rolreplication, rolbypassrls, rolconnlimit, rolpassword is null as no_password
from pg_authid where rolname = 'supabase_monitor_admin';
rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | no_password
----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------
f | t | f | f | t | f | t | 2 | t
(1 row)

-- per-role settings (read-only by default, empty search_path, timeouts)
select unnest(rolconfig) as setting from pg_roles where rolname = 'supabase_monitor_admin' order by 1;
setting
----------------------------------------
default_transaction_read_only=on
idle_in_transaction_session_timeout=3s
lock_timeout=3s
search_path=""
statement_timeout=3s
transaction_read_only=on
(6 rows)

-- memberships: exactly pg_monitor and pg_read_all_data
select roleid::regrole::text as role, admin_option
from pg_auth_members where member = 'supabase_monitor_admin'::regrole order by 1;
role | admin_option
------------------+--------------
pg_monitor | f
pg_read_all_data | f
(2 rows)

-- nobody is a member of it
select count(*) from pg_auth_members where roleid = 'supabase_monitor_admin'::regrole;
count
-------
0
(1 row)

-- owns nothing and holds no object-level ACL entries anywhere
select count(*) from pg_shdepend
where refclassid = 'pg_authid'::regclass
and refobjid = 'supabase_monitor_admin'::regrole
and deptype in ('o', 'a');
count
-------
0
(1 row)

-- no CREATE on the database
select has_database_privilege('supabase_monitor_admin', current_database(), 'CREATE');
has_database_privilege
------------------------
f
(1 row)

-- reads every table regardless of RLS, but cannot write. NOTE: this uses
-- SET SESSION AUTHORIZATION, which a future supautils impersonation guard
-- would block; switch to a probe role with identical grants when that lands
-- (see quick_explainer.md).
create table public.monitor_probe (id int);
insert into public.monitor_probe values (1), (2);
alter table public.monitor_probe enable row level security;
alter table public.monitor_probe force row level security;
alter table public.monitor_probe owner to postgres;
set session authorization supabase_monitor_admin;
select count(*) from public.monitor_probe;
count
-------
2
(1 row)

insert into public.monitor_probe values (3);
ERROR: permission denied for table monitor_probe
reset session authorization;
drop table public.monitor_probe;
-- reserved role (supautils.conf.j2): the non-superuser postgres role cannot
-- grant it, modify it, grant to it, or drop it. Same pattern as
-- supautils_reserved_roles.sql.
begin;
set role postgres;
savepoint s;
grant supabase_monitor_admin to postgres;
ERROR: "supabase_monitor_admin" role memberships are reserved, only superusers can grant them
rollback to savepoint s;
alter role supabase_monitor_admin password 'x';
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
alter role supabase_monitor_admin set search_path = public;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
grant authenticated to supabase_monitor_admin;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
drop role supabase_monitor_admin;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
reset role;
rollback;
8 changes: 6 additions & 2 deletions nix/tests/expected/z_17_roles.out
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ order by
supabase_etl_admin | pg_monitor | f
supabase_etl_admin | pg_read_all_data | f
supabase_etl_admin | supabase_privileged_role | f
supabase_monitor_admin | pg_monitor | f
supabase_monitor_admin | pg_read_all_data | f
supabase_read_only_user | pg_monitor | f
supabase_read_only_user | pg_read_all_data | f
supabase_storage_admin | authenticator | f
(25 rows)
(27 rows)

-- Check version-specific privileges of the roles on the schemas
select schema_name, privilege_type, grantee, default_for
Expand Down Expand Up @@ -166,10 +168,12 @@ order by
supabase_etl_admin | pg_monitor | f
supabase_etl_admin | pg_read_all_data | f
supabase_etl_admin | supabase_privileged_role | f
supabase_monitor_admin | pg_monitor | f
supabase_monitor_admin | pg_read_all_data | f
supabase_read_only_user | pg_monitor | f
supabase_read_only_user | pg_read_all_data | f
supabase_storage_admin | authenticator | f
(24 rows)
(26 rows)

-- Check all privileges of non-superuser roles on functions
select
Expand Down
109 changes: 109 additions & 0 deletions nix/tests/expected/z_17_supabase_monitor_admin.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
-- supabase_monitor_admin: passwordless, read-only, for monitoring from localhost
-- migrations/db/init-scripts/00000000000000-initial-schema.sql)
-- attributes: login, no password, bypassrls, nothing else
-- only accessible via localhost
select rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin,
rolreplication, rolbypassrls, rolconnlimit, rolpassword is null as no_password
from pg_authid where rolname = 'supabase_monitor_admin';
rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | no_password
----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------
f | t | f | f | t | f | t | 2 | t
(1 row)

-- per-role settings (read-only by default, empty search_path, timeouts)
select unnest(rolconfig) as setting from pg_roles where rolname = 'supabase_monitor_admin' order by 1;
setting
----------------------------------------
default_transaction_read_only=on
idle_in_transaction_session_timeout=3s
lock_timeout=3s
search_path=""
statement_timeout=3s
transaction_read_only=on
(6 rows)

-- memberships: exactly pg_monitor and pg_read_all_data (inherited; the role is
-- a plain INHERIT role). SET ROLE to them only drops privileges, checked below.
select roleid::regrole::text as role, admin_option, inherit_option, set_option
from pg_auth_members where member = 'supabase_monitor_admin'::regrole order by 1;
role | admin_option | inherit_option | set_option
------------------+--------------+----------------+------------
pg_monitor | f | t | t
pg_read_all_data | f | t | t
(2 rows)

-- nobody is a member of it
select count(*) from pg_auth_members where roleid = 'supabase_monitor_admin'::regrole;
count
-------
0
(1 row)

-- owns nothing and holds no object-level ACL entries anywhere
-- keep in mind, the postgres role can assign it access to objects at a later point, but that's fine.
select count(*) from pg_shdepend
where refclassid = 'pg_authid'::regclass
and refobjid = 'supabase_monitor_admin'::regrole
and deptype in ('o', 'a');
count
-------
0
(1 row)

-- no CREATE on the database
select has_database_privilege('supabase_monitor_admin', current_database(), 'CREATE');
has_database_privilege
------------------------
f
(1 row)

-- reads every table regardless of RLS, but cannot write; SET ROLE to a
-- predefined role only loses privileges (BYPASSRLS stays with supabase_monitor_admin,
-- so the RLS-protected rows disappear). NOTE: this uses SET SESSION
-- AUTHORIZATION, which supautils impersonation may block
create table public.monitor_probe (id int);
insert into public.monitor_probe values (1), (2);
alter table public.monitor_probe enable row level security;
alter table public.monitor_probe force row level security;
alter table public.monitor_probe owner to postgres;
set session authorization supabase_monitor_admin;
select count(*) from public.monitor_probe;
count
-------
2
(1 row)

insert into public.monitor_probe values (3);
ERROR: permission denied for table monitor_probe
set role pg_read_all_data;
select count(*) from public.monitor_probe;
count
-------
0
(1 row)

reset session authorization;
drop table public.monitor_probe;
-- reserved role (supautils.conf.j2): the non-superuser postgres role cannot
-- grant it, modify it, grant to it, or drop it. Same pattern as
-- supautils_reserved_roles.sql.
begin;
set role postgres;
savepoint s;
grant supabase_monitor_admin to postgres;
ERROR: "supabase_monitor_admin" role memberships are reserved, only superusers can grant them
rollback to savepoint s;
alter role supabase_monitor_admin password 'x';
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
alter role supabase_monitor_admin set search_path = public;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
grant authenticated to supabase_monitor_admin;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
drop role supabase_monitor_admin;
ERROR: "supabase_monitor_admin" is a reserved role, only superusers can modify it
rollback to savepoint s;
reset role;
rollback;
8 changes: 6 additions & 2 deletions nix/tests/expected/z_multigres-17_roles.out
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ order by
supabase_etl_admin | pg_monitor | f
supabase_etl_admin | pg_read_all_data | f
supabase_etl_admin | supabase_privileged_role | f
supabase_monitor_admin | pg_monitor | f
supabase_monitor_admin | pg_read_all_data | f
supabase_read_only_user | pg_monitor | f
supabase_read_only_user | pg_read_all_data | f
supabase_storage_admin | authenticator | f
(22 rows)
(24 rows)

-- Check version-specific privileges of the roles on the schemas
select schema_name, privilege_type, grantee, default_for
Expand Down Expand Up @@ -158,10 +160,12 @@ order by
supabase_etl_admin | pg_monitor | f
supabase_etl_admin | pg_read_all_data | f
supabase_etl_admin | supabase_privileged_role | f
supabase_monitor_admin | pg_monitor | f
supabase_monitor_admin | pg_read_all_data | f
supabase_read_only_user | pg_monitor | f
supabase_read_only_user | pg_read_all_data | f
supabase_storage_admin | authenticator | f
(21 rows)
(23 rows)

-- Check all privileges of non-superuser roles on functions
select
Expand Down
Loading
Loading