github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/jwt.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "errors" 22 "net/http" 23 "time" 24 25 jwtgo "github.com/golang-jwt/jwt/v4" 26 jwtreq "github.com/golang-jwt/jwt/v4/request" 27 lru "github.com/hashicorp/golang-lru" 28 "github.com/minio/minio/internal/auth" 29 xjwt "github.com/minio/minio/internal/jwt" 30 "github.com/minio/minio/internal/logger" 31 "github.com/minio/pkg/v2/policy" 32 ) 33 34 const ( 35 jwtAlgorithm = "Bearer" 36 37 // Default JWT token for web handlers is one day. 38 defaultJWTExpiry = 24 * time.Hour 39 40 // Inter-node JWT token expiry is 15 minutes. 41 defaultInterNodeJWTExpiry = 15 * time.Minute 42 ) 43 44 var ( 45 errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records") 46 errAccessKeyDisabled = errors.New("The access key you provided is disabled") 47 errAuthentication = errors.New("Authentication failed, check your access credentials") 48 errNoAuthToken = errors.New("JWT token missing") 49 errSkewedAuthTime = errors.New("Skewed authentication date/time") 50 errMalformedAuth = errors.New("Malformed authentication input") 51 ) 52 53 // cachedAuthenticateNode will cache authenticateNode results for given values up to ttl. 54 func cachedAuthenticateNode(ttl time.Duration) func(accessKey, secretKey, audience string) (string, error) { 55 type key struct { 56 accessKey, secretKey, audience string 57 } 58 type value struct { 59 created time.Time 60 res string 61 err error 62 } 63 cache, err := lru.NewARC(100) 64 if err != nil { 65 logger.LogIf(GlobalContext, err) 66 return authenticateNode 67 } 68 return func(accessKey, secretKey, audience string) (string, error) { 69 k := key{accessKey: accessKey, secretKey: secretKey, audience: audience} 70 v, ok := cache.Get(k) 71 if ok { 72 if val, ok := v.(*value); ok && time.Since(val.created) < ttl { 73 return val.res, val.err 74 } 75 } 76 s, err := authenticateNode(accessKey, secretKey, audience) 77 cache.Add(k, &value{created: time.Now(), res: s, err: err}) 78 return s, err 79 } 80 } 81 82 func authenticateNode(accessKey, secretKey, audience string) (string, error) { 83 claims := xjwt.NewStandardClaims() 84 claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry)) 85 claims.SetAccessKey(accessKey) 86 claims.SetAudience(audience) 87 88 jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims) 89 return jwt.SignedString([]byte(secretKey)) 90 } 91 92 // Check if the request is authenticated. 93 // Returns nil if the request is authenticated. errNoAuthToken if token missing. 94 // Returns errAuthentication for all other errors. 95 func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, bool, error) { 96 token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req) 97 if err != nil { 98 if err == jwtreq.ErrNoTokenInRequest { 99 return nil, nil, false, errNoAuthToken 100 } 101 return nil, nil, false, err 102 } 103 claims := xjwt.NewMapClaims() 104 if err := xjwt.ParseWithClaims(token, claims, func(claims *xjwt.MapClaims) ([]byte, error) { 105 if claims.AccessKey != globalActiveCred.AccessKey { 106 u, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) 107 if !ok { 108 // Credentials will be invalid but for disabled 109 // return a different error in such a scenario. 110 if u.Credentials.Status == auth.AccountOff { 111 return nil, errAccessKeyDisabled 112 } 113 return nil, errInvalidAccessKeyID 114 } 115 cred := u.Credentials 116 // Expired credentials return error. 117 if cred.IsTemp() && cred.IsExpired() { 118 return nil, errInvalidAccessKeyID 119 } 120 return []byte(cred.SecretKey), nil 121 } // this means claims.AccessKey == rootAccessKey 122 if !globalAPIConfig.permitRootAccess() { 123 // if root access is disabled, fail this request. 124 return nil, errAccessKeyDisabled 125 } 126 return []byte(globalActiveCred.SecretKey), nil 127 }); err != nil { 128 return claims, nil, false, errAuthentication 129 } 130 owner := true 131 var groups []string 132 if globalActiveCred.AccessKey != claims.AccessKey { 133 // Check if the access key is part of users credentials. 134 u, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) 135 if !ok { 136 return nil, nil, false, errInvalidAccessKeyID 137 } 138 ucred := u.Credentials 139 // get embedded claims 140 eclaims, s3Err := checkClaimsFromToken(req, ucred) 141 if s3Err != ErrNone { 142 return nil, nil, false, errAuthentication 143 } 144 145 for k, v := range eclaims { 146 claims.MapClaims[k] = v 147 } 148 149 // if root access is disabled, disable all its service accounts and temporary credentials. 150 if ucred.ParentUser == globalActiveCred.AccessKey && !globalAPIConfig.permitRootAccess() { 151 return nil, nil, false, errAccessKeyDisabled 152 } 153 154 // Now check if we have a sessionPolicy. 155 if _, ok = eclaims[policy.SessionPolicyName]; ok { 156 owner = false 157 } else { 158 owner = globalActiveCred.AccessKey == ucred.ParentUser 159 } 160 161 groups = ucred.Groups 162 } 163 164 return claims, groups, owner, nil 165 } 166 167 // newCachedAuthToken returns a token that is cached up to 15 seconds. 168 // If globalActiveCred is updated it is reflected at once. 169 func newCachedAuthToken() func(audience string) string { 170 fn := cachedAuthenticateNode(15 * time.Second) 171 return func(audience string) string { 172 cred := globalActiveCred 173 token, err := fn(cred.AccessKey, cred.SecretKey, audience) 174 logger.CriticalIf(GlobalContext, err) 175 return token 176 } 177 }