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  }