github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmd/authn/hserv.go (about) 1 // Package authn is authentication server for AIStore. 2 /* 3 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package main 6 7 import ( 8 "fmt" 9 "net/http" 10 "time" 11 12 "github.com/NVIDIA/aistore/api/apc" 13 "github.com/NVIDIA/aistore/api/authn" 14 "github.com/NVIDIA/aistore/cmd/authn/tok" 15 "github.com/NVIDIA/aistore/cmn" 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/nlog" 18 jsoniter "github.com/json-iterator/go" 19 ) 20 21 const svcName = "AuthN" 22 23 type hserv struct { 24 mux *http.ServeMux 25 s *http.Server 26 mgr *mgr 27 } 28 29 func newServer(mgr *mgr) *hserv { 30 srv := &hserv{mgr: mgr} 31 srv.mux = http.NewServeMux() 32 33 return srv 34 } 35 36 func parseURL(w http.ResponseWriter, r *http.Request, itemsAfter int, items []string) ([]string, error) { 37 items, err := cmn.ParseURL(r.URL.Path, items, itemsAfter, true) 38 if err != nil { 39 cmn.WriteErr(w, r, err) 40 return nil, err 41 } 42 return items, err 43 } 44 45 // Run public server to manage users and generate tokens 46 func (h *hserv) Run() (err error) { 47 portstring := fmt.Sprintf(":%d", Conf.Net.HTTP.Port) 48 nlog.Infof("Listening on *:%s", portstring) 49 50 h.registerPublicHandlers() 51 h.s = &http.Server{ 52 Addr: portstring, 53 Handler: h.mux, 54 ReadHeaderTimeout: apc.ReadHeaderTimeout, 55 } 56 if timeout, isSet := cmn.ParseReadHeaderTimeout(); isSet { // optional env var 57 h.s.ReadHeaderTimeout = timeout 58 } 59 if Conf.Net.HTTP.UseHTTPS { 60 if err = h.s.ListenAndServeTLS(Conf.Net.HTTP.Certificate, Conf.Net.HTTP.Key); err == nil { 61 return nil 62 } 63 goto rerr 64 } 65 if err = h.s.ListenAndServe(); err == nil { 66 return nil 67 } 68 rerr: 69 if err != http.ErrServerClosed { 70 nlog.Errorf("Terminated with err: %v", err) 71 return err 72 } 73 return nil 74 } 75 76 func (h *hserv) registerHandler(path string, handler func(http.ResponseWriter, *http.Request)) { 77 h.mux.HandleFunc(path, handler) 78 if !cos.IsLastB(path, '/') { 79 h.mux.HandleFunc(path+"/", handler) 80 } 81 } 82 83 func (h *hserv) registerPublicHandlers() { 84 h.registerHandler(apc.URLPathUsers.S, h.userHandler) 85 h.registerHandler(apc.URLPathTokens.S, h.tokenHandler) 86 h.registerHandler(apc.URLPathClusters.S, h.clusterHandler) 87 h.registerHandler(apc.URLPathRoles.S, h.roleHandler) 88 h.registerHandler(apc.URLPathDae.S, configHandler) 89 } 90 91 func (h *hserv) userHandler(w http.ResponseWriter, r *http.Request) { 92 switch r.Method { 93 case http.MethodDelete: 94 h.httpUserDel(w, r) 95 case http.MethodPost: 96 h.httpUserPost(w, r) 97 case http.MethodPut: 98 h.httpUserPut(w, r) 99 case http.MethodGet: 100 h.httpUserGet(w, r) 101 default: 102 cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut) 103 } 104 } 105 106 func (h *hserv) tokenHandler(w http.ResponseWriter, r *http.Request) { 107 switch r.Method { 108 case http.MethodDelete: 109 h.httpRevokeToken(w, r) 110 default: 111 cmn.WriteErr405(w, r, http.MethodDelete) 112 } 113 } 114 115 func (h *hserv) clusterHandler(w http.ResponseWriter, r *http.Request) { 116 switch r.Method { 117 case http.MethodGet: 118 h.httpSrvGet(w, r) 119 case http.MethodPost: 120 h.httpSrvPost(w, r) 121 case http.MethodPut: 122 h.httpSrvPut(w, r) 123 case http.MethodDelete: 124 h.httpSrvDelete(w, r) 125 default: 126 cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut) 127 } 128 } 129 130 // Deletes existing token, h.k.h log out 131 func (h *hserv) httpRevokeToken(w http.ResponseWriter, r *http.Request) { 132 if _, err := parseURL(w, r, 0, apc.URLPathTokens.L); err != nil { 133 return 134 } 135 msg := &authn.TokenMsg{} 136 if err := cmn.ReadJSON(w, r, msg); err != nil { 137 return 138 } 139 if msg.Token == "" { 140 cmn.WriteErrMsg(w, r, "empty token") 141 return 142 } 143 secret := Conf.Secret() 144 if _, err := tok.DecryptToken(msg.Token, secret); err != nil { 145 cmn.WriteErr(w, r, err) 146 return 147 } 148 h.mgr.revokeToken(msg.Token) 149 } 150 151 func (h *hserv) httpUserDel(w http.ResponseWriter, r *http.Request) { 152 apiItems, err := parseURL(w, r, 1, apc.URLPathUsers.L) 153 if err != nil { 154 return 155 } 156 if err = validateAdminPerms(w, r); err != nil { 157 return 158 } 159 if err := h.mgr.delUser(apiItems[0]); err != nil { 160 nlog.Errorf("Failed to delete user: %v\n", err) 161 cmn.WriteErrMsg(w, r, "Failed to delete user: "+err.Error()) 162 } 163 } 164 165 func (h *hserv) httpUserPost(w http.ResponseWriter, r *http.Request) { 166 apiItems, err := parseURL(w, r, 0, apc.URLPathUsers.L) 167 if err != nil { 168 return 169 } 170 if len(apiItems) == 0 { 171 h.userAdd(w, r) 172 } else { 173 h.userLogin(w, r) 174 } 175 } 176 177 // Updates user credentials 178 func (h *hserv) httpUserPut(w http.ResponseWriter, r *http.Request) { 179 apiItems, err := parseURL(w, r, 1, apc.URLPathUsers.L) 180 if err != nil { 181 return 182 } 183 if err = validateAdminPerms(w, r); err != nil { 184 return 185 } 186 var ( 187 userID = apiItems[0] 188 updateReq = &authn.User{} 189 ) 190 err = jsoniter.NewDecoder(r.Body).Decode(updateReq) 191 if err != nil { 192 cmn.WriteErrMsg(w, r, "Invalid request") 193 return 194 } 195 if Conf.Verbose() { 196 nlog.Infof("PUT user %q", userID) 197 } 198 if err := h.mgr.updateUser(userID, updateReq); err != nil { 199 cmn.WriteErr(w, r, err) 200 return 201 } 202 } 203 204 // Adds h new user to user list 205 func (h *hserv) userAdd(w http.ResponseWriter, r *http.Request) { 206 if err := validateAdminPerms(w, r); err != nil { 207 return 208 } 209 info := &authn.User{} 210 if err := cmn.ReadJSON(w, r, info); err != nil { 211 return 212 } 213 if err := h.mgr.addUser(info); err != nil { 214 cmn.WriteErrMsg(w, r, fmt.Sprintf("Failed to add user: %v", err), http.StatusInternalServerError) 215 return 216 } 217 if Conf.Verbose() { 218 nlog.Infof("Add user %q", info.ID) 219 } 220 } 221 222 // Returns list of users (without superusers) 223 func (h *hserv) httpUserGet(w http.ResponseWriter, r *http.Request) { 224 items, err := parseURL(w, r, 0, apc.URLPathUsers.L) 225 if err != nil { 226 return 227 } 228 if len(items) > 1 { 229 cmn.WriteErrMsg(w, r, "invalid request") 230 return 231 } 232 233 var users map[string]*authn.User 234 if len(items) == 0 { 235 if users, err = h.mgr.userList(); err != nil { 236 cmn.WriteErr(w, r, err) 237 return 238 } 239 for _, uInfo := range users { 240 uInfo.Password = "" 241 } 242 writeJSON(w, users, "list users") 243 return 244 } 245 246 uInfo, err := h.mgr.lookupUser(items[0]) 247 if err != nil { 248 cmn.WriteErr(w, r, err) 249 return 250 } 251 uInfo.Password = "" 252 clus, err := h.mgr.clus() 253 if err != nil { 254 cmn.WriteErr(w, r, err) 255 return 256 } 257 for _, clu := range uInfo.ClusterACLs { 258 if cInfo, ok := clus[clu.ID]; ok { 259 clu.Alias = cInfo.Alias 260 } 261 } 262 writeJSON(w, uInfo, "user info") 263 } 264 265 // Checks if the request header contains valid admin credentials. 266 // (admin is created at deployment time and cannot be modified via API) 267 func validateAdminPerms(w http.ResponseWriter, r *http.Request) error { 268 token, err := tok.ExtractToken(r.Header) 269 if err != nil { 270 cmn.WriteErr(w, r, err, http.StatusUnauthorized) 271 return err 272 } 273 secret := Conf.Secret() 274 tk, err := tok.DecryptToken(token, secret) 275 if err != nil { 276 cmn.WriteErr(w, r, err, http.StatusUnauthorized) 277 return err 278 } 279 if tk.Expires.Before(time.Now()) { 280 err := fmt.Errorf("not authorized: %s", tk) 281 cmn.WriteErr(w, r, err, http.StatusUnauthorized) 282 return err 283 } 284 if !tk.IsAdmin { 285 err := fmt.Errorf("not authorized: requires admin (%s)", tk) 286 cmn.WriteErr(w, r, err, http.StatusUnauthorized) 287 return err 288 } 289 return nil 290 } 291 292 // Generate h token for h user if provided credentials are valid. 293 // If h token is already issued and it is not expired yet then the old 294 // token is returned 295 func (h *hserv) userLogin(w http.ResponseWriter, r *http.Request) { 296 var err error 297 apiItems, err := parseURL(w, r, 1, apc.URLPathUsers.L) 298 if err != nil { 299 return 300 } 301 msg := &authn.LoginMsg{} 302 if err = cmn.ReadJSON(w, r, msg); err != nil { 303 return 304 } 305 if msg.Password == "" { 306 cmn.WriteErrMsg(w, r, "Not authorized", http.StatusUnauthorized) 307 return 308 } 309 userID := apiItems[0] 310 pass := msg.Password 311 312 tokenString, err := h.mgr.issueToken(userID, pass, msg) 313 if err != nil { 314 nlog.Errorf("Failed to generate token for user %q: %v\n", userID, err) 315 cmn.WriteErr(w, r, err, http.StatusUnauthorized) 316 return 317 } 318 319 repl := fmt.Sprintf(`{"token": %q}`, tokenString) 320 writeBytes(w, []byte(repl), "auth") 321 } 322 323 func writeJSON(w http.ResponseWriter, val any, tag string) { 324 w.Header().Set(cos.HdrContentType, cos.ContentJSON) 325 var err error 326 if err = jsoniter.NewEncoder(w).Encode(val); err == nil { 327 return 328 } 329 nlog.Errorf("%s: failed to write json, err: %v", tag, err) 330 } 331 332 func writeBytes(w http.ResponseWriter, jsbytes []byte, tag string) { 333 w.Header().Set(cos.HdrContentType, cos.ContentJSON) 334 var err error 335 if _, err = w.Write(jsbytes); err == nil { 336 return 337 } 338 nlog.Errorf("%s: failed to write json, err: %v", tag, err) 339 } 340 341 func (h *hserv) httpSrvPost(w http.ResponseWriter, r *http.Request) { 342 if _, err := parseURL(w, r, 0, apc.URLPathClusters.L); err != nil { 343 return 344 } 345 if err := validateAdminPerms(w, r); err != nil { 346 return 347 } 348 cluConf := &authn.CluACL{} 349 if err := cmn.ReadJSON(w, r, cluConf); err != nil { 350 return 351 } 352 if err := h.mgr.addCluster(cluConf); err != nil { 353 cmn.WriteErr(w, r, err, http.StatusInternalServerError) 354 } 355 } 356 357 func (h *hserv) httpSrvPut(w http.ResponseWriter, r *http.Request) { 358 apiItems, err := parseURL(w, r, 1, apc.URLPathClusters.L) 359 if err != nil { 360 return 361 } 362 if err := validateAdminPerms(w, r); err != nil { 363 return 364 } 365 cluConf := &authn.CluACL{} 366 if err := cmn.ReadJSON(w, r, cluConf); err != nil { 367 return 368 } 369 cluID := apiItems[0] 370 if err := h.mgr.updateCluster(cluID, cluConf); err != nil { 371 cmn.WriteErr(w, r, err, http.StatusInternalServerError) 372 } 373 } 374 375 func (h *hserv) httpSrvDelete(w http.ResponseWriter, r *http.Request) { 376 apiItems, err := parseURL(w, r, 0, apc.URLPathClusters.L) 377 if err != nil { 378 return 379 } 380 if err = validateAdminPerms(w, r); err != nil { 381 return 382 } 383 384 if len(apiItems) == 0 { 385 cmn.WriteErrMsg(w, r, "cluster name or ID is not defined", http.StatusInternalServerError) 386 return 387 } 388 if err := h.mgr.delCluster(apiItems[0]); err != nil { 389 if cos.IsErrNotFound(err) { 390 cmn.WriteErr(w, r, err, http.StatusNotFound) 391 } else { 392 cmn.WriteErr(w, r, err, http.StatusInternalServerError) 393 } 394 } 395 } 396 397 func (h *hserv) httpSrvGet(w http.ResponseWriter, r *http.Request) { 398 apiItems, err := parseURL(w, r, 0, apc.URLPathClusters.L) 399 if err != nil { 400 return 401 } 402 var cluList *authn.RegisteredClusters 403 if len(apiItems) != 0 { 404 cid := apiItems[0] 405 clu, err := h.mgr.getCluster(cid) 406 if err != nil { 407 if cos.IsErrNotFound(err) { 408 cmn.WriteErr(w, r, err, http.StatusNotFound) 409 } else { 410 cmn.WriteErr(w, r, err, http.StatusInternalServerError) 411 } 412 return 413 } 414 cluList = &authn.RegisteredClusters{ 415 M: map[string]*authn.CluACL{clu.ID: clu}, 416 } 417 } else { 418 clus, err := h.mgr.clus() 419 if err != nil { 420 cmn.WriteErr(w, r, err, http.StatusInternalServerError) 421 return 422 } 423 cluList = &authn.RegisteredClusters{M: clus} 424 } 425 writeJSON(w, cluList, "auth") 426 } 427 428 func (h *hserv) roleHandler(w http.ResponseWriter, r *http.Request) { 429 switch r.Method { 430 case http.MethodPost: 431 h.httpRolePost(w, r) 432 case http.MethodPut: 433 h.httpRolePut(w, r) 434 case http.MethodDelete: 435 h.httpRoleDel(w, r) 436 case http.MethodGet: 437 h.httpRoleGet(w, r) 438 default: 439 cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut) 440 } 441 } 442 443 func (h *hserv) httpRoleGet(w http.ResponseWriter, r *http.Request) { 444 apiItems, err := parseURL(w, r, 0, apc.URLPathRoles.L) 445 if err != nil { 446 return 447 } 448 if len(apiItems) > 1 { 449 cmn.WriteErrMsg(w, r, "invalid request") 450 return 451 } 452 453 if len(apiItems) == 0 { 454 roles, err := h.mgr.roleList() 455 if err != nil { 456 cmn.WriteErr(w, r, err) 457 return 458 } 459 writeJSON(w, roles, "rolelist") 460 return 461 } 462 463 role, err := h.mgr.lookupRole(apiItems[0]) 464 if err != nil { 465 cmn.WriteErr(w, r, err) 466 return 467 } 468 clus, err := h.mgr.clus() 469 if err != nil { 470 cmn.WriteErr(w, r, err) 471 return 472 } 473 for _, clu := range role.ClusterACLs { 474 if cInfo, ok := clus[clu.ID]; ok { 475 clu.Alias = cInfo.Alias 476 } 477 } 478 writeJSON(w, role, "role") 479 } 480 481 func (h *hserv) httpRoleDel(w http.ResponseWriter, r *http.Request) { 482 apiItems, err := parseURL(w, r, 1, apc.URLPathRoles.L) 483 if err != nil { 484 return 485 } 486 if err = validateAdminPerms(w, r); err != nil { 487 return 488 } 489 490 roleID := apiItems[0] 491 if err = h.mgr.delRole(roleID); err != nil { 492 cmn.WriteErr(w, r, err) 493 } 494 } 495 496 func (h *hserv) httpRolePost(w http.ResponseWriter, r *http.Request) { 497 _, err := parseURL(w, r, 0, apc.URLPathRoles.L) 498 if err != nil { 499 return 500 } 501 if err = validateAdminPerms(w, r); err != nil { 502 return 503 } 504 info := &authn.Role{} 505 if err := cmn.ReadJSON(w, r, info); err != nil { 506 return 507 } 508 if err := h.mgr.addRole(info); err != nil { 509 cmn.WriteErrMsg(w, r, fmt.Sprintf("Failed to add role: %v", err), http.StatusInternalServerError) 510 } 511 } 512 513 func (h *hserv) httpRolePut(w http.ResponseWriter, r *http.Request) { 514 apiItems, err := parseURL(w, r, 1, apc.URLPathRoles.L) 515 if err != nil { 516 return 517 } 518 if err = validateAdminPerms(w, r); err != nil { 519 return 520 } 521 522 role := apiItems[0] 523 updateReq := &authn.Role{} 524 err = jsoniter.NewDecoder(r.Body).Decode(updateReq) 525 if err != nil { 526 cmn.WriteErrMsg(w, r, "Invalid request") 527 return 528 } 529 if Conf.Verbose() { 530 nlog.Infof("PUT role %q\n", role) 531 } 532 if err := h.mgr.updateRole(role, updateReq); err != nil { 533 if cos.IsErrNotFound(err) { 534 cmn.WriteErr(w, r, err, http.StatusNotFound) 535 } else { 536 cmn.WriteErr(w, r, err) 537 } 538 } 539 }