github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmd/authn/mgr.go (about) 1 // Package authn is authentication server for AIStore. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package main 6 7 import ( 8 "encoding/hex" 9 "errors" 10 "fmt" 11 "net/http" 12 "time" 13 14 "github.com/NVIDIA/aistore/api/apc" 15 "github.com/NVIDIA/aistore/api/authn" 16 "github.com/NVIDIA/aistore/cmd/authn/tok" 17 "github.com/NVIDIA/aistore/cmn" 18 "github.com/NVIDIA/aistore/cmn/cos" 19 "github.com/NVIDIA/aistore/cmn/debug" 20 "github.com/NVIDIA/aistore/cmn/kvdb" 21 "github.com/NVIDIA/aistore/cmn/nlog" 22 jsoniter "github.com/json-iterator/go" 23 "golang.org/x/crypto/bcrypt" 24 ) 25 26 type mgr struct { 27 clientH *http.Client 28 clientTLS *http.Client 29 db kvdb.Driver 30 } 31 32 var ( 33 errInvalidCredentials = errors.New("invalid credentials") 34 35 predefinedRoles = []struct { 36 prefix string 37 desc string 38 perms apc.AccessAttrs 39 }{ 40 {ClusterOwnerRole, "Admin access to %s", apc.AccessAll}, 41 {BucketOwnerRole, "Full access to buckets in %s", apc.AccessRW}, 42 {GuestRole, "Read-only access to buckets in %s", apc.AccessRO}, 43 } 44 ) 45 46 // If user DB exists, loads the data from the file and decrypts passwords 47 func newMgr(driver kvdb.Driver) (m *mgr, err error) { 48 m = &mgr{ 49 db: driver, 50 } 51 m.clientH, m.clientTLS = cmn.NewDefaultClients(time.Duration(Conf.Timeout.Default)) 52 err = initializeDB(driver) 53 return 54 } 55 56 func (*mgr) String() string { return svcName } 57 58 // 59 // users ============================================================ 60 // 61 62 // Registers a new user. It is info from a user, so the password 63 // is not encrypted and a few fields are not filled(e.g, Access). 64 func (m *mgr) addUser(info *authn.User) error { 65 if info.ID == "" || info.Password == "" { 66 return errInvalidCredentials 67 } 68 69 _, err := m.db.GetString(usersCollection, info.ID) 70 if err == nil { 71 return fmt.Errorf("user %q already registered", info.ID) 72 } 73 info.Password = encryptPassword(info.Password) 74 return m.db.Set(usersCollection, info.ID, info) 75 } 76 77 // Deletes an existing user 78 func (m *mgr) delUser(userID string) error { 79 if userID == adminUserID { 80 return fmt.Errorf("cannot remove built-in %q account", adminUserID) 81 } 82 return m.db.Delete(usersCollection, userID) 83 } 84 85 // Updates an existing user. The function invalidates user tokens after 86 // successful update. 87 func (m *mgr) updateUser(userID string, updateReq *authn.User) error { 88 uInfo := &authn.User{} 89 err := m.db.Get(usersCollection, userID, uInfo) 90 if err != nil { 91 return cos.NewErrNotFound(m, "user "+userID) 92 } 93 if userID == adminUserID && len(updateReq.Roles) != 0 { 94 return errors.New("cannot change administrator's role") 95 } 96 97 if updateReq.Password != "" { 98 uInfo.Password = encryptPassword(updateReq.Password) 99 } 100 if len(updateReq.Roles) != 0 { 101 uInfo.Roles = updateReq.Roles 102 } 103 uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, updateReq.ClusterACLs, "") 104 uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, updateReq.BucketACLs, "") 105 106 return m.db.Set(usersCollection, userID, uInfo) 107 } 108 109 func (m *mgr) lookupUser(userID string) (*authn.User, error) { 110 uInfo := &authn.User{} 111 err := m.db.Get(usersCollection, userID, uInfo) 112 if err != nil { 113 return nil, err 114 } 115 116 // update ACLs with roles's ones 117 for _, role := range uInfo.Roles { 118 rInfo := &authn.Role{} 119 err := m.db.Get(rolesCollection, role, rInfo) 120 if err != nil { 121 continue 122 } 123 uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, rInfo.ClusterACLs, "") 124 uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, rInfo.BucketACLs, "") 125 } 126 127 return uInfo, nil 128 } 129 130 func (m *mgr) userList() (map[string]*authn.User, error) { 131 recs, err := m.db.GetAll(usersCollection, "") 132 if err != nil { 133 return nil, err 134 } 135 users := make(map[string]*authn.User, 4) 136 for _, str := range recs { 137 uInfo := &authn.User{} 138 err := jsoniter.Unmarshal([]byte(str), uInfo) 139 cos.AssertNoErr(err) 140 users[uInfo.ID] = uInfo 141 } 142 return users, nil 143 } 144 145 // 146 // roles ============================================================ 147 // 148 149 // Registers a new role 150 func (m *mgr) addRole(info *authn.Role) error { 151 if info.ID == "" { 152 return errors.New("role name is undefined") 153 } 154 if info.IsAdmin { 155 return fmt.Errorf("only built-in roles can have %q permissions", adminUserID) 156 } 157 158 _, err := m.db.GetString(rolesCollection, info.ID) 159 if err == nil { 160 return fmt.Errorf("role %q already exists", info.ID) 161 } 162 return m.db.Set(rolesCollection, info.ID, info) 163 } 164 165 // Deletes an existing role 166 func (m *mgr) delRole(role string) error { 167 if role == authn.AdminRole { 168 return fmt.Errorf("cannot remove built-in %q role", authn.AdminRole) 169 } 170 return m.db.Delete(rolesCollection, role) 171 } 172 173 // Updates an existing role 174 func (m *mgr) updateRole(role string, updateReq *authn.Role) error { 175 if role == authn.AdminRole { 176 return fmt.Errorf("cannot modify built-in %q role", authn.AdminRole) 177 } 178 rInfo := &authn.Role{} 179 err := m.db.Get(rolesCollection, role, rInfo) 180 if err != nil { 181 return cos.NewErrNotFound(m, "role "+role) 182 } 183 184 if updateReq.Desc != "" { 185 rInfo.Desc = updateReq.Desc 186 } 187 if len(updateReq.Roles) != 0 { 188 rInfo.Roles = updateReq.Roles 189 } 190 rInfo.ClusterACLs = mergeClusterACLs(rInfo.ClusterACLs, updateReq.ClusterACLs, "") 191 rInfo.BucketACLs = mergeBckACLs(rInfo.BucketACLs, updateReq.BucketACLs, "") 192 193 return m.db.Set(rolesCollection, role, rInfo) 194 } 195 196 func (m *mgr) lookupRole(roleID string) (*authn.Role, error) { 197 rInfo := &authn.Role{} 198 err := m.db.Get(rolesCollection, roleID, rInfo) 199 if err != nil { 200 return nil, err 201 } 202 return rInfo, nil 203 } 204 205 func (m *mgr) roleList() ([]*authn.Role, error) { 206 recs, err := m.db.GetAll(rolesCollection, "") 207 if err != nil { 208 return nil, err 209 } 210 roles := make([]*authn.Role, 0, len(recs)) 211 for _, str := range recs { 212 role := &authn.Role{} 213 err := jsoniter.Unmarshal([]byte(str), role) 214 if err != nil { 215 return nil, err 216 } 217 roles = append(roles, role) 218 } 219 return roles, nil 220 } 221 222 // Creates predefined roles for just added clusters. Errors are logged and 223 // are not returned to a caller as it is not crucial. 224 func (m *mgr) createRolesForCluster(clu *authn.CluACL) { 225 for _, pr := range predefinedRoles { 226 suffix := cos.Either(clu.Alias, clu.ID) 227 uid := pr.prefix + "-" + suffix 228 rInfo := &authn.Role{} 229 if err := m.db.Get(rolesCollection, uid, rInfo); err == nil { 230 continue 231 } 232 rInfo.ID = uid 233 cluName := clu.ID 234 if clu.Alias != "" { 235 cluName += "[" + clu.Alias + "]" 236 } 237 rInfo.Desc = fmt.Sprintf(pr.desc, cluName) 238 rInfo.ClusterACLs = []*authn.CluACL{ 239 {ID: clu.ID, Access: pr.perms}, 240 } 241 if err := m.db.Set(rolesCollection, uid, rInfo); err != nil { 242 nlog.Errorf("Failed to create role %s: %v", uid, err) 243 } 244 } 245 } 246 247 // 248 // clusters ============================================================ 249 // 250 251 func (m *mgr) clus() (map[string]*authn.CluACL, error) { 252 clusters, err := m.db.GetAll(clustersCollection, "") 253 if err != nil { 254 return nil, err 255 } 256 clus := make(map[string]*authn.CluACL, len(clusters)) 257 for cid, s := range clusters { 258 cInfo := &authn.CluACL{} 259 if err := jsoniter.Unmarshal([]byte(s), cInfo); err != nil { 260 nlog.Errorf("Failed to parse cluster %s info: %v", cid, err) 261 continue 262 } 263 clus[cInfo.ID] = cInfo 264 } 265 return clus, nil 266 } 267 268 // Returns a cluster ID which ID or Alias equal cluID or cluAlias. 269 func (m *mgr) cluLookup(cluID, cluAlias string) string { 270 clus, err := m.clus() 271 if err != nil { 272 return "" 273 } 274 for cid, cInfo := range clus { 275 if cid != "" && (cid == cluID || cid == cluAlias) { 276 return cid 277 } 278 if cluAlias == "" { 279 continue 280 } 281 if cInfo.ID == cluAlias || cInfo.Alias == cluAlias { 282 return cid 283 } 284 } 285 return "" 286 } 287 288 // Get an existing cluster 289 func (m *mgr) getCluster(cluID string) (*authn.CluACL, error) { 290 cid := m.cluLookup(cluID, cluID) 291 if cid == "" { 292 return nil, cos.NewErrNotFound(m, "cluster "+cluID) 293 } 294 clu := &authn.CluACL{} 295 err := m.db.Get(clustersCollection, cid, clu) 296 return clu, err 297 } 298 299 // Registers a new cluster 300 func (m *mgr) addCluster(clu *authn.CluACL) error { 301 if clu.ID == "" { 302 return errors.New("cluster UUID is undefined") 303 } 304 305 cid := m.cluLookup(clu.ID, clu.Alias) 306 if cid != "" { 307 return fmt.Errorf("cluster %s[%s] already registered", clu.ID, cid) 308 } 309 310 // secret handshake 311 if err := m.validateSecret(clu); err != nil { 312 return err 313 } 314 315 if err := m.db.Set(clustersCollection, clu.ID, clu); err != nil { 316 return err 317 } 318 m.createRolesForCluster(clu) 319 320 go m.syncTokenList(clu) 321 return nil 322 } 323 324 func (m *mgr) updateCluster(cluID string, info *authn.CluACL) error { 325 if info.ID == "" { 326 return errors.New("cluster ID is undefined") 327 } 328 clu := &authn.CluACL{} 329 if err := m.db.Get(clustersCollection, cluID, clu); err != nil { 330 return err 331 } 332 if info.Alias != "" { 333 cid := m.cluLookup("", info.Alias) 334 if cid != "" && cid != clu.ID { 335 return fmt.Errorf("alias %q is used for cluster %q", info.Alias, cid) 336 } 337 clu.Alias = info.Alias 338 } 339 if len(info.URLs) != 0 { 340 clu.URLs = info.URLs 341 } 342 343 // secret handshake 344 if err := m.validateSecret(clu); err != nil { 345 return err 346 } 347 348 return m.db.Set(clustersCollection, cluID, clu) 349 } 350 351 // Unregister an existing cluster 352 func (m *mgr) delCluster(cluID string) error { 353 cid := m.cluLookup(cluID, cluID) 354 if cid == "" { 355 return cos.NewErrNotFound(m, "cluster "+cluID) 356 } 357 return m.db.Delete(clustersCollection, cid) 358 } 359 360 // 361 // tokens ============================================================ 362 // 363 364 // Generates a token for a user if user credentials are valid. If the token is 365 // already generated and is not expired yet the existing token is returned. 366 // Token includes user ID, permissions, and token expiration time. 367 // If a new token was generated then it sends the proxy a new valid token list 368 func (m *mgr) issueToken(userID, pwd string, msg *authn.LoginMsg) (string, error) { 369 var ( 370 err error 371 expires time.Time 372 token string 373 uInfo = &authn.User{} 374 cid string 375 ) 376 377 err = m.db.Get(usersCollection, userID, uInfo) 378 if err != nil { 379 nlog.Errorln(err) 380 return "", errInvalidCredentials 381 } 382 if !isSamePassword(pwd, uInfo.Password) { 383 return "", errInvalidCredentials 384 } 385 if !uInfo.IsAdmin() { 386 if msg.ClusterID == "" { 387 return "", fmt.Errorf("Couldn't issue token for %q: cluster ID not set", userID) 388 } 389 cid = m.cluLookup(msg.ClusterID, msg.ClusterID) 390 if cid == "" { 391 return "", cos.NewErrNotFound(m, "cluster "+msg.ClusterID) 392 } 393 uInfo.ClusterACLs = mergeClusterACLs(make([]*authn.CluACL, 0, len(uInfo.ClusterACLs)), uInfo.ClusterACLs, cid) 394 uInfo.BucketACLs = mergeBckACLs(make([]*authn.BckACL, 0, len(uInfo.BucketACLs)), uInfo.BucketACLs, cid) 395 } 396 397 // update ACLs with roles's ones 398 for _, role := range uInfo.Roles { 399 rInfo := &authn.Role{} 400 err := m.db.Get(rolesCollection, role, rInfo) 401 if err != nil { 402 continue 403 } 404 uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, rInfo.ClusterACLs, cid) 405 uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, rInfo.BucketACLs, cid) 406 } 407 408 // generate token 409 Conf.RLock() 410 defer Conf.RUnlock() 411 issued := time.Now() 412 expDelta := time.Duration(Conf.Server.ExpirePeriod) 413 if msg.ExpiresIn != nil { 414 expDelta = *msg.ExpiresIn 415 } 416 if expDelta == 0 { 417 expDelta = foreverTokenTime 418 } 419 expires = issued.Add(expDelta) 420 421 // put all useful info into token: who owns the token, when it was issued, 422 // when it expires and credentials to log in AWS, GCP etc. 423 // If a user is a super user, it is enough to pass only isAdmin marker 424 if uInfo.IsAdmin() { 425 token, err = tok.IssueAdminJWT(expires, userID, Conf.Server.Secret) 426 } else { 427 m.fixClusterIDs(uInfo.ClusterACLs) 428 token, err = tok.IssueJWT(expires, userID, uInfo.BucketACLs, uInfo.ClusterACLs, Conf.Server.Secret) 429 } 430 return token, err 431 } 432 433 // Before putting a list of cluster permissions to a token, cluster aliases 434 // must be replaced with their IDs. 435 func (m *mgr) fixClusterIDs(lst []*authn.CluACL) { 436 clus, err := m.clus() 437 if err != nil { 438 return 439 } 440 for _, cInfo := range lst { 441 if _, ok := clus[cInfo.ID]; ok { 442 continue 443 } 444 for _, clu := range clus { 445 if clu.Alias == cInfo.ID { 446 cInfo.ID = clu.ID 447 } 448 } 449 } 450 } 451 452 // Delete existing token, a.k.a log out 453 // If the token was removed successfully then it sends the proxy a new valid token list 454 func (m *mgr) revokeToken(token string) error { 455 err := m.db.Set(revokedCollection, token, "!") 456 if err != nil { 457 return err 458 } 459 460 // send the token in all case to allow an admin to revoke 461 // an existing token even after cluster restart 462 go m.broadcastRevoked(token) 463 return nil 464 } 465 466 // Create a list of non-expired and valid revoked tokens. 467 // Obsolete and invalid tokens are removed from the database. 468 func (m *mgr) generateRevokedTokenList() ([]string, error) { 469 tokens, err := m.db.List(revokedCollection, "") 470 if err != nil { 471 debug.AssertNoErr(err) 472 return nil, err 473 } 474 475 now := time.Now() 476 revokeList := make([]string, 0, len(tokens)) 477 secret := Conf.Secret() 478 for _, token := range tokens { 479 tk, err := tok.DecryptToken(token, secret) 480 if err != nil { 481 m.db.Delete(revokedCollection, token) 482 continue 483 } 484 if tk.Expires.Before(now) { 485 nlog.Infof("removing %s", tk) 486 m.db.Delete(revokedCollection, token) 487 continue 488 } 489 revokeList = append(revokeList, token) 490 } 491 return revokeList, nil 492 } 493 494 // 495 // private helpers ============================================================ 496 // 497 498 func encryptPassword(password string) string { 499 b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 500 cos.AssertNoErr(err) 501 return hex.EncodeToString(b) 502 } 503 504 func isSamePassword(password, hashed string) bool { 505 b, err := hex.DecodeString(hashed) 506 if err != nil { 507 return false 508 } 509 return bcrypt.CompareHashAndPassword(b, []byte(password)) == nil 510 } 511 512 // If the DB is empty, the function prefills some data 513 func initializeDB(driver kvdb.Driver) error { 514 users, err := driver.List(usersCollection, "") 515 if err != nil || len(users) != 0 { 516 // return on erros or when DB is already initialized 517 return err 518 } 519 role := &authn.Role{ 520 ID: authn.AdminRole, 521 Desc: "AuthN administrator", 522 IsAdmin: true, 523 } 524 if err := driver.Set(rolesCollection, authn.AdminRole, role); err != nil { 525 return err 526 } 527 su := &authn.User{ 528 ID: adminUserID, 529 Password: encryptPassword(adminUserPass), 530 Roles: []string{authn.AdminRole}, 531 } 532 return driver.Set(usersCollection, adminUserID, su) 533 }