diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index f08873052..5d866e38f 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -155,12 +155,7 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string) return cred, nil, owner, s3Err } - claims, s3Err := checkClaimsFromToken(r, cred) - if s3Err != ErrNone { - return cred, nil, owner, s3Err - } - - return cred, claims, owner, ErrNone + return cred, cred.Claims, owner, ErrNone } // checkAdminRequestAuth checks for authentication and authorization for the incoming @@ -318,12 +313,6 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action return cred, owner, s3Err } - var claims map[string]interface{} - claims, s3Err = checkClaimsFromToken(r, cred) - if s3Err != ErrNone { - return cred, owner, s3Err - } - // LocationConstraint is valid only for CreateBucketAction. var locationConstraint string if action == policy.CreateBucketAction { @@ -388,10 +377,10 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action Groups: cred.Groups, Action: iampolicy.Action(action), BucketName: bucketName, - ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ObjectName: objectName, IsOwner: owner, - Claims: claims, + Claims: cred.Claims, }) { // Request is allowed return the appropriate access key. return cred, owner, ErrNone @@ -405,10 +394,10 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action Groups: cred.Groups, Action: iampolicy.ListBucketAction, BucketName: bucketName, - ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ObjectName: objectName, IsOwner: owner, - Claims: claims, + Claims: cred.Claims, }) { // Request is allowed return the appropriate access key. return cred, owner, ErrNone @@ -520,75 +509,39 @@ func setAuthHandler(h http.Handler) http.Handler { }) } -func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool, map[string]interface{}, APIErrorCode) { +func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool, APIErrorCode) { var cred auth.Credentials var owner bool var s3Err APIErrorCode switch atype { case authTypeUnknown, authTypeStreamingSigned: - return cred, owner, nil, ErrSignatureVersionNotSupported + return cred, owner, ErrSignatureVersionNotSupported case authTypeSignedV2, authTypePresignedV2: if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone { - return cred, owner, nil, s3Err + return cred, owner, s3Err } cred, owner, s3Err = getReqAccessKeyV2(r) case authTypePresigned, authTypeSigned: region := globalServerRegion if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone { - return cred, owner, nil, s3Err + return cred, owner, s3Err } cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3) } if s3Err != ErrNone { - return cred, owner, nil, s3Err + return cred, owner, s3Err } - claims, s3Err := checkClaimsFromToken(r, cred) - if s3Err != ErrNone { - return cred, owner, nil, s3Err - } - - return cred, owner, claims, ErrNone + return cred, owner, ErrNone } -func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate time.Time, retMode objectlock.RetMode, byPassSet bool, r *http.Request, cred auth.Credentials, owner bool, claims map[string]interface{}) (s3Err APIErrorCode) { +func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate time.Time, retMode objectlock.RetMode, byPassSet bool, r *http.Request, cred auth.Credentials, owner bool) (s3Err APIErrorCode) { var retSet bool if cred.AccessKey == "" { - conditions := getConditionValues(r, "", "", nil) - conditions["object-lock-mode"] = []string{string(retMode)} - conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)} - if retDays > 0 { - conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)} - } - if retMode == objectlock.RetGovernance && byPassSet { - byPassSet = globalPolicySys.IsAllowed(policy.Args{ - AccountName: cred.AccessKey, - Groups: cred.Groups, - Action: policy.BypassGovernanceRetentionAction, - BucketName: bucketName, - ConditionValues: conditions, - IsOwner: false, - ObjectName: objectName, - }) - } - if globalPolicySys.IsAllowed(policy.Args{ - AccountName: cred.AccessKey, - Groups: cred.Groups, - Action: policy.PutObjectRetentionAction, - BucketName: bucketName, - ConditionValues: conditions, - IsOwner: false, - ObjectName: objectName, - }) { - retSet = true - } - if byPassSet || retSet { - return ErrNone - } return ErrAccessDenied } - conditions := getConditionValues(r, "", cred.AccessKey, claims) + conditions := getConditionValues(r, "", cred.AccessKey, cred.Claims) conditions["object-lock-mode"] = []string{string(retMode)} conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)} if retDays > 0 { @@ -603,7 +556,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t ObjectName: objectName, ConditionValues: conditions, IsOwner: owner, - Claims: claims, + Claims: cred.Claims, }) } if globalIAMSys.IsAllowed(iampolicy.Args{ @@ -614,7 +567,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t ConditionValues: conditions, ObjectName: objectName, IsOwner: owner, - Claims: claims, + Claims: cred.Claims, }) { retSet = true } @@ -643,11 +596,6 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN return s3Err } - claims, s3Err := checkClaimsFromToken(r, cred) - if s3Err != ErrNone { - return s3Err - } - if cred.AccessKey != "" { logger.GetReqInfo(ctx).AccessKey = cred.AccessKey } @@ -681,10 +629,10 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN Groups: cred.Groups, Action: action, BucketName: bucketName, - ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ObjectName: objectName, IsOwner: owner, - Claims: claims, + Claims: cred.Claims, }) { return ErrNone } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index e6c3dc4f1..a75e77505 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -19,7 +19,6 @@ package cmd import ( "bytes" - "crypto/subtle" "encoding/base64" "encoding/json" "encoding/xml" @@ -345,9 +344,6 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R // Set delimiter value for "s3:delimiter" policy conditionals. r.Header.Set("delimiter", SlashSeparator) - // err will be nil here as we already called this function - // earlier in this request. - claims, _ := getClaimsFromToken(getSessionToken(r)) n := 0 // Use the following trick to filter in place // https://github.com/golang/go/wiki/SliceTricks#filter-in-place @@ -357,10 +353,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R Groups: cred.Groups, Action: iampolicy.ListBucketAction, BucketName: bucketInfo.Name, - ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), IsOwner: owner, ObjectName: "", - Claims: claims, + Claims: cred.Claims, }) { bucketsInfo[n] = bucketInfo n++ @@ -899,41 +895,17 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h // Once signature is validated, check if the user has // explicit permissions for the user. - { - token := formValues.Get(xhttp.AmzSecurityToken) - if token != "" && cred.AccessKey == "" { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoAccessKey), r.URL) - return - } - - if cred.IsServiceAccount() && token == "" { - token = cred.SessionToken - } - - if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidToken), r.URL) - return - } - - // Extract claims if any. - claims, err := getClaimsFromToken(token) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - if !globalIAMSys.IsAllowed(iampolicy.Args{ - AccountName: cred.AccessKey, - Action: iampolicy.PutObjectAction, - ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), - BucketName: bucket, - ObjectName: object, - IsOwner: globalActiveCred.AccessKey == cred.AccessKey, - Claims: claims, - }) { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL) - return - } + if !globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: cred.AccessKey, + Action: iampolicy.PutObjectAction, + ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), + BucketName: bucket, + ObjectName: object, + IsOwner: globalActiveCred.AccessKey == cred.AccessKey, + Claims: cred.Claims, + }) { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL) + return } policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) diff --git a/cmd/bucket-object-lock.go b/cmd/bucket-object-lock.go index 49d17c743..465447b82 100644 --- a/cmd/bucket-object-lock.go +++ b/cmd/bucket-object-lock.go @@ -175,7 +175,7 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke // For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR // governance bypass headers are set and user has governance bypass permissions. // Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted. -func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool, claims map[string]interface{}) (ObjectInfo, APIErrorCode) { +func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) (ObjectInfo, APIErrorCode) { byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header) opts, err := getOpts(ctx, r, bucket, object) if err != nil { @@ -203,7 +203,7 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, perm := isPutRetentionAllowed(bucket, object, days, objRetention.RetainUntilDate.Time, objRetention.Mode, byPassSet, r, cred, - owner, claims) + owner) return oi, perm } @@ -211,7 +211,7 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, case objectlock.RetGovernance: govPerm := isPutRetentionAllowed(bucket, object, days, objRetention.RetainUntilDate.Time, objRetention.Mode, - byPassSet, r, cred, owner, claims) + byPassSet, r, cred, owner) // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set. if !byPassSet { if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) { @@ -227,7 +227,7 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, } compliancePerm := isPutRetentionAllowed(bucket, object, days, objRetention.RetainUntilDate.Time, objRetention.Mode, - false, r, cred, owner, claims) + false, r, cred, owner) return oi, compliancePerm } return oi, ErrNone @@ -235,7 +235,7 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, perm := isPutRetentionAllowed(bucket, object, days, objRetention.RetainUntilDate.Time, - objRetention.Mode, byPassSet, r, cred, owner, claims) + objRetention.Mode, byPassSet, r, cred, owner) return oi, perm } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index d349c3df8..4b1662a17 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -3502,7 +3502,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r return } - cred, owner, claims, s3Err := validateSignature(getRequestAuthType(r), r) + cred, owner, s3Err := validateSignature(getRequestAuthType(r), r) if s3Err != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) return @@ -3542,7 +3542,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r getObjectInfo = api.CacheAPI().GetObjectInfo } - objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, objRetention, cred, owner, claims) + objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, objRetention, cred, owner) if s3Err != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) return diff --git a/cmd/signature-v2.go b/cmd/signature-v2.go index dd56e095f..43f35c195 100644 --- a/cmd/signature-v2.go +++ b/cmd/signature-v2.go @@ -78,7 +78,9 @@ const ( // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign func doesPolicySignatureV2Match(formValues http.Header) (auth.Credentials, APIErrorCode) { accessKey := formValues.Get(xhttp.AmzAccessKeyID) - cred, _, s3Err := checkKeyValid(accessKey) + + r := &http.Request{Header: formValues} + cred, _, s3Err := checkKeyValid(r, accessKey) if s3Err != ErrNone { return cred, s3Err } @@ -153,7 +155,7 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { return ErrInvalidQueryParams } - cred, _, s3Err := checkKeyValid(accessKey) + cred, _, s3Err := checkKeyValid(r, accessKey) if s3Err != ErrNone { return s3Err } @@ -184,7 +186,7 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { func getReqAccessKeyV2(r *http.Request) (auth.Credentials, bool, APIErrorCode) { if accessKey := r.Form.Get(xhttp.AmzAccessKeyID); accessKey != "" { - return checkKeyValid(accessKey) + return checkKeyValid(r, accessKey) } // below is V2 Signed Auth header format, splitting on `space` (after the `AWS` string). @@ -200,7 +202,7 @@ func getReqAccessKeyV2(r *http.Request) (auth.Credentials, bool, APIErrorCode) { return auth.Credentials{}, false, ErrMissingFields } - return checkKeyValid(keySignFields[0]) + return checkKeyValid(r, keySignFields[0]) } // Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature; diff --git a/cmd/signature-v4-parser.go b/cmd/signature-v4-parser.go index f40696833..a19e6ad1e 100644 --- a/cmd/signature-v4-parser.go +++ b/cmd/signature-v4-parser.go @@ -63,7 +63,7 @@ func getReqAccessKeyV4(r *http.Request, region string, stype serviceType) (auth. return auth.Credentials{}, false, s3Err } } - return checkKeyValid(ch.accessKey) + return checkKeyValid(r, ch.accessKey) } // parse credentialHeader string into its structured form. diff --git a/cmd/signature-v4-utils.go b/cmd/signature-v4-utils.go index 856d36abb..d281c7951 100644 --- a/cmd/signature-v4-utils.go +++ b/cmd/signature-v4-utils.go @@ -31,6 +31,7 @@ import ( "github.com/minio/minio/internal/auth" xhttp "github.com/minio/minio/internal/http" "github.com/minio/minio/internal/logger" + iampolicy "github.com/minio/pkg/iam/policy" ) // http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the @@ -120,7 +121,7 @@ func isValidRegion(reqRegion string, confRegion string) bool { // check if the access key is valid and recognized, additionally // also returns if the access key is owner/admin. -func checkKeyValid(accessKey string) (auth.Credentials, bool, APIErrorCode) { +func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, APIErrorCode) { if !globalIAMSys.Initialized() && !globalIsGateway { // Check if server has initialized, then only proceed // to check for IAM users otherwise its okay for clients @@ -135,7 +136,17 @@ func checkKeyValid(accessKey string) (auth.Credentials, bool, APIErrorCode) { if !ok { return cred, false, ErrInvalidAccessKeyID } - owner = cred.AccessKey == ucred.ParentUser + claims, s3Err := checkClaimsFromToken(r, cred) + if s3Err != ErrNone { + return cred, false, s3Err + } + ucred.Claims = claims + // Now check if we have a sessionPolicy. + if _, ok = claims[iampolicy.SessionPolicyName]; ok { + owner = false + } else { + owner = cred.AccessKey == ucred.ParentUser + } cred = ucred } return cred, owner, ErrNone diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index 9da2a16b1..044da6d4e 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -181,7 +181,8 @@ func doesPolicySignatureV4Match(formValues http.Header) (auth.Credentials, APIEr return auth.Credentials{}, s3Err } - cred, _, s3Err := checkKeyValid(credHeader.accessKey) + r := &http.Request{Header: formValues} + cred, _, s3Err := checkKeyValid(r, credHeader.accessKey) if s3Err != ErrNone { return cred, s3Err } @@ -214,7 +215,7 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s return err } - cred, _, s3Err := checkKeyValid(pSignValues.Credential.accessKey) + cred, _, s3Err := checkKeyValid(r, pSignValues.Credential.accessKey) if s3Err != ErrNone { return s3Err } @@ -349,7 +350,7 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, region string, st return errCode } - cred, _, s3Err := checkKeyValid(signV4Values.Credential.accessKey) + cred, _, s3Err := checkKeyValid(r, signV4Values.Credential.accessKey) if s3Err != ErrNone { return s3Err } diff --git a/cmd/streaming-signature-v4.go b/cmd/streaming-signature-v4.go index accfb4c26..c161d7c6c 100644 --- a/cmd/streaming-signature-v4.go +++ b/cmd/streaming-signature-v4.go @@ -93,7 +93,7 @@ func calculateSeedSignature(r *http.Request) (cred auth.Credentials, signature s return cred, "", "", time.Time{}, errCode } - cred, _, errCode = checkKeyValid(signV4Values.Credential.accessKey) + cred, _, errCode = checkKeyValid(r, signV4Values.Credential.accessKey) if errCode != ErrNone { return cred, "", "", time.Time{}, errCode } diff --git a/internal/auth/credentials.go b/internal/auth/credentials.go index fe59353b7..29a3b3daa 100644 --- a/internal/auth/credentials.go +++ b/internal/auth/credentials.go @@ -94,13 +94,14 @@ const ( // 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"` + 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 {