{"id":14968780,"url":"https://github.com/supabase/supautils","last_synced_at":"2025-04-09T07:10:02.401Z","repository":{"id":38849388,"uuid":"361550360","full_name":"supabase/supautils","owner":"supabase","description":"PostgreSQL extension that secures a cluster on a cloud environment","archived":false,"fork":false,"pushed_at":"2025-03-31T22:39:01.000Z","size":251,"stargazers_count":61,"open_issues_count":19,"forks_count":14,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-04-02T00:36:39.644Z","etag":null,"topics":["postgresql","postgresql-extension","roles","security"],"latest_commit_sha":null,"homepage":"https://supabase.github.io/supautils","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/supabase.png","metadata":{"funding":{"github":["supabase"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null},"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-25T22:13:52.000Z","updated_at":"2025-03-31T22:07:57.000Z","dependencies_parsed_at":"2024-01-29T02:44:21.576Z","dependency_job_id":"6e0c4d3c-e318-458c-97d7-4b954813f4ab","html_url":"https://github.com/supabase/supautils","commit_stats":{"total_commits":156,"total_committers":10,"mean_commits":15.6,"dds":0.5769230769230769,"last_synced_commit":"e861d3ed6c8f43b9063540b95fb837b594862640"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fsupautils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fsupautils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fsupautils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fsupautils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/supabase","download_url":"https://codeload.github.com/supabase/supautils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246758365,"owners_count":20828919,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["postgresql","postgresql-extension","roles","security"],"created_at":"2024-09-24T13:40:32.795Z","updated_at":"2025-04-09T07:10:02.393Z","avatar_url":"https://github.com/supabase.png","language":"C","funding_links":["https://github.com/sponsors/supabase"],"categories":["C"],"sub_categories":[],"readme":"# supautils\n\n[![Coverage Status](https://coveralls.io/repos/github/supabase/supautils/badge.svg?branch=master)](https://coveralls.io/github/supabase/supautils?branch=master)\n\nSupautils is an extension that secures PostgreSQL on a cloud environment, where SUPERUSER cannot be granted to users.\n\nIt's completely controlled through settings, it doesn't require database objects (tables, functions or security labels). So it can be configured cluster-wide entirely in `postgresql.conf`.\n\nTested to work on PostgreSQL 13, 14, 15, 16 and 17.\n\n## Installation\n\nClone this repo and run\n\n```bash\nmake \u0026\u0026 make install\n```\n\nTo make supautils available to the whole cluster, you can add the following to `postgresql.conf` (use `SHOW config_file` for finding the location).\n\n```\nshared_preload_libraries = 'supautils'\n```\n\nOr to make it available only on some PostgreSQL roles use `session_preload_libraries`.\n\n```\nALTER ROLE role1 SET session_preload_libraries TO 'supautils';\n```\n\n## Features\n\n- [Privileged Role](#privileged-role)\n- [Privileged extensions](#privileged-extensions)\n- [Constrained extensions](#constrained-extensions)\n- [Extensions Parameter Overrides](#extensions-parameter-overrides)\n- [Table Ownership Bypass](#table-ownership-bypass)\n- [Reserved Roles](#reserved-roles)\n\n### Privileged Role\n\nPostgreSQL doesn't allow non-superusers to create certain database objects like publications, foreign data wrappers or event triggers. supautils allows creating these by configuring a `supautils.privileged_role`.\nThis role is a proxy role for a SUPERUSER, which is configured by `supautils.superuser` (defaults to the bootstrap user, i.e. the role used to bootstrap the Postgres cluster).\n\n#### Non-Superuser Publications\n\nThe privileged role can create publications. When it executes `create publication`, supautils will detect the statement and:\n\n- It will switch to the `supautils.superuser`, allowing the operation and creating the publication.\n- It will change the ownership of the publication to the privileged role.\n- Finally, it will switch back to the privileged role.\n\n#### Non-Superuser Foreign Data Wrappers\n\nThe privileged role can also execute `create foreign data wrapper..`, the logic followed is analogous to publication creation.\n\n#### Non-Superuser Event Triggers\n\nThe privileged role is also able to create event triggers, this while adding protection for privilege escalation.\n\nTo protect against privilege escalation, the event triggers created by the privileged role:\n\n- Will be executed for any non-superuser role.\n- Will be skipped for any superuser role.\n- For PostgreSQL \u003c 16: Will also be skipped for [Reserved Roles](#reserved-roles).\n\nSuperuser event triggers work as usual, with the additional restriction that the event trigger function must be owned by a superuser.\n\n```sql\ncreate event trigger evtrig on ddl_command_end\nexecute procedure func(); -- func must be owned by the superuser\n```\n\nThe privileged role won't be able to ALTER or DROP a superuser event trigger.\n\n\u003e [!IMPORTANT]\n\u003e Limitation: privileged role event triggers won't fire when creating publications, foreign data wrappers or extensions.\n\u003e This is due to implementation details, since supautils has to switch to `supautils.superuser` when creating the above database objects, and we have to skip privileged role event triggers here to avoid privilege escalation.\n\n#### Non-Superuser Settings\n\nCertain settings like `session_replication_role` can only be set by superusers. The privileged role can be allowed to change these settings by listing them in:\n\n```\nsupautils.privileged_role_allowed_configs=\"session_replication_role\"\n```\n\nSome extensions also have their own superuser settings with a prefix, those can be configured by:\n\n```\nsupautils.privileged_role_allowed_configs=\"ext.setting, other.nested\"\n```\n\nYou can also choose to allow all the extension settings by using a wildcard:\n\n```\nsupautils.privileged_role_allowed_configs=\"ext.*\"\n```\n\n### Privileged Extensions\n\nThis functionality is adapted from [pgextwlist](https://github.com/dimitri/pgextwlist).\n\nsupautils allows you to let non-superusers manage extensions that would normally require being a superuser. e.g. the `hstore` extension creates a base type, which requires being a superuser to perform.\n\nTo handle this, you can put the extension in `supautils.privileged_extensions`:\n\n```psql\nsupautils.privileged_extensions = 'hstore'\n```\n\nOnce you do, the extension creation will be delegated to the configured `supautils.superuser`. That means the `hstore` extension would be created as if by the superuser.\n\nNote that extension creation would behave normally (i.e. no delegation) if the current role is already a superuser.\n\nThis also works for updating and dropping privileged extensions.\n\nIf you don't want to enable this functionality, simply leave `supautils.privileged_extensions` empty. Extensions **not** in `supautils.privileged_extensions` would behave normally, i.e. created using the current role.\n\nsupautils also lets you set custom scripts per privileged extension that gets run at certain events. Currently supported scripts are `before-create` and `after-create`.\n\nTo make this work, configure the setting below:\n\n```\nsupautils.privileged_extensions_custom_scripts_path = '/opt/postgresql/privileged_extensions_custom_scripts'\n```\n\nThen put the scripts inside the path, e.g.:\n\n```sql\n-- /opt/postgresql/privileged_extensions_custom_scripts/hstore/after-create.sql\ngrant all on type hstore to non_superuser_role;\n```\n\nThis is useful for things like creating a dedicated role per extension and granting privileges as needed to that role.\n\n#### Configuration\n\nSettings available:\n\n```\nsupautils.privileged_extensions = 'hstore,moddatetime'\nsupautils.privileged_extensions_custom_scripts_path = '/opt/postgresql/privileged_extensions_custom_scripts'\nsupautils.privileged_extensions_superuser = 'postgres'\n```\n\n### Constrained Extensions\n\nYou can constrain the resources needed for an extension to be installed. This is done through:\n\n```\nsupautils.constrained_extensions = '{\"plrust\": {\"cpu\": 16, \"mem\": \"1 GB\", \"disk\": \"500 MB\"}, \"any_extension_name\": { \"mem\": \"1 GB\"}}'\n```\n\nThe `supautils.constrained_extensions` is a json object, any other json type will result in an error.\n\nEach top field of the json object corresponds to an extension name, the only value these top fields can take is a json object composed of 3 keys: `cpu`, `mem` and `disk`.\n\n- `cpu`: is the minimum number of cpus this extension needs. It's a json number.\n- `mem`: is the minimum amount of memory this extension needs. It's a json string that takes a human-readable format of bytes.\n- `disk`: is the minimum amount of free disk space this extension needs. It's a json string that takes a human-readable format of bytes.\n  + The free space of the disk is taken from the filesystem where PGDATA (data directory) is located.\n\nNote: this human-readable format is the same that [pg_size_pretty](https://pgpedia.info/p/pg_size_pretty.html) would give.\n\n`CREATE EXTENSION` will fail if any of the resource constraints are not met:\n\n```sql\ncreate extension plrust;\n\nERROR:  not enough CPUs for using this extension\nDETAIL:  required CPUs: 16\nHINT:  upgrade to an instance with higher resources\n```\n\n### Extensions Parameter Overrides\n\nYou can override `CREATE EXTENSION` parameters like so:\n\n```\nsupautils.extensions_parameter_overrides = '{ \"pg_cron\": { \"schema\": \"pg_catalog\" } }'\n```\n\nCurrently, only the `schema` parameter is supported.\n\nThese overrides will apply on `CREATE EXTENSION`, e.g.:\n\n```sql\npostgres=\u003e create extension pg_cron schema public;\nCREATE EXTENSION\npostgres=\u003e \\dx pg_cron\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description\n---------+---------+------------+------------------------------\n pg_cron | 1.5     | pg_catalog | Job scheduler for PostgreSQL\n(1 row)\n```\n\n### Table Ownership Bypass\n\n#### Manage Policies\n\nIn Postgres, only table owners can create RLS policies for a table. This can be\nlimiting if you need to allow certain roles to manage policies without allowing\nthem to perform other DDL (e.g. to prevent them from dropping the table).\n\nWith supautils, this can be done like so:\n\n```\nsupautils.policy_grants = '{ \"my_role\": [\"public.not_my_table\", \"public.also_not_my_table\"] }'\n```\n\nThis allows `my_role` to manage policies for `public.not_my_table` and\n`public.also_not_my_table` without being an owner of these tables.\n\n#### Drop Triggers\n\nYou can also allow certain roles to drop triggers on a table without being the table owner:\n\n```\nsupautils.drop_trigger_grants = '{ \"my_role\": [\"public.not_my_table\", \"public.also_not_my_table\"] }'\n```\n\n### Reserved Roles\n\n\u003e [!IMPORTANT]\n\u003e This feature is disabled starting from PostgreSQL 16, from this version onwards the underlying CREATEROLE problem is fixed.\n\nNon-superusers with the CREATEROLE privilege can ALTER, DROP or GRANT non-superuser roles without restrictions.\n\nFrom [role attributes docs](https://www.postgresql.org/docs/15/role-attributes.html):\n\n\u003e A role with CREATEROLE privilege can **alter and drop other roles, too, as well as grant or revoke membership in them**.\n\u003e However, to create, alter, drop, or change membership of a superuser role, superuser status is required;\n\u003e CREATEROLE is insufficient for that.\n\nThe above problem can be solved by configuring this extension to protect a set of roles, using the `reserved_roles` setting.\n\n```\nsupautils.reserved_roles = 'connector, storage_admin'\n```\n\nRoles with the CREATEROLE privilege cannot ALTER or DROP the above reserved roles.\n\nThis extension also allows restricting roles memberships. Certain default postgres roles are dangerous to expose to every database user.\nFrom [pg default roles](https://www.postgresql.org/docs/11/default-roles.html):\n\n\u003e The pg_read_server_files, pg_write_server_files and pg_execute_server_program roles are intended to allow administrators to have trusted,\n\u003e but non-superuser, roles which are able to access files and run programs on the database server as the user the database runs as.\n\u003e As these roles are able to access any file on the server file system, they bypass all database-level permission checks when accessing files directly\n\u003e and **they could be used to gain superuser-level access**, therefore great care should be taken when granting these roles to users.\n\nFor example, you can restrict doing `GRANT pg_read_server_files TO my_role` by setting:\n\n```\nsupautils.reserved_memberships = 'pg_read_server_files'\n```\n\n#### Reserved Roles Settings\n\nBy default, reserved roles cannot have their settings changed. However their settings can be modified by the [Privileged Role](#privileged-role) if they're configured like so:\n\n```\nsupautils.reserved_roles = 'connector*, storage_admin*'\n```\n\n## Development\n\n[Nix](https://nixos.org/download.html) is required to set up the environment.\n\n### Testing\n\nFor testing the module locally, execute:\n\n```bash\n# might take a while in downloading all the dependencies\n$ nix-shell\n\n# test on pg 13\n$ xpg -v 13 make installcheck\n\n# test on pg 14\n$ xpg -v 14 make installcheck\n\n# you can also test manually with\n$ xpg -v 13 psql -U rolecreator\n```\n\n### Coverage\n\nFor coverage, execute:\n\n```bash\n$ xpg -v 17 coverage\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase%2Fsupautils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsupabase%2Fsupautils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase%2Fsupautils/lists"}