From ee20d0644a0d33a3265b0996b7d33d232fccf5f8 Mon Sep 17 00:00:00 2001 From: Brian Faherty Date: Mon, 25 Jun 2018 13:00:20 -0400 Subject: [PATCH] Add whitelist for set_user() target The GUC variable `set_user.nosuperuser_target_whitelist` can be used to limit the roles that any user can become when given access to execute set_user(). Previously, anyone with access to call set_user() could become any user that was not a superuser. This allowed for bypassing some security features by becoming the owner of an object. With the addition of a whitelist around the allowed target roles to set_user() the scope of set_user can be limited to an approved list. With the default of `set_user.nosuperuser_target_whitelist` being '*' this change is backwards compatible and can be installed on existing configurations, without reconfiguring. This has no effect on the current `superuser_whitelist`. As it is only called on when the target role is not a superuser. Discussion in: #18 --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++--------- set_user.c | 11 +++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 32fbf23..0290b43 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,11 @@ again for reset. * list of user roles (i.e. `, ,...,`) * Group roles may be indicated by `+` * The wildcard character `*` + * set_user.nosuperuser_target_whitelist = `''` + * `` can contain any of the following: + * list of user roles (i.e. `, ,...,`) + * Group roles may be indicated by `+` + * The wildcard character `*` * To make use of the optional `set_user` and `reset_user` hooks, please refer to the [hooks](#post-execution-hooks) section. @@ -63,13 +68,13 @@ Specifically, when an allowed user executes `set_user('rolename')` or * [Post-execution hook](#post_set_user_hook) for `set_user` is called if it is set. Only users with `EXECUTE` permission on `set_user_u('rolename')` may escalate to -superuser. Additionally, only roles explicitly listed or included by a group -that is explicitly listed (e.g. `'+admin'`) in `set_user.superuser_whitelist` -can escalate to superuser. If `set_user.superuser_whitelist` is explicitly set -to the empty set, `''`, superuser escalation is blocked for all users. If the -whitelist is equal to the wildcard character, `'*'`, all users with `EXECUTE` -permission on `set_user_u()` can escalate to superuser. The default value of -`set_user.superuser_whitelist` is `'*'`. +superuser. Additionally, all rules in [Superuser Whitelist](#set_usersuperuser_whitelist-rules-and-logic) +apply to `set_user.superuser_whitelist` and `set_user_u('rolename')`. + +Postgres roles calling `set_user('rolename')` can only transition to roles listed or +included in `set_user.nosuperuser_target_whitelist` (defaults to all roles). Additionally +the logic in [Nosuperuser Whitelist](#set_usernosuperuser_target_whitelist-rules-and-logic) +applies to `current_user` when `set_user()` is invoked. Additionally, with `set_user('rolename','token')` the `token` is stored for the lifetime of the session. @@ -134,10 +139,10 @@ Alternatively, transitions can be made to superusers through use of SELECT set_user_u('postgres'); ``` -**Note:** Superuser escalation is only allowed for the roles listed in -`set_user.superuser_whitelist`. If the whitelist is equal to `'*'`, all roles -that have been granted `EXECUTE` on `set_user_u` can escalate to superuser. -This is the default setting of `set_user.superuser_whitelist`. +**Note:** See rules in [Superuser Whitelist](#set_usersuperuser_whitelist-rules-and-logic) +for logic around calling set_user_u. See +[Nosuperuser Whitelist](#set_usernosuperuser_target_whitelist-rules-and-logic) for +reference logic around calling set_user. Once one or more unprivileged users are able to run `set_user_u()` in order to escalate their privileges, the superuser account (typically `postgres`) can be @@ -149,6 +154,43 @@ to ensure there are no other PostgreSQL roles existing which are both superuser and can log in. Additionally there must be no unprivileged PostgreSQL roles which have been granted access to one of the existing superuser roles. +#### `set_user.superuser_whitelist` Rules and Logic + +The following rules govern escalation to superuser via the set_user_u('rolename') +function: + +* current_user must be GRANTed EXECUTE ON FUNCTION set_user_u('rolename') OR + current_user must be the OWNER of the set_user_u function OR current_user must + be a superuser. +* current_user must be listed in set_user.superuser_whitelist OR current_user + must belong to a group that is listed in set_user.superuser_whitelist + (e.g. '+admin') +* If set_user.superuser_whitelist is the empty set , '', superuser escalation is + blocked for all users. +* If set_user.superuser_whitelist is the wildcard character, '*', all users + with EXECUTE permission on set_user_u('rolename') can escalate to superuser. +* If set_user.superuser_whitelist is not specified, the value defaults to the + wildcard character, '*'. + +#### `set_user.nosuperuser_target_whitelist` Rules and Logic + +The following rules govern non-superuser role transitions through use of the + set_user('rolename') function: + +* current_user must be GRANTed EXECUTE ON FUNCTION set_user('rolename') OR + current_user must be the OWNER of the set_user function OR current_user must + be a superuser. +* The destination rolename must be listed in set_user.nosuperuser_target_whitelist + OR the destination rolename must belong to a group that is listed in + set_user.nosuperuser_target_whitelist (e.g. '+client') +* If set_user.nosuperuser_target_whitelist is the empty set , '', set_user transitioning + to non-superusers is blocked for all users. +* If set_user.nosuperuser_target_whitelist is the wildcard character, '*', all users + with EXECUTE permission on set_user('rolename') can transition to any other + non-superuser role. +* If set_user.nosuperuser_target_whitelist is not specified, the value defaults to the + wildcard character, '*'. + #### Perform Actions With Enhanced Logging Once a transition has been made, the current session behaves as if it has the @@ -361,6 +403,7 @@ set_user.block_alter_system = off #defaults to "on" set_user.block_copy_program = off #defaults to "on" set_user.block_log_statement = off #defaults to "on" set_user.superuser_whitelist = '' #defaults to '*' +set_user.nosuperuser_target_whitelist = '' #defaults to '*' ``` Finally, restart PostgreSQL (method may vary): @@ -432,6 +475,8 @@ shared_preload_libraries = 'set_user, ' * `set_user.block_log_statement = on` * Allow list of roles to escalate to superuser * `set_user.superuser_whitelist = ',,...,'` +* Allowed list of roles that can be switched to (not used in set_user_u) + * `set_user.nosuperuser_target_whitelist = ',,...,'` ## Examples diff --git a/set_user.c b/set_user.c index 76652ad..ec97cb1 100644 --- a/set_user.c +++ b/set_user.c @@ -128,6 +128,7 @@ static bool Block_CP = false; static bool Block_LS = false; static char *SU_Whitelist = NULL; +static char *NOSU_TargetWhitelist = NULL; static char *SU_AuditTag = NULL; #ifdef HAS_TWO_ARG_GETUSERNAMEFROMID @@ -349,6 +350,11 @@ set_user(PG_FUNCTION_ARGS) errmsg("switching to superuser not allowed"), errhint("Add current user to set_user.superuser_whitelist."))); } + else if(!check_user_whitelist(NewUserId, NOSU_TargetWhitelist)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("switching to role is not allowed"), + errhint("Add the role to set_user.nosuperuser_target_whitelist."))); /* keep track of original userid and value of log_statement */ save_OldUserId = OldUserId; @@ -467,6 +473,11 @@ _PG_init(void) NULL, &Block_LS, true, PGC_SIGHUP, 0, NULL, NULL, NULL); + DefineCustomStringVariable("set_user.nosuperuser_target_whitelist", + "List of roles that can be an argument to set_user", + NULL, &NOSU_TargetWhitelist, WHITELIST_WILDCARD, PGC_SIGHUP, + 0, NULL, NULL, NULL); + DefineCustomStringVariable("set_user.superuser_whitelist", "Allows a list of users to use set_user_u for superuser escalation", NULL, &SU_Whitelist, WHITELIST_WILDCARD, PGC_SIGHUP,