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  }