diff options
author | Robert Haas <rhaas@postgresql.org> | 2022-11-18 12:32:50 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2022-11-18 12:32:56 -0500 |
commit | 3d14e171e9e2236139e8976f3309a588bcc8683b (patch) | |
tree | 3a93134fefd39082982fc4aedc9cecedceea48a5 /src/backend/utils/adt/acl.c | |
parent | f84ff0c6d4eb4e470e55f48103a7edd269d13c49 (diff) | |
download | postgresql-3d14e171e9e2236139e8976f3309a588bcc8683b.tar.gz postgresql-3d14e171e9e2236139e8976f3309a588bcc8683b.zip |
Add a SET option to the GRANT command.
Similar to how the INHERIT option controls whether or not the
permissions of the granted role are automatically available to the
grantee, the new SET permission controls whether or not the grantee
may use the SET ROLE command to assume the privileges of the granted
role.
In addition, the new SET permission controls whether or not it
is possible to transfer ownership of objects to the target role
or to create new objects owned by the target role using commands
such as CREATE DATABASE .. OWNER. We could alternatively have made
this controlled by the INHERIT option, or allow it when either
option is given. An advantage of this approach is that if you
are granted a predefined role with INHERIT TRUE, SET FALSE, you
can't go and create objects owned by that role.
The underlying theory here is that the ability to create objects
as a target role is not a privilege per se, and thus does not
depend on whether you inherit the target role's privileges. However,
it's surely something you could do anyway if you could SET ROLE
to the target role, and thus making it contingent on whether you
have that ability is reasonable.
Design review by Nathan Bossat, Wolfgang Walther, Jeff Davis,
Peter Eisentraut, and Stephen Frost.
Discussion: http://postgr.es/m/CA+Tgmob+zDSRS6JXYrgq0NWdzCXuTNzT5eK54Dn2hhgt17nm8A@mail.gmail.com
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r-- | src/backend/utils/adt/acl.c | 106 |
1 files changed, 80 insertions, 26 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 8bdb9461b7f..d4d68f97243 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -67,16 +67,17 @@ typedef struct * * Each element of cached_roles is an OID list of constituent roles for the * corresponding element of cached_role (always including the cached_role - * itself). One cache has ROLERECURSE_PRIVS semantics, and the other has - * ROLERECURSE_MEMBERS semantics. + * itself). There's a separate cache for each RoleRecurseType, with the + * corresponding semantics. */ enum RoleRecurseType { - ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */ - ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */ + ROLERECURSE_MEMBERS = 0, /* recurse unconditionally */ + ROLERECURSE_PRIVS = 1, /* recurse through inheritable grants */ + ROLERECURSE_SETROLE = 2 /* recurse through grants with set_option */ }; -static Oid cached_role[] = {InvalidOid, InvalidOid}; -static List *cached_roles[] = {NIL, NIL}; +static Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid}; +static List *cached_roles[] = {NIL, NIL, NIL}; static uint32 cached_db_hash; @@ -4691,10 +4692,13 @@ convert_role_priv_string(text *priv_type_text) static const priv_map role_priv_map[] = { {"USAGE", ACL_USAGE}, {"MEMBER", ACL_CREATE}, + {"SET", ACL_SET}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; @@ -4723,6 +4727,11 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode) if (has_privs_of_role(roleid, role_oid)) return ACLCHECK_OK; } + if (mode & ACL_SET) + { + if (member_can_set_role(roleid, role_oid)) + return ACLCHECK_OK; + } return ACLCHECK_NO_PRIV; } @@ -4771,15 +4780,17 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue) } /* Force membership caches to be recomputed on next use */ - cached_role[ROLERECURSE_PRIVS] = InvalidOid; cached_role[ROLERECURSE_MEMBERS] = InvalidOid; + cached_role[ROLERECURSE_PRIVS] = InvalidOid; + cached_role[ROLERECURSE_SETROLE] = InvalidOid; } /* * Get a list of roles that the specified roleid is a member of * - * Type ROLERECURSE_PRIVS recurses only through inheritable grants, - * while ROLERECURSE_MEMBERS recurses through all grants. + * Type ROLERECURSE_MEMBERS recurses through all grants; ROLERECURSE_PRIVS + * recurses only through inheritable grants; and ROLERECURSE_SETROLe recurses + * only through grants with set_option. * * Since indirect membership testing is relatively expensive, we cache * a list of memberships. Hence, the result is only guaranteed good until @@ -4870,6 +4881,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type, if (type == ROLERECURSE_PRIVS && !form->inherit_option) continue; + /* If we're supposed to ignore non-SET grants, do so. */ + if (type == ROLERECURSE_SETROLE && !form->set_option) + continue; + /* * Even though there shouldn't be any loops in the membership * graph, we must test for having already seen this role. It is @@ -4909,9 +4924,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type, /* * Does member have the privileges of role (directly or indirectly)? * - * This is defined not to recurse through grants that are not inherited; - * in such cases, membership implies the ability to do SET ROLE, but - * the privileges are not available until you've done so. + * This is defined not to recurse through grants that are not inherited, + * and only inherited grants confer the associated privileges automatically. + * + * See also member_can_set_role, below. */ bool has_privs_of_role(Oid member, Oid role) @@ -4933,49 +4949,87 @@ has_privs_of_role(Oid member, Oid role) role); } - /* - * Is member a member of role (directly or indirectly)? + * Can member use SET ROLE to this role? * - * This is defined to recurse through grants whether they are inherited or not. + * There must be a chain of grants from 'member' to 'role' each of which + * permits SET ROLE; that is, each of which has set_option = true. * - * Do not use this for privilege checking, instead use has_privs_of_role() + * It doesn't matter whether the grants are inheritable. That's a separate + * question; see has_privs_of_role. + * + * This function should be used to determine whether the session user can + * use SET ROLE to become the target user. We also use it to determine whether + * the session user can change an existing object to be owned by the target + * user, or create new objects owned by the target user. */ bool -is_member_of_role(Oid member, Oid role) +member_can_set_role(Oid member, Oid role) { /* Fast path for simple case */ if (member == role) return true; - /* Superusers have every privilege, so are part of every role */ + /* Superusers have every privilege, so can always SET ROLE */ if (superuser_arg(member)) return true; /* - * Find all the roles that member is a member of, including multi-level - * recursion, then see if target role is any one of them. + * Find all the roles that member can access via SET ROLE, including + * multi-level recursion, then see if target role is any one of them. */ - return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + return list_member_oid(roles_is_member_of(member, ROLERECURSE_SETROLE, InvalidOid, NULL), role); } /* - * check_is_member_of_role - * is_member_of_role with a standard permission-violation error if not + * Permission violation eror unless able to SET ROLE to target role. */ void -check_is_member_of_role(Oid member, Oid role) +check_can_set_role(Oid member, Oid role) { - if (!is_member_of_role(member, role)) + if (!member_can_set_role(member, role)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be member of role \"%s\"", + errmsg("must be able to SET ROLE \"%s\"", GetUserNameFromId(role, false)))); } /* + * Is member a member of role (directly or indirectly)? + * + * This is defined to recurse through grants whether they are inherited or not. + * + * Do not use this for privilege checking, instead use has_privs_of_role(). + * Don't use it for determining whether it's possible to SET ROLE to some + * other role; for that, use member_can_set_role(). And don't use it for + * determining whether it's OK to create an object owned by some other role: + * use member_can_set_role() for that, too. + * + * In short, calling this function is the wrong thing to do nearly everywhere. + */ +bool +is_member_of_role(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* Superusers have every privilege, so are part of every role */ + if (superuser_arg(member)) + return true; + + /* + * Find all the roles that member is a member of, including multi-level + * recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + InvalidOid, NULL), + role); +} + +/* * Is member a member of role, not considering superuserness? * * This is identical to is_member_of_role except we ignore superuser |