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 }