github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/create_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  	"regexp"
    17  
    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  // CreateRoleNode creates entries in the system.users table.
    29  // This is called from CREATE USER and CREATE ROLE.
    30  type CreateRoleNode struct {
    31  	ifNotExists bool
    32  	isRole      bool
    33  	roleOptions roleoption.List
    34  	userNameInfo
    35  }
    36  
    37  var userTableName = tree.NewTableName("system", "users")
    38  
    39  // RoleOptionsTableName represents system.role_options.
    40  var RoleOptionsTableName = tree.NewTableName("system", "role_options")
    41  
    42  // CreateRole represents a CREATE ROLE statement.
    43  // Privileges: INSERT on system.users.
    44  //   notes: postgres allows the creation of users with an empty password. We do
    45  //          as well, but disallow password authentication for these users.
    46  func (p *planner) CreateRole(ctx context.Context, n *tree.CreateRole) (planNode, error) {
    47  	return p.CreateRoleNode(ctx, n.Name, n.IfNotExists, n.IsRole,
    48  		"CREATE ROLE", n.KVOptions)
    49  }
    50  
    51  // CreateRoleNode creates a "create user" plan node.
    52  // This can be called from CREATE USER or CREATE ROLE.
    53  func (p *planner) CreateRoleNode(
    54  	ctx context.Context,
    55  	nameE tree.Expr,
    56  	ifNotExists bool,
    57  	isRole bool,
    58  	opName string,
    59  	kvOptions tree.KVOptions,
    60  ) (*CreateRoleNode, error) {
    61  	if err := p.HasRoleOption(ctx, roleoption.CREATEROLE); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	asStringOrNull := func(e tree.Expr, op string) (func() (bool, string, error), error) {
    66  		return p.TypeAsStringOrNull(ctx, e, op)
    67  	}
    68  	roleOptions, err := kvOptions.ToRoleOptions(asStringOrNull, opName)
    69  
    70  	// Using CREATE ROLE syntax enables NOLOGIN by default.
    71  	if isRole && !roleOptions.Contains(roleoption.LOGIN) &&
    72  		!roleOptions.Contains(roleoption.NOLOGIN) {
    73  		roleOptions = append(roleOptions,
    74  			roleoption.RoleOption{Option: roleoption.NOLOGIN, HasValue: false})
    75  	}
    76  
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if err := roleOptions.CheckRoleOptionConflicts(); err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	ua, err := p.getUserAuthInfo(ctx, nameE, opName)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	return &CreateRoleNode{
    91  		userNameInfo: ua,
    92  		ifNotExists:  ifNotExists,
    93  		isRole:       isRole,
    94  		roleOptions:  roleOptions,
    95  	}, nil
    96  }
    97  
    98  func (n *CreateRoleNode) startExec(params runParams) error {
    99  	var opName string
   100  	if n.isRole {
   101  		sqltelemetry.IncIAMCreateCounter(sqltelemetry.Role)
   102  		opName = "create-role"
   103  	} else {
   104  		sqltelemetry.IncIAMCreateCounter(sqltelemetry.User)
   105  		opName = "create-user"
   106  	}
   107  
   108  	normalizedUsername, err := n.userNameInfo.resolveUsername()
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	var hashedPassword []byte
   114  	if n.roleOptions.Contains(roleoption.PASSWORD) {
   115  		hashedPassword, err = n.roleOptions.GetHashedPassword()
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		if len(hashedPassword) > 0 && params.extendedEvalCtx.ExecCfg.RPCContext.Insecure {
   121  			// We disallow setting a non-empty password in insecure mode
   122  			// because insecure means an observer may have MITM'ed the change
   123  			// and learned the password.
   124  			//
   125  			// It's valid to clear the password (WITH PASSWORD NULL) however
   126  			// since that forces cert auth when moving back to secure mode,
   127  			// and certs can't be MITM'ed over the insecure SQL connection.
   128  			return pgerror.New(pgcode.InvalidPassword,
   129  				"setting or updating a password is not supported in insecure mode")
   130  		}
   131  	} else {
   132  		// v20.1 and below crash during authentication if they find a NULL value
   133  		// in system.users.hashedPassword. v20.2 and above handle this correctly,
   134  		// but we need to maintain mixed version compatibility for at least one
   135  		// release.
   136  		// TODO(nvanbenschoten): remove this for v21.1.
   137  		hashedPassword = []byte{}
   138  	}
   139  
   140  	// Reject the "public" role. It does not have an entry in the users table but is reserved.
   141  	if normalizedUsername == sqlbase.PublicRole {
   142  		return pgerror.Newf(pgcode.ReservedName, "role name %q is reserved", sqlbase.PublicRole)
   143  	}
   144  
   145  	// Check if the user/role exists.
   146  	row, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.QueryRowEx(
   147  		params.ctx,
   148  		opName,
   149  		params.p.txn,
   150  		sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   151  		fmt.Sprintf(`select "isRole" from %s where username = $1`, userTableName),
   152  		normalizedUsername,
   153  	)
   154  	if err != nil {
   155  		return errors.Wrapf(err, "error looking up user")
   156  	}
   157  	if row != nil {
   158  		if n.ifNotExists {
   159  			return nil
   160  		}
   161  		return pgerror.Newf(pgcode.DuplicateObject,
   162  			"a role/user named %s already exists", normalizedUsername)
   163  	}
   164  
   165  	// TODO(richardjcai): move hashedPassword column to system.role_options.
   166  	rowsAffected, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.Exec(
   167  		params.ctx,
   168  		opName,
   169  		params.p.txn,
   170  		fmt.Sprintf("insert into %s values ($1, $2, $3)", userTableName),
   171  		normalizedUsername,
   172  		hashedPassword,
   173  		n.isRole,
   174  	)
   175  
   176  	if err != nil {
   177  		return err
   178  	} else if rowsAffected != 1 {
   179  		return errors.AssertionFailedf("%d rows affected by user creation; expected exactly one row affected",
   180  			rowsAffected,
   181  		)
   182  	}
   183  
   184  	// Get a map of statements to execute for role options and their values.
   185  	stmts, err := n.roleOptions.GetSQLStmts(sqltelemetry.CreateRole)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	for stmt, value := range stmts {
   191  		qargs := []interface{}{normalizedUsername}
   192  
   193  		if value != nil {
   194  			isNull, val, err := value()
   195  			if err != nil {
   196  				return err
   197  			}
   198  			if isNull {
   199  				// If the value of the role option is NULL, ensure that nil is passed
   200  				// into the statement placeholder, since val is string type "NULL"
   201  				// will not be interpreted as NULL by the InternalExecutor.
   202  				qargs = append(qargs, nil)
   203  			} else {
   204  				qargs = append(qargs, val)
   205  			}
   206  		}
   207  
   208  		_, err = params.extendedEvalCtx.ExecCfg.InternalExecutor.ExecEx(
   209  			params.ctx,
   210  			opName,
   211  			params.p.txn,
   212  			sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   213  			stmt,
   214  			qargs...,
   215  		)
   216  		if err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // Next implements the planNode interface.
   225  func (*CreateRoleNode) Next(runParams) (bool, error) { return false, nil }
   226  
   227  // Values implements the planNode interface.
   228  func (*CreateRoleNode) Values() tree.Datums { return tree.Datums{} }
   229  
   230  // Close implements the planNode interface.
   231  func (*CreateRoleNode) Close(context.Context) {}
   232  
   233  const usernameHelp = "Usernames are case insensitive, must start with a letter, " +
   234  	"digit or underscore, may contain letters, digits, dashes, periods, or underscores, and must not exceed 63 characters."
   235  
   236  var usernameRE = regexp.MustCompile(`^[\p{Ll}0-9_][---\p{Ll}0-9_.]*$`)
   237  
   238  var blacklistedUsernames = map[string]struct{}{
   239  	security.NodeUser: {},
   240  }
   241  
   242  // NormalizeAndValidateUsername case folds the specified username and verifies
   243  // it validates according to the usernameRE regular expression.
   244  // It rejects reserved user names.
   245  func NormalizeAndValidateUsername(username string) (string, error) {
   246  	username, err := NormalizeAndValidateUsernameNoBlacklist(username)
   247  	if err != nil {
   248  		return "", err
   249  	}
   250  	if _, ok := blacklistedUsernames[username]; ok {
   251  		return "", pgerror.Newf(pgcode.ReservedName, "username %q reserved", username)
   252  	}
   253  	return username, nil
   254  }
   255  
   256  // NormalizeAndValidateUsernameNoBlacklist case folds the specified username and verifies
   257  // it validates according to the usernameRE regular expression.
   258  func NormalizeAndValidateUsernameNoBlacklist(username string) (string, error) {
   259  	username = tree.Name(username).Normalize()
   260  	if !usernameRE.MatchString(username) {
   261  		return "", errors.WithHint(pgerror.Newf(pgcode.InvalidName, "username %q invalid", username), usernameHelp)
   262  	}
   263  	if len(username) > 63 {
   264  		return "", errors.WithHint(pgerror.Newf(pgcode.NameTooLong, "username %q is too long", username), usernameHelp)
   265  	}
   266  	return username, nil
   267  }
   268  
   269  var errNoUserNameSpecified = errors.New("no username specified")
   270  
   271  type userNameInfo struct {
   272  	name func() (string, error)
   273  }
   274  
   275  func (p *planner) getUserAuthInfo(
   276  	ctx context.Context, nameE tree.Expr, context string,
   277  ) (userNameInfo, error) {
   278  	name, err := p.TypeAsString(ctx, nameE, context)
   279  	if err != nil {
   280  		return userNameInfo{}, err
   281  	}
   282  
   283  	return userNameInfo{name: name}, nil
   284  }
   285  
   286  // resolveUsername returns the actual user name.
   287  func (ua *userNameInfo) resolveUsername() (string, error) {
   288  	name, err := ua.name()
   289  	if err != nil {
   290  		return "", err
   291  	}
   292  	if name == "" {
   293  		return "", errNoUserNameSpecified
   294  	}
   295  	normalizedUsername, err := NormalizeAndValidateUsername(name)
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  
   300  	return normalizedUsername, nil
   301  }