-
Notifications
You must be signed in to change notification settings - Fork 2
Add extension-custom-scripts for supautils ownership/grant handoffs #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
moizpgedge
wants to merge
17
commits into
main
Choose a base branch
from
fix/pg-cron-ownership-handoff
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
0c404e3
Add extension_custom_scripts_path scripts for ownership/grant handoffs
moizpgedge e93955e
Generalize extension-custom-scripts: pg_database_owner, spock check
moizpgedge 4268f67
Fix postgis_topology's after-create.sql to cover the full lifecycle
moizpgedge 035a176
Remove lolor's before-create.sql, now handled by an event trigger
moizpgedge b0fbabe
Document why extension-custom-scripts can't cover a role-independent …
moizpgedge cf13df0
Fix lolor's trust flag instead of relying on session scoping
moizpgedge 2cbba3a
Gate restricted extensions with a role check instead of session scoping
moizpgedge f167734
Force row level security on cron.job so its own owner can't bypass it
moizpgedge fd85f16
fix: remove role-check before-create.sql scripts, gate is session-scoped
moizpgedge 3e8bd95
fix(address_standardizer_data_us): grant against the extension's real…
moizpgedge 0c6d4c9
docs(lolor): record that before-create.sql does fire despite no allow…
moizpgedge 8a312d4
test: exercise the gate and extension-custom-scripts, not just that s…
moizpgedge b8744fe
fix(address_standardizer_data_us): grant schema USAGE, not just table…
moizpgedge ac319d8
docs(Dockerfile): clarify the custom-scripts path is inert until conf…
moizpgedge 47bfa1b
fix(pg_cron): drop ownership and FORCE RLS, SELECT is enough
moizpgedge 5c2daad
fix(pg_cron,postgis): review follow-ups, extension-custom-scripts tes…
moizpgedge 5352166
fix: grant reference-data access to PUBLIC
moizpgedge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # extension-custom-scripts | ||
|
|
||
| Scripts for `supautils.extension_custom_scripts_path`, baked into the | ||
| `standard` image at `/etc/pgedge/extension-custom-scripts`. supautils | ||
| runs these around `CREATE EXTENSION`, as the superuser session it | ||
| already switches to for a privileged install (see | ||
| `supautils.superuser`), so a script here can assume superuser | ||
| privileges, not just the installing role's own. | ||
|
|
||
| Layout, per [supautils' own convention](https://github.com/supabase/supautils#readme): | ||
|
|
||
| ``` | ||
| extension-custom-scripts/ | ||
| <extension-name>/ | ||
| before-create.sql # optional, runs before CREATE EXTENSION | ||
| after-create.sql # optional, runs after CREATE EXTENSION | ||
| ``` | ||
|
|
||
| This image is not exclusive to any one deployment's role model, and an | ||
| extension can be installed into any database, owned by whatever role | ||
| happens to own it. A script granting access to "the role that should be | ||
| able to use this" should grant to | ||
| [`pg_database_owner`](https://www.postgresql.org/docs/current/predefined-roles.html#PREDEFINED-ROLE-PG-DATABASE-OWNER), | ||
| not a hardcoded role name: Postgres automatically maintains membership | ||
| in this predefined role to match whoever currently owns the database, | ||
| so the grant keeps working even if that database is later reassigned | ||
| to a different owner, and needs no assumption about what the owner is | ||
| named. See `pg_cron`'s `after-create.sql` for the pattern. |
53 changes: 53 additions & 0 deletions
53
extension-custom-scripts/address_standardizer_data_us/after-create.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| -- us_lex/us_gaz/us_rules land wherever the extension itself was | ||
| -- installed, owned by the supautils superuser. Unlike the other five | ||
| -- scripts in this directory, this extension is relocatable | ||
| -- (control file has no fixed schema), so a caller can run | ||
| -- CREATE EXTENSION address_standardizer_data_us SCHEMA gis and these | ||
| -- three tables land in gis, not public. A hardcoded public.us_lex | ||
| -- here would silently fail against relations that don't exist in | ||
| -- that case, which is why this looks the schema up at runtime rather | ||
| -- than assuming it. | ||
| -- | ||
| -- Looked up via pg_extension.extnamespace rather than supautils' own | ||
| -- @extschema@ substitution: that token is only populated when the | ||
| -- caller's CREATE EXTENSION included an explicit SCHEMA clause, and | ||
| -- is otherwise substituted as SQL NULL, which is the common case | ||
| -- (no explicit SCHEMA at all). pg_extension.extnamespace is populated | ||
| -- unconditionally, by Postgres itself, once the extension exists, so | ||
| -- it covers both cases with the same query. | ||
| -- | ||
| -- Granted to pg_database_owner rather than a hardcoded role name, so | ||
| -- this keeps working if the database is later reassigned to a | ||
| -- different owner. See | ||
| -- https://www.postgresql.org/docs/current/predefined-roles.html. | ||
| -- | ||
| -- Also grants USAGE on the schema itself, not just SELECT on the | ||
| -- tables: table-level SELECT alone is not enough to query a table | ||
| -- outside the search path, Postgres separately checks USAGE on the | ||
| -- schema before it will even look a table up in it. The default, | ||
| -- unrelocated case (public) happens to work without this, since | ||
| -- public grants USAGE to PUBLIC by default, but a schema named on an | ||
| -- explicit SCHEMA clause has no such default and would otherwise | ||
| -- leave pg_database_owner with a grant it can never actually use. | ||
| -- | ||
| -- Also granted to PUBLIC: pg_database_owner's own grant carries no | ||
| -- GRANT OPTION, so there is no way to pass it on to another role | ||
| -- afterward, and the attempt is a silent no-op, not an error. | ||
| DO $$ | ||
| DECLARE ext_schema name; | ||
| BEGIN | ||
| SELECT n.nspname INTO ext_schema | ||
| FROM pg_catalog.pg_extension e | ||
| JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace | ||
| WHERE e.extname = 'address_standardizer_data_us'; | ||
|
|
||
| EXECUTE format( | ||
| 'GRANT USAGE ON SCHEMA %I TO pg_database_owner, PUBLIC', | ||
| ext_schema | ||
| ); | ||
| EXECUTE format( | ||
| 'GRANT SELECT ON TABLE %I.us_lex, %I.us_gaz, %I.us_rules TO pg_database_owner, PUBLIC', | ||
| ext_schema, ext_schema, ext_schema | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| END | ||
| $$; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| -- supautils runs this immediately after CREATE EXTENSION pg_cron, as the | ||
| -- same superuser session used to install the extension (see | ||
| -- supautils.superuser). pg_cron's install script creates cron.job and | ||
| -- cron.job_run_details owned by that superuser, leaving the current | ||
| -- database's own owner with no path to manage its own scheduled jobs | ||
| -- or review their run history. | ||
| -- | ||
| -- Granted to pg_database_owner rather than a hardcoded role name: | ||
| -- Postgres automatically maintains membership in this predefined role to | ||
| -- match whoever currently owns the database pg_cron was installed in, so | ||
| -- this keeps working correctly if that database is later reassigned to a | ||
| -- different owner, and needs no assumption about what that owner is | ||
| -- named. See https://www.postgresql.org/docs/current/predefined-roles.html. | ||
| -- | ||
| -- SELECT only, ownership stays with the installing superuser: | ||
| -- cron.schedule() and cron.unschedule() are not SECURITY DEFINER, they | ||
| -- run as the caller, but they write to cron.job through pg_cron's own | ||
| -- internal C code, not through a normal caller-privileged INSERT or | ||
| -- UPDATE. Confirmed directly: a role with only this SELECT grant can | ||
| -- schedule, list, and unschedule its own jobs through those functions, | ||
| -- and a raw INSERT or UPDATE against cron.job as that role is refused | ||
| -- outright, permission denied, with no ownership or row-level-security | ||
| -- involved at all. Nothing here ever needs ownership to work. | ||
| GRANT USAGE ON SCHEMA cron TO pg_database_owner; | ||
| GRANT SELECT ON cron.job TO pg_database_owner; | ||
| GRANT SELECT ON cron.job_run_details TO pg_database_owner; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| -- Read access to admin-configured tokenizer definitions | ||
| -- (tokenizer_catalog.*), not write: whoever configures tokenizers | ||
| -- stays a separate, more privileged concern. Scoped to this one | ||
| -- schema, not a database-wide default, so a future gated extension's | ||
| -- own schema isn't exposed without its own deliberate grant here. | ||
| -- | ||
| -- Also granted to PUBLIC: pg_database_owner's own grant carries no | ||
| -- GRANT OPTION, so there is no way to pass it on to another role | ||
| -- afterward, and the attempt is a silent no-op, not an error. The | ||
| -- write side stays restricted to pg_database_owner only. | ||
| GRANT USAGE ON SCHEMA tokenizer_catalog TO pg_database_owner, PUBLIC; | ||
| GRANT SELECT ON ALL TABLES IN SCHEMA tokenizer_catalog TO pg_database_owner, PUBLIC; | ||
| ALTER DEFAULT PRIVILEGES FOR ROLE CURRENT_USER IN SCHEMA tokenizer_catalog | ||
| GRANT SELECT ON TABLES TO pg_database_owner, PUBLIC; | ||
|
|
||
| -- Schema USAGE does not just unlock reading the tables above, it | ||
| -- makes every function in this schema callable by any role, since | ||
| -- Postgres grants EXECUTE on new functions to PUBLIC by default. Most | ||
| -- of what lives here manages tokenizer/model configuration | ||
| -- (create_*, drop_*, add_preload_model, and friends), none of it | ||
| -- SECURITY DEFINER, so the table writes those functions attempt are | ||
| -- still refused on ACL, confirmed directly. But create_huggingface_model | ||
| -- and create_lindera_model run real work, parsing a config and | ||
| -- attempting to load a model, before any permission check fires, and | ||
| -- a role with only schema USAGE can reach them now. Revokes EXECUTE | ||
| -- from PUBLIC on everything in the schema, keeps it for | ||
| -- pg_database_owner explicitly rather than leaving it dependent on | ||
| -- the PUBLIC default just revoked, then re-grants PUBLIC only the | ||
| -- three functions the read-only use case actually needs: tokenize() | ||
| -- and apply_text_analyzer() to process text against an existing | ||
| -- configuration, and list_preload_models() to see what is available. | ||
| -- Configuring a new tokenizer, model, or analyzer stays a privileged | ||
| -- operation. | ||
| REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA tokenizer_catalog FROM PUBLIC; | ||
| GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA tokenizer_catalog TO pg_database_owner; | ||
| GRANT EXECUTE ON FUNCTION tokenizer_catalog.tokenize(text, text) TO PUBLIC; | ||
| GRANT EXECUTE ON FUNCTION tokenizer_catalog.apply_text_analyzer(text, text) TO PUBLIC; | ||
| GRANT EXECUTE ON FUNCTION tokenizer_catalog.list_preload_models() TO PUBLIC; |
12 changes: 12 additions & 0 deletions
12
extension-custom-scripts/postgis_tiger_geocoder/after-create.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| -- Read access to the reference tables the extension's own functions | ||
| -- query (tiger.*, created at CREATE EXTENSION time). The actual | ||
| -- Census dataset a user bulk-loads afterward is a separate step this | ||
| -- script cannot reach, it only runs once, at CREATE EXTENSION time. | ||
| -- | ||
| -- Also granted to PUBLIC: pg_database_owner's own grant carries no | ||
| -- GRANT OPTION, so there is no way to pass it on to another role | ||
| -- afterward, and the attempt is a silent no-op, not an error. | ||
| GRANT USAGE ON SCHEMA tiger TO pg_database_owner, PUBLIC; | ||
| GRANT SELECT ON ALL TABLES IN SCHEMA tiger TO pg_database_owner, PUBLIC; | ||
| ALTER DEFAULT PRIVILEGES FOR ROLE CURRENT_USER IN SCHEMA tiger | ||
| GRANT SELECT ON TABLES TO pg_database_owner, PUBLIC; |
27 changes: 27 additions & 0 deletions
27
extension-custom-scripts/postgis_topology/after-create.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| -- PostGIS's own install script grants PUBLIC only read access to the | ||
| -- topology schema (USAGE on the schema, SELECT on its tables), enough | ||
| -- to read an existing topology, not to manage one. The extension's | ||
| -- actual purpose needs a lot more than INSERT on topology.topology and | ||
| -- topology.layer: DropTopology()/DropTopoGeometryColumn() DELETE from | ||
| -- both, RenameTopology()/RenameTopoGeometryColumn() UPDATE them, and | ||
| -- RenameTopoGeometryColumn() additionally runs ALTER TABLE ... | ||
| -- DISABLE/ENABLE TRIGGER on topology.layer, which Postgres never | ||
| -- grants, only an owner (or superuser) can do it. Unlike pg_cron, | ||
| -- whose write functions bypass ACL checks through internal C code, | ||
| -- postgis_topology's functions run as the caller through ordinary | ||
| -- ACL-checked DML, so a grant-only approach can't cover the trigger | ||
| -- toggle: confirmed directly, a role with full DML and even the | ||
| -- TRIGGER privilege on both tables still gets "must be owner of table | ||
| -- layer" from RenameTopoGeometryColumn(). Reassigning ownership of | ||
| -- exactly these two tables is the only way to cover all of that. | ||
| -- Scoped to exactly these two tables rather than the whole schema, so | ||
| -- it doesn't also hand write access to other gated extensions' | ||
| -- catalogs that are meant to stay admin-only. | ||
| -- | ||
| -- Reassigned to pg_database_owner rather than a hardcoded role name, | ||
| -- so this keeps working if the database is later reassigned to a | ||
| -- different owner. See | ||
| -- https://www.postgresql.org/docs/current/predefined-roles.html. | ||
| ALTER TABLE topology.topology OWNER TO pg_database_owner; | ||
| ALTER TABLE topology.layer OWNER TO pg_database_owner; | ||
| GRANT USAGE ON SEQUENCE topology.topology_id_seq TO pg_database_owner; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| -- USAGE on bm25_catalog is needed to declare a column of its | ||
| -- bm25vector type and call its functions (search_bm25query, | ||
| -- to_bm25query); Postgres already grants EXECUTE on new functions to | ||
| -- PUBLIC by default, so no separate function grant is needed. The | ||
| -- schema holds only the type and its support functions, no tables. | ||
| -- | ||
| -- Also granted to PUBLIC: pg_database_owner's own grant carries no | ||
| -- GRANT OPTION, so there is no way to pass it on to another role | ||
| -- afterward, and the attempt is a silent no-op, not an error. | ||
| GRANT USAGE ON SCHEMA bm25_catalog TO pg_database_owner, PUBLIC; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.