github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/authorization.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/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/privilege" 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/types" 25 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 26 "github.com/cockroachdb/errors" 27 ) 28 29 // MembershipCache is a shared cache for role membership information. 30 type MembershipCache struct { 31 syncutil.Mutex 32 tableVersion sqlbase.DescriptorVersion 33 // userCache is a mapping from username to userRoleMembership. 34 userCache map[string]userRoleMembership 35 } 36 37 // userRoleMembership is a mapping of "rolename" -> "with admin option". 38 type userRoleMembership map[string]bool 39 40 // AuthorizationAccessor for checking authorization (e.g. desc privileges). 41 type AuthorizationAccessor interface { 42 // CheckPrivilege verifies that the user has `privilege` on `descriptor`. 43 CheckPrivilege( 44 ctx context.Context, descriptor sqlbase.DescriptorProto, privilege privilege.Kind, 45 ) error 46 47 // CheckAnyPrivilege returns nil if user has any privileges at all. 48 CheckAnyPrivilege(ctx context.Context, descriptor sqlbase.DescriptorProto) error 49 50 // HasAdminRole returns tuple of bool and error: 51 // (true, nil) means that the user has an admin role (i.e. root or node) 52 // (false, nil) means that the user has NO admin role 53 // (false, err) means that there was an error running the query on 54 // the `system.users` table 55 HasAdminRole(ctx context.Context) (bool, error) 56 57 // RequireAdminRole is a wrapper on top of HasAdminRole. 58 // It errors if HasAdminRole errors or if the user isn't a super-user. 59 // Includes the named action in the error message. 60 RequireAdminRole(ctx context.Context, action string) error 61 62 // MemberOfWithAdminOption looks up all the roles (direct and indirect) that 'member' is a member 63 // of and returns a map of role -> isAdmin. 64 MemberOfWithAdminOption(ctx context.Context, member string) (map[string]bool, error) 65 } 66 67 var _ AuthorizationAccessor = &planner{} 68 69 // CheckPrivilege implements the AuthorizationAccessor interface. 70 // Requires a valid transaction to be open. 71 func (p *planner) CheckPrivilege( 72 ctx context.Context, descriptor sqlbase.DescriptorProto, privilege privilege.Kind, 73 ) error { 74 // Verify that the txn is valid in any case, so that 75 // we don't get the risk to say "OK" to root requests 76 // with an invalid API usage. 77 if p.txn == nil || !p.txn.IsOpen() { 78 return errors.AssertionFailedf("cannot use CheckPrivilege without a txn") 79 } 80 81 // Test whether the object is being audited, and if so, record an 82 // audit event. We place this check here to increase the likelihood 83 // it will not be forgotten if features are added that access 84 // descriptors (since every use of descriptors presumably need a 85 // permission check). 86 p.maybeAudit(descriptor, privilege) 87 88 user := p.SessionData().User 89 privs := descriptor.GetPrivileges() 90 91 // Check if 'user' itself has privileges. 92 if privs.CheckPrivilege(user, privilege) { 93 return nil 94 } 95 96 // Check if the 'public' pseudo-role has privileges. 97 if privs.CheckPrivilege(sqlbase.PublicRole, privilege) { 98 return nil 99 } 100 101 // Expand role memberships. 102 memberOf, err := p.MemberOfWithAdminOption(ctx, user) 103 if err != nil { 104 return err 105 } 106 107 // Iterate over the roles that 'user' is a member of. We don't care about the admin option. 108 for role := range memberOf { 109 if privs.CheckPrivilege(role, privilege) { 110 return nil 111 } 112 } 113 114 return pgerror.Newf(pgcode.InsufficientPrivilege, 115 "user %s does not have %s privilege on %s %s", 116 user, privilege, descriptor.TypeName(), descriptor.GetName()) 117 } 118 119 // CheckAnyPrivilege implements the AuthorizationAccessor interface. 120 // Requires a valid transaction to be open. 121 func (p *planner) CheckAnyPrivilege(ctx context.Context, descriptor sqlbase.DescriptorProto) error { 122 // Verify that the txn is valid in any case, so that 123 // we don't get the risk to say "OK" to root requests 124 // with an invalid API usage. 125 if p.txn == nil || !p.txn.IsOpen() { 126 return errors.AssertionFailedf("cannot use CheckAnyPrivilege without a txn") 127 } 128 129 user := p.SessionData().User 130 privs := descriptor.GetPrivileges() 131 132 // Check if 'user' itself has privileges. 133 if privs.AnyPrivilege(user) { 134 return nil 135 } 136 137 // Check if 'public' has privileges. 138 if privs.AnyPrivilege(sqlbase.PublicRole) { 139 return nil 140 } 141 142 // Expand role memberships. 143 memberOf, err := p.MemberOfWithAdminOption(ctx, user) 144 if err != nil { 145 return err 146 } 147 148 // Iterate over the roles that 'user' is a member of. We don't care about the admin option. 149 for role := range memberOf { 150 if privs.AnyPrivilege(role) { 151 return nil 152 } 153 } 154 155 return pgerror.Newf(pgcode.InsufficientPrivilege, 156 "user %s has no privileges on %s %s", 157 p.SessionData().User, descriptor.TypeName(), descriptor.GetName()) 158 } 159 160 // HasAdminRole implements the AuthorizationAccessor interface. 161 // Requires a valid transaction to be open. 162 func (p *planner) HasAdminRole(ctx context.Context) (bool, error) { 163 user := p.SessionData().User 164 if user == "" { 165 return false, errors.AssertionFailedf("empty user") 166 } 167 // Verify that the txn is valid in any case, so that 168 // we don't get the risk to say "OK" to root requests 169 // with an invalid API usage. 170 if p.txn == nil || !p.txn.IsOpen() { 171 return false, errors.AssertionFailedf("cannot use HasAdminRole without a txn") 172 } 173 174 // Check if user is 'root' or 'node'. 175 // TODO(knz): planner HasAdminRole has no business authorizing 176 // the "node" principal - node should not be issuing SQL queries. 177 if user == security.RootUser || user == security.NodeUser { 178 return true, nil 179 } 180 181 // Expand role memberships. 182 memberOf, err := p.MemberOfWithAdminOption(ctx, user) 183 if err != nil { 184 return false, err 185 } 186 187 // Check is 'user' is a member of role 'admin'. 188 if _, ok := memberOf[sqlbase.AdminRole]; ok { 189 return true, nil 190 } 191 192 return false, nil 193 } 194 195 // RequireAdminRole implements the AuthorizationAccessor interface. 196 // Requires a valid transaction to be open. 197 func (p *planner) RequireAdminRole(ctx context.Context, action string) error { 198 ok, err := p.HasAdminRole(ctx) 199 200 if err != nil { 201 return err 202 } 203 if !ok { 204 //raise error if user is not a super-user 205 return pgerror.Newf(pgcode.InsufficientPrivilege, 206 "only users with the admin role are allowed to %s", action) 207 } 208 return nil 209 } 210 211 // MemberOfWithAdminOption looks up all the roles 'member' belongs to (direct and indirect) and 212 // returns a map of "role" -> "isAdmin". 213 // The "isAdmin" flag applies to both direct and indirect members. 214 // Requires a valid transaction to be open. 215 func (p *planner) MemberOfWithAdminOption( 216 ctx context.Context, member string, 217 ) (map[string]bool, error) { 218 if p.txn == nil || !p.txn.IsOpen() { 219 return nil, errors.AssertionFailedf("cannot use MemberOfWithAdminoption without a txn") 220 } 221 222 roleMembersCache := p.execCfg.RoleMemberCache 223 224 // Lookup table version. 225 objDesc, err := p.PhysicalSchemaAccessor().GetObjectDesc( 226 ctx, 227 p.txn, 228 p.ExecCfg().Settings, 229 p.ExecCfg().Codec, 230 roleMembersTableName.Catalog(), 231 roleMembersTableName.Schema(), 232 roleMembersTableName.Table(), 233 p.ObjectLookupFlags(true /*required*/, false /*requireMutable*/), 234 ) 235 if err != nil { 236 return nil, err 237 } 238 tableDesc := objDesc.TableDesc() 239 tableVersion := tableDesc.Version 240 241 // We loop in case the table version changes while we're looking up memberships. 242 for { 243 // Check version and maybe clear cache while holding the mutex. 244 // We release the lock here instead of using defer as we need to keep 245 // going and re-lock if adding the looked-up entry. 246 roleMembersCache.Lock() 247 if roleMembersCache.tableVersion != tableVersion { 248 // Update version and drop the map. 249 roleMembersCache.tableVersion = tableVersion 250 roleMembersCache.userCache = make(map[string]userRoleMembership) 251 } 252 253 userMapping, ok := roleMembersCache.userCache[member] 254 roleMembersCache.Unlock() 255 256 if ok { 257 // Found: return. 258 return userMapping, nil 259 } 260 261 // Lookup memberships outside the lock. 262 memberships, err := p.resolveMemberOfWithAdminOption(ctx, member) 263 if err != nil { 264 return nil, err 265 } 266 267 // Update membership. 268 roleMembersCache.Lock() 269 if roleMembersCache.tableVersion != tableVersion { 270 // Table version has changed while we were looking, unlock and start over. 271 tableVersion = roleMembersCache.tableVersion 272 roleMembersCache.Unlock() 273 continue 274 } 275 276 // Table version remains the same: update map, unlock, return. 277 roleMembersCache.userCache[member] = memberships 278 roleMembersCache.Unlock() 279 return memberships, nil 280 } 281 } 282 283 // resolveMemberOfWithAdminOption performs the actual recursive role membership lookup. 284 // TODO(mberhault): this is the naive way and performs a full lookup for each user, 285 // we could save detailed memberships (as opposed to fully expanded) and reuse them 286 // across users. We may then want to lookup more than just this user. 287 func (p *planner) resolveMemberOfWithAdminOption( 288 ctx context.Context, member string, 289 ) (map[string]bool, error) { 290 ret := map[string]bool{} 291 292 // Keep track of members we looked up. 293 visited := map[string]struct{}{} 294 toVisit := []string{member} 295 lookupRolesStmt := `SELECT "role", "isAdmin" FROM system.role_members WHERE "member" = $1` 296 297 for len(toVisit) > 0 { 298 // Pop first element. 299 m := toVisit[0] 300 toVisit = toVisit[1:] 301 if _, ok := visited[m]; ok { 302 continue 303 } 304 visited[m] = struct{}{} 305 306 rows, err := p.ExecCfg().InternalExecutor.Query( 307 ctx, "expand-roles", nil /* txn */, lookupRolesStmt, m, 308 ) 309 if err != nil { 310 return nil, err 311 } 312 313 for _, row := range rows { 314 roleName := tree.MustBeDString(row[0]) 315 isAdmin := row[1].(*tree.DBool) 316 317 ret[string(roleName)] = bool(*isAdmin) 318 319 // We need to expand this role. Let the "pop" worry about already-visited elements. 320 toVisit = append(toVisit, string(roleName)) 321 } 322 } 323 324 return ret, nil 325 } 326 327 // HasRoleOption converts the roleoption to it's SQL column name and 328 // checks if the user belongs to a role where the roleprivilege has value true. 329 // Only works on checking the "positive version" of the privilege. 330 // Requires a valid transaction to be open. 331 // Example: CREATEROLE instead of NOCREATEROLE. 332 func (p *planner) HasRoleOption(ctx context.Context, roleOption roleoption.Option) error { 333 // Verify that the txn is valid in any case, so that 334 // we don't get the risk to say "OK" to root requests 335 // with an invalid API usage. 336 if p.txn == nil || !p.txn.IsOpen() { 337 return errors.AssertionFailedf("cannot use HasRoleOption without a txn") 338 } 339 340 user := p.SessionData().User 341 if user == security.RootUser || user == security.NodeUser { 342 return nil 343 } 344 345 normalizedName, err := NormalizeAndValidateUsername(user) 346 if err != nil { 347 return err 348 } 349 350 // Create list of roles for sql WHERE IN clause. 351 memberOf, err := p.MemberOfWithAdminOption(ctx, normalizedName) 352 if err != nil { 353 return err 354 } 355 356 var roles = tree.NewDArray(types.String) 357 err = roles.Append(tree.NewDString(normalizedName)) 358 if err != nil { 359 return err 360 } 361 for role := range memberOf { 362 err := roles.Append(tree.NewDString(role)) 363 if err != nil { 364 return err 365 } 366 } 367 368 hasRolePrivilege, err := p.ExecCfg().InternalExecutor.QueryEx( 369 ctx, "has-role-option", p.Txn(), 370 sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, 371 fmt.Sprintf( 372 `SELECT 1 from %s WHERE option = '%s' AND username = ANY($1) LIMIT 1`, 373 RoleOptionsTableName, 374 roleOption.String()), 375 roles) 376 377 if err != nil { 378 return err 379 } 380 381 if len(hasRolePrivilege) != 0 { 382 return nil 383 } 384 385 // User is not a member of a role that has CREATEROLE privilege. 386 return pgerror.Newf(pgcode.InsufficientPrivilege, 387 "user %s does not have %s privilege", user, roleOption.String()) 388 } 389 390 // ConnAuditingClusterSettingName is the name of the cluster setting 391 // for the cluster setting that enables pgwire-level connection audit 392 // logs. 393 // 394 // This name is defined here because it is needed in the telemetry 395 // counts in SetClusterSetting() and importing pgwire here would 396 // create a circular dependency. 397 const ConnAuditingClusterSettingName = "server.auth_log.sql_connections.enabled" 398 399 // AuthAuditingClusterSettingName is the name of the cluster setting 400 // for the cluster setting that enables pgwire-level authentication audit 401 // logs. 402 // 403 // This name is defined here because it is needed in the telemetry 404 // counts in SetClusterSetting() and importing pgwire here would 405 // create a circular dependency. 406 const AuthAuditingClusterSettingName = "server.auth_log.sql_sessions.enabled"