github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/grant_role.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql 12 13 import ( 14 "context" 15 "strings" 16 17 "github.com/cockroachdb/cockroach/pkg/security" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 20 "github.com/cockroachdb/cockroach/pkg/sql/roleoption" 21 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 22 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 23 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 24 "github.com/cockroachdb/cockroach/pkg/util/tracing" 25 "github.com/cockroachdb/errors" 26 ) 27 28 // GrantRoleNode creates entries in the system.role_members table. 29 // This is called from GRANT <ROLE> 30 type GrantRoleNode struct { 31 roles tree.NameList 32 members tree.NameList 33 adminOption bool 34 35 run grantRoleRun 36 } 37 38 type grantRoleRun struct { 39 rowsAffected int 40 } 41 42 // GrantRole represents a GRANT ROLE statement. 43 func (p *planner) GrantRole(ctx context.Context, n *tree.GrantRole) (planNode, error) { 44 return p.GrantRoleNode(ctx, n) 45 } 46 47 func (p *planner) GrantRoleNode(ctx context.Context, n *tree.GrantRole) (*GrantRoleNode, error) { 48 sqltelemetry.IncIAMGrantCounter(n.AdminOption) 49 50 ctx, span := tracing.ChildSpan(ctx, n.StatementTag()) 51 defer tracing.FinishSpan(span) 52 53 hasAdminRole, err := p.HasAdminRole(ctx) 54 if err != nil { 55 return nil, err 56 } 57 // Check permissions on each role. 58 allRoles, err := p.MemberOfWithAdminOption(ctx, p.User()) 59 if err != nil { 60 return nil, err 61 } 62 for _, r := range n.Roles { 63 // If the user is an admin, don't check if the user is allowed to add/drop 64 // roles in the role. However, if the role being modified is the admin role, then 65 // make sure the user is an admin with the admin option. 66 if hasAdminRole && string(r) != sqlbase.AdminRole { 67 continue 68 } 69 if isAdmin, ok := allRoles[string(r)]; !ok || !isAdmin { 70 if string(r) == sqlbase.AdminRole { 71 return nil, pgerror.Newf(pgcode.InsufficientPrivilege, 72 "%s is not a role admin for role %s", p.User(), r) 73 } 74 return nil, pgerror.Newf(pgcode.InsufficientPrivilege, 75 "%s is not a superuser or role admin for role %s", p.User(), r) 76 } 77 } 78 79 // Check that roles exist. 80 // TODO(mberhault): just like GRANT/REVOKE privileges, we fetch the list of all roles. 81 // This is wasteful when we have a LOT of roles compared to the number of roles being operated on. 82 roles, err := p.GetAllRoles(ctx) 83 if err != nil { 84 return nil, err 85 } 86 87 // NOTE: membership manipulation involving the "public" pseudo-role fails with 88 // "role public does not exist". This matches postgres behavior. 89 90 for _, r := range n.Roles { 91 if _, ok := roles[string(r)]; !ok { 92 for name := range roleoption.ByName { 93 if uppercase := strings.ToUpper(string(r)); uppercase == name { 94 return nil, errors.WithHintf( 95 pgerror.Newf(pgcode.UndefinedObject, "role/user %s does not exist", r), 96 "%s is a role option, try using ALTER ROLE to change a role's options.", uppercase) 97 } 98 } 99 return nil, pgerror.Newf(pgcode.UndefinedObject, "role/user %s does not exist", r) 100 } 101 } 102 103 for _, m := range n.Members { 104 if _, ok := roles[string(m)]; !ok { 105 return nil, pgerror.Newf(pgcode.UndefinedObject, "role/user %s does not exist", m) 106 } 107 } 108 109 // Given an acyclic directed membership graph, adding a new edge (grant.Member ∈ grant.Role) 110 // means checking whether we have an expanded relationship (grant.Role ∈ ... ∈ grant.Member) 111 // For each grant.Role, we lookup all the roles it is a member of. 112 // After adding a given edge (grant.Member ∈ grant.Role), we add the edge to the list as well. 113 allRoleMemberships := make(map[string]map[string]bool) 114 for _, rawR := range n.Roles { 115 r := string(rawR) 116 allRoles, err := p.MemberOfWithAdminOption(ctx, r) 117 if err != nil { 118 return nil, err 119 } 120 allRoleMemberships[r] = allRoles 121 } 122 123 // Since we perform no queries here, check all role/member pairs for cycles. 124 // Only if there are no errors do we proceed to write them. 125 for _, rawR := range n.Roles { 126 r := string(rawR) 127 for _, rawM := range n.Members { 128 m := string(rawM) 129 if r == m { 130 // self-cycle. 131 return nil, pgerror.Newf(pgcode.InvalidGrantOperation, "%s cannot be a member of itself", m) 132 } 133 // Check if grant.Role ∈ ... ∈ grant.Member 134 if memberOf, ok := allRoleMemberships[r]; ok { 135 if _, ok = memberOf[m]; ok { 136 return nil, pgerror.Newf(pgcode.InvalidGrantOperation, 137 "making %s a member of %s would create a cycle", m, r) 138 } 139 } 140 // Add the new membership. We don't care about the actual bool value. 141 if _, ok := allRoleMemberships[m]; !ok { 142 allRoleMemberships[m] = make(map[string]bool) 143 } 144 allRoleMemberships[m][r] = false 145 } 146 } 147 148 return &GrantRoleNode{ 149 roles: n.Roles, 150 members: n.Members, 151 adminOption: n.AdminOption, 152 }, nil 153 } 154 155 func (n *GrantRoleNode) startExec(params runParams) error { 156 opName := "grant-role" 157 // Add memberships. Existing memberships are allowed. 158 // If admin option is false, we do not remove it from existing memberships. 159 memberStmt := `INSERT INTO system.role_members ("role", "member", "isAdmin") VALUES ($1, $2, $3) ON CONFLICT ("role", "member")` 160 if n.adminOption { 161 // admin option: true, set "isAdmin" even if the membership exists. 162 memberStmt += ` DO UPDATE SET "isAdmin" = true` 163 } else { 164 // admin option: false, do not clear it from existing memberships. 165 memberStmt += ` DO NOTHING` 166 } 167 168 var rowsAffected int 169 for _, r := range n.roles { 170 for _, m := range n.members { 171 affected, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.ExecEx( 172 params.ctx, 173 opName, 174 params.p.txn, 175 sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, 176 memberStmt, 177 r, m, n.adminOption, 178 ) 179 if err != nil { 180 return err 181 } 182 183 rowsAffected += affected 184 } 185 } 186 187 // We need to bump the table version to trigger a refresh if anything changed. 188 if rowsAffected > 0 { 189 if err := params.p.BumpRoleMembershipTableVersion(params.ctx); err != nil { 190 return err 191 } 192 } 193 194 n.run.rowsAffected += rowsAffected 195 196 return nil 197 } 198 199 // Next implements the planNode interface. 200 func (*GrantRoleNode) Next(runParams) (bool, error) { return false, nil } 201 202 // Values implements the planNode interface. 203 func (*GrantRoleNode) Values() tree.Datums { return tree.Datums{} } 204 205 // Close implements the planNode interface. 206 func (*GrantRoleNode) Close(context.Context) {}