{"id":20119101,"url":"https://github.com/pgaudit/set_user","last_synced_at":"2025-04-06T04:10:54.406Z","repository":{"id":44643316,"uuid":"48071008","full_name":"pgaudit/set_user","owner":"pgaudit","description":"PostgreSQL extension allowing privilege escalation with enhanced logging and control","archived":false,"fork":false,"pushed_at":"2025-01-06T15:42:06.000Z","size":139,"stargazers_count":74,"open_issues_count":6,"forks_count":22,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-03-30T03:11:04.128Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pgaudit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2015-12-15T21:49:56.000Z","updated_at":"2025-03-28T09:09:48.000Z","dependencies_parsed_at":"2025-01-15T06:09:11.184Z","dependency_job_id":"00b4e685-7737-4f62-b7e0-c9acd96fa676","html_url":"https://github.com/pgaudit/set_user","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgaudit%2Fset_user","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgaudit%2Fset_user/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgaudit%2Fset_user/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgaudit%2Fset_user/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pgaudit","download_url":"https://codeload.github.com/pgaudit/set_user/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247430870,"owners_count":20937874,"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":[],"created_at":"2024-11-13T19:14:23.139Z","updated_at":"2025-04-06T04:10:54.386Z","avatar_url":"https://github.com/pgaudit.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# PostgreSQL set_user Extension Module\n\n## Syntax\n\n```\nset_user(text rolename) returns text\nset_user(text rolename, text token) returns text\nset_user_u(text rolename) returns text\nreset_user() returns text\nreset_user(text token) returns text\nset_session_auth(text rolename) returns text\n```\n\n## Inputs\n\n`rolename` is the role to be transitioned to.\n`token` if provided during set_user is saved, and then required to be provided\nagain for reset.\n\n## Configuration Options\n\n* Add `set_user` to `shared_preload_libraries` in postgresql.conf.\n\n* Optionally, the following custom parameters may be set to control their\n  respective commands:\n  * set_user.block_alter_system = off (defaults to \"on\")\n  * set_user.block_copy_program = off (defaults to \"on\")\n  * set_user.block_log_statement = off (defaults to \"on\")\n  * set_user.superuser_allowlist = `'\u003crole list\u003e'`\n    * `\u003crole list\u003e` can contain any of the following:\n      * list of user roles (i.e. `\u003crole1\u003e, \u003crole2\u003e,...,\u003croleN\u003e`)\n      * Group roles may be indicated by `+\u003croleN\u003e`\n      * The wildcard character `*`\n  * set_user.nosuperuser_target_allowlist = `'\u003crole list\u003e'`\n    * `\u003crole list\u003e` can contain any of the following:\n      * list of user roles (i.e. `\u003crole1\u003e, \u003crole2\u003e,...,\u003croleN\u003e`)\n      * Group roles may be indicated by `+\u003croleN\u003e`\n      * The wildcard character `*`\n  * set_user.exit_on_error = off (defaults to \"on\")\n* To make use of the optional `set_user` and `reset_user` hooks, please refer to\n  the [hooks](#post-execution-hooks) section.\n\n## Description\n\nThis PostgreSQL extension allows switching users and optional privilege\nescalation with enhanced logging and control. It provides an additional layer of\nlogging and control when unprivileged users must escalate themselves to\nsuperuser or object owner roles in order to perform needed maintenance tasks.\nSpecifically, when an allowed user executes `set_user(text)` or\n`set_user_u(text)`, several actions occur:\n\n* The current effective user becomes `rolename`.\n* The role transition is logged, with a specific notation if `rolename` is a\n  superuser.\n* `log_statement` setting is set to \"all\", meaning every SQL statement executed\n  while in this state will also get logged.\n* If `set_user.block_alter_system` is set to \"on\", `ALTER SYSTEM` commands will\n  be blocked.\n* If `set_user.block_copy_program` is set to \"on\", `COPY PROGRAM` commands will\n  be blocked.\n* If `set_user.block_log_statement` is set to \"on\", `SET log_statement` and\n  variations will be blocked.\n* If `set_user.block_log_statement` is set to \"on\" and `rolename` is a database\n  superuser, the current `log_statement` setting is changed to \"all\", meaning\n  every SQL statement executed\n* If `set_user.superuser_audit_tag` is set, the string value will be appended to\n  `log_line_prefix` upon superuser escalation. All logs after superuser\n  escalation will be tagged with the value of `set_user.superuser_audit_tag`.\n  This value defaults to `'AUDIT'`.\n* If `set_user.exit_on_error` is set to \"on\", the backend process will exit on\n  ERROR during calls to set_session_auth().\n* [Post-execution hook](#post_set_user_hook)  for `set_user` is called if it is\n  set.\n\nOnly users with `EXECUTE` permission on `set_user_u(text)` may escalate to\nsuperuser. Additionally, all rules in [Superuser\nAllowlist](#set_usersuperuser_allowlist-rules-and-logic)\napply to `set_user.superuser_allowlist` and `set_user_u(text)`.\n\nPostgres roles calling `set_user(text)` can only transition to roles listed or\nincluded in `set_user.nosuperuser_target_allowlist` (defaults to all roles).\nAdditionally the logic in [Nosuperuser\nAllowlist](#set_usernosuperuser_target_allowlist-rules-and-logic) applies to\n`current_user` when `set_user()` is invoked.\n\nAdditionally, with `set_user('rolename','token')` the `token` is stored for the\nlifetime of the session.\n\nWhen finished with required actions as `rolename`, the `reset_user()` function\nis executed to restore the original user. At that point, these actions occur:\n\n* Role transition is logged.\n* `log_statement` setting is set to its original value.\n* Blocked command behaviors return to normal.\n* [Post-execution hook](#post_reset_user_hook) for `reset_user` is called if it\n  is set.\n\nIf `set_user`, was provided with a `token`, then `reset_user('token')` must be\ncalled instead of `reset_user()`:\n\n* The provided `token` is compared with the stored token.\n* If the tokens do not match, or if a `token` was provided to `set_user` but not\n  `reset_user`, an ERROR occurs.\n\nWhen set_session_auth(text) is called, the effective session and current user is\nswitched to the rolename supplied, irrevocably. Unlike set_user() or set_user_u(),\nit does not affect logging nor allowed statements. If `set_user.exit_on_error` is\n\"on\" (the default), and any error occurs during execution, a FATAL error is thrown\nand the backend session exits.\n\n### `set_user` Usage\n\nTypical use of the `set_user` extension is as follows:\n\n#### `GRANT EXECUTE` to Functions\n\nIn order to make use of the `set_user` functions, some database roles must be\nable to execute the functions. Allow these privileges by `GRANT`ing `EXECUTE` on\nthe appropriate functions to their intended users.\n\n```sql\nGRANT EXECUTE ON FUNCTION set_user(text) TO dbclient,dbclient2;\nGRANT EXECUTE ON FUNCTION set_user(text, text) to dbclient,dbclient2;\nGRANT EXECUTE ON FUNCTION set_user_u(text) TO dbadmin;\n```\n\nThis example assumes that there are three users of `set_user`:\n\n1) `dbclient` is an unprivileged user that can run as `dbclient2` through calls\nto `set_user`.\n2) `dbclient2` is an unprivileged user that can run as `dbclient` through calls\nto `set_user`.\n3) `dbadmin` is the privileged (non-superuser) role, which is able to escalate\nprivileges to superuser with Enhanced Logging.\n\n#### Call `set_user` to Transition\n\nTransitioning to other roles through use of `set_user` provides the ability to\nchange the session's `current_user`.\n\nTransitions can be made to unprivileged users through use of `set_user` (with\noptional `token`, as described above).\n\n```sql\nSELECT set_user('dbclient2');\n```\n\nAlternatively, transitions can be made to superusers through use of\n`set_user_u`:\n\n```sql\nSELECT set_user_u('postgres');\n```\n\n**Note:** See rules in [Superuser\nAllowlist](#set_usersuperuser_allowlist-rules-and-logic)\nfor logic around calling `set_user_u(text)`. See [Nosuperuser\nAllowlist](#set_usernosuperuser_target_allowlist-rules-and-logic) for reference\nlogic around calling `set_user(text)`.\n\nOnce one or more unprivileged users are able to run `set_user_u()` in order to\nescalate their privileges, the superuser account (typically `postgres`) can be\naltered to `NOLOGIN`, preventing any direct database connection by a superuser\nwhich would bypass the enhanced logging.\n\nNaturally for this to work as expected, the PostgreSQL cluster must be audited\nto ensure there are no other PostgreSQL roles existing which are both superuser\nand can log in. Additionally there must be no unprivileged PostgreSQL roles\nwhich have been granted access to one of the existing superuser roles.\n\n#### `set_user.superuser_allowlist` Rules and Logic\n\nThe following rules govern escalation to superuser via the `set_user_u(text)`\nfunction:\n\n* `current_user` must be `GRANT`ed `EXECUTE ON FUNCTION set_user_u(text)` OR\n  `current_user` must be the `OWNER` of the `set_user_u(text)` function OR\n  `current_user` must be a superuser.\n* `current_user` must be listed in `set_user.superuser_allowlist` OR\n  `current_user` must belong to a group that is listed in\n  `set_user.superuser_allowlist` (e.g. `'+admin'`)\n* If `set_user.superuser_allowlist` is the empty set , `''`, superuser\n  escalation is blocked for all users.\n* If `set_user.superuser_allowlist` is the wildcard character, `'*'`, all users\n  with `EXECUTE` permission on `set_user_u(text)` can escalate to superuser.\n* If `set_user.superuser_allowlist` is not specified, the value defaults to the\n  wildcard character, `'*'`.\n\n#### `set_user.nosuperuser_target_allowlist` Rules and Logic\n\nThe following rules govern non-superuser role transitions through use of\n  `set_user(text)` or `set_user(text, text)` function (for simplicity, only\n  `set_user(text)` is used):\n\n* `current_user` must be `GRANT`ed `EXECUTE ON FUNCTION set_user(text)` OR\n  `current_user` must be the `OWNER` of the `set_user(text)` function OR\n  `current_user` must be a superuser.\n* The target rolename must be listed in `set_user.nosuperuser_target_allowlist`\n  OR the target rolename must belong to a group that is listed in\n  `set_user.nosuperuser_target_allowlist` (e.g. `'+client'`)\n* If `set_user.nosuperuser_target_allowlist` is the empty set , `''`,\n  `set_user(text)` transitions to non-superusers are blocked for all users.\n* If `set_user.nosuperuser_target_allowlist` is the wildcard character, `'*'`,\n  all users with `EXECUTE` permission on `set_user(text)` can transition to any\n  other non-superuser role.\n* If `set_user.nosuperuser_target_allowlist` is not specified, the value\n  defaults to the wildcard character, `'*'`.\n\n#### Perform Actions With Enhanced Logging\n\nOnce a transition has been made, the current session behaves as if it has the\nprivileges of the new `current_user`. The optional enhanced logging creates an\naudit trail upon transition to an alternate role, ensuring that any privilege\nescalation/alteration does not go unmonitored.\n\nThis audit trail is tagged with the value of `set_user.superuser_audit_tag`,\nsuch that actions after superuser escalation are easily identifiable.\n\n#### Reset to Previous User\n\n```sql\nSELECT reset_user();\n```\n\nIf `set_user()` was initially called with a `token`, the same `token` must be\nprovided in order to reset back to the previous user.\n\n```sql\nSELECT set_user('dbclient2', 'some_token_string');\nSELECT reset_user('some_token_string');\n```\n\n### Blocking `ALTER SYSTEM` and `COPY PROGRAM`\n\nNote that for the blocking of `ALTER SYSTEM` and `COPY PROGRAM` to work\nproperly, you must include `set_user` in `shared_preload_libraries` in\n`postgresql.conf` and restart PostgreSQL.\n\n\nNotes:\n\nIf set_user.block_log_statement is set to \"off\", the `log_statement` setting is\nleft unchanged.\n\nFor the blocking of `ALTER SYSTEM` and `COPY PROGRAM` to work properly, you must\ninclude `set_user` in shared_preload_libraries in postgresql.conf and restart\nPostgreSQL.\n\nNeither `set_user(text)` nor `set_user_u(text)` may be executed from\nwithin an explicit transaction block.\n\n### `set_session_auth` Usage\n\nTypical use of the `set_session_auth` function is as follows:\n\n#### `GRANT EXECUTE` to Functions\n\nIn order to make use of the `set_session_auth` function, some database roles must be\nable to execute the function. Allow these privileges by `GRANT`ing `EXECUTE` on\nthe function to their intended users.\n\n```sql\nGRANT EXECUTE ON FUNCTION set_session_auth(text) TO dbclient,dbclient2;\n```\n\n## Caveats\n\nIn its current state, this extension cannot prevent `rolename` from performing a\nvariety of nefarious or otherwise undesireable actions. However, these actions\nwill be logged providing an audit trail, which could also be used to trigger\nalerts.\n\nThis extension supports PostgreSQL versions 12 and higher. Prior versions of\nPostgreSQL are supported by prior versions of set_user.\n\n##  Post-Execution Hooks\n\n`set_user` exposes two hooks that may be used to control post-execution behavior\nfor `set_user` and `reset_user`.\n\n### Description\n\nThe following hooks are called (if set) directly before returning from\nsuccessful calls to `set_user` and `reset_user`. These hooks are meant to give\nother extensions awareness of `set_user` actions. This is helpful, for instance,\nto keep track of dynamic user switching within a session.\n\nTo avoid order-dependency in `shared_preload_libraries`, these hooks are\nregistered in the rendezvous hash table of core Postgres. The header defines a\n[utility function](set_user.h#L13) for doing all of the necessary setup.\n\n###### `post_set_user` hook\n\nAllows another extension to take action after calls to `set_user`. This hook\ntakes the username as an argument so that the hook implementation is aware of\nthe username.\n\n###### `post_reset_user` hook\n\nAllows another extension to take action after calls to `reset_user`. This hook\ndoes not take any arguments, since the resulting username will always be the\n`session_user`.\n\n### Configuration\n\nFollow the instructions below to implement `set_user` and `reset_user`\npost-execution hooks in another extension:\n\n* Add '-I$(includedir)' to `CPPFLAGS` of the extension which implements the\n  post-execution hooks.\n* `#include set_user.h` in whichever file implements the hooks.\n* Register hook implementations in `rendezvous_variable` hash using the\n  `register_set_user_hooks` utility function.\n\nConfiguration is described in more detail in the [post-execution\nhooks](#install-set_user-post-execution-hooks) subsection of the Install\ndocumentation.\n\n### Caveats\n\nIf another extension implements the post-execution hooks, `post_set_user_hook`\nand `post_reset_user_hook`, `set_user` must be listed before that extension in\n`shared_preload_libraries`. This is due to the way `shared_preload_libraries`\nare opened and loaded into memory by Postgres: the hooks need to be loaded into\nmemory before their implementations can access them.\n\n## Installation\n\n### Requirements\n\n* PostgreSQL 9.4 or higher.\n\n### Compile and Install\n\nClone PostgreSQL repository:\n\n```bash\n$\u003e git clone https://github.com/postgres/postgres.git\n```\n\nCheckout REL_15_STABLE (for example) branch:\n\n```bash\n$\u003e git checkout REL_15_STABLE\n```\n\nMake PostgreSQL:\n\n```bash\n$\u003e ./configure\n$\u003e make install -s\n```\n\nChange to the contrib directory:\n\n```bash\n$\u003e cd contrib\n```\n\nClone `set_user` extension:\n\n```bash\n$\u003e git clone https://github.com/pgaudit/set_user\n```\n\nChange to `set_user` directory:\n\n```bash\n$\u003e cd set_user\n```\n\nBuild `set_user`:\n\n```bash\n$\u003e make\n```\n\nInstall `set_user`:\n\n```bash\n$\u003e make install\n```\n\n#### Using PGXS\n\nIf an instance of PostgreSQL is already installed, then PGXS can be utilized to\nbuild and install `set_user`.  Ensure that PostgreSQL binaries are available via\nthe `$PATH` environment variable then use the following commands.\n\n```bash\n$\u003e make USE_PGXS=1\n$\u003e make USE_PGXS=1 install\n```\n\n### Configure\n\nThe following bash commands should configure your system to utilize `set_user`.\nReplace all paths as appropriate. It may be prudent to visually inspect the\nfiles afterward to ensure the changes took place.\n\n###### Initialize PostgreSQL (if needed):\n\n```bash\n$\u003e initdb -D /path/to/data/directory\n```\n\n###### Create Target Database (if needed):\n\n```bash\n$\u003e createdb \u003cdatabase\u003e\n```\n\n###### Install `set_user` functions:\n\nEdit postgresql.conf and add `set_user` to the `shared_preload_libraries` line,\noptionally also changing custom settings as mentioned above.\n\nFirst edit postgresql.conf in your favorite editor:\n\n```\n$\u003e vi $PGDATA/postgresql.conf\n```\n\nThen add these lines to the end of the file:\n```\n# Add set_user to any existing list\nshared_preload_libraries = 'set_user'\n# The following lines are only required to modify the\n# blocking of each respective command if desired\nset_user.block_alter_system = off       #defaults to \"on\"\nset_user.block_copy_program = off       #defaults to \"on\"\nset_user.block_log_statement = off      #defaults to \"on\"\nset_user.superuser_allowlist = ''       #defaults to '*'\nset_user.nosuperuser_target_allowlist = ''       #defaults to '*'\n```\n\nFinally, restart PostgreSQL (method may vary):\n\n```\n$\u003e service postgresql restart\n```\n\nInstall the extension into your database:\n\n```bash\npsql \u003cdatabase\u003e\nCREATE EXTENSION set_user;\n```\n\n######  Install `set_user` post-execution hooks:\n\nEnsure that `set_user.h` is copied to `$(includedir)`.\n\nThis can be done automatically upon normal installation:\n\n```bash\n$\u003e make USE_PGXS=1 install\n```\n\nThere is also an explicit make target available to copy the header file to the\nappropriate directory:\n\n```bash\n$\u003e make USE_PGXS=1 install-headers\n```\n\nEnsure that the implementing extension adds `-I$(includedir)` to `CPPFLAGS` in\nits Makefile:\n\n```\n# Add -I$(includedir) to CPPFLAGS so the set_user header is included\noverride CPPFLAGS += -I$(includedir)\n```\n\nEnsure that the implementing extension includes the `set_user` header file in\nthe appropriate C file:\n\n```c\n/* Include set_user hooks in whichever C file implements the hooks */\n#include \"set_user.h\"\n\n```\nCreate your `set_user` hooks and register them in the rendezvous_variable hash:\n\n```c\nvoid _PG_Init(void)\n{\n\t/*\n\t * Your _PG_Init code here\n\t */\n\n\t register_set_user_hooks(extension_post_set_user, extension_post_reset_user);\n\n\t/*\n\t * more _PG_Init code\n\t */\n}\n\n/*\n * extension_post_set_user\n *\n * Entrypoint of the set_user post-exec hook.\n */\nstatic void\nextension_post_set_user(void)\n{\n\t/* Some magic */\n}\n\n/*\n * extension_post_reset_user\n *\n * Entrypoint of the reset_user post-exec hook.\n */\nstatic void\nextension_post_reset_user(void)\n{\n\t/* Some magic */\n}\n\n```\n\n## GUC Parameters\n\n* Block `ALTER SYSTEM` commands\n  * `set_user.block_alter_system = on`\n* Block `COPY PROGRAM` commands\n  * `set_user.block_copy_program = on`\n* Block `SET log_statement` commands\n  * `set_user.block_log_statement = on`\n* Allow list of roles to escalate to superuser\n  * `set_user.superuser_allowlist = '\u003crole1\u003e,\u003crole2\u003e,...,\u003croleN\u003e'`\n* Allowed list of roles that can be switched to (not used in set_user_u)\n  * `set_user.nosuperuser_target_allowlist = '\u003crole1\u003e,\u003crole2\u003e,...,\u003croleN\u003e'`\n\n\n## Examples\n\nset_user() and related:\n```\n#################################\n# OS command line, terminal 1\n#################################\npsql -U postgres \u003cdbname\u003e\n\n---------------------------------\n-- psql command line, terminal 1\n---------------------------------\nSELECT rolname FROM pg_authid WHERE rolsuper and rolcanlogin;\n rolname\n----------\n postgres\n(1 row)\n\nCREATE EXTENSION set_user;\nCREATE USER dba_user;\nGRANT EXECUTE ON FUNCTION set_user(text) TO dba_user;\nGRANT EXECUTE ON FUNCTION set_user_u(text) TO dba_user;\n\n#################################\n# OS command line, terminal 2\n#################################\npsql -U dba_user \u003cdbname\u003e\n\n---------------------------------\n-- psql command line, terminal 2\n---------------------------------\nSELECT set_user('postgres');\nERROR:  Switching to superuser only allowed for privileged procedure:\n'set_user_u'\nSELECT set_user_u('postgres');\nSELECT CURRENT_USER, SESSION_USER;\n current_user | session_user\n--------------+--------------\n postgres     | dba_user\n(1 row)\n\nSELECT reset_user();\nSELECT CURRENT_USER, SESSION_USER;\n current_user | session_user\n--------------+--------------\n dba_user     | dba_user\n(1 row)\n\n\\q\n\n---------------------------------\n-- psql command line, terminal 1\n---------------------------------\nALTER USER postgres NOLOGIN;\n-- repeat terminal 2 test with dba_user before exiting\n\\q\n\n#################################\n# OS command line, terminal 1\n#################################\ntail -n 6 \u003cpostgres log\u003e\nLOG:  Role dba_user transitioning to Superuser Role postgres\nSTATEMENT:  SELECT set_user_u('postgres');\nLOG:  statement: SELECT CURRENT_USER, SESSION_USER;\nLOG:  statement: SELECT reset_user();\nLOG:  Superuser Role postgres transitioning to Role dba_user\nSTATEMENT:  SELECT reset_user();\n\n#################################\n# OS command line, terminal 2\n#################################\npsql -U dba_user \u003cdbname\u003e\n\n---------------------------------\n-- psql command line, terminal 2\n---------------------------------\n-- Verify there are no superusers that can login directly\nSELECT rolname FROM pg_authid WHERE rolsuper and rolcanlogin;\n rolname\n---------\n(0 rows)\n\n-- Verify there are no unprivileged roles that can login directly\n-- that are granted a superuser role even if it is multiple layers\n-- removed\nDROP VIEW IF EXISTS roletree;\nCREATE OR REPLACE VIEW roletree AS\nWITH RECURSIVE\nroltree AS (\n  SELECT u.rolname AS rolname,\n         u.oid AS roloid,\n         u.rolcanlogin,\n         u.rolsuper,\n         '{}'::name[] AS rolparents,\n         NULL::oid AS parent_roloid,\n         NULL::name AS parent_rolname\n  FROM pg_catalog.pg_authid u\n  LEFT JOIN pg_catalog.pg_auth_members m on u.oid = m.member\n  LEFT JOIN pg_catalog.pg_authid g on m.roleid = g.oid\n  WHERE g.oid IS NULL\n  UNION ALL\n  SELECT u.rolname AS rolname,\n         u.oid AS roloid,\n         u.rolcanlogin,\n         u.rolsuper,\n         t.rolparents || g.rolname AS rolparents,\n         g.oid AS parent_roloid,\n         g.rolname AS parent_rolname\n  FROM pg_catalog.pg_authid u\n  JOIN pg_catalog.pg_auth_members m on u.oid = m.member\n  JOIN pg_catalog.pg_authid g on m.roleid = g.oid\n  JOIN roltree t on t.roloid = g.oid\n)\nSELECT\n  r.rolname,\n  r.roloid,\n  r.rolcanlogin,\n  r.rolsuper,\n  r.rolparents\nFROM roltree r\nORDER BY 1;\n\n-- For example purposes, given this set of roles\nSELECT r.rolname, r.rolsuper, r.rolinherit,\n  r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n  r.rolconnlimit, r.rolvaliduntil,\n  ARRAY(SELECT b.rolname\n        FROM pg_catalog.pg_auth_members m\n        JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n        WHERE m.member = r.oid) as memberof\n, r.rolreplication\n, r.rolbypassrls\nFROM pg_catalog.pg_roles r\nORDER BY 1;\n                                    List of roles\n Role name |                         Attributes                         | Member of\n-----------+------------------------------------------------------------+------------\n bob       |                                                            | {}\n dba_user  |                                                            | {su}\n joe       |                                                            | {newbs}\n newbs     | Cannot login                                               | {}\n postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}\n su        | No inheritance, Cannot login                               | {postgres}\n\n-- This query shows current status is not acceptable\n-- 1) postgres can login directly\n-- 2) dba_user can login and is able to escalate without using set_user()\nSELECT\n  ro.rolname,\n  ro.roloid,\n  ro.rolcanlogin,\n  ro.rolsuper,\n  ro.rolparents\nFROM roletree ro\nWHERE (ro.rolcanlogin AND ro.rolsuper)\nOR\n(\n    ro.rolcanlogin AND EXISTS\n    (\n      SELECT TRUE FROM roletree ri\n      WHERE ri.rolname = ANY (ro.rolparents)\n      AND ri.rolsuper\n    )\n);\n rolname  | roloid | rolcanlogin | rolsuper |  rolparents\n----------+--------+-------------+----------+---------------\n dba_user |  16387 | t           | f        | {postgres,su}\n postgres |     10 | t           | t        | {}\n(2 rows)\n\n-- Fix it\nREVOKE postgres FROM su;\nALTER USER postgres NOLOGIN;\n\n-- Rerun the query - shows current status is acceptable\nSELECT\n  ro.rolname,\n  ro.roloid,\n  ro.rolcanlogin,\n  ro.rolsuper,\n  ro.rolparents\nFROM roletree ro\nWHERE (ro.rolcanlogin AND ro.rolsuper)\nOR\n(\n    ro.rolcanlogin AND EXISTS\n    (\n      SELECT TRUE FROM roletree ri\n      WHERE ri.rolname = ANY (ro.rolparents)\n      AND ri.rolsuper\n    )\n);\n rolname | roloid | rolcanlogin | rolsuper | rolparents\n---------+--------+-------------+----------+------------\n(0 rows)\n```\n\nset_session_auth():\n```\n# psql -U postgres test\npsql (15.4)\nType \"help\" for help.\n\ntest=# grant EXECUTE on FUNCTION set_session_auth(text) to dbclient;\n\\q\n\n# psql -U dbclient test\npsql (15.4)\nType \"help\" for help.\n\ntest=\u003e select session_user, current_user, user, current_role;\n session_user | current_user |   user   | current_role \n--------------+--------------+----------+--------------\n dbclient     | dbclient     | dbclient | dbclient\n(1 row)\n\ntest=\u003e select set_session_auth('jeff');\n set_session_auth \n------------------\n OK\n(1 row)\n\ntest=\u003e select session_user, current_user, user, current_role;\n session_user | current_user | user | current_role \n--------------+--------------+------+--------------\n jeff         | jeff         | jeff | jeff\n(1 row)\n\ntest=\u003e -- the role switch is irrevocable\ntest=\u003e reset role;\nRESET\ntest=\u003e select session_user, current_user, user, current_role;\n session_user | current_user | user | current_role \n--------------+--------------+------+--------------\n jeff         | jeff         | jeff | jeff\n(1 row)\n\ntest=\u003e reset session authorization;\nRESET\ntest=\u003e select session_user, current_user, user, current_role;\n session_user | current_user | user | current_role \n--------------+--------------+------+--------------\n jeff         | jeff         | jeff | jeff\n(1 row)\n\ntest=\u003e set role none;\nSET\ntest=\u003e select session_user, current_user, user, current_role;\n session_user | current_user | user | current_role \n--------------+--------------+------+--------------\n jeff         | jeff         | jeff | jeff\n(1 row)\n```\n\n##  Licensing\n\nPlease see the [LICENSE](./LICENSE) file.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgaudit%2Fset_user","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpgaudit%2Fset_user","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgaudit%2Fset_user/lists"}