Skip to content
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

set_user.dest_role_whitelist for set_user('rolename') #18

Merged
merged 2 commits into from
Jun 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 67 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ again for reset.
* list of user roles (i.e. `<role1>, <role2>,...,<roleN>`)
* Group roles may be indicated by `+<roleN>`
* The wildcard character `*`
* set_user.nosuperuser_target_whitelist = `'<role list>'`
* `<role list>` can contain any of the following:
* list of user roles (i.e. `<role1>, <role2>,...,<roleN>`)
* Group roles may be indicated by `+<roleN>`
* The wildcard character `*`
* To make use of the optional `set_user` and `reset_user` hooks, please refer to
the [hooks](#post-execution-hooks) section.

Expand All @@ -39,8 +44,8 @@ This PostgreSQL extension allows switching users and optional privilege
escalation with enhanced logging and control. It provides an additional layer of
logging and control when unprivileged users must escalate themselves to
superuser or object owner roles in order to perform needed maintenance tasks.
Specifically, when an allowed user executes `set_user('rolename')` or
`set_user_u('rolename')`, several actions occur:
Specifically, when an allowed user executes `set_user(text)` or
`set_user_u(text)`, several actions occur:

* The current effective user becomes `rolename`.
* The role transition is logged, with a specific notation if `rolename` is a
Expand All @@ -60,16 +65,19 @@ Specifically, when an allowed user executes `set_user('rolename')` or
`log_line_prefix` upon superuser escalation. All logs after superuser
escalation will be tagged with the value of `set_user.superuser_audit_tag`.
This value defaults to `'AUDIT'`.
* [Post-execution hook](#post_set_user_hook) for `set_user` is called if it is set.
* [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(text)` may escalate to
superuser. Additionally, all rules in [Superuser
Whitelist](#set_usersuperuser_whitelist-rules-and-logic)
apply to `set_user.superuser_whitelist` and `set_user_u(text)`.

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 `'*'`.
Postgres roles calling `set_user(text)` 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.
Expand Down Expand Up @@ -134,10 +142,11 @@ 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(text)`. See [Nosuperuser
Whitelist](#set_usernosuperuser_target_whitelist-rules-and-logic) for reference
logic around calling `set_user(text)`.

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
Expand All @@ -149,10 +158,48 @@ 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(text)`
function:

* `current_user` must be `GRANT`ed `EXECUTE ON FUNCTION set_user_u(text)` OR
`current_user` must be the `OWNER` of the `set_user_u(text)` 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(text)` 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
`set_user(text)` or `set_user(text, text)` function (for simplicity, only
`set_user(text)` is used):

* `current_user` must be `GRANT`ed `EXECUTE ON FUNCTION set_user(text)` OR
`current_user` must be the `OWNER` of the `set_user(text)` function OR
`current_user` must be a superuser.
* The target rolename must be listed in `set_user.nosuperuser_target_whitelist`
OR the target 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(text)` transitions to non-superusers are blocked for all users.
* If `set_user.nosuperuser_target_whitelist` is the wildcard character, `'*'`,
all users with `EXECUTE` permission on `set_user(text)` 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
privileges of the new `current_user`. The optional enhanced logging creates an
privileges of the new `current_user`. The optional enhanced logging creates an
audit trail upon transition to an alternate role, ensuring that any privilege
escalation/alteration does not go unmonitored.

Expand Down Expand Up @@ -189,7 +236,7 @@ For the blocking of `ALTER SYSTEM` and `COPY PROGRAM` to work properly, you must
include `set_user` in shared_preload_libraries in postgresql.conf and restart
PostgreSQL.

Neither `set_user('rolename')` nor `set_user_u('rolename')` may be executed from
Neither `set_user(text)` nor `set_user_u(text)` may be executed from
within an explicit transaction block.

## Caveats
Expand Down Expand Up @@ -361,6 +408,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):
Expand Down Expand Up @@ -432,6 +480,8 @@ shared_preload_libraries = 'set_user, <extension_name>'
* `set_user.block_log_statement = on`
* Allow list of roles to escalate to superuser
* `set_user.superuser_whitelist = '<role1>,<role2>,...,<roleN>'`
* Allowed list of roles that can be switched to (not used in set_user_u)
* `set_user.nosuperuser_target_whitelist = '<role1>,<role2>,...,<roleN>'`


## Examples
Expand Down
13 changes: 13 additions & 0 deletions set_user.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -349,6 +350,13 @@ 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 target role to set_user.nosuperuser_target_whitelist.")));
}

/* keep track of original userid and value of log_statement */
save_OldUserId = OldUserId;
Expand Down Expand Up @@ -467,6 +475,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,
Expand Down