diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index a932ae809..5ceaa49ff 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -157,6 +157,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) { if !implicitPerm { if !globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: accessKey, + Groups: cred.Groups, Action: iampolicy.GetUserAdminAction, ConditionValues: getConditionValues(r, "", accessKey, claims), IsOwner: owner, @@ -397,6 +398,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { } if !globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: parentUser, + Groups: cred.Groups, Action: iampolicy.CreateUserAdminAction, ConditionValues: getConditionValues(r, "", parentUser, claims), IsOwner: owner, @@ -409,6 +411,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { if implicitPerm && !globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: accessKey, + Groups: cred.Groups, Action: iampolicy.CreateUserAdminAction, ConditionValues: getConditionValues(r, "", accessKey, claims), IsOwner: owner, @@ -677,6 +680,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ // https://github.com/golang/go/wiki/SliceTricks#filter-in-place if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.ListBucketAction, BucketName: bucketName, ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), @@ -689,6 +693,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.PutObjectAction, BucketName: bucketName, ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 66b5ae9d6..cd68bd01f 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -272,7 +272,7 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in // for authenticated requests validates IAM policies. // returns APIErrorCode if any to be replied to the client. func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) { - _, _, s3Err = checkRequestAuthTypeToAccessKey(ctx, r, action, bucketName, objectName) + _, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action, bucketName, objectName) return s3Err } @@ -282,14 +282,13 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac // for authenticated requests validates IAM policies. // returns APIErrorCode if any to be replied to the client. // Additionally returns the accessKey used in the request, and if this request is by an admin. -func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (accessKey string, owner bool, s3Err APIErrorCode) { - var cred auth.Credentials +func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (cred auth.Credentials, owner bool, s3Err APIErrorCode) { switch getRequestAuthType(r) { case authTypeUnknown, authTypeStreamingSigned: - return accessKey, owner, ErrSignatureVersionNotSupported + return cred, owner, ErrSignatureVersionNotSupported case authTypePresignedV2, authTypeSignedV2: if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone { - return accessKey, owner, s3Err + return cred, owner, s3Err } cred, owner, s3Err = getReqAccessKeyV2(r) case authTypeSigned, authTypePresigned: @@ -299,18 +298,18 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio region = "" } if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone { - return accessKey, owner, s3Err + return cred, owner, s3Err } cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3) } if s3Err != ErrNone { - return accessKey, owner, s3Err + return cred, owner, s3Err } var claims map[string]interface{} claims, s3Err = checkClaimsFromToken(r, cred) if s3Err != ErrNone { - return accessKey, owner, s3Err + return cred, owner, s3Err } // LocationConstraint is valid only for CreateBucketAction. @@ -320,7 +319,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize)) if err != nil { logger.LogIf(ctx, err, logger.Application) - return accessKey, owner, ErrMalformedXML + return cred, owner, ErrMalformedXML } // Populate payload to extract location constraint. @@ -329,7 +328,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio var s3Error APIErrorCode locationConstraint, s3Error = parseLocationConstraint(r) if s3Error != ErrNone { - return accessKey, owner, s3Error + return cred, owner, s3Error } // Populate payload again to handle it in HTTP handler. @@ -350,7 +349,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio ObjectName: objectName, }) { // Request is allowed return the appropriate access key. - return cred.AccessKey, owner, ErrNone + return cred, owner, ErrNone } if action == policy.ListBucketVersionsAction { @@ -365,15 +364,16 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio ObjectName: objectName, }) { // Request is allowed return the appropriate access key. - return cred.AccessKey, owner, ErrNone + return cred, owner, ErrNone } } - return cred.AccessKey, owner, ErrAccessDenied + return cred, owner, ErrAccessDenied } if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.Action(action), BucketName: bucketName, ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), @@ -382,7 +382,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio Claims: claims, }) { // Request is allowed return the appropriate access key. - return cred.AccessKey, owner, ErrNone + return cred, owner, ErrNone } if action == policy.ListBucketVersionsAction { @@ -390,6 +390,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio // verify as a fallback. if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.ListBucketAction, BucketName: bucketName, ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), @@ -398,11 +399,11 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio Claims: claims, }) { // Request is allowed return the appropriate access key. - return cred.AccessKey, owner, ErrNone + return cred, owner, ErrNone } } - return cred.AccessKey, owner, ErrAccessDenied + return cred, owner, ErrAccessDenied } // Verify if request has valid AWS Signature Version '2'. @@ -549,6 +550,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t if retMode == objectlock.RetGovernance && byPassSet { byPassSet = globalPolicySys.IsAllowed(policy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: policy.BypassGovernanceRetentionAction, BucketName: bucketName, ConditionValues: conditions, @@ -558,6 +560,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t } if globalPolicySys.IsAllowed(policy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: policy.PutObjectRetentionAction, BucketName: bucketName, ConditionValues: conditions, @@ -581,6 +584,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t if retMode == objectlock.RetGovernance && byPassSet { byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.BypassGovernanceRetentionAction, BucketName: bucketName, ObjectName: objectName, @@ -591,6 +595,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t } if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.PutObjectRetentionAction, BucketName: bucketName, ConditionValues: conditions, @@ -646,6 +651,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN if cred.AccessKey == "" { if globalPolicySys.IsAllowed(policy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: policy.Action(action), BucketName: bucketName, ConditionValues: getConditionValues(r, "", "", nil), @@ -659,6 +665,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN if globalIAMSys.IsAllowed(iampolicy.Args{ AccountName: cred.AccessKey, + Groups: cred.Groups, Action: action, BucketName: bucketName, ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 2ab18cf44..7e9660b2e 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -291,7 +291,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R listBuckets := objectAPI.ListBuckets - accessKey, owner, s3Error := checkRequestAuthTypeToAccessKey(ctx, r, policy.ListAllMyBucketsAction, "", "") + cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction, "", "") if s3Error != ErrNone && s3Error != ErrAccessDenied { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) return @@ -343,10 +343,11 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R // https://github.com/golang/go/wiki/SliceTricks#filter-in-place for _, bucketInfo := range bucketsInfo { if globalIAMSys.IsAllowed(iampolicy.Args{ - AccountName: accessKey, + AccountName: cred.AccessKey, + Groups: cred.Groups, Action: iampolicy.ListBucketAction, BucketName: bucketInfo.Name, - ConditionValues: getConditionValues(r, "", accessKey, claims), + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), IsOwner: owner, ObjectName: "", Claims: claims, diff --git a/cmd/iam.go b/cmd/iam.go index 0e36090d6..f5fa63d60 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1661,6 +1661,34 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is return nil } +// PolicyDBGetLDAP is only used by LDAP code, it is similar to PolicyDBGet +func (sys *IAMSys) PolicyDBGetLDAP(name string, groups ...string) ([]string, error) { + if !sys.Initialized() { + return nil, errServerNotInitialized + } + + if name == "" { + return nil, errInvalidArgument + } + + sys.store.rlock() + defer sys.store.runlock() + + var policies []string + mp, ok := sys.iamUserPolicyMap[name] + if ok { + // returned policy could be empty + policies = append(policies, mp.toSlice()...) + } + + for _, group := range groups { + p := sys.iamGroupPolicyMap[group] + policies = append(policies, p.toSlice()...) + } + + return policies, nil +} + // PolicyDBGet - gets policy set on a user or group. Since a user may // be a member of multiple groups, this function returns an array of // applicable policies @@ -1863,7 +1891,7 @@ func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args, parentUser string) bool } // Check policy for this LDAP user. - ldapPolicies, err := sys.PolicyDBGet(args.AccountName, false) + ldapPolicies, err := sys.PolicyDBGetLDAP(args.AccountName, args.Groups...) if err != nil { return false } diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index abbdddc07..64f07bd24 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -498,20 +498,11 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * } // Check if this user or their groups have a policy applied. - globalIAMSys.Lock() - found := false - if _, ok := globalIAMSys.iamUserPolicyMap[ldapUserDN]; ok { - found = true - } - for _, groupDistName := range groupDistNames { - if _, ok := globalIAMSys.iamGroupPolicyMap[groupDistName]; ok { - found = true - break - } - } - globalIAMSys.Unlock() - if !found { - writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("expecting a policy to be set for user `%s` or one of their groups: `%s` - rejecting this request", ldapUserDN, strings.Join(groupDistNames, "`,`"))) + ldapPolicies, _ := globalIAMSys.PolicyDBGetLDAP(ldapUserDN, groupDistNames...) + if len(ldapPolicies) == 0 { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, + fmt.Errorf("expecting a policy to be set for user `%s` or one of their groups: `%s` - rejecting this request", + ldapUserDN, strings.Join(groupDistNames, "`,`"))) return } diff --git a/pkg/bucket/policy/policy.go b/pkg/bucket/policy/policy.go index 4defbc44e..fc5edaeb1 100644 --- a/pkg/bucket/policy/policy.go +++ b/pkg/bucket/policy/policy.go @@ -27,6 +27,7 @@ const DefaultVersion = "2012-10-17" // Args - arguments to policy to check whether it is allowed type Args struct { AccountName string `json:"account"` + Groups []string `json:"groups"` Action Action `json:"action"` BucketName string `json:"bucket"` ConditionValues map[string][]string `json:"conditions"` diff --git a/pkg/iam/policy/policy.go b/pkg/iam/policy/policy.go index 230b68dc1..1b060c1d0 100644 --- a/pkg/iam/policy/policy.go +++ b/pkg/iam/policy/policy.go @@ -31,6 +31,7 @@ const DefaultVersion = "2012-10-17" // Args - arguments to policy to check whether it is allowed type Args struct { AccountName string `json:"account"` + Groups []string `json:"groups"` Action Action `json:"action"` BucketName string `json:"bucket"` ConditionValues map[string][]string `json:"conditions"`