storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/auth/credentials.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package auth 18 19 import ( 20 "crypto/rand" 21 "crypto/subtle" 22 "encoding/base64" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "strconv" 27 "strings" 28 "time" 29 30 jwtgo "github.com/dgrijalva/jwt-go" 31 32 "storj.io/minio/cmd/jwt" 33 ) 34 35 const ( 36 // Minimum length for MinIO access key. 37 accessKeyMinLen = 3 38 39 // Maximum length for MinIO access key. 40 // There is no max length enforcement for access keys 41 accessKeyMaxLen = 20 42 43 // Minimum length for MinIO secret key for both server and gateway mode. 44 secretKeyMinLen = 8 45 46 // Maximum secret key length for MinIO, this 47 // is used when autogenerating new credentials. 48 // There is no max length enforcement for secret keys 49 secretKeyMaxLen = 40 50 51 // Alpha numeric table used for generating access keys. 52 alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" 53 54 // Total length of the alpha numeric table. 55 alphaNumericTableLen = byte(len(alphaNumericTable)) 56 ) 57 58 // Common errors generated for access and secret key validation. 59 var ( 60 ErrInvalidAccessKeyLength = fmt.Errorf("access key must be minimum %v or more characters long", accessKeyMinLen) 61 ErrInvalidSecretKeyLength = fmt.Errorf("secret key must be minimum %v or more characters long", secretKeyMinLen) 62 ) 63 64 // IsAccessKeyValid - validate access key for right length. 65 func IsAccessKeyValid(accessKey string) bool { 66 return len(accessKey) >= accessKeyMinLen 67 } 68 69 // IsSecretKeyValid - validate secret key for right length. 70 func IsSecretKeyValid(secretKey string) bool { 71 return len(secretKey) >= secretKeyMinLen 72 } 73 74 // Default access and secret keys. 75 const ( 76 DefaultAccessKey = "minioadmin" 77 DefaultSecretKey = "minioadmin" 78 ) 79 80 // Default access credentials 81 var ( 82 DefaultCredentials = Credentials{ 83 AccessKey: DefaultAccessKey, 84 SecretKey: DefaultSecretKey, 85 } 86 ) 87 88 const ( 89 // AccountOn indicates that credentials are enabled 90 AccountOn = "on" 91 // AccountOff indicates that credentials are disabled 92 AccountOff = "off" 93 ) 94 95 // Credentials holds access and secret keys. 96 type Credentials struct { 97 AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"` 98 AccessGrant string `xml:"AccessGrant" json:"accessGrant,omitempty"` 99 SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"` 100 Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"` 101 SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"` 102 Status string `xml:"-" json:"status,omitempty"` 103 ParentUser string `xml:"-" json:"parentUser,omitempty"` 104 Groups []string `xml:"-" json:"groups,omitempty"` 105 } 106 107 func (cred Credentials) String() string { 108 var s strings.Builder 109 s.WriteString(cred.AccessKey) 110 s.WriteString(":") 111 s.WriteString(cred.SecretKey) 112 if cred.SessionToken != "" { 113 s.WriteString("\n") 114 s.WriteString(cred.SessionToken) 115 } 116 if !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel) { 117 s.WriteString("\n") 118 s.WriteString(cred.Expiration.String()) 119 } 120 return s.String() 121 } 122 123 // IsExpired - returns whether Credential is expired or not. 124 func (cred Credentials) IsExpired() bool { 125 if cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel) { 126 return false 127 } 128 129 return cred.Expiration.Before(time.Now().UTC()) 130 } 131 132 // IsTemp - returns whether credential is temporary or not. 133 func (cred Credentials) IsTemp() bool { 134 return cred.SessionToken != "" && !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel) 135 } 136 137 // IsServiceAccount - returns whether credential is a service account or not 138 func (cred Credentials) IsServiceAccount() bool { 139 return cred.ParentUser != "" && (cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel)) 140 } 141 142 // IsValid - returns whether credential is valid or not. 143 func (cred Credentials) IsValid() bool { 144 // Verify credentials if its enabled or not set. 145 if cred.Status == AccountOff { 146 return false 147 } 148 return IsAccessKeyValid(cred.AccessKey) && IsSecretKeyValid(cred.SecretKey) && !cred.IsExpired() 149 } 150 151 // Equal - returns whether two credentials are equal or not. 152 func (cred Credentials) Equal(ccred Credentials) bool { 153 if !ccred.IsValid() { 154 return false 155 } 156 return (cred.AccessKey == ccred.AccessKey && subtle.ConstantTimeCompare([]byte(cred.SecretKey), []byte(ccred.SecretKey)) == 1 && 157 subtle.ConstantTimeCompare([]byte(cred.SessionToken), []byte(ccred.SessionToken)) == 1) 158 } 159 160 var timeSentinel = time.Unix(0, 0).UTC() 161 162 // ErrInvalidDuration invalid token expiry 163 var ErrInvalidDuration = errors.New("invalid token expiry") 164 165 // ExpToInt64 - convert input interface value to int64. 166 func ExpToInt64(expI interface{}) (expAt int64, err error) { 167 switch exp := expI.(type) { 168 case string: 169 expAt, err = strconv.ParseInt(exp, 10, 64) 170 case float64: 171 expAt, err = int64(exp), nil 172 case int64: 173 expAt, err = exp, nil 174 case int: 175 expAt, err = int64(exp), nil 176 case uint64: 177 expAt, err = int64(exp), nil 178 case uint: 179 expAt, err = int64(exp), nil 180 case json.Number: 181 expAt, err = exp.Int64() 182 case time.Duration: 183 expAt, err = time.Now().UTC().Add(exp).Unix(), nil 184 case nil: 185 expAt, err = 0, nil 186 default: 187 expAt, err = 0, ErrInvalidDuration 188 } 189 if expAt < 0 { 190 return 0, ErrInvalidDuration 191 } 192 return expAt, err 193 } 194 195 // GetNewCredentialsWithMetadata generates and returns new credential with expiry. 196 func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string) (cred Credentials, err error) { 197 readBytes := func(size int) (data []byte, err error) { 198 data = make([]byte, size) 199 var n int 200 if n, err = rand.Read(data); err != nil { 201 return nil, err 202 } else if n != size { 203 return nil, fmt.Errorf("Not enough data. Expected to read: %v bytes, got: %v bytes", size, n) 204 } 205 return data, nil 206 } 207 208 // Generate access key. 209 keyBytes, err := readBytes(accessKeyMaxLen) 210 if err != nil { 211 return cred, err 212 } 213 for i := 0; i < accessKeyMaxLen; i++ { 214 keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen] 215 } 216 accessKey := string(keyBytes) 217 218 // Generate secret key. 219 keyBytes, err = readBytes(secretKeyMaxLen) 220 if err != nil { 221 return cred, err 222 } 223 224 secretKey := strings.Replace(string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]), 225 "/", "+", -1) 226 227 return CreateNewCredentialsWithMetadata(accessKey, secretKey, m, tokenSecret) 228 } 229 230 // CreateNewCredentialsWithMetadata - creates new credentials using the specified access & secret keys 231 // and generate a session token if a secret token is provided. 232 func CreateNewCredentialsWithMetadata(accessKey, secretKey string, m map[string]interface{}, tokenSecret string) (cred Credentials, err error) { 233 if len(accessKey) < accessKeyMinLen || len(accessKey) > accessKeyMaxLen { 234 return Credentials{}, fmt.Errorf("access key length should be between %d and %d", accessKeyMinLen, accessKeyMaxLen) 235 } 236 237 if len(secretKey) < secretKeyMinLen || len(secretKey) > secretKeyMaxLen { 238 return Credentials{}, fmt.Errorf("secret key length should be between %d and %d", secretKeyMinLen, secretKeyMaxLen) 239 } 240 241 cred.AccessKey = accessKey 242 cred.SecretKey = secretKey 243 cred.Status = AccountOn 244 245 if tokenSecret == "" { 246 cred.Expiration = timeSentinel 247 return cred, nil 248 } 249 250 expiry, err := ExpToInt64(m["exp"]) 251 if err != nil { 252 return cred, err 253 } 254 cred.Expiration = time.Unix(expiry, 0).UTC() 255 256 cred.SessionToken, err = JWTSignWithAccessKey(cred.AccessKey, m, tokenSecret) 257 if err != nil { 258 return cred, err 259 } 260 261 return cred, nil 262 } 263 264 // JWTSignWithAccessKey - generates a session token. 265 func JWTSignWithAccessKey(accessKey string, m map[string]interface{}, tokenSecret string) (string, error) { 266 m["accessKey"] = accessKey 267 jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m)) 268 return jwt.SignedString([]byte(tokenSecret)) 269 } 270 271 // ExtractClaims extracts JWT claims from a security token using a secret key 272 func ExtractClaims(token, secretKey string) (*jwt.MapClaims, error) { 273 if token == "" || secretKey == "" { 274 return nil, errors.New("invalid argument") 275 } 276 277 claims := jwt.NewMapClaims() 278 stsTokenCallback := func(claims *jwt.MapClaims) ([]byte, error) { 279 return []byte(secretKey), nil 280 } 281 282 if err := jwt.ParseWithClaims(token, claims, stsTokenCallback); err != nil { 283 return nil, err 284 } 285 286 return claims, nil 287 } 288 289 // GetNewCredentials generates and returns new credential. 290 func GetNewCredentials() (cred Credentials, err error) { 291 return GetNewCredentialsWithMetadata(map[string]interface{}{}, "") 292 } 293 294 // CreateCredentials returns new credential with the given access key and secret key. 295 // Error is returned if given access key or secret key are invalid length. 296 func CreateCredentials(accessKey, secretKey string) (cred Credentials, err error) { 297 if !IsAccessKeyValid(accessKey) { 298 return cred, ErrInvalidAccessKeyLength 299 } 300 if !IsSecretKeyValid(secretKey) { 301 return cred, ErrInvalidSecretKeyLength 302 } 303 cred.AccessKey = accessKey 304 cred.SecretKey = secretKey 305 cred.Expiration = timeSentinel 306 cred.Status = AccountOn 307 return cred, nil 308 }