github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/alter_role.go (about)

     1  // Copyright 2017 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  	"fmt"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/clusterversion"
    18  	"github.com/cockroachdb/cockroach/pkg/security"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/roleoption"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  // AlterRoleNode represents an ALTER ROLE ... [WITH] OPTION... statement.
    29  type alterRoleNode struct {
    30  	userNameInfo
    31  	ifExists    bool
    32  	isRole      bool
    33  	roleOptions roleoption.List
    34  }
    35  
    36  // AlterRole represents a ALTER ROLE statement.
    37  // Privileges: CREATEROLE privilege.
    38  func (p *planner) AlterRole(ctx context.Context, n *tree.AlterRole) (planNode, error) {
    39  	return p.AlterRoleNode(ctx, n.Name, n.IfExists, n.IsRole, "ALTER ROLE", n.KVOptions)
    40  }
    41  
    42  func (p *planner) AlterRoleNode(
    43  	ctx context.Context,
    44  	nameE tree.Expr,
    45  	ifExists bool,
    46  	isRole bool,
    47  	opName string,
    48  	kvOptions tree.KVOptions,
    49  ) (*alterRoleNode, error) {
    50  	// Note that for Postgres, only superuser can ALTER another superuser.
    51  	// CockroachDB does not support superuser privilege right now.
    52  	// However we make it so the admin role cannot be edited (done in startExec).
    53  	if err := p.HasRoleOption(ctx, roleoption.CREATEROLE); err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	asStringOrNull := func(e tree.Expr, op string) (func() (bool, string, error), error) {
    58  		return p.TypeAsStringOrNull(ctx, e, op)
    59  	}
    60  	roleOptions, err := kvOptions.ToRoleOptions(asStringOrNull, opName)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	if err := roleOptions.CheckRoleOptionConflicts(); err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	ua, err := p.getUserAuthInfo(ctx, nameE, opName)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	return &alterRoleNode{
    74  		userNameInfo: ua,
    75  		ifExists:     ifExists,
    76  		isRole:       isRole,
    77  		roleOptions:  roleOptions,
    78  	}, nil
    79  }
    80  
    81  func (n *alterRoleNode) startExec(params runParams) error {
    82  	var opName string
    83  	if n.isRole {
    84  		sqltelemetry.IncIAMAlterCounter(sqltelemetry.Role)
    85  		opName = "alter-role"
    86  	} else {
    87  		sqltelemetry.IncIAMAlterCounter(sqltelemetry.User)
    88  		opName = "alter-user"
    89  	}
    90  	name, err := n.name()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if name == "" {
    95  		return errNoUserNameSpecified
    96  	}
    97  	if name == "admin" {
    98  		return pgerror.Newf(pgcode.InsufficientPrivilege,
    99  			"cannot edit admin role")
   100  	}
   101  	normalizedUsername, err := NormalizeAndValidateUsername(name)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	// Check if role exists.
   107  	row, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.QueryRowEx(
   108  		params.ctx,
   109  		opName,
   110  		params.p.txn,
   111  		sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   112  		fmt.Sprintf("SELECT 1 FROM %s WHERE username = $1", userTableName),
   113  		normalizedUsername,
   114  	)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	if row == nil {
   119  		if n.ifExists {
   120  			return nil
   121  		}
   122  		return errors.Newf("role/user %s does not exist", normalizedUsername)
   123  	}
   124  
   125  	if n.roleOptions.Contains(roleoption.PASSWORD) {
   126  		hashedPassword, err := n.roleOptions.GetHashedPassword()
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		// TODO(knz): Remove in 20.2.
   132  		if normalizedUsername == security.RootUser && len(hashedPassword) > 0 &&
   133  			!params.EvalContext().Settings.Version.IsActive(params.ctx, clusterversion.VersionRootPassword) {
   134  			return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState,
   135  				`setting a root password requires all nodes to be upgraded to %s`,
   136  				clusterversion.VersionByKey(clusterversion.VersionRootPassword),
   137  			)
   138  		}
   139  
   140  		if len(hashedPassword) > 0 && params.extendedEvalCtx.ExecCfg.RPCContext.Insecure {
   141  			// We disallow setting a non-empty password in insecure mode
   142  			// because insecure means an observer may have MITM'ed the change
   143  			// and learned the password.
   144  			//
   145  			// It's valid to clear the password (WITH PASSWORD NULL) however
   146  			// since that forces cert auth when moving back to secure mode,
   147  			// and certs can't be MITM'ed over the insecure SQL connection.
   148  			return pgerror.New(pgcode.InvalidPassword,
   149  				"setting or updating a password is not supported in insecure mode")
   150  		}
   151  
   152  		if hashedPassword == nil {
   153  			// v20.1 and below crash during authentication if they find a NULL value
   154  			// in system.users.hashedPassword. v20.2 and above handle this correctly,
   155  			// but we need to maintain mixed version compatibility for at least one
   156  			// release.
   157  			// TODO(nvanbenschoten): remove this for v21.1.
   158  			hashedPassword = []byte{}
   159  		}
   160  
   161  		// Updating PASSWORD is a special case since PASSWORD lives in system.users
   162  		// while the rest of the role options lives in system.role_options.
   163  		_, err = params.extendedEvalCtx.ExecCfg.InternalExecutor.Exec(
   164  			params.ctx,
   165  			opName,
   166  			params.p.txn,
   167  			`UPDATE system.users SET "hashedPassword" = $2 WHERE username = $1`,
   168  			normalizedUsername,
   169  			hashedPassword,
   170  		)
   171  		if err != nil {
   172  			return err
   173  		}
   174  	}
   175  
   176  	// Get a map of statements to execute for role options and their values.
   177  	stmts, err := n.roleOptions.GetSQLStmts(sqltelemetry.AlterRole)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	for stmt, value := range stmts {
   183  		qargs := []interface{}{normalizedUsername}
   184  
   185  		if value != nil {
   186  			isNull, val, err := value()
   187  			if err != nil {
   188  				return err
   189  			}
   190  			if isNull {
   191  				// If the value of the role option is NULL, ensure that nil is passed
   192  				// into the statement placeholder, since val is string type "NULL"
   193  				// will not be interpreted as NULL by the InternalExecutor.
   194  				qargs = append(qargs, nil)
   195  			} else {
   196  				qargs = append(qargs, val)
   197  			}
   198  		}
   199  
   200  		_, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.ExecEx(
   201  			params.ctx,
   202  			opName,
   203  			params.p.txn,
   204  			sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   205  			stmt,
   206  			qargs...,
   207  		)
   208  		if err != nil {
   209  			return err
   210  		}
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  func (*alterRoleNode) Next(runParams) (bool, error) { return false, nil }
   217  func (*alterRoleNode) Values() tree.Datums          { return tree.Datums{} }
   218  func (*alterRoleNode) Close(context.Context)        {}