diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 9911d7b71..37b634f80 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -1649,7 +1649,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { if err == nil { return ErrNone } - // Verify if the underlying error is signature mismatch. + switch err { case errInvalidArgument: apiErr = ErrAdminInvalidArgument diff --git a/cmd/iam.go b/cmd/iam.go index e215a52eb..b8a648c50 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -22,6 +22,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strings" "github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio/cmd/config" @@ -166,6 +167,10 @@ type MappedPolicy struct { Policy string `json:"policy"` } +func (mp MappedPolicy) policySet() set.StringSet { + return set.CreateStringSet(strings.Split(mp.Policy, ",")...) +} + func newMappedPolicy(policy string) MappedPolicy { return MappedPolicy{Version: 1, Policy: policy} } @@ -591,11 +596,26 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa // temporary user which match with pre-configured canned // policies for this server. if globalPolicyOPA == nil && policyName != "" { - p, ok := sys.iamPolicyDocsMap[policyName] - if !ok { - return errInvalidArgument + var availablePolicies []iampolicy.Policy + for _, pname := range strings.Split(policyName, ",") { + pname = strings.TrimSpace(pname) + if pname == "" { + continue + } + p, found := sys.iamPolicyDocsMap[pname] + if !found { + return fmt.Errorf("%w: (%s)", errNoSuchPolicy, pname) + } + availablePolicies = append(availablePolicies, p) } - if p.IsEmpty() { + + combinedPolicy := availablePolicies[0] + for i := 1; i < len(availablePolicies); i++ { + combinedPolicy.Statements = append(combinedPolicy.Statements, + availablePolicies[i].Statements...) + } + + if combinedPolicy.IsEmpty() { delete(sys.iamUserPolicyMap, accessKey) return nil } @@ -1265,13 +1285,10 @@ func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error { // policyDBSet - sets a policy for user in the policy db. Assumes that caller // has sys.Lock(). If policy == "", then policy mapping is removed. -func (sys *IAMSys) policyDBSet(name, policy string, userType IAMUserType, isGroup bool) error { +func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, isGroup bool) error { if name == "" { return errInvalidArgument } - if _, ok := sys.iamPolicyDocsMap[policy]; !ok && policy != "" { - return errNoSuchPolicy - } if sys.usersSysType == MinIOUsersSysType { if !isGroup { @@ -1286,7 +1303,7 @@ func (sys *IAMSys) policyDBSet(name, policy string, userType IAMUserType, isGrou } // Handle policy mapping removal - if policy == "" { + if policyName == "" { if err := sys.store.deleteMappedPolicy(name, userType, isGroup); err != nil && err != errNoSuchPolicy { return err } @@ -1298,8 +1315,19 @@ func (sys *IAMSys) policyDBSet(name, policy string, userType IAMUserType, isGrou return nil } + for _, pname := range strings.Split(policyName, ",") { + pname = strings.TrimSpace(pname) + if pname == "" { + continue + } + if _, found := sys.iamPolicyDocsMap[pname]; !found { + logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, pname)) + return errNoSuchPolicy + } + } + // Handle policy mapping set/update - mp := newMappedPolicy(policy) + mp := newMappedPolicy(policyName) if err := sys.store.saveMappedPolicy(name, userType, isGroup, mp); err != nil { return err } @@ -1644,14 +1672,15 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { return combinedPolicy.IsAllowed(args) } - pnameSlice, ok := args.GetPolicies(iamPolicyClaimNameOpenID()) + policies, ok := args.GetPolicies(iamPolicyClaimNameOpenID()) if !ok { // When claims are set, it should have a policy claim field. return false } - // When claims are set, it should have a policy claim field. - if len(pnameSlice) == 0 { + // When claims are set, it should have policies as claim. + if policies.IsEmpty() { + // No policy, no access! return false } @@ -1661,18 +1690,33 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { // If policy is available for given user, check the policy. mp, ok := sys.iamUserPolicyMap[args.AccountName] if !ok { - // No policy available reject. + // No policy set for the user that we can find, no access! return false } - name := mp.Policy - if pnameSlice[0] != name { + if !policies.Equals(mp.policySet()) { // When claims has a policy, it should match the // policy of args.AccountName which server remembers. // if not reject such requests. return false } + var availablePolicies []iampolicy.Policy + for pname := range policies { + p, found := sys.iamPolicyDocsMap[pname] + if !found { + logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, pname)) + return false + } + availablePolicies = append(availablePolicies, p) + } + + combinedPolicy := availablePolicies[0] + for i := 1; i < len(availablePolicies); i++ { + combinedPolicy.Statements = append(combinedPolicy.Statements, + availablePolicies[i].Statements...) + } + // Now check if we have a sessionPolicy. spolicy, ok := args.Claims[iampolicy.SessionPolicyName] if ok { @@ -1697,14 +1741,12 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { } // Sub policy is set and valid. - p, ok := sys.iamPolicyDocsMap[pnameSlice[0]] - return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args) + return combinedPolicy.IsAllowed(args) && subPolicy.IsAllowed(args) } // Sub policy not set, this is most common since subPolicy - // is optional, use the top level policy only. - p, ok := sys.iamPolicyDocsMap[pnameSlice[0]] - return ok && p.IsAllowed(args) + // is optional, use the inherited policies. + return combinedPolicy.IsAllowed(args) } // IsAllowed - checks given policy args is allowed to continue the Rest API. diff --git a/pkg/iam/policy/policy.go b/pkg/iam/policy/policy.go index 4b874ef82..3d1138c93 100644 --- a/pkg/iam/policy/policy.go +++ b/pkg/iam/policy/policy.go @@ -21,6 +21,7 @@ import ( "io" "strings" + "github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio/pkg/bucket/policy" ) @@ -39,17 +40,31 @@ type Args struct { } // GetPolicies get policies -func (a Args) GetPolicies(policyClaimName string) ([]string, bool) { +func (a Args) GetPolicies(policyClaimName string) (set.StringSet, bool) { + s := set.NewStringSet() pname, ok := a.Claims[policyClaimName] if !ok { - return nil, false + return s, false } - pnameStr, ok := pname.(string) - if ok { - return strings.Split(pnameStr, ","), true + pnames, ok := pname.([]string) + if !ok { + pnameStr, ok := pname.(string) + if ok { + pnames = strings.Split(pnameStr, ",") + } else { + return s, false + } } - pnameSlice, ok := pname.([]string) - return pnameSlice, ok + for _, pname := range pnames { + pname = strings.TrimSpace(pname) + if pname == "" { + // ignore any empty strings, considerate + // towards some user errors. + continue + } + s.Add(pname) + } + return s, true } // Policy - iam bucket iamp.