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) {}