github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/prxauth.go (about) 1 // Package ais provides core functionality for the AIStore object storage. 2 /* 3 * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package ais 6 7 import ( 8 "fmt" 9 "net/http" 10 "sync" 11 "time" 12 13 "github.com/NVIDIA/aistore/api/apc" 14 "github.com/NVIDIA/aistore/api/authn" 15 "github.com/NVIDIA/aistore/cmd/authn/tok" 16 "github.com/NVIDIA/aistore/cmn" 17 "github.com/NVIDIA/aistore/cmn/cos" 18 "github.com/NVIDIA/aistore/cmn/debug" 19 "github.com/NVIDIA/aistore/cmn/nlog" 20 "github.com/NVIDIA/aistore/core/meta" 21 "github.com/NVIDIA/aistore/memsys" 22 ) 23 24 type ( 25 tokenList authn.TokenList // token strings 26 tkList map[string]*tok.Token // tk structs 27 authManager struct { 28 sync.Mutex 29 // cache of decrypted tokens 30 tkList tkList 31 // list of invalid tokens(revoked or of deleted users) 32 // Authn sends these tokens to primary for broadcasting 33 revokedTokens map[string]bool 34 version int64 35 } 36 ) 37 38 ///////////////// 39 // authManager // 40 ///////////////// 41 42 func newAuthManager() *authManager { 43 return &authManager{tkList: make(tkList), revokedTokens: make(map[string]bool), version: 1} 44 } 45 46 // Add tokens to list of invalid ones. After that it cleans up the list 47 // from expired tokens 48 func (a *authManager) updateRevokedList(newRevoked *tokenList) (allRevoked *tokenList) { 49 a.Lock() 50 switch { 51 case newRevoked.Version == 0: // manually revoked 52 a.version++ 53 case newRevoked.Version > a.version: 54 a.version = newRevoked.Version 55 default: 56 nlog.Errorf("Current token list v%d is greater than received v%d", a.version, newRevoked.Version) 57 a.Unlock() 58 return 59 } 60 // add new 61 for _, token := range newRevoked.Tokens { 62 a.revokedTokens[token] = true 63 delete(a.tkList, token) 64 } 65 allRevoked = &tokenList{ 66 Tokens: make([]string, 0, len(a.revokedTokens)), 67 Version: a.version, 68 } 69 var ( 70 now = time.Now() 71 secret = cmn.GCO.Get().Auth.Secret 72 ) 73 for token := range a.revokedTokens { 74 tk, err := tok.DecryptToken(token, secret) 75 debug.AssertNoErr(err) 76 if tk.Expires.Before(now) { 77 delete(a.revokedTokens, token) 78 } else { 79 allRevoked.Tokens = append(allRevoked.Tokens, token) 80 } 81 } 82 a.Unlock() 83 if len(allRevoked.Tokens) == 0 { 84 allRevoked = nil 85 } 86 return 87 } 88 89 func (a *authManager) revokedTokenList() (allRevoked *tokenList) { 90 a.Lock() 91 l := len(a.revokedTokens) 92 if l == 0 { 93 a.Unlock() 94 return 95 } 96 allRevoked = &tokenList{Tokens: make([]string, 0, l), Version: a.version} 97 for token := range a.revokedTokens { 98 allRevoked.Tokens = append(allRevoked.Tokens, token) 99 } 100 a.Unlock() 101 return 102 } 103 104 // Checks if a token is valid: 105 // - must not be revoked one 106 // - must not be expired 107 // - must have all mandatory fields: userID, creds, issued, expires 108 // 109 // Returns decrypted token information if it is valid 110 func (a *authManager) validateToken(token string) (tk *tok.Token, err error) { 111 a.Lock() 112 if _, ok := a.revokedTokens[token]; ok { 113 tk, err = nil, fmt.Errorf("%v: %s", tok.ErrTokenRevoked, tk) 114 } else { 115 tk, err = a.validateAddRm(token, time.Now()) 116 } 117 a.Unlock() 118 return 119 } 120 121 // Decrypts and validates token. Adds it to authManager.token if not found. Removes if expired. 122 // Must be called under lock. 123 func (a *authManager) validateAddRm(token string, now time.Time) (*tok.Token, error) { 124 tk, ok := a.tkList[token] 125 if !ok || tk == nil { 126 var ( 127 err error 128 secret = cmn.GCO.Get().Auth.Secret 129 ) 130 if tk, err = tok.DecryptToken(token, secret); err != nil { 131 nlog.Errorln(err) 132 return nil, tok.ErrInvalidToken 133 } 134 a.tkList[token] = tk 135 } 136 if tk.Expires.Before(now) { 137 delete(a.tkList, token) 138 return nil, fmt.Errorf("%v: %s", tok.ErrTokenExpired, tk) 139 } 140 return tk, nil 141 } 142 143 /////////////// 144 // tokenList // 145 /////////////// 146 147 // interface guard 148 var _ revs = (*tokenList)(nil) 149 150 func (*tokenList) tag() string { return revsTokenTag } 151 func (t *tokenList) version() int64 { return t.Version } // no versioning: receivers keep adding tokens to their lists 152 func (t *tokenList) marshal() []byte { return cos.MustMarshal(t) } 153 func (t *tokenList) jit(_ *proxy) revs { return t } 154 func (*tokenList) sgl() *memsys.SGL { return nil } 155 func (t *tokenList) String() string { return fmt.Sprintf("TokenList v%d", t.Version) } 156 157 // 158 // proxy cont-ed 159 // 160 161 // [METHOD] /v1/tokens 162 func (p *proxy) tokenHandler(w http.ResponseWriter, r *http.Request) { 163 switch r.Method { 164 case http.MethodPost: 165 p.validateSecret(w, r) 166 case http.MethodDelete: 167 p.httpTokenDelete(w, r) 168 default: 169 cmn.WriteErr405(w, r, http.MethodDelete) 170 } 171 } 172 173 func (p *proxy) validateSecret(w http.ResponseWriter, r *http.Request) { 174 if _, err := p.parseURL(w, r, apc.URLPathTokens.L, 0, false); err != nil { 175 return 176 } 177 cksum := cos.NewCksumHash(cos.ChecksumSHA256) 178 cksum.H.Write([]byte(cmn.GCO.Get().Auth.Secret)) 179 cksum.Finalize() 180 181 cluConf := &authn.ServerConf{} 182 if err := cmn.ReadJSON(w, r, cluConf); err != nil { 183 return 184 } 185 if cksum.Val() != cluConf.Secret { 186 p.writeErrf(w, r, "%s: invalid secret sha256(%q)", p, cos.SHead(cluConf.Secret)) 187 } 188 } 189 190 func (p *proxy) httpTokenDelete(w http.ResponseWriter, r *http.Request) { 191 if _, err := p.parseURL(w, r, apc.URLPathTokens.L, 0, false); err != nil { 192 return 193 } 194 if p.forwardCP(w, r, nil, "revoke token") { 195 return 196 } 197 tokenList := &tokenList{} 198 if err := cmn.ReadJSON(w, r, tokenList); err != nil { 199 return 200 } 201 allRevoked := p.authn.updateRevokedList(tokenList) 202 if allRevoked != nil && p.owner.smap.get().isPrimary(p.si) { 203 msg := p.newAmsgStr(apc.ActNewPrimary, nil) 204 _ = p.metasyncer.sync(revsPair{allRevoked, msg}) 205 } 206 } 207 208 // Validates a token from the request header 209 func (p *proxy) validateToken(hdr http.Header) (*tok.Token, error) { 210 token, err := tok.ExtractToken(hdr) 211 if err != nil { 212 return nil, err 213 } 214 tk, err := p.authn.validateToken(token) 215 if err != nil { 216 nlog.Errorf("invalid token: %v", err) 217 return nil, err 218 } 219 return tk, nil 220 } 221 222 // When AuthN is on, accessing a bucket requires two permissions: 223 // - access to the bucket is granted to a user 224 // - bucket ACL allows the required operation 225 // Exception: a superuser can always PATCH the bucket/Set ACL 226 // 227 // If AuthN is off, only bucket permissions are checked. 228 // 229 // Exceptions: 230 // - read-only access to a bucket is always granted 231 // - PATCH cannot be forbidden 232 func (p *proxy) checkAccess(w http.ResponseWriter, r *http.Request, bck *meta.Bck, ace apc.AccessAttrs) (err error) { 233 if err = p.access(r.Header, bck, ace); err != nil { 234 p.writeErr(w, r, err, aceErrToCode(err)) 235 } 236 return 237 } 238 239 func aceErrToCode(err error) (status int) { 240 switch err { 241 case nil: 242 case tok.ErrNoToken, tok.ErrInvalidToken: 243 status = http.StatusUnauthorized 244 default: 245 status = http.StatusForbidden 246 } 247 return status 248 } 249 250 func (p *proxy) access(hdr http.Header, bck *meta.Bck, ace apc.AccessAttrs) (err error) { 251 var ( 252 tk *tok.Token 253 bucket *cmn.Bck 254 ) 255 if p.isIntraCall(hdr, false /*from primary*/) == nil { 256 return nil 257 } 258 if cmn.Rom.AuthEnabled() { // config.Auth.Enabled 259 tk, err = p.validateToken(hdr) 260 if err != nil { 261 // NOTE: making exception to allow 3rd party clients read remote ht://bucket 262 if err == tok.ErrNoToken && bck != nil && bck.IsHTTP() { 263 err = nil 264 } 265 return err 266 } 267 uid := p.owner.smap.Get().UUID 268 if bck != nil { 269 bucket = bck.Bucket() 270 } 271 if err := tk.CheckPermissions(uid, bucket, ace); err != nil { 272 return err 273 } 274 } 275 if bck == nil { 276 // cluster ACL: create/list buckets, node management, etc. 277 return nil 278 } 279 280 // bucket access conventions: 281 // - without AuthN: read-only access, PATCH, and ACL 282 // - with AuthN: superuser can PATCH and change ACL 283 if !cmn.Rom.AuthEnabled() { 284 ace &^= (apc.AcePATCH | apc.AceBckSetACL | apc.AccessRO) 285 } else if tk.IsAdmin { 286 ace &^= (apc.AcePATCH | apc.AceBckSetACL) 287 } 288 if ace == 0 { 289 return nil 290 } 291 return bck.Allow(ace) 292 }