mirror of
				https://github.com/minio/minio.git
				synced 2025-10-31 08:11:19 +01:00 
			
		
		
		
	- remove some duplicated code - reported a bug, separately fixed in #13664 - using strings.ReplaceAll() when needed - using filepath.ToSlash() use when needed - remove all non-Go style comments from the codebase Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
		
			
				
	
	
		
			319 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) 2015-2021 MinIO, Inc.
 | |
| //
 | |
| // This file is part of MinIO Object Storage stack
 | |
| //
 | |
| // This program is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Affero General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // This program is distributed in the hope that it will be useful
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU Affero General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Affero General Public License
 | |
| // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package auth
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"crypto/subtle"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	jwtgo "github.com/golang-jwt/jwt/v4"
 | |
| 	"github.com/minio/minio/internal/jwt"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// Minimum length for MinIO access key.
 | |
| 	accessKeyMinLen = 3
 | |
| 
 | |
| 	// Maximum length for MinIO access key.
 | |
| 	// There is no max length enforcement for access keys
 | |
| 	accessKeyMaxLen = 20
 | |
| 
 | |
| 	// Minimum length for MinIO secret key for both server and gateway mode.
 | |
| 	secretKeyMinLen = 8
 | |
| 
 | |
| 	// Maximum secret key length for MinIO, this
 | |
| 	// is used when autogenerating new credentials.
 | |
| 	// There is no max length enforcement for secret keys
 | |
| 	secretKeyMaxLen = 40
 | |
| 
 | |
| 	// Alpha numeric table used for generating access keys.
 | |
| 	alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | |
| 
 | |
| 	// Total length of the alpha numeric table.
 | |
| 	alphaNumericTableLen = byte(len(alphaNumericTable))
 | |
| )
 | |
| 
 | |
| // Common errors generated for access and secret key validation.
 | |
| var (
 | |
| 	ErrInvalidAccessKeyLength = fmt.Errorf("access key length should be between %d and %d", accessKeyMinLen, accessKeyMaxLen)
 | |
| 	ErrInvalidSecretKeyLength = fmt.Errorf("secret key length should be between %d and %d", secretKeyMinLen, secretKeyMaxLen)
 | |
| )
 | |
| 
 | |
| // IsAccessKeyValid - validate access key for right length.
 | |
| func IsAccessKeyValid(accessKey string) bool {
 | |
| 	return len(accessKey) >= accessKeyMinLen
 | |
| }
 | |
| 
 | |
| // IsSecretKeyValid - validate secret key for right length.
 | |
| func IsSecretKeyValid(secretKey string) bool {
 | |
| 	return len(secretKey) >= secretKeyMinLen
 | |
| }
 | |
| 
 | |
| // Default access and secret keys.
 | |
| const (
 | |
| 	DefaultAccessKey = "minioadmin"
 | |
| 	DefaultSecretKey = "minioadmin"
 | |
| )
 | |
| 
 | |
| // Default access credentials
 | |
| var (
 | |
| 	DefaultCredentials = Credentials{
 | |
| 		AccessKey: DefaultAccessKey,
 | |
| 		SecretKey: DefaultSecretKey,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// AccountOn indicates that credentials are enabled
 | |
| 	AccountOn = "on"
 | |
| 	// AccountOff indicates that credentials are disabled
 | |
| 	AccountOff = "off"
 | |
| )
 | |
| 
 | |
| // Credentials holds access and secret keys.
 | |
| type Credentials struct {
 | |
| 	AccessKey    string                 `xml:"AccessKeyId" json:"accessKey,omitempty"`
 | |
| 	SecretKey    string                 `xml:"SecretAccessKey" json:"secretKey,omitempty"`
 | |
| 	Expiration   time.Time              `xml:"Expiration" json:"expiration,omitempty"`
 | |
| 	SessionToken string                 `xml:"SessionToken" json:"sessionToken,omitempty"`
 | |
| 	Status       string                 `xml:"-" json:"status,omitempty"`
 | |
| 	ParentUser   string                 `xml:"-" json:"parentUser,omitempty"`
 | |
| 	Groups       []string               `xml:"-" json:"groups,omitempty"`
 | |
| 	Claims       map[string]interface{} `xml:"-" json:"claims,omitempty"`
 | |
| }
 | |
| 
 | |
| func (cred Credentials) String() string {
 | |
| 	var s strings.Builder
 | |
| 	s.WriteString(cred.AccessKey)
 | |
| 	s.WriteString(":")
 | |
| 	s.WriteString(cred.SecretKey)
 | |
| 	if cred.SessionToken != "" {
 | |
| 		s.WriteString("\n")
 | |
| 		s.WriteString(cred.SessionToken)
 | |
| 	}
 | |
| 	if !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel) {
 | |
| 		s.WriteString("\n")
 | |
| 		s.WriteString(cred.Expiration.String())
 | |
| 	}
 | |
| 	return s.String()
 | |
| }
 | |
| 
 | |
| // IsExpired - returns whether Credential is expired or not.
 | |
| func (cred Credentials) IsExpired() bool {
 | |
| 	if cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return cred.Expiration.Before(time.Now().UTC())
 | |
| }
 | |
| 
 | |
| // IsTemp - returns whether credential is temporary or not.
 | |
| func (cred Credentials) IsTemp() bool {
 | |
| 	return cred.SessionToken != "" && !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel)
 | |
| }
 | |
| 
 | |
| // IsServiceAccount - returns whether credential is a service account or not
 | |
| func (cred Credentials) IsServiceAccount() bool {
 | |
| 	return cred.ParentUser != "" && (cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel))
 | |
| }
 | |
| 
 | |
| // IsValid - returns whether credential is valid or not.
 | |
| func (cred Credentials) IsValid() bool {
 | |
| 	// Verify credentials if its enabled or not set.
 | |
| 	if cred.Status == AccountOff {
 | |
| 		return false
 | |
| 	}
 | |
| 	return IsAccessKeyValid(cred.AccessKey) && IsSecretKeyValid(cred.SecretKey) && !cred.IsExpired()
 | |
| }
 | |
| 
 | |
| // Equal - returns whether two credentials are equal or not.
 | |
| func (cred Credentials) Equal(ccred Credentials) bool {
 | |
| 	if !ccred.IsValid() {
 | |
| 		return false
 | |
| 	}
 | |
| 	return (cred.AccessKey == ccred.AccessKey && subtle.ConstantTimeCompare([]byte(cred.SecretKey), []byte(ccred.SecretKey)) == 1 &&
 | |
| 		subtle.ConstantTimeCompare([]byte(cred.SessionToken), []byte(ccred.SessionToken)) == 1)
 | |
| }
 | |
| 
 | |
| var timeSentinel = time.Unix(0, 0).UTC()
 | |
| 
 | |
| // ErrInvalidDuration invalid token expiry
 | |
| var ErrInvalidDuration = errors.New("invalid token expiry")
 | |
| 
 | |
| // ExpToInt64 - convert input interface value to int64.
 | |
| func ExpToInt64(expI interface{}) (expAt int64, err error) {
 | |
| 	switch exp := expI.(type) {
 | |
| 	case string:
 | |
| 		expAt, err = strconv.ParseInt(exp, 10, 64)
 | |
| 	case float64:
 | |
| 		expAt, err = int64(exp), nil
 | |
| 	case int64:
 | |
| 		expAt, err = exp, nil
 | |
| 	case int:
 | |
| 		expAt, err = int64(exp), nil
 | |
| 	case uint64:
 | |
| 		expAt, err = int64(exp), nil
 | |
| 	case uint:
 | |
| 		expAt, err = int64(exp), nil
 | |
| 	case json.Number:
 | |
| 		expAt, err = exp.Int64()
 | |
| 	case time.Duration:
 | |
| 		expAt, err = time.Now().UTC().Add(exp).Unix(), nil
 | |
| 	case nil:
 | |
| 		expAt, err = 0, nil
 | |
| 	default:
 | |
| 		expAt, err = 0, ErrInvalidDuration
 | |
| 	}
 | |
| 	if expAt < 0 {
 | |
| 		return 0, ErrInvalidDuration
 | |
| 	}
 | |
| 	return expAt, err
 | |
| }
 | |
| 
 | |
| // GenerateCredentials - creates randomly generated credentials of maximum
 | |
| // allowed length.
 | |
| func GenerateCredentials() (accessKey, secretKey string, err error) {
 | |
| 	readBytes := func(size int) (data []byte, err error) {
 | |
| 		data = make([]byte, size)
 | |
| 		var n int
 | |
| 		if n, err = rand.Read(data); err != nil {
 | |
| 			return nil, err
 | |
| 		} else if n != size {
 | |
| 			return nil, fmt.Errorf("Not enough data. Expected to read: %v bytes, got: %v bytes", size, n)
 | |
| 		}
 | |
| 		return data, nil
 | |
| 	}
 | |
| 
 | |
| 	// Generate access key.
 | |
| 	keyBytes, err := readBytes(accessKeyMaxLen)
 | |
| 	if err != nil {
 | |
| 		return "", "", err
 | |
| 	}
 | |
| 	for i := 0; i < accessKeyMaxLen; i++ {
 | |
| 		keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen]
 | |
| 	}
 | |
| 	accessKey = string(keyBytes)
 | |
| 
 | |
| 	// Generate secret key.
 | |
| 	keyBytes, err = readBytes(secretKeyMaxLen)
 | |
| 	if err != nil {
 | |
| 		return "", "", err
 | |
| 	}
 | |
| 
 | |
| 	secretKey = strings.ReplaceAll(string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]),
 | |
| 		"/", "+")
 | |
| 
 | |
| 	return accessKey, secretKey, nil
 | |
| }
 | |
| 
 | |
| // GetNewCredentialsWithMetadata generates and returns new credential with expiry.
 | |
| func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string) (Credentials, error) {
 | |
| 	accessKey, secretKey, err := GenerateCredentials()
 | |
| 	if err != nil {
 | |
| 		return Credentials{}, err
 | |
| 	}
 | |
| 	return CreateNewCredentialsWithMetadata(accessKey, secretKey, m, tokenSecret)
 | |
| }
 | |
| 
 | |
| // CreateNewCredentialsWithMetadata - creates new credentials using the specified access & secret keys
 | |
| // and generate a session token if a secret token is provided.
 | |
| func CreateNewCredentialsWithMetadata(accessKey, secretKey string, m map[string]interface{}, tokenSecret string) (cred Credentials, err error) {
 | |
| 	if len(accessKey) < accessKeyMinLen || len(accessKey) > accessKeyMaxLen {
 | |
| 		return Credentials{}, ErrInvalidAccessKeyLength
 | |
| 	}
 | |
| 
 | |
| 	if len(secretKey) < secretKeyMinLen || len(secretKey) > secretKeyMaxLen {
 | |
| 		return Credentials{}, ErrInvalidSecretKeyLength
 | |
| 	}
 | |
| 
 | |
| 	cred.AccessKey = accessKey
 | |
| 	cred.SecretKey = secretKey
 | |
| 	cred.Status = AccountOn
 | |
| 
 | |
| 	if tokenSecret == "" {
 | |
| 		cred.Expiration = timeSentinel
 | |
| 		return cred, nil
 | |
| 	}
 | |
| 
 | |
| 	expiry, err := ExpToInt64(m["exp"])
 | |
| 	if err != nil {
 | |
| 		return cred, err
 | |
| 	}
 | |
| 	cred.Expiration = time.Unix(expiry, 0).UTC()
 | |
| 
 | |
| 	cred.SessionToken, err = JWTSignWithAccessKey(cred.AccessKey, m, tokenSecret)
 | |
| 	if err != nil {
 | |
| 		return cred, err
 | |
| 	}
 | |
| 
 | |
| 	return cred, nil
 | |
| }
 | |
| 
 | |
| // JWTSignWithAccessKey - generates a session token.
 | |
| func JWTSignWithAccessKey(accessKey string, m map[string]interface{}, tokenSecret string) (string, error) {
 | |
| 	m["accessKey"] = accessKey
 | |
| 	jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
 | |
| 	return jwt.SignedString([]byte(tokenSecret))
 | |
| }
 | |
| 
 | |
| // ExtractClaims extracts JWT claims from a security token using a secret key
 | |
| func ExtractClaims(token, secretKey string) (*jwt.MapClaims, error) {
 | |
| 	if token == "" || secretKey == "" {
 | |
| 		return nil, errors.New("invalid argument")
 | |
| 	}
 | |
| 
 | |
| 	claims := jwt.NewMapClaims()
 | |
| 	stsTokenCallback := func(claims *jwt.MapClaims) ([]byte, error) {
 | |
| 		return []byte(secretKey), nil
 | |
| 	}
 | |
| 
 | |
| 	if err := jwt.ParseWithClaims(token, claims, stsTokenCallback); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return claims, nil
 | |
| }
 | |
| 
 | |
| // GetNewCredentials generates and returns new credential.
 | |
| func GetNewCredentials() (cred Credentials, err error) {
 | |
| 	return GetNewCredentialsWithMetadata(map[string]interface{}{}, "")
 | |
| }
 | |
| 
 | |
| // CreateCredentials returns new credential with the given access key and secret key.
 | |
| // Error is returned if given access key or secret key are invalid length.
 | |
| func CreateCredentials(accessKey, secretKey string) (cred Credentials, err error) {
 | |
| 	if !IsAccessKeyValid(accessKey) {
 | |
| 		return cred, ErrInvalidAccessKeyLength
 | |
| 	}
 | |
| 	if !IsSecretKeyValid(secretKey) {
 | |
| 		return cred, ErrInvalidSecretKeyLength
 | |
| 	}
 | |
| 	cred.AccessKey = accessKey
 | |
| 	cred.SecretKey = secretKey
 | |
| 	cred.Expiration = timeSentinel
 | |
| 	cred.Status = AccountOn
 | |
| 	return cred, nil
 | |
| }
 |