diff --git a/migrations/db/migrations/20250417190610_update_pgbouncer_get_auth.sql b/migrations/db/migrations/20250417190610_update_pgbouncer_get_auth.sql new file mode 100644 index 000000000..5e6e6a582 --- /dev/null +++ b/migrations/db/migrations/20250417190610_update_pgbouncer_get_auth.sql @@ -0,0 +1,24 @@ +-- migrate:up + +create or replace function pgbouncer.get_auth(p_usename text) returns table (username text, password text) + language plpgsql security definer + as $$ +begin + raise debug 'PgBouncer auth request: %', p_usename; + + return query + select + rolname::text, + case when rolvaliduntil < now() + then null + else rolpassword::text + end + from pg_authid + where rolname=$1 and rolcanlogin; +end; +$$; + +-- from migrations/db/migrations/20250312095419_pgbouncer_ownership.sql +grant execute on function pgbouncer.get_auth(p_usename text) to postgres; + +-- migrate:down diff --git a/migrations/schema-15.sql b/migrations/schema-15.sql index 802fbae79..8ff507a84 100644 --- a/migrations/schema-15.sql +++ b/migrations/schema-15.sql @@ -483,15 +483,21 @@ COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeh CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text) LANGUAGE plpgsql SECURITY DEFINER - AS $$ -BEGIN - RAISE WARNING 'PgBouncer auth request: %', p_usename; - - RETURN QUERY - SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow - WHERE usename = p_usename; -END; -$$; + AS $_$ +begin + raise debug 'PgBouncer auth request: %', p_usename; + + return query + select + rolname::text, + case when rolvaliduntil < now() + then null + else rolpassword::text + end + from pg_authid + where rolname=$1 and rolcanlogin; +end; +$_$; -- diff --git a/migrations/schema-17.sql b/migrations/schema-17.sql index eac1280b1..06fe1e7e9 100644 --- a/migrations/schema-17.sql +++ b/migrations/schema-17.sql @@ -470,15 +470,21 @@ COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeh CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text) LANGUAGE plpgsql SECURITY DEFINER - AS $$ -BEGIN - RAISE WARNING 'PgBouncer auth request: %', p_usename; - - RETURN QUERY - SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow - WHERE usename = p_usename; -END; -$$; + AS $_$ +begin + raise debug 'PgBouncer auth request: %', p_usename; + + return query + select + rolname::text, + case when rolvaliduntil < now() + then null + else rolpassword::text + end + from pg_authid + where rolname=$1 and rolcanlogin; +end; +$_$; -- diff --git a/migrations/schema-orioledb-17.sql b/migrations/schema-orioledb-17.sql index 013b23d1f..1cd6aecaa 100644 --- a/migrations/schema-orioledb-17.sql +++ b/migrations/schema-orioledb-17.sql @@ -484,15 +484,21 @@ COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeh CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text) LANGUAGE plpgsql SECURITY DEFINER - AS $$ -BEGIN - RAISE WARNING 'PgBouncer auth request: %', p_usename; - - RETURN QUERY - SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow - WHERE usename = p_usename; -END; -$$; + AS $_$ +begin + raise debug 'PgBouncer auth request: %', p_usename; + + return query + select + rolname::text, + case when rolvaliduntil < now() + then null + else rolpassword::text + end + from pg_authid + where rolname=$1 and rolcanlogin; +end; +$_$; -- diff --git a/migrations/tests/database/exists.sql b/migrations/tests/database/exists.sql index 54b2a3861..bc19cd32f 100644 --- a/migrations/tests/database/exists.sql +++ b/migrations/tests/database/exists.sql @@ -1,6 +1,7 @@ SELECT has_schema('public'); SELECT has_schema('auth'); +SELECT has_schema('pgbouncer'); SELECT has_schema('extensions'); SELECT has_schema('graphql'); SELECT has_schema('graphql_public'); diff --git a/nix/tests/expected/pgbouncer.out b/nix/tests/expected/pgbouncer.out new file mode 100644 index 000000000..17f2c6293 --- /dev/null +++ b/nix/tests/expected/pgbouncer.out @@ -0,0 +1,68 @@ +-- pgbouncer schema owner +select + n.nspname as schema_name, + r.rolname as owner +from + pg_namespace n +join + pg_roles r on n.nspowner = r.oid +where + n.nspname = 'pgbouncer'; + schema_name | owner +-------------+----------- + pgbouncer | pgbouncer +(1 row) + +-- pgbouncer schema functions with owners +select + n.nspname as schema_name, + p.proname as function_name, + r.rolname as owner +from + pg_proc p +join + pg_namespace n on p.pronamespace = n.oid +join + pg_roles r on p.proowner = r.oid +where + n.nspname = 'pgbouncer' +order by + p.proname; + schema_name | function_name | owner +-------------+---------------+---------------- + pgbouncer | get_auth | supabase_admin +(1 row) + +-- Tests role privileges on the pgbouncer objects +WITH schema_obj AS ( + SELECT oid, nspname + FROM pg_namespace + WHERE nspname = 'pgbouncer' +) +SELECT + s.nspname AS schema, + c.relname AS object_name, + acl.grantee::regrole::text AS grantee, + acl.privilege_type +FROM pg_class c +JOIN schema_obj s ON s.oid = c.relnamespace +CROSS JOIN LATERAL aclexplode(c.relacl) AS acl +WHERE c.relkind IN ('r', 'v', 'm', 'f', 'p') + AND acl.privilege_type <> 'MAINTAIN' +UNION ALL +SELECT + s.nspname AS schema, + p.proname AS object_name, + acl.grantee::regrole::text AS grantee, + acl.privilege_type +FROM pg_proc p +JOIN schema_obj s ON s.oid = p.pronamespace +CROSS JOIN LATERAL aclexplode(p.proacl) AS acl +ORDER BY object_name, grantee, privilege_type; + schema | object_name | grantee | privilege_type +-----------+-------------+----------------+---------------- + pgbouncer | get_auth | pgbouncer | EXECUTE + pgbouncer | get_auth | postgres | EXECUTE + pgbouncer | get_auth | supabase_admin | EXECUTE +(3 rows) + diff --git a/nix/tests/sql/pgbouncer.sql b/nix/tests/sql/pgbouncer.sql new file mode 100644 index 000000000..4ddac10e2 --- /dev/null +++ b/nix/tests/sql/pgbouncer.sql @@ -0,0 +1,53 @@ +-- pgbouncer schema owner +select + n.nspname as schema_name, + r.rolname as owner +from + pg_namespace n +join + pg_roles r on n.nspowner = r.oid +where + n.nspname = 'pgbouncer'; + +-- pgbouncer schema functions with owners +select + n.nspname as schema_name, + p.proname as function_name, + r.rolname as owner +from + pg_proc p +join + pg_namespace n on p.pronamespace = n.oid +join + pg_roles r on p.proowner = r.oid +where + n.nspname = 'pgbouncer' +order by + p.proname; + +-- Tests role privileges on the pgbouncer objects +WITH schema_obj AS ( + SELECT oid, nspname + FROM pg_namespace + WHERE nspname = 'pgbouncer' +) +SELECT + s.nspname AS schema, + c.relname AS object_name, + acl.grantee::regrole::text AS grantee, + acl.privilege_type +FROM pg_class c +JOIN schema_obj s ON s.oid = c.relnamespace +CROSS JOIN LATERAL aclexplode(c.relacl) AS acl +WHERE c.relkind IN ('r', 'v', 'm', 'f', 'p') + AND acl.privilege_type <> 'MAINTAIN' +UNION ALL +SELECT + s.nspname AS schema, + p.proname AS object_name, + acl.grantee::regrole::text AS grantee, + acl.privilege_type +FROM pg_proc p +JOIN schema_obj s ON s.oid = p.pronamespace +CROSS JOIN LATERAL aclexplode(p.proacl) AS acl +ORDER BY object_name, grantee, privilege_type;