diff --git a/ansible/files/postgresql_extension_custom_scripts/pg_cron/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pg_cron/after-create.sql index 6ac9d6b6ea..4f1886767f 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pg_cron/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pg_cron/after-create.sql @@ -11,3 +11,4 @@ alter default privileges for user supabase_admin in schema cron grant all grant all privileges on all tables in schema cron to postgres with grant option; revoke all on table cron.job from postgres; grant select on table cron.job to postgres with grant option; +revoke trigger on cron.job_run_details from postgres; diff --git a/migrations/db/migrations/20260730000000_revoke_trigger_on_cron_tables_from_postgres.sql b/migrations/db/migrations/20260730000000_revoke_trigger_on_cron_tables_from_postgres.sql new file mode 100644 index 0000000000..97dcb4143b --- /dev/null +++ b/migrations/db/migrations/20260730000000_revoke_trigger_on_cron_tables_from_postgres.sql @@ -0,0 +1,49 @@ +-- migrate:up + +-- postgres is granted ALL on the cron tables via `grant all ... in schema cron to +-- postgres`, which includes TRIGGER on cron.job_run_details. postgres does not need to +-- create triggers on that table, so revoke TRIGGER there and stop grant_pg_cron_access() +-- from re-granting it on a later CREATE EXTENSION (which re-runs `grant all`). cron.job is +-- already SELECT-only for postgres. + +do $$ +begin + if exists (select from pg_extension where extname = 'pg_cron') then + revoke trigger on cron.job_run_details from postgres; + end if; +end $$; + +CREATE OR REPLACE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF EXISTS ( + SELECT + FROM pg_event_trigger_ddl_commands() AS ev + JOIN pg_extension AS ext + ON ev.objid = ext.oid + WHERE ext.extname = 'pg_cron' + ) + THEN + grant usage on schema cron to postgres with grant option; + + alter default privileges in schema cron grant all on tables to postgres with grant option; + alter default privileges in schema cron grant all on functions to postgres with grant option; + alter default privileges in schema cron grant all on sequences to postgres with grant option; + + alter default privileges for user supabase_admin in schema cron grant all + on sequences to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on tables to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on functions to postgres with grant option; + + grant all privileges on all tables in schema cron to postgres with grant option; + revoke all on table cron.job from postgres; + grant select on table cron.job to postgres with grant option; + revoke trigger on cron.job_run_details from postgres; + END IF; +END; +$$; + +-- migrate:down diff --git a/migrations/schema-15.sql b/migrations/schema-15.sql index 0ee211e3c9..a8ee391864 100644 --- a/migrations/schema-15.sql +++ b/migrations/schema-15.sql @@ -195,6 +195,7 @@ BEGIN grant all privileges on all tables in schema cron to postgres with grant option; revoke all on table cron.job from postgres; grant select on table cron.job to postgres with grant option; + revoke trigger on cron.job_run_details from postgres; END IF; END; $$; diff --git a/migrations/schema-17.sql b/migrations/schema-17.sql index 30f476e50c..80f552d7f9 100644 --- a/migrations/schema-17.sql +++ b/migrations/schema-17.sql @@ -196,6 +196,7 @@ BEGIN grant all privileges on all tables in schema cron to postgres with grant option; revoke all on table cron.job from postgres; grant select on table cron.job to postgres with grant option; + revoke trigger on cron.job_run_details from postgres; END IF; END; $$; diff --git a/migrations/schema-orioledb-17.sql b/migrations/schema-orioledb-17.sql index 48319e2037..9b649dc19d 100644 --- a/migrations/schema-orioledb-17.sql +++ b/migrations/schema-orioledb-17.sql @@ -210,6 +210,7 @@ BEGIN grant all privileges on all tables in schema cron to postgres with grant option; revoke all on table cron.job from postgres; grant select on table cron.job to postgres with grant option; + revoke trigger on cron.job_run_details from postgres; END IF; END; $$; diff --git a/nix/checks.nix b/nix/checks.nix index d56a33306b..5a3d02ce77 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -253,6 +253,7 @@ "extensions_schema" # tests extension loading "roles" # includes roles/schemas from extensions not in CLI (pgtle, pgmq, repack, topology) "pg_net_worker_privileges" # needs the authenticated/postgres roles from the full migrations, not present in the CLI prime file + "pg_cron_trigger_privileges" # needs pg_cron + the postgres role and cron-schema grants from the full migrations, not in the CLI prime file "supautils_restrict_versions" # needs the postgres role + primed hstore from the full migrations/prime, not present in the CLI variant # Version-specific extension tests "z_17_ext_interface" diff --git a/nix/tests/expected/pg_cron_trigger_privileges.out b/nix/tests/expected/pg_cron_trigger_privileges.out new file mode 100644 index 0000000000..f1f77a0b1d --- /dev/null +++ b/nix/tests/expected/pg_cron_trigger_privileges.out @@ -0,0 +1,27 @@ +-- Regression test: postgres should not hold TRIGGER on the cron tables. +-- +-- postgres is granted ALL on the cron schema, so without the fix it also gets TRIGGER on +-- cron.job_run_details, which it does not need. Creating the extension fires +-- extensions.grant_pg_cron_access(), which grants postgres access to the cron schema and +-- then revokes TRIGGER on cron.job_run_details. This test asserts postgres ends up without +-- TRIGGER on either cron table (cron.job is already SELECT-only) while keeping SELECT. It +-- runs inside a transaction that is rolled back, so creating pg_cron here does not pollute +-- the shared regression database (the roles and extension-enumeration tests run +-- afterwards). Silent on success, raises on regression. +begin; +set local client_min_messages = warning; +create extension if not exists pg_cron; +do $$ +begin + if has_table_privilege('postgres', 'cron.job_run_details', 'TRIGGER') then + raise exception 'postgres still has TRIGGER on cron.job_run_details'; + end if; + if has_table_privilege('postgres', 'cron.job', 'TRIGGER') then + raise exception 'postgres still has TRIGGER on cron.job'; + end if; + if not has_table_privilege('postgres', 'cron.job_run_details', 'SELECT') then + raise exception 'postgres unexpectedly lost SELECT on cron.job_run_details'; + end if; +end +$$; +rollback; diff --git a/nix/tests/sql/pg_cron_trigger_privileges.sql b/nix/tests/sql/pg_cron_trigger_privileges.sql new file mode 100644 index 0000000000..57fef02abb --- /dev/null +++ b/nix/tests/sql/pg_cron_trigger_privileges.sql @@ -0,0 +1,31 @@ +-- Regression test: postgres should not hold TRIGGER on the cron tables. +-- +-- postgres is granted ALL on the cron schema, so without the fix it also gets TRIGGER on +-- cron.job_run_details, which it does not need. Creating the extension fires +-- extensions.grant_pg_cron_access(), which grants postgres access to the cron schema and +-- then revokes TRIGGER on cron.job_run_details. This test asserts postgres ends up without +-- TRIGGER on either cron table (cron.job is already SELECT-only) while keeping SELECT. It +-- runs inside a transaction that is rolled back, so creating pg_cron here does not pollute +-- the shared regression database (the roles and extension-enumeration tests run +-- afterwards). Silent on success, raises on regression. +begin; + +set local client_min_messages = warning; + +create extension if not exists pg_cron; + +do $$ +begin + if has_table_privilege('postgres', 'cron.job_run_details', 'TRIGGER') then + raise exception 'postgres still has TRIGGER on cron.job_run_details'; + end if; + if has_table_privilege('postgres', 'cron.job', 'TRIGGER') then + raise exception 'postgres still has TRIGGER on cron.job'; + end if; + if not has_table_privilege('postgres', 'cron.job_run_details', 'SELECT') then + raise exception 'postgres unexpectedly lost SELECT on cron.job_run_details'; + end if; +end +$$; + +rollback;