github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmd/authn/aisreq.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 "bytes" 9 "fmt" 10 "io" 11 "net/http" 12 "sync" 13 "time" 14 15 "github.com/NVIDIA/aistore/api/apc" 16 "github.com/NVIDIA/aistore/api/authn" 17 "github.com/NVIDIA/aistore/cmn" 18 "github.com/NVIDIA/aistore/cmn/cos" 19 "github.com/NVIDIA/aistore/cmn/nlog" 20 jsoniter "github.com/json-iterator/go" 21 ) 22 23 const ( 24 retryCount = 4 25 retrySleep = 3 * time.Second 26 retry503 = time.Minute 27 ) 28 29 func (m *mgr) validateSecret(clu *authn.CluACL) (err error) { 30 const tag = "validate-secret" 31 var ( 32 secret = Conf.Secret() 33 cksum = cos.NewCksumHash(cos.ChecksumSHA256) 34 ) 35 cksum.H.Write([]byte(secret)) 36 cksum.Finalize() 37 38 body := cos.MustMarshal(&authn.ServerConf{Secret: cksum.Val()}) 39 for _, u := range clu.URLs { 40 if err = m.call(http.MethodPost, u, apc.Tokens, body, tag); err == nil { 41 return 42 } 43 err = fmt.Errorf("failed to %s with %s: %v", tag, clu, err) 44 } 45 return 46 } 47 48 // update list of revoked token on all clusters 49 func (m *mgr) broadcastRevoked(token string) { 50 tokenList := authn.TokenList{Tokens: []string{token}} 51 body := cos.MustMarshal(tokenList) 52 m.broadcast(http.MethodDelete, apc.Tokens, body, "broadcast-revoked") 53 } 54 55 // broadcast the request to all clusters. If a cluster has a few URLS, 56 // it sends to the first working one. Clusters are processed in parallel. 57 func (m *mgr) broadcast(method, path string, body []byte, tag string) { 58 clus, err := m.clus() 59 if err != nil { 60 nlog.Errorf("Failed to read cluster list: %v", err) 61 return 62 } 63 wg := &sync.WaitGroup{} 64 for _, clu := range clus { 65 wg.Add(1) 66 go func(clu *authn.CluACL) { 67 var err error 68 for _, u := range clu.URLs { 69 if err = m.call(method, u, path, body, tag); err == nil { 70 break 71 } 72 } 73 if err != nil { 74 nlog.Errorf("failed to %s with %s: %v", tag, clu, err) 75 } 76 wg.Done() 77 }(clu) 78 } 79 wg.Wait() 80 } 81 82 // Send valid and non-expired revoked token list to a cluster. 83 func (m *mgr) syncTokenList(clu *authn.CluACL) { 84 const tag = "sync-tokens" 85 tokenList, err := m.generateRevokedTokenList() 86 if err != nil { 87 nlog.Errorf("failed to sync token list with %q(%q): %v", clu.ID, clu.Alias, err) 88 return 89 } 90 if len(tokenList) == 0 { 91 return 92 } 93 body := cos.MustMarshal(authn.TokenList{Tokens: tokenList}) 94 for _, u := range clu.URLs { 95 if err = m.call(http.MethodDelete, u, apc.Tokens, body, tag); err == nil { 96 break 97 } 98 err = fmt.Errorf("failed to %s with %s: %v", tag, clu, err) 99 } 100 if err != nil { 101 nlog.Errorln(err) 102 } 103 } 104 105 // TODO: reuse api/client.go reqParams.do() 106 func (m *mgr) call(method, proxyURL, path string, injson []byte, tag string) error { 107 var ( 108 rerr error 109 msg []byte 110 retries = retryCount 111 sleep = retrySleep 112 url = proxyURL + cos.JoinWords(apc.Version, path) 113 client = m.clientH 114 ) 115 if cos.IsHTTPS(proxyURL) { 116 client = m.clientTLS 117 } 118 // while cos.IsRetriableConnErr() 119 for i := 1; i <= retries; i++ { 120 req, nerr := http.NewRequest(method, url, bytes.NewBuffer(injson)) 121 if nerr != nil { 122 return nerr 123 } 124 req.Header.Set(cos.HdrContentType, cos.ContentJSON) 125 resp, err := client.Do(req) 126 if resp != nil { 127 if resp.Body != nil { 128 msg, _ = io.ReadAll(resp.Body) 129 resp.Body.Close() 130 } 131 } 132 if err == nil { 133 if resp.StatusCode < http.StatusBadRequest { 134 return nil 135 } 136 } else { 137 if cos.IsRetriableConnErr(err) { 138 continue 139 } 140 if resp == nil { 141 return err 142 } 143 } 144 if resp.StatusCode == http.StatusServiceUnavailable { 145 if retries == retryCount { 146 retries = int(retry503 / retrySleep) 147 } 148 } else { 149 var herr *cmn.ErrHTTP 150 if jsonErr := jsoniter.Unmarshal(msg, &herr); jsonErr == nil { 151 return herr 152 } 153 } 154 if i < retries { 155 nlog.Warningf("failed to %q %s: %v - retrying...", tag, url, err) 156 time.Sleep(sleep) 157 if i > retries/2+1 && sleep == retrySleep { 158 sleep *= 2 159 } 160 } 161 rerr = err 162 } 163 return rerr 164 }