From 0ea5c9d8e88197dc87dfe36184dd808569929746 Mon Sep 17 00:00:00 2001 From: Poorna Date: Fri, 1 Jul 2022 13:19:13 -0700 Subject: [PATCH] site healing: Skip stale iam asset updates from peer. (#15203) Allow healing to apply IAM change only when peer gave the most recent update. --- cmd/admin-handlers-site-replication.go | 16 +- cmd/admin-handlers-users.go | 102 ++++--- cmd/iam-dummy-store.go | 6 +- cmd/iam-etcd-store.go | 10 +- cmd/iam-object-store.go | 6 +- cmd/iam-store.go | 282 +++++++++--------- cmd/iam.go | 182 +++++------ cmd/jwt.go | 11 +- cmd/signature-v4-utils.go | 6 +- cmd/site-replication.go | 167 ++++++++--- cmd/sts-handlers.go | 18 +- .../run-multi-site-minio-idp.sh | 32 +- 12 files changed, 489 insertions(+), 349 deletions(-) diff --git a/cmd/admin-handlers-site-replication.go b/cmd/admin-handlers-site-replication.go index 8eb3ef123..d1840f451 100644 --- a/cmd/admin-handlers-site-replication.go +++ b/cmd/admin-handlers-site-replication.go @@ -160,7 +160,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http. err = errSRInvalidRequest(errInvalidArgument) case madmin.SRIAMItemPolicy: if item.Policy == nil { - err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil) + err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt) } else { policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy)) if perr != nil { @@ -168,21 +168,21 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http. return } if policy.IsEmpty() { - err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil) + err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt) } else { - err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy) + err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy, item.UpdatedAt) } } case madmin.SRIAMItemSvcAcc: - err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange) + err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange, item.UpdatedAt) case madmin.SRIAMItemPolicyMapping: - err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping) + err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping, item.UpdatedAt) case madmin.SRIAMItemSTSAcc: - err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential) + err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential, item.UpdatedAt) case madmin.SRIAMItemIAMUser: - err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser) + err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser, item.UpdatedAt) case madmin.SRIAMItemGroupInfo: - err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo) + err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo, item.UpdatedAt) } if err != nil { logger.LogIf(ctx, err) diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 3c9ff2618..51717d1eb 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -72,6 +72,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) { AccessKey: accessKey, IsDeleteReq: true, }, + UpdatedAt: UTCNow(), }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -240,9 +241,9 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) return } - + var updatedAt time.Time if updReq.IsRemove { - err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) + updatedAt, err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) } else { // Check if group already exists if _, gerr := globalIAMSys.GetGroupDescription(updReq.Group); gerr != nil { @@ -253,7 +254,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ return } } - err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) } if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) @@ -265,6 +266,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ GroupInfo: &madmin.SRGroupInfo{ UpdateReq: updReq, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -341,12 +343,15 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) group := vars["group"] status := vars["status"] - var err error + var ( + err error + updatedAt time.Time + ) switch status { case statusEnabled: - err = globalIAMSys.SetGroupStatus(ctx, group, true) + updatedAt, err = globalIAMSys.SetGroupStatus(ctx, group, true) case statusDisabled: - err = globalIAMSys.SetGroupStatus(ctx, group, false) + updatedAt, err = globalIAMSys.SetGroupStatus(ctx, group, false) default: err = errInvalidArgument } @@ -364,6 +369,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) IsRemove: false, }, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -391,7 +397,8 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) return } - if err := globalIAMSys.SetUserStatus(ctx, accessKey, madmin.AccountStatus(status)); err != nil { + updatedAt, err := globalIAMSys.SetUserStatus(ctx, accessKey, madmin.AccountStatus(status)) + if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return } @@ -405,6 +412,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) Status: madmin.AccountStatus(status), }, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -439,8 +447,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { return } - userCred, exists := globalIAMSys.GetUser(ctx, accessKey) - if exists && (userCred.IsTemp() || userCred.IsServiceAccount()) { + user, exists := globalIAMSys.GetUser(ctx, accessKey) + if exists && (user.Credentials.IsTemp() || user.Credentials.IsServiceAccount()) { // Updating STS credential is not allowed, and this API does not // support updating service accounts. writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL) @@ -501,7 +509,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { return } - if err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil { + updatedAt, err := globalIAMSys.CreateUser(ctx, accessKey, ureq) + if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return } @@ -513,6 +522,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { IsDeleteReq: false, UserReq: &ureq, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -674,7 +684,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque } opts.sessionPolicy = sp - newCred, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts) + newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -717,6 +727,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque Status: auth.AccountOn, }, }, + UpdatedAt: updatedAt, }) if err != nil { logger.LogIf(ctx, err) @@ -800,7 +811,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re status: updateReq.NewStatus, sessionPolicy: sp, } - err = globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts) + updatedAt, err := globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -818,6 +829,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re SessionPolicy: updateReq.NewPolicy, }, }, + UpdatedAt: updatedAt, }) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) @@ -1057,6 +1069,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re AccessKey: serviceAccount, }, }, + UpdatedAt: UTCNow(), }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -1395,8 +1408,9 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ // Call cluster-replication policy creation hook to replicate policy deletion to // other minio clusters. if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{ - Type: madmin.SRIAMItemPolicy, - Name: policyName, + Type: madmin.SRIAMItemPolicy, + Name: policyName, + UpdatedAt: UTCNow(), }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -1453,7 +1467,8 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request return } - if err = globalIAMSys.SetPolicy(ctx, policyName, *iamPolicy); err != nil { + updatedAt, err := globalIAMSys.SetPolicy(ctx, policyName, *iamPolicy) + if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return } @@ -1461,9 +1476,10 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request // Call cluster-replication policy creation hook to replicate policy to // other minio clusters. if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{ - Type: madmin.SRIAMItemPolicy, - Name: policyName, - Policy: iamPolicyBytes, + Type: madmin.SRIAMItemPolicy, + Name: policyName, + Policy: iamPolicyBytes, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -1498,7 +1514,8 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http } } - if err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, isGroup); err != nil { + updatedAt, err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, isGroup) + if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return } @@ -1510,6 +1527,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http IsGroup: isGroup, Policy: policyName, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -1597,22 +1615,22 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { return } case allUsersFile: - userCreds := make(map[string]auth.Credentials) + userIdentities := make(map[string]UserIdentity) globalIAMSys.store.rlock() - err := globalIAMSys.store.loadUsers(ctx, regUser, userCreds) + err := globalIAMSys.store.loadUsers(ctx, regUser, userIdentities) globalIAMSys.store.runlock() if err != nil { writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) return } userAccounts := make(map[string]madmin.AddOrUpdateUserReq) - for u, cred := range userCreds { + for u, uid := range userIdentities { status := madmin.AccountDisabled - if cred.IsValid() { + if uid.Credentials.IsValid() { status = madmin.AccountEnabled } userAccounts[u] = madmin.AddOrUpdateUserReq{ - SecretKey: cred.SecretKey, + SecretKey: uid.Credentials.SecretKey, Status: status, } } @@ -1646,7 +1664,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { return } case allSvcAcctsFile: - serviceAccounts := make(map[string]auth.Credentials) + serviceAccounts := make(map[string]UserIdentity) globalIAMSys.store.rlock() err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts) globalIAMSys.store.runlock() @@ -1661,12 +1679,12 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { // site replication is enabled. continue } - claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey) + claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey) if err != nil { writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) return } - _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.AccessKey) + _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey) if err != nil { writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) return @@ -1681,13 +1699,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) { } } svcAccts[user] = madmin.SRSvcAccCreate{ - Parent: acc.ParentUser, + Parent: acc.Credentials.ParentUser, AccessKey: user, - SecretKey: acc.SecretKey, - Groups: acc.Groups, + SecretKey: acc.Credentials.SecretKey, + Groups: acc.Credentials.Groups, Claims: claims, SessionPolicy: json.RawMessage(policyJSON), - Status: acc.Status, + Status: acc.Credentials.Status, } } @@ -1832,7 +1850,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { if policy.IsEmpty() { err = globalIAMSys.DeletePolicy(ctx, policyName, true) } else { - err = globalIAMSys.SetPolicy(ctx, policyName, policy) + _, err = globalIAMSys.SetPolicy(ctx, policyName, policy) } if err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, allPoliciesFile, policyName), r.URL) @@ -1870,8 +1888,8 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { return } - userCred, exists := globalIAMSys.GetUser(ctx, accessKey) - if exists && (userCred.IsTemp() || userCred.IsServiceAccount()) { + user, exists := globalIAMSys.GetUser(ctx, accessKey) + if exists && (user.Credentials.IsTemp() || user.Credentials.IsServiceAccount()) { // Updating STS credential is not allowed, and this API does not // support updating service accounts. writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL) @@ -1910,7 +1928,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL) return } - if err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil { + if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil { writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, toAdminAPIErrCode(ctx, err), err, allUsersFile, accessKey), r.URL) return } @@ -1949,7 +1967,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { return } } - if gerr := globalIAMSys.AddUsersToGroup(ctx, group, grpInfo.Members); gerr != nil { + if _, gerr := globalIAMSys.AddUsersToGroup(ctx, group, grpInfo.Members); gerr != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, allGroupsFile, group), r.URL) return } @@ -2018,7 +2036,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { status: svcAcctReq.Status, sessionPolicy: sp, } - err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts) + _, err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts) if err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL) return @@ -2044,7 +2062,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { opts.claims[ldapUser] = targetUser // username DN } - if _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil { + if _, _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL) return } @@ -2084,7 +2102,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, userPolicyMappingsFile, u), r.URL) return } - if err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil { + if _, err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, userPolicyMappingsFile, u), r.URL) return } @@ -2113,7 +2131,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { return } for g, pm := range grpPolicyMap { - if err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil { + if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, g), r.URL) return } @@ -2152,7 +2170,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL) return } - if err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil { + if _, err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL) return } @@ -2181,7 +2199,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { return } for g, pm := range grpPolicyMap { - if err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil { + if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, stsGroupPolicyMappingsFile, g), r.URL) return } diff --git a/cmd/iam-dummy-store.go b/cmd/iam-dummy-store.go index f70a19d5f..ce173cdcc 100644 --- a/cmd/iam-dummy-store.go +++ b/cmd/iam-dummy-store.go @@ -20,8 +20,6 @@ package cmd import ( "context" "sync" - - "github.com/minio/minio/internal/auth" ) type iamDummyStore struct { @@ -79,7 +77,7 @@ func (ids *iamDummyStore) loadPolicyDocs(ctx context.Context, m map[string]Polic return nil } -func (ids *iamDummyStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error { +func (ids *iamDummyStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error { u, ok := ids.iamUsersMap[user] if !ok { return errNoSuchUser @@ -88,7 +86,7 @@ func (ids *iamDummyStore) loadUser(ctx context.Context, user string, userType IA return nil } -func (ids *iamDummyStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error { +func (ids *iamDummyStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error { for k, v := range ids.iamUsersMap { m[k] = v } diff --git a/cmd/iam-etcd-store.go b/cmd/iam-etcd-store.go index 0f0215803..4efeff3b2 100644 --- a/cmd/iam-etcd-store.go +++ b/cmd/iam-etcd-store.go @@ -336,7 +336,7 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]Policy return nil } -func (ies *IAMEtcdStore) getUserKV(ctx context.Context, userkv *mvccpb.KeyValue, userType IAMUserType, m map[string]auth.Credentials, basePrefix string) error { +func (ies *IAMEtcdStore) getUserKV(ctx context.Context, userkv *mvccpb.KeyValue, userType IAMUserType, m map[string]UserIdentity, basePrefix string) error { var u UserIdentity err := getIAMConfig(&u, userkv.Value, string(userkv.Key)) if err != nil { @@ -349,7 +349,7 @@ func (ies *IAMEtcdStore) getUserKV(ctx context.Context, userkv *mvccpb.KeyValue, return ies.addUser(ctx, user, userType, u, m) } -func (ies *IAMEtcdStore) addUser(ctx context.Context, user string, userType IAMUserType, u UserIdentity, m map[string]auth.Credentials) error { +func (ies *IAMEtcdStore) addUser(ctx context.Context, user string, userType IAMUserType, u UserIdentity, m map[string]UserIdentity) error { if u.Credentials.IsExpired() { // Delete expired identity. deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, userType)) @@ -359,11 +359,11 @@ func (ies *IAMEtcdStore) addUser(ctx context.Context, user string, userType IAMU if u.Credentials.AccessKey == "" { u.Credentials.AccessKey = user } - m[user] = u.Credentials + m[user] = u return nil } -func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error { +func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error { var u UserIdentity err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) if err != nil { @@ -375,7 +375,7 @@ func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAM return ies.addUser(ctx, user, userType, u, m) } -func (ies *IAMEtcdStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error { +func (ies *IAMEtcdStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error { var basePrefix string switch userType { case svcUser: diff --git a/cmd/iam-object-store.go b/cmd/iam-object-store.go index c52a5be6d..274d5c884 100644 --- a/cmd/iam-object-store.go +++ b/cmd/iam-object-store.go @@ -287,7 +287,7 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]Po return nil } -func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error { +func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error { var u UserIdentity err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) if err != nil { @@ -308,11 +308,11 @@ func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType u.Credentials.AccessKey = user } - m[user] = u.Credentials + m[user] = u return nil } -func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error { +func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error { var basePrefix string switch userType { case svcUser: diff --git a/cmd/iam-store.go b/cmd/iam-store.go index dd07eccbb..bf3485b0b 100644 --- a/cmd/iam-store.go +++ b/cmd/iam-store.go @@ -249,7 +249,7 @@ type iamCache struct { // map of policy names to policy definitions iamPolicyDocsMap map[string]PolicyDoc // map of usernames to credentials - iamUsersMap map[string]auth.Credentials + iamUsersMap map[string]UserIdentity // map of group names to group info iamGroupsMap map[string]GroupInfo // map of user names to groups they are a member of @@ -263,7 +263,7 @@ type iamCache struct { func newIamCache() *iamCache { return &iamCache{ iamPolicyDocsMap: map[string]PolicyDoc{}, - iamUsersMap: map[string]auth.Credentials{}, + iamUsersMap: map[string]UserIdentity{}, iamGroupsMap: map[string]GroupInfo{}, iamUserGroupMemberships: map[string]set.StringSet{}, iamUserPolicyMap: map[string]MappedPolicy{}, @@ -346,16 +346,16 @@ func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([] var parentName string u, ok := c.iamUsersMap[name] if ok { - if !u.IsValid() { + if !u.Credentials.IsValid() { return nil, time.Time{}, nil } - parentName = u.ParentUser + parentName = u.Credentials.ParentUser } mp, ok := c.iamUserPolicyMap[name] if !ok { // Service accounts with root credentials, inherit parent permissions - if parentName == globalActiveCred.AccessKey && u.IsServiceAccount() { + if parentName == globalActiveCred.AccessKey && u.Credentials.IsServiceAccount() { // even if this is set, the claims present in the service // accounts apply the final permissions if any. return []string{"consoleAdmin"}, mp.UpdatedAt, nil @@ -395,8 +395,8 @@ type IAMStorageAPI interface { getUsersSysType() UsersSysType loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error - loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error - loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error + loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error + loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error loadGroups(ctx context.Context, m map[string]GroupInfo) error loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error @@ -526,12 +526,12 @@ func (store *IAMStoreSys) HasWatcher() bool { } // GetUser - fetches credential from memory. -func (store *IAMStoreSys) GetUser(user string) (auth.Credentials, bool) { +func (store *IAMStoreSys) GetUser(user string) (UserIdentity, bool) { cache := store.rlock() defer store.runlock() - c, ok := cache.iamUsersMap[user] - return c, ok + u, ok := cache.iamUsersMap[user] + return u, ok } // GetMappedPolicy - fetches mapped policy from memory. @@ -614,9 +614,9 @@ func (store *IAMStoreSys) PolicyDBGet(name string, isGroup bool, groups ...strin } // AddUsersToGroup - adds users to group, creating the group if needed. -func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, members []string) error { +func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { if group == "" { - return errInvalidArgument + return updatedAt, errInvalidArgument } cache := store.lock() @@ -624,12 +624,13 @@ func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, mem // Validate that all members exist. for _, member := range members { - cr, ok := cache.iamUsersMap[member] + u, ok := cache.iamUsersMap[member] if !ok { - return errNoSuchUser + return updatedAt, errNoSuchUser } + cr := u.Credentials if cr.IsTemp() || cr.IsServiceAccount() { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } } @@ -640,10 +641,11 @@ func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, mem gi = newGroupInfo(members) } else { gi.Members = set.CreateStringSet(append(gi.Members, members...)...).ToSlice() + gi.UpdatedAt = UTCNow() } if err := store.saveGroupInfo(ctx, group, gi); err != nil { - return err + return updatedAt, err } cache.iamGroupsMap[group] = gi @@ -660,16 +662,15 @@ func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, mem } cache.updatedAt = time.Now() - - return nil + return gi.UpdatedAt, nil } // helper function - does not take any locks. Updates only cache if // updateCacheOnly is set. -func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamCache, group string, members []string, updateCacheOnly bool) error { +func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamCache, group string, members []string, updateCacheOnly bool) (updatedAt time.Time, err error) { gi, ok := cache.iamGroupsMap[group] if !ok { - return errNoSuchGroup + return updatedAt, errNoSuchGroup } s := set.CreateStringSet(gi.Members...) @@ -679,9 +680,10 @@ func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamC if !updateCacheOnly { err := store.saveGroupInfo(ctx, group, gi) if err != nil { - return err + return updatedAt, err } } + gi.UpdatedAt = UTCNow() cache.iamGroupsMap[group] = gi // update user-group membership map @@ -695,13 +697,13 @@ func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamC } cache.updatedAt = time.Now() - return nil + return gi.UpdatedAt, nil } // RemoveUsersFromGroup - removes users from group, deleting it if it is empty. -func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) error { +func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { if group == "" { - return errInvalidArgument + return updatedAt, errInvalidArgument } cache := store.lock() @@ -709,23 +711,24 @@ func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string // Validate that all members exist. for _, member := range members { - cr, ok := cache.iamUsersMap[member] + u, ok := cache.iamUsersMap[member] if !ok { - return errNoSuchUser + return updatedAt, errNoSuchUser } + cr := u.Credentials if cr.IsTemp() || cr.IsServiceAccount() { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } } gi, ok := cache.iamGroupsMap[group] if !ok { - return errNoSuchGroup + return updatedAt, errNoSuchGroup } // Check if attempting to delete a non-empty group. if len(members) == 0 && len(gi.Members) != 0 { - return errGroupNotEmpty + return updatedAt, errGroupNotEmpty } if len(members) == 0 { @@ -734,26 +737,26 @@ func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string // Remove the group from storage. First delete the // mapped policy. No-mapped-policy case is ignored. if err := store.deleteMappedPolicy(ctx, group, regUser, true); err != nil && err != errNoSuchPolicy { - return err + return updatedAt, err } if err := store.deleteGroupInfo(ctx, group); err != nil && err != errNoSuchGroup { - return err + return updatedAt, err } // Delete from server memory delete(cache.iamGroupsMap, group) delete(cache.iamGroupPolicyMap, group) cache.updatedAt = time.Now() - return nil + return cache.updatedAt, nil } return removeMembersFromGroup(ctx, store, cache, group, members, false) } // SetGroupStatus - updates group status -func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enabled bool) error { +func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enabled bool) (updatedAt time.Time, err error) { if group == "" { - return errInvalidArgument + return updatedAt, errInvalidArgument } cache := store.lock() @@ -761,7 +764,7 @@ func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enab gi, ok := cache.iamGroupsMap[group] if !ok { - return errNoSuchGroup + return updatedAt, errNoSuchGroup } if enabled { @@ -769,15 +772,15 @@ func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enab } else { gi.Status = statusDisabled } - + gi.UpdatedAt = UTCNow() if err := store.saveGroupInfo(ctx, group, gi); err != nil { - return err + return gi.UpdatedAt, err } cache.iamGroupsMap[group] = gi cache.updatedAt = time.Now() - return nil + return gi.UpdatedAt, nil } // GetGroupDescription - builds up group description @@ -851,9 +854,9 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err // PolicyDBSet - update the policy mapping for the given user or group in // storage and in cache. -func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, userType IAMUserType, isGroup bool) error { +func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, userType IAMUserType, isGroup bool) (updatedAt time.Time, err error) { if name == "" { - return errInvalidArgument + return updatedAt, errInvalidArgument } cache := store.lock() @@ -863,11 +866,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, if store.getUsersSysType() == MinIOUsersSysType { if !isGroup { if _, ok := cache.iamUsersMap[name]; !ok { - return errNoSuchUser + return updatedAt, errNoSuchUser } } else { if _, ok := cache.iamGroupsMap[name]; !ok { - return errNoSuchGroup + return updatedAt, errNoSuchGroup } } } @@ -882,7 +885,7 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, } err := store.deleteMappedPolicy(ctx, name, userType, isGroup) if err != nil && err != errNoSuchPolicy { - return err + return updatedAt, err } if !isGroup { delete(cache.iamUserPolicyMap, name) @@ -890,8 +893,7 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, delete(cache.iamGroupPolicyMap, name) } cache.updatedAt = time.Now() - - return nil + return cache.updatedAt, nil } // Handle policy mapping set/update @@ -899,12 +901,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, for _, p := range mp.toSlice() { if _, found := cache.iamPolicyDocsMap[p]; !found { logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, p)) - return errNoSuchPolicy + return updatedAt, errNoSuchPolicy } } if err := store.saveMappedPolicy(ctx, name, userType, isGroup, mp); err != nil { - return err + return updatedAt, err } if !isGroup { cache.iamUserPolicyMap[name] = mp @@ -912,7 +914,7 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, cache.iamGroupPolicyMap[name] = mp } cache.updatedAt = time.Now() - return nil + return mp.UpdatedAt, nil } // PolicyNotificationHandler - loads given policy from storage. If not present, @@ -1068,9 +1070,9 @@ func (store *IAMStoreSys) GetPolicyDoc(name string) (r PolicyDoc, err error) { } // SetPolicy - creates a policy with name. -func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iampolicy.Policy) error { +func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iampolicy.Policy) (time.Time, error) { if policy.IsEmpty() || name == "" { - return errInvalidArgument + return time.Time{}, errInvalidArgument } cache := store.lock() @@ -1087,13 +1089,13 @@ func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iam } if err := store.savePolicyDoc(ctx, name, d); err != nil { - return err + return d.UpdateDate, err } cache.iamPolicyDocsMap[name] = d cache.updatedAt = time.Now() - return nil + return d.UpdateDate, nil } // ListPolicies - fetches all policies from storage and updates cache as well. @@ -1198,7 +1200,8 @@ func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserI result := map[string]madmin.UserInfo{} for k, v := range cache.iamUsersMap { - if v.IsTemp() || v.IsServiceAccount() { + c := v.Credentials + if c.IsTemp() || c.IsServiceAccount() { continue } var policies []string @@ -1216,7 +1219,7 @@ func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserI result[k] = madmin.UserInfo{ PolicyName: matchedPolicies, Status: func() madmin.AccountStatus { - if v.IsValid() { + if c.IsValid() { return madmin.AccountEnabled } return madmin.AccountDisabled @@ -1235,7 +1238,9 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo { defer store.runlock() result := map[string]madmin.UserInfo{} - for k, v := range cache.iamUsersMap { + for k, u := range cache.iamUsersMap { + v := u.Credentials + if v.IsTemp() || v.IsServiceAccount() { continue } @@ -1281,8 +1286,8 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error // return that info. Otherwise we return error. var groups []string for _, v := range cache.iamUsersMap { - if v.ParentUser == name { - groups = v.Groups + if v.Credentials.ParentUser == name { + groups = v.Credentials.Groups break } } @@ -1297,11 +1302,11 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error }, nil } - cred, found := cache.iamUsersMap[name] + ui, found := cache.iamUsersMap[name] if !found { return u, errNoSuchUser } - + cred := ui.Credentials if cred.IsTemp() || cred.IsServiceAccount() { return u, errIAMActionNotAllowed } @@ -1363,7 +1368,7 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey if store.getUsersSysType() == MinIOUsersSysType { memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() for _, group := range memberOf { - removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, true) + _, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, true) if removeErr == errNoSuchGroup { removeErr = nil } @@ -1376,11 +1381,11 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey // 2. Remove any derived credentials from memory if userType == regUser { for _, u := range cache.iamUsersMap { - if u.IsServiceAccount() && u.ParentUser == accessKey { - delete(cache.iamUsersMap, u.AccessKey) + if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey { + delete(cache.iamUsersMap, u.Credentials.AccessKey) } - if u.IsTemp() && u.ParentUser == accessKey { - delete(cache.iamUsersMap, u.AccessKey) + if u.Credentials.IsTemp() && u.Credentials.ParentUser == accessKey { + delete(cache.iamUsersMap, u.Credentials.AccessKey) } } } @@ -1412,8 +1417,9 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey // This mapping is necessary to ensure that valid credentials // have necessary ParentUser present - this is mainly for only // webIdentity based STS tokens. - cred, ok := cache.iamUsersMap[accessKey] + u, ok := cache.iamUsersMap[accessKey] if ok { + cred := u.Credentials if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey { if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok { cache.iamUserPolicyMap[cred.ParentUser] = cache.iamUserPolicyMap[accessKey] @@ -1439,7 +1445,7 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user if store.getUsersSysType() == MinIOUsersSysType && userType == regUser { memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() for _, group := range memberOf { - removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, false) + _, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, false) if removeErr != nil { return removeErr } @@ -1451,7 +1457,8 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user // Delete any STS and service account derived from this credential // first. if userType == regUser { - for _, u := range cache.iamUsersMap { + for _, ui := range cache.iamUsersMap { + u := ui.Credentials if u.IsServiceAccount() && u.ParentUser == accessKey { _ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser) delete(cache.iamUsersMap, u.AccessKey) @@ -1483,9 +1490,9 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user // SetTempUser - saves temporary (STS) credential to storage and cache. If a // policy name is given, it is associated with the parent user specified in the // credential. -func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) error { +func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) (time.Time, error) { if accessKey == "" || !cred.IsTemp() || cred.IsExpired() || cred.ParentUser == "" { - return errInvalidArgument + return time.Time{}, errInvalidArgument } ttl := int64(cred.Expiration.Sub(UTCNow()).Seconds()) @@ -1498,12 +1505,12 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre _, combinedPolicyStmt := filterPolicies(cache, mp.Policies, "") if combinedPolicyStmt.IsEmpty() { - return fmt.Errorf("specified policy %s, not found %w", policyName, errNoSuchPolicy) + return time.Time{}, fmt.Errorf("specified policy %s, not found %w", policyName, errNoSuchPolicy) } err := store.saveMappedPolicy(ctx, cred.ParentUser, stsUser, false, mp, options{ttl: ttl}) if err != nil { - return err + return time.Time{}, err } cache.iamUserPolicyMap[cred.ParentUser] = mp @@ -1512,14 +1519,14 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre u := newUserIdentity(cred) err := store.saveUserIdentity(ctx, accessKey, stsUser, u, options{ttl: ttl}) if err != nil { - return err + return time.Time{}, err } - cache.iamUsersMap[accessKey] = cred + cache.iamUsersMap[accessKey] = u cache.updatedAt = time.Now() - return nil + return u.UpdatedAt, nil } // DeleteUsers - given a set of users or access keys, deletes them along with @@ -1531,8 +1538,10 @@ func (store *IAMStoreSys) DeleteUsers(ctx context.Context, users []string) error var deleted bool usersToDelete := set.CreateStringSet(users...) - for user, cred := range cache.iamUsersMap { + for user, ui := range cache.iamUsersMap { userType := regUser + cred := ui.Credentials + if cred.IsServiceAccount() { userType = svcUser } else if cred.IsTemp() { @@ -1575,7 +1584,8 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo { defer store.runlock() res := map[string]ParentUserInfo{} - for _, cred := range cache.iamUsersMap { + for _, ui := range cache.iamUsersMap { + cred := ui.Credentials // Only consider service account or STS credentials with // non-empty session tokens. if !(cred.IsServiceAccount() || cred.IsTemp()) || @@ -1633,21 +1643,22 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo { } // SetUserStatus - sets current user status. -func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) error { +func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) { if accessKey != "" && status != madmin.AccountEnabled && status != madmin.AccountDisabled { - return errInvalidArgument + return updatedAt, errInvalidArgument } cache := store.lock() defer store.unlock() - cred, ok := cache.iamUsersMap[accessKey] + ui, ok := cache.iamUsersMap[accessKey] if !ok { - return errNoSuchUser + return updatedAt, errNoSuchUser } + cred := ui.Credentials if cred.IsTemp() || cred.IsServiceAccount() { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } uinfo := newUserIdentity(auth.Credentials{ @@ -1663,17 +1674,17 @@ func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, s }) if err := store.saveUserIdentity(ctx, accessKey, regUser, uinfo); err != nil { - return err + return updatedAt, err } - cache.iamUsersMap[accessKey] = uinfo.Credentials + cache.iamUsersMap[accessKey] = uinfo cache.updatedAt = time.Now() - return nil + return uinfo.UpdatedAt, nil } // AddServiceAccount - add a new service account -func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Credentials) error { +func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Credentials) (updatedAt time.Time, err error) { cache := store.lock() defer store.unlock() @@ -1683,44 +1694,45 @@ func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Crede // Found newly requested service account, to be an existing account - // reject such operation (updates to the service account are handled in // a different API). - if scred, found := cache.iamUsersMap[accessKey]; found { + if su, found := cache.iamUsersMap[accessKey]; found { + scred := su.Credentials if scred.ParentUser != parentUser { - return errIAMServiceAccountUsed + return updatedAt, errIAMServiceAccountUsed } - return errIAMServiceAccount + return updatedAt, errIAMServiceAccount } // Parent user must not be a service account. - if cr, found := cache.iamUsersMap[parentUser]; found && cr.IsServiceAccount() { - return errIAMServiceAccount + if u, found := cache.iamUsersMap[parentUser]; found && u.Credentials.IsServiceAccount() { + return updatedAt, errIAMServiceAccount } u := newUserIdentity(cred) - err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u) + err = store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u) if err != nil { - return err + return updatedAt, err } - cache.iamUsersMap[u.Credentials.AccessKey] = u.Credentials + cache.iamUsersMap[u.Credentials.AccessKey] = u cache.updatedAt = time.Now() - return nil + return u.UpdatedAt, nil } // UpdateServiceAccount - updates a service account on storage. -func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) error { +func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) (updatedAt time.Time, err error) { cache := store.lock() defer store.unlock() - cr, ok := cache.iamUsersMap[accessKey] - if !ok || !cr.IsServiceAccount() { - return errNoSuchServiceAccount + ui, ok := cache.iamUsersMap[accessKey] + if !ok || !ui.Credentials.IsServiceAccount() { + return updatedAt, errNoSuchServiceAccount } - + cr := ui.Credentials currentSecretKey := cr.SecretKey if opts.secretKey != "" { if !auth.IsSecretKeyValid(opts.secretKey) { - return auth.ErrInvalidSecretKeyLength + return updatedAt, auth.ErrInvalidSecretKeyLength } cr.SecretKey = opts.secretKey } @@ -1736,12 +1748,12 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st case auth.AccountOn, auth.AccountOff: cr.Status = opts.status default: - return errors.New("unknown account status value") + return updatedAt, errors.New("unknown account status value") } m, err := getClaimsFromTokenWithSecret(cr.SessionToken, currentSecretKey) if err != nil { - return fmt.Errorf("unable to get svc acc claims: %v", err) + return updatedAt, fmt.Errorf("unable to get svc acc claims: %v", err) } // Extracted session policy name string can be removed as its not useful @@ -1757,16 +1769,16 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st if opts.sessionPolicy != nil { if err := opts.sessionPolicy.Validate(); err != nil { - return err + return updatedAt, err } policyBuf, err := json.Marshal(opts.sessionPolicy) if err != nil { - return err + return updatedAt, err } if len(policyBuf) > 16*humanize.KiByte { - return fmt.Errorf("Session policy should not exceed 16 KiB characters") + return updatedAt, fmt.Errorf("Session policy should not exceed 16 KiB characters") } // Overwrite session policy claims. @@ -1776,41 +1788,41 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st cr.SessionToken, err = auth.JWTSignWithAccessKey(accessKey, m, cr.SecretKey) if err != nil { - return err + return updatedAt, err } u := newUserIdentity(cr) if err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u); err != nil { - return err + return updatedAt, err } - cache.iamUsersMap[u.Credentials.AccessKey] = u.Credentials + cache.iamUsersMap[u.Credentials.AccessKey] = u cache.updatedAt = time.Now() - return nil + return u.UpdatedAt, nil } // ListTempAccounts - lists only temporary accounts from the cache. -func (store *IAMStoreSys) ListTempAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) { +func (store *IAMStoreSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) { cache := store.rlock() defer store.runlock() userExists := false - var tempAccounts []auth.Credentials + var tempAccounts []UserIdentity for _, v := range cache.iamUsersMap { isDerived := false - if v.IsServiceAccount() || v.IsTemp() { + if v.Credentials.IsServiceAccount() || v.Credentials.IsTemp() { isDerived = true } - if !isDerived && v.AccessKey == accessKey { + if !isDerived && v.Credentials.AccessKey == accessKey { userExists = true - } else if isDerived && v.ParentUser == accessKey { + } else if isDerived && v.Credentials.ParentUser == accessKey { userExists = true - if v.IsTemp() { + if v.Credentials.IsTemp() { // Hide secret key & session key here - v.SecretKey = "" - v.SessionToken = "" + v.Credentials.SecretKey = "" + v.Credentials.SessionToken = "" tempAccounts = append(tempAccounts, v) } } @@ -1830,8 +1842,9 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str userExists := false var serviceAccounts []auth.Credentials - for _, v := range cache.iamUsersMap { + for _, u := range cache.iamUsersMap { isDerived := false + v := u.Credentials if v.IsServiceAccount() || v.IsTemp() { isDerived = true } @@ -1857,17 +1870,17 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str } // AddUser - adds/updates long term user account to storage. -func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) error { +func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) { cache := store.lock() defer store.unlock() cache.updatedAt = time.Now() - cr, ok := cache.iamUsersMap[accessKey] + ui, ok := cache.iamUsersMap[accessKey] // It is not possible to update an STS account. - if ok && cr.IsTemp() { - return errIAMActionNotAllowed + if ok && ui.Credentials.IsTemp() { + return updatedAt, errIAMActionNotAllowed } u := newUserIdentity(auth.Credentials{ @@ -1883,12 +1896,12 @@ func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq ma }) if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil { - return err + return updatedAt, err } - cache.iamUsersMap[accessKey] = u.Credentials + cache.iamUsersMap[accessKey] = u - return nil + return u.UpdatedAt, nil } // UpdateUserSecretKey - sets user secret key to storage. @@ -1898,18 +1911,18 @@ func (store *IAMStoreSys) UpdateUserSecretKey(ctx context.Context, accessKey, se cache.updatedAt = time.Now() - cred, ok := cache.iamUsersMap[accessKey] + ui, ok := cache.iamUsersMap[accessKey] if !ok { return errNoSuchUser } - + cred := ui.Credentials cred.SecretKey = secretKey u := newUserIdentity(cred) if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil { return err } - cache.iamUsersMap[accessKey] = cred + cache.iamUsersMap[accessKey] = u return nil } @@ -1919,7 +1932,8 @@ func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials { defer store.runlock() var res []auth.Credentials - for _, cred := range cache.iamUsersMap { + for _, u := range cache.iamUsersMap { + cred := u.Credentials if cred.IsTemp() || cred.IsServiceAccount() { res = append(res, cred) } @@ -1940,13 +1954,13 @@ func (store *IAMStoreSys) UpdateUserIdentity(ctx context.Context, cred auth.Cred } else if cred.IsTemp() { userType = stsUser } - + ui := newUserIdentity(cred) // Overwrite the user identity here. As store should be // atomic, it shouldn't cause any corruption. - if err := store.saveUserIdentity(ctx, cred.AccessKey, userType, newUserIdentity(cred)); err != nil { + if err := store.saveUserIdentity(ctx, cred.AccessKey, userType, ui); err != nil { return err } - cache.iamUsersMap[cred.AccessKey] = cred + cache.iamUsersMap[cred.AccessKey] = ui return nil } @@ -1969,9 +1983,9 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) { if svc, found := cache.iamUsersMap[accessKey]; found { // Load parent user and mapped policies. if store.getUsersSysType() == MinIOUsersSysType { - store.loadUser(ctx, svc.ParentUser, regUser, cache.iamUsersMap) + store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap) } - store.loadMappedPolicy(ctx, svc.ParentUser, regUser, false, cache.iamUserPolicyMap) + store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap) } else { // check for STS account store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap) diff --git a/cmd/iam.go b/cmd/iam.go index 1659bfdfe..2e6f0c043 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -599,14 +599,14 @@ func (sys *IAMSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[s } // SetPolicy - sets a new named policy. -func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p iampolicy.Policy) error { +func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p iampolicy.Policy) (time.Time, error) { if !sys.Initialized() { - return errServerNotInitialized + return time.Time{}, errServerNotInitialized } - err := sys.store.SetPolicy(ctx, policyName, p) + updatedAt, err := sys.store.SetPolicy(ctx, policyName, p) if err != nil { - return err + return updatedAt, err } if !sys.HasWatcher() { @@ -618,7 +618,7 @@ func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p iampolicy } } } - return nil + return updatedAt, nil } // DeleteUser - delete user (only for long-term users not STS users). @@ -707,9 +707,9 @@ func (sys *IAMSys) notifyForUser(ctx context.Context, accessKey string, isTemp b // elsewhere), the AssumeRole case (because the parent user is real and their // policy is associated via policy-set API) and the AssumeRoleWithLDAP case // (because the policy association is made via policy-set API). -func (sys *IAMSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) error { +func (sys *IAMSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) (time.Time, error) { if !sys.Initialized() { - return errServerNotInitialized + return time.Time{}, errServerNotInitialized } if newGlobalAuthZPluginFn() != nil { @@ -717,14 +717,14 @@ func (sys *IAMSys) SetTempUser(ctx context.Context, accessKey string, cred auth. policyName = "" } - err := sys.store.SetTempUser(ctx, accessKey, cred, policyName) + updatedAt, err := sys.store.SetTempUser(ctx, accessKey, cred, policyName) if err != nil { - return err + return time.Time{}, err } sys.notifyForUser(ctx, cred.AccessKey, true) - return nil + return updatedAt, nil } // ListBucketUsers - list all users who can access this 'bucket' @@ -782,11 +782,11 @@ func (sys *IAMSys) IsTempUser(name string) (bool, string, error) { return false, "", errServerNotInitialized } - cred, found := sys.store.GetUser(name) + u, found := sys.store.GetUser(name) if !found { return false, "", errNoSuchUser } - + cred := u.Credentials if cred.IsTemp() { return true, cred.ParentUser, nil } @@ -800,11 +800,11 @@ func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) { return false, "", errServerNotInitialized } - cred, found := sys.store.GetUser(name) + u, found := sys.store.GetUser(name) if !found { return false, "", errNoSuchUser } - + cred := u.Credentials if cred.IsServiceAccount() { return true, cred.ParentUser, nil } @@ -828,22 +828,22 @@ func (sys *IAMSys) GetUserInfo(ctx context.Context, name string) (u madmin.UserI } // SetUserStatus - sets current user status, supports disabled or enabled. -func (sys *IAMSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) error { +func (sys *IAMSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } if sys.usersSysType != MinIOUsersSysType { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } - err := sys.store.SetUserStatus(ctx, accessKey, status) + updatedAt, err = sys.store.SetUserStatus(ctx, accessKey, status) if err != nil { - return err + return } sys.notifyForUser(ctx, accessKey, false) - return nil + return updatedAt, nil } func (sys *IAMSys) notifyForServiceAccount(ctx context.Context, accessKey string) { @@ -867,34 +867,34 @@ type newServiceAccountOpts struct { } // NewServiceAccount - create a new service account -func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, groups []string, opts newServiceAccountOpts) (auth.Credentials, error) { +func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, groups []string, opts newServiceAccountOpts) (auth.Credentials, time.Time, error) { if !sys.Initialized() { - return auth.Credentials{}, errServerNotInitialized + return auth.Credentials{}, time.Time{}, errServerNotInitialized } if parentUser == "" { - return auth.Credentials{}, errInvalidArgument + return auth.Credentials{}, time.Time{}, errInvalidArgument } var policyBuf []byte if opts.sessionPolicy != nil { err := opts.sessionPolicy.Validate() if err != nil { - return auth.Credentials{}, err + return auth.Credentials{}, time.Time{}, err } policyBuf, err = json.Marshal(opts.sessionPolicy) if err != nil { - return auth.Credentials{}, err + return auth.Credentials{}, time.Time{}, err } if len(policyBuf) > 16*humanize.KiByte { - return auth.Credentials{}, fmt.Errorf("Session policy should not exceed 16 KiB characters") + return auth.Credentials{}, time.Time{}, fmt.Errorf("Session policy should not exceed 16 KiB characters") } } // found newly requested service account, to be same as // parentUser, reject such operations. if parentUser == opts.accessKey { - return auth.Credentials{}, errIAMActionNotAllowed + return auth.Credentials{}, time.Time{}, errIAMActionNotAllowed } m := make(map[string]interface{}) @@ -922,24 +922,24 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro } else { accessKey, secretKey, err = auth.GenerateCredentials() if err != nil { - return auth.Credentials{}, err + return auth.Credentials{}, time.Time{}, err } } cred, err := auth.CreateNewCredentialsWithMetadata(accessKey, secretKey, m, secretKey) if err != nil { - return auth.Credentials{}, err + return auth.Credentials{}, time.Time{}, err } cred.ParentUser = parentUser cred.Groups = groups cred.Status = string(auth.AccountOn) - err = sys.store.AddServiceAccount(ctx, cred) + updatedAt, err := sys.store.AddServiceAccount(ctx, cred) if err != nil { - return auth.Credentials{}, err + return auth.Credentials{}, time.Time{}, err } sys.notifyForServiceAccount(ctx, cred.AccessKey) - return cred, nil + return cred, updatedAt, nil } type updateServiceAccountOpts struct { @@ -949,18 +949,18 @@ type updateServiceAccountOpts struct { } // UpdateServiceAccount - edit a service account -func (sys *IAMSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) error { +func (sys *IAMSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } - err := sys.store.UpdateServiceAccount(ctx, accessKey, opts) + updatedAt, err = sys.store.UpdateServiceAccount(ctx, accessKey, opts) if err != nil { - return err + return updatedAt, err } sys.notifyForServiceAccount(ctx, accessKey) - return nil + return updatedAt, nil } // ListServiceAccounts - lists all services accounts associated to a specific user @@ -978,7 +978,7 @@ func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([ } // ListTempAccounts - lists all services accounts associated to a specific user -func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) { +func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) { if !sys.Initialized() { return nil, errServerNotInitialized } @@ -995,32 +995,32 @@ func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]au func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) { sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey) if err != nil { - return sa, embeddedPolicy, err + return auth.Credentials{}, embeddedPolicy, err } // Hide secret & session keys - sa.SecretKey = "" - sa.SessionToken = "" - return sa, embeddedPolicy, nil + sa.Credentials.SecretKey = "" + sa.Credentials.SessionToken = "" + return sa.Credentials, embeddedPolicy, nil } // getServiceAccount - gets information about a service account -func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) { +func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (u UserIdentity, p *iampolicy.Policy, err error) { if !sys.Initialized() { - return auth.Credentials{}, nil, errServerNotInitialized + return u, nil, errServerNotInitialized } sa, ok := sys.store.GetUser(accessKey) - if !ok || !sa.IsServiceAccount() { - return auth.Credentials{}, nil, errNoSuchServiceAccount + if !ok || !sa.Credentials.IsServiceAccount() { + return u, nil, errNoSuchServiceAccount } var embeddedPolicy *iampolicy.Policy - jwtClaims, err := auth.ExtractClaims(sa.SessionToken, sa.SecretKey) + jwtClaims, err := auth.ExtractClaims(sa.Credentials.SessionToken, sa.Credentials.SecretKey) if err != nil { - jwtClaims, err = auth.ExtractClaims(sa.SessionToken, globalActiveCred.SecretKey) + jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey) if err != nil { - return auth.Credentials{}, nil, err + return u, nil, err } } pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA()) @@ -1028,11 +1028,11 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (aut if ptok && spok && pt == embeddedPolicyType { policyBytes, err := base64.StdEncoding.DecodeString(sp) if err != nil { - return auth.Credentials{}, nil, err + return u, nil, err } embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) if err != nil { - return auth.Credentials{}, nil, err + return u, nil, err } } @@ -1050,13 +1050,13 @@ func (sys *IAMSys) GetClaimsForSvcAcc(ctx context.Context, accessKey string) (ma } sa, ok := sys.store.GetUser(accessKey) - if !ok || !sa.IsServiceAccount() { + if !ok || !sa.Credentials.IsServiceAccount() { return nil, errNoSuchServiceAccount } - jwtClaims, err := auth.ExtractClaims(sa.SessionToken, sa.SecretKey) + jwtClaims, err := auth.ExtractClaims(sa.Credentials.SessionToken, sa.Credentials.SecretKey) if err != nil { - jwtClaims, err = auth.ExtractClaims(sa.SessionToken, globalActiveCred.SecretKey) + jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey) if err != nil { return nil, err } @@ -1071,7 +1071,7 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string, n } sa, ok := sys.store.GetUser(accessKey) - if !ok || !sa.IsServiceAccount() { + if !ok || !sa.Credentials.IsServiceAccount() { return nil } @@ -1093,30 +1093,30 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string, n // CreateUser - create new user credentials and policy, if user already exists // they shall be rewritten with new inputs. -func (sys *IAMSys) CreateUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) error { +func (sys *IAMSys) CreateUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } if sys.usersSysType != MinIOUsersSysType { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } if !auth.IsAccessKeyValid(accessKey) { - return auth.ErrInvalidAccessKeyLength + return updatedAt, auth.ErrInvalidAccessKeyLength } if !auth.IsSecretKeyValid(ureq.SecretKey) { - return auth.ErrInvalidSecretKeyLength + return updatedAt, auth.ErrInvalidSecretKeyLength } - err := sys.store.AddUser(ctx, accessKey, ureq) + updatedAt, err = sys.store.AddUser(ctx, accessKey, ureq) if err != nil { - return err + return updatedAt, err } sys.notifyForUser(ctx, accessKey, false) - return nil + return updatedAt, nil } // SetUserSecretKey - sets user secret key @@ -1291,9 +1291,9 @@ func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) { } // GetUser - get user credentials -func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (cred auth.Credentials, ok bool) { +func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (u UserIdentity, ok bool) { if !sys.Initialized() { - return cred, false + return u, false } fallback := false @@ -1304,7 +1304,7 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (cred auth.Cre fallback = true } - cred, ok = sys.store.GetUser(accessKey) + u, ok = sys.store.GetUser(accessKey) if !ok && !fallback { // accessKey not found, also // IAM store is not in fallback mode @@ -1313,10 +1313,10 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (cred auth.Cre // exists now. If it doesn't proceed to // fail. sys.store.LoadUser(ctx, accessKey) - cred, ok = sys.store.GetUser(accessKey) + u, ok = sys.store.GetUser(accessKey) } - return cred, ok && cred.IsValid() + return u, ok && u.Credentials.IsValid() } // Notify all other MinIO peers to load group. @@ -1333,61 +1333,61 @@ func (sys *IAMSys) notifyForGroup(ctx context.Context, group string) { // AddUsersToGroup - adds users to a group, creating the group if // needed. No error if user(s) already are in the group. -func (sys *IAMSys) AddUsersToGroup(ctx context.Context, group string, members []string) error { +func (sys *IAMSys) AddUsersToGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } if sys.usersSysType != MinIOUsersSysType { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } - err := sys.store.AddUsersToGroup(ctx, group, members) + updatedAt, err = sys.store.AddUsersToGroup(ctx, group, members) if err != nil { - return err + return updatedAt, err } sys.notifyForGroup(ctx, group) - return nil + return updatedAt, nil } // RemoveUsersFromGroup - remove users from group. If no users are // given, and the group is empty, deletes the group as well. -func (sys *IAMSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) error { +func (sys *IAMSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } if sys.usersSysType != MinIOUsersSysType { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } - err := sys.store.RemoveUsersFromGroup(ctx, group, members) + updatedAt, err = sys.store.RemoveUsersFromGroup(ctx, group, members) if err != nil { - return err + return updatedAt, err } sys.notifyForGroup(ctx, group) - return nil + return updatedAt, nil } // SetGroupStatus - enable/disabled a group -func (sys *IAMSys) SetGroupStatus(ctx context.Context, group string, enabled bool) error { +func (sys *IAMSys) SetGroupStatus(ctx context.Context, group string, enabled bool) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } if sys.usersSysType != MinIOUsersSysType { - return errIAMActionNotAllowed + return updatedAt, errIAMActionNotAllowed } - err := sys.store.SetGroupStatus(ctx, group, enabled) + updatedAt, err = sys.store.SetGroupStatus(ctx, group, enabled) if err != nil { - return err + return updatedAt, err } sys.notifyForGroup(ctx, group) - return nil + return updatedAt, nil } // GetGroupDescription - builds up group description @@ -1414,9 +1414,9 @@ func (sys *IAMSys) ListGroups(ctx context.Context) (r []string, err error) { } // PolicyDBSet - sets a policy for a user or group in the PolicyDB. -func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, isGroup bool) error { +func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, isGroup bool) (updatedAt time.Time, err error) { if !sys.Initialized() { - return errServerNotInitialized + return updatedAt, errServerNotInitialized } // Determine user-type based on IDP mode. @@ -1425,9 +1425,9 @@ func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, isGroup userType = stsUser } - err := sys.store.PolicyDBSet(ctx, name, policy, userType, isGroup) + updatedAt, err = sys.store.PolicyDBSet(ctx, name, policy, userType, isGroup) if err != nil { - return err + return } // Notify all other MinIO peers to reload policy @@ -1440,7 +1440,7 @@ func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, isGroup } } - return nil + return updatedAt, nil } // PolicyDBGet - gets policy set on a user or group. If a list of groups is diff --git a/cmd/jwt.go b/cmd/jwt.go index 1df051733..cbbd4e988 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -63,11 +63,11 @@ func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (st func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt time.Time) (string, error) { serverCred := globalActiveCred if serverCred.AccessKey != credentials.AccessKey { - var ok bool - serverCred, ok = globalIAMSys.GetUser(context.TODO(), credentials.AccessKey) + u, ok := globalIAMSys.GetUser(context.TODO(), credentials.AccessKey) if !ok { return "", errInvalidAccessKeyID } + serverCred = u.Credentials } if !serverCred.Equal(credentials) { @@ -145,10 +145,11 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b if claims.AccessKey == globalActiveCred.AccessKey { return []byte(globalActiveCred.SecretKey), nil } - cred, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) + u, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) if !ok { return nil, errInvalidAccessKeyID } + cred := u.Credentials return []byte(cred.SecretKey), nil }); err != nil { return claims, nil, false, errAuthentication @@ -157,11 +158,11 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b var groups []string if globalActiveCred.AccessKey != claims.AccessKey { // Check if the access key is part of users credentials. - ucred, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) + u, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) if !ok { return nil, nil, false, errInvalidAccessKeyID } - + ucred := u.Credentials // get embedded claims eclaims, s3Err := checkClaimsFromToken(req, ucred) if s3Err != ErrNone { diff --git a/cmd/signature-v4-utils.go b/cmd/signature-v4-utils.go index 4698413de..17999ab27 100644 --- a/cmd/signature-v4-utils.go +++ b/cmd/signature-v4-utils.go @@ -152,16 +152,16 @@ func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, A cred := globalActiveCred if cred.AccessKey != accessKey { // Check if the access key is part of users credentials. - ucred, ok := globalIAMSys.GetUser(r.Context(), accessKey) + u, ok := globalIAMSys.GetUser(r.Context(), accessKey) if !ok { // Credentials will be invalid but and disabled // return a different error in such a scenario. - if ucred.Status == auth.AccountOff { + if u.Credentials.Status == auth.AccountOff { return cred, false, ErrAccessKeyDisabled } return cred, false, ErrInvalidAccessKeyID } - cred = ucred + cred = u.Credentials } claims, s3Err := checkClaimsFromToken(r, cred) diff --git a/cmd/site-replication.go b/cmd/site-replication.go index a37e1707f..d1637a3d2 100644 --- a/cmd/site-replication.go +++ b/cmd/site-replication.go @@ -403,14 +403,15 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi // Generate a secret key for the service account if not created already. var secretKey string - svcCred, _, err := globalIAMSys.getServiceAccount(ctx, siteReplicatorSvcAcc) + var svcCred auth.Credentials + sa, _, err := globalIAMSys.getServiceAccount(ctx, siteReplicatorSvcAcc) switch { case err == errNoSuchServiceAccount: _, secretKey, err = auth.GenerateCredentials() if err != nil { return madmin.ReplicateAddStatus{}, errSRServiceAccount(fmt.Errorf("unable to create local service account: %w", err)) } - svcCred, err = globalIAMSys.NewServiceAccount(ctx, sites[selfIdx].AccessKey, nil, newServiceAccountOpts{ + svcCred, _, err = globalIAMSys.NewServiceAccount(ctx, sites[selfIdx].AccessKey, nil, newServiceAccountOpts{ accessKey: siteReplicatorSvcAcc, secretKey: secretKey, }) @@ -418,6 +419,7 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi return madmin.ReplicateAddStatus{}, errSRServiceAccount(fmt.Errorf("unable to create local service account: %w", err)) } case err == nil: + svcCred = sa.Credentials secretKey = svcCred.SecretKey default: return madmin.ReplicateAddStatus{}, errSRBackendIssue(err) @@ -525,7 +527,7 @@ func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJ _, _, err := globalIAMSys.GetServiceAccount(ctx, arg.SvcAcctAccessKey) if err == errNoSuchServiceAccount { - _, err = globalIAMSys.NewServiceAccount(ctx, arg.SvcAcctParent, nil, newServiceAccountOpts{ + _, _, err = globalIAMSys.NewServiceAccount(ctx, arg.SvcAcctParent, nil, newServiceAccountOpts{ accessKey: arg.SvcAcctAccessKey, secretKey: arg.SvcAcctSecretKey, }) @@ -1022,12 +1024,18 @@ func (c *SiteReplicationSys) IAMChangeHook(ctx context.Context, item madmin.SRIA // PeerAddPolicyHandler - copies IAM policy to local. A nil policy argument, // causes the named policy to be deleted. -func (c *SiteReplicationSys) PeerAddPolicyHandler(ctx context.Context, policyName string, p *iampolicy.Policy) error { +func (c *SiteReplicationSys) PeerAddPolicyHandler(ctx context.Context, policyName string, p *iampolicy.Policy, updatedAt time.Time) error { var err error + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if p, err := globalIAMSys.store.GetPolicyDoc(policyName); err == nil && p.UpdateDate.After(updatedAt) { + return nil + } + } if p == nil { err = globalIAMSys.DeletePolicy(ctx, policyName, true) } else { - err = globalIAMSys.SetPolicy(ctx, policyName, *p) + _, err = globalIAMSys.SetPolicy(ctx, policyName, *p) } if err != nil { return wrapSRErr(err) @@ -1036,10 +1044,17 @@ func (c *SiteReplicationSys) PeerAddPolicyHandler(ctx context.Context, policyNam } // PeerIAMUserChangeHandler - copies IAM user to local. -func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, change *madmin.SRIAMUser) error { +func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, change *madmin.SRIAMUser, updatedAt time.Time) error { if change == nil { return errSRInvalidRequest(errInvalidArgument) } + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if ui, err := globalIAMSys.GetUserInfo(ctx, change.AccessKey); err == nil && ui.UpdatedAt.After(updatedAt) { + return nil + } + } + var err error if change.IsDeleteReq { err = globalIAMSys.DeleteUser(ctx, change.AccessKey, true) @@ -1051,9 +1066,9 @@ func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, chang if userReq.Status != "" && userReq.SecretKey == "" { // Status is set without secretKey updates means we are // only changing the account status. - err = globalIAMSys.SetUserStatus(ctx, change.AccessKey, userReq.Status) + _, err = globalIAMSys.SetUserStatus(ctx, change.AccessKey, userReq.Status) } else { - err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq) + _, err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq) } } if err != nil { @@ -1063,19 +1078,27 @@ func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, chang } // PeerGroupInfoChangeHandler - copies group changes to local. -func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, change *madmin.SRGroupInfo) error { +func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, change *madmin.SRGroupInfo, updatedAt time.Time) error { if change == nil { return errSRInvalidRequest(errInvalidArgument) } updReq := change.UpdateReq var err error + + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if gd, err := globalIAMSys.GetGroupDescription(updReq.Group); err == nil && gd.UpdatedAt.After(updatedAt) { + return nil + } + } + if updReq.IsRemove { - err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) + _, err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) } else { if updReq.Status != "" && len(updReq.Members) == 0 { - err = globalIAMSys.SetGroupStatus(ctx, updReq.Group, updReq.Status == madmin.GroupEnabled) + _, err = globalIAMSys.SetGroupStatus(ctx, updReq.Group, updReq.Status == madmin.GroupEnabled) } else { - err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + _, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) } } if err != nil { @@ -1085,7 +1108,7 @@ func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, cha } // PeerSvcAccChangeHandler - copies service-account change to local. -func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change *madmin.SRSvcAccChange) error { +func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change *madmin.SRSvcAccChange, updatedAt time.Time) error { if change == nil { return errSRInvalidRequest(errInvalidArgument) } @@ -1099,14 +1122,19 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change return wrapSRErr(err) } } - + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() && change.Create.AccessKey != "" { + if sa, _, err := globalIAMSys.getServiceAccount(ctx, change.Create.AccessKey); err == nil && sa.UpdatedAt.After(updatedAt) { + return nil + } + } opts := newServiceAccountOpts{ accessKey: change.Create.AccessKey, secretKey: change.Create.SecretKey, sessionPolicy: sp, claims: change.Create.Claims, } - _, err = globalIAMSys.NewServiceAccount(ctx, change.Create.Parent, change.Create.Groups, opts) + _, _, err = globalIAMSys.NewServiceAccount(ctx, change.Create.Parent, change.Create.Groups, opts) if err != nil { return wrapSRErr(err) } @@ -1120,20 +1148,31 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change return wrapSRErr(err) } } + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if sa, _, err := globalIAMSys.getServiceAccount(ctx, change.Update.AccessKey); err == nil && sa.UpdatedAt.After(updatedAt) { + return nil + } + } opts := updateServiceAccountOpts{ secretKey: change.Update.SecretKey, status: change.Update.Status, sessionPolicy: sp, } - err = globalIAMSys.UpdateServiceAccount(ctx, change.Update.AccessKey, opts) + _, err = globalIAMSys.UpdateServiceAccount(ctx, change.Update.AccessKey, opts) if err != nil { return wrapSRErr(err) } case change.Delete != nil: - err := globalIAMSys.DeleteServiceAccount(ctx, change.Delete.AccessKey, true) - if err != nil { + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if sa, _, err := globalIAMSys.getServiceAccount(ctx, change.Delete.AccessKey); err == nil && sa.UpdatedAt.After(updatedAt) { + return nil + } + } + if err := globalIAMSys.DeleteServiceAccount(ctx, change.Delete.AccessKey, true); err != nil { return wrapSRErr(err) } @@ -1143,11 +1182,19 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change } // PeerPolicyMappingHandler - copies policy mapping to local. -func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mapping *madmin.SRPolicyMapping) error { +func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mapping *madmin.SRPolicyMapping, updatedAt time.Time) error { if mapping == nil { return errSRInvalidRequest(errInvalidArgument) } - err := globalIAMSys.PolicyDBSet(ctx, mapping.UserOrGroup, mapping.Policy, mapping.IsGroup) + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + mp, ok := globalIAMSys.store.GetMappedPolicy(mapping.Policy, mapping.IsGroup) + if ok && mp.UpdatedAt.After(updatedAt) { + return nil + } + } + + _, err := globalIAMSys.PolicyDBSet(ctx, mapping.UserOrGroup, mapping.Policy, mapping.IsGroup) if err != nil { return wrapSRErr(err) } @@ -1155,10 +1202,19 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi } // PeerSTSAccHandler - replicates STS credential locally. -func (c *SiteReplicationSys) PeerSTSAccHandler(ctx context.Context, stsCred *madmin.SRSTSCredential) error { +func (c *SiteReplicationSys) PeerSTSAccHandler(ctx context.Context, stsCred *madmin.SRSTSCredential, updatedAt time.Time) error { if stsCred == nil { return errSRInvalidRequest(errInvalidArgument) } + // skip overwrite of local update if peer sent stale info + if !updatedAt.IsZero() { + if u, err := globalIAMSys.GetUserInfo(ctx, stsCred.AccessKey); err == nil { + ok, _, _ := globalIAMSys.IsTempUser(stsCred.AccessKey) + if ok && u.UpdatedAt.After(updatedAt) { + return nil + } + } + } // Verify the session token of the stsCred claims, err := auth.ExtractClaims(stsCred.SessionToken, globalActiveCred.SecretKey) @@ -1195,7 +1251,7 @@ func (c *SiteReplicationSys) PeerSTSAccHandler(ctx context.Context, stsCred *mad } // Set these credentials to IAM. - if err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, stsCred.ParentPolicyMapping); err != nil { + if _, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, stsCred.ParentPolicyMapping); err != nil { return fmt.Errorf("unable to save STS credential and/or parent policy mapping: %w", err) } @@ -1426,11 +1482,11 @@ func (c *SiteReplicationSys) getAdminClientWithEndpoint(ctx context.Context, dep } func (c *SiteReplicationSys) getPeerCreds() (*auth.Credentials, error) { - creds, ok := globalIAMSys.store.GetUser(c.state.ServiceAccountAccessKey) + u, ok := globalIAMSys.store.GetUser(c.state.ServiceAccountAccessKey) if !ok { return nil, errors.New("site replication service account not found") } - return &creds, nil + return &u.Credentials, nil } // listBuckets returns a consistent common view of latest unique buckets across @@ -1606,20 +1662,21 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { // Policies should be synced first. { // Replicate IAM policies on local to all peers. - allPolicies, err := globalIAMSys.ListPolicies(ctx, "") + allPolicyDocs, err := globalIAMSys.ListPolicyDocs(ctx, "") if err != nil { return errSRBackendIssue(err) } - for pname, policy := range allPolicies { - policyJSON, err := json.Marshal(policy) + for pname, pdoc := range allPolicyDocs { + policyJSON, err := json.Marshal(pdoc.Policy) if err != nil { return wrapSRErr(err) } err = c.IAMChangeHook(ctx, madmin.SRIAMItem{ - Type: madmin.SRIAMItemPolicy, - Name: pname, - Policy: policyJSON, + Type: madmin.SRIAMItemPolicy, + Name: pname, + Policy: policyJSON, + UpdatedAt: pdoc.UpdateDate, }) if err != nil { return errSRIAMError(err) @@ -1630,7 +1687,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { // Next should be userAccounts those are local users, OIDC and LDAP will not // may not have any local users. { - userAccounts := make(map[string]auth.Credentials) + userAccounts := make(map[string]UserIdentity) globalIAMSys.store.rlock() err := globalIAMSys.store.loadUsers(ctx, regUser, userAccounts) globalIAMSys.store.runlock() @@ -1642,13 +1699,14 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { if err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ Type: madmin.SRIAMItemIAMUser, IAMUser: &madmin.SRIAMUser{ - AccessKey: acc.AccessKey, + AccessKey: acc.Credentials.AccessKey, IsDeleteReq: false, UserReq: &madmin.AddOrUpdateUserReq{ - SecretKey: acc.SecretKey, - Status: madmin.AccountStatus(acc.Status), + SecretKey: acc.Credentials.SecretKey, + Status: madmin.AccountStatus(acc.Credentials.Status), }, }, + UpdatedAt: acc.UpdatedAt, }); err != nil { return errSRIAMError(err) } @@ -1678,6 +1736,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { IsRemove: false, }, }, + UpdatedAt: group.UpdatedAt, }); err != nil { return errSRIAMError(err) } @@ -1687,7 +1746,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { // Service accounts are the static accounts that should be synced with // valid claims. { - serviceAccounts := make(map[string]auth.Credentials) + serviceAccounts := make(map[string]UserIdentity) globalIAMSys.store.rlock() err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts) globalIAMSys.store.runlock() @@ -1702,12 +1761,12 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { continue } - claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey) + claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey) if err != nil { return errSRBackendIssue(err) } - _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.AccessKey) + _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey) if err != nil { return errSRBackendIssue(err) } @@ -1724,15 +1783,16 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { Type: madmin.SRIAMItemSvcAcc, SvcAccChange: &madmin.SRSvcAccChange{ Create: &madmin.SRSvcAccCreate{ - Parent: acc.ParentUser, + Parent: acc.Credentials.ParentUser, AccessKey: user, - SecretKey: acc.SecretKey, - Groups: acc.Groups, + SecretKey: acc.Credentials.SecretKey, + Groups: acc.Credentials.Groups, Claims: claims, SessionPolicy: json.RawMessage(policyJSON), - Status: acc.Status, + Status: acc.Credentials.Status, }, }, + UpdatedAt: acc.UpdatedAt, }) if err != nil { return errSRIAMError(err) @@ -1761,6 +1821,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { IsGroup: false, Policy: mp.Policies, }, + UpdatedAt: mp.UpdatedAt, }) if err != nil { return errSRIAMError(err) @@ -1779,6 +1840,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { IsGroup: true, Policy: mp.Policies, }, + UpdatedAt: mp.UpdatedAt, }) if err != nil { return errSRIAMError(err) @@ -1807,6 +1869,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { IsGroup: false, Policy: mp.Policies, }, + UpdatedAt: mp.UpdatedAt, }) if err != nil { return errSRIAMError(err) @@ -1825,6 +1888,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error { IsGroup: true, Policy: mp.Policies, }, + UpdatedAt: mp.UpdatedAt, }) if err != nil { return errSRIAMError(err) @@ -3244,8 +3308,8 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI return info, errSRBackendIssue(err) } for _, tempAcct := range tempAccts { - info.UserInfoMap[tempAcct.AccessKey] = madmin.UserInfo{ - Status: madmin.AccountStatus(tempAcct.Status), + info.UserInfoMap[tempAcct.Credentials.AccessKey] = madmin.UserInfo{ + Status: madmin.AccountStatus(tempAcct.Credentials.Status), } } } @@ -4163,9 +4227,10 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye } peerName := info.Sites[dID].Name err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ - Type: madmin.SRIAMItemPolicy, - Name: policy, - Policy: latestPolicyStat.policy.Policy, + Type: madmin.SRIAMItemPolicy, + Name: policy, + Policy: latestPolicyStat.policy.Policy, + UpdatedAt: lastUpdate, }) if err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err)) @@ -4225,6 +4290,7 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object IsGroup: false, Policy: latestUserStat.userPolicy.Policy, }, + UpdatedAt: lastUpdate, }) if err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err)) @@ -4286,6 +4352,7 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec IsGroup: true, Policy: latestGroupStat.groupPolicy.Policy, }, + UpdatedAt: lastUpdate, }) if err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err)) @@ -4340,11 +4407,11 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, peerName := info.Sites[dID].Name - creds, ok := globalIAMSys.GetUser(ctx, user) + u, ok := globalIAMSys.GetUser(ctx, user) if !ok { continue } - + creds := u.Credentials // heal only the user accounts that are local users if creds.IsServiceAccount() { claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey) @@ -4381,6 +4448,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, Status: creds.Status, }, }, + UpdatedAt: lastUpdate, }); err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) } @@ -4402,6 +4470,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, ParentUser: creds.ParentUser, ParentPolicyMapping: u.PolicyName, }, + UpdatedAt: lastUpdate, }); err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) } @@ -4417,6 +4486,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, Status: latestUserStat.userInfo.Status, }, }, + UpdatedAt: lastUpdate, }); err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) } @@ -4481,6 +4551,7 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer, IsRemove: false, }, }, + UpdatedAt: lastUpdate, }); err != nil { logger.LogIf(ctx, fmt.Errorf("Error healing group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err)) } diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index 7daeb17c0..a33529825 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -275,7 +275,8 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { cred.ParentUser = user.AccessKey // Set the newly generated credentials. - if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, ""); err != nil { + updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, "") + if err != nil { writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) return } @@ -290,6 +291,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { SessionToken: cred.SessionToken, ParentUser: cred.ParentUser, }, + UpdatedAt: updatedAt, }); err != nil { logger.LogIf(ctx, err) } @@ -469,7 +471,8 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ } // Set the newly generated credentials. - if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, policyName); err != nil { + updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, policyName) + if err != nil { writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) return } @@ -484,6 +487,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ ParentUser: cred.ParentUser, ParentPolicyMapping: policyName, }, + UpdatedAt: updatedAt, }); err != nil { logger.LogIf(ctx, err) } @@ -639,7 +643,8 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * // Set the newly generated credentials, policyName is empty on purpose // LDAP policies are applied automatically using their ldapUser, ldapGroups // mapping. - if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, ""); err != nil { + updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, "") + if err != nil { writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) return } @@ -653,6 +658,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * SessionToken: cred.SessionToken, ParentUser: cred.ParentUser, }, + UpdatedAt: updatedAt, }); err != nil { logger.LogIf(ctx, err) } @@ -797,7 +803,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h tmpCredentials.ParentUser = parentUser policyName := certificate.Subject.CommonName - err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName) + updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName) if err != nil { writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) return @@ -813,6 +819,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h ParentUser: tmpCredentials.ParentUser, ParentPolicyMapping: policyName, }, + UpdatedAt: updatedAt, }); err != nil { logger.LogIf(ctx, err) } @@ -918,7 +925,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h } tmpCredentials.ParentUser = parentUser - err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "") + updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "") if err != nil { writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) return @@ -933,6 +940,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h SessionToken: tmpCredentials.SessionToken, ParentUser: tmpCredentials.ParentUser, }, + UpdatedAt: updatedAt, }); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return diff --git a/docs/site-replication/run-multi-site-minio-idp.sh b/docs/site-replication/run-multi-site-minio-idp.sh index 8bd1f7093..bd4e42466 100755 --- a/docs/site-replication/run-multi-site-minio-idp.sh +++ b/docs/site-replication/run-multi-site-minio-idp.sh @@ -285,13 +285,43 @@ if [ "${val}" != "val1" ]; then echo "expected bucket tag to have replicated, exiting..." exit_1; fi +# Create user with policy consoleAdmin on minio1 +./mc admin user add minio1 foobarx foobar123 +if [ $? -ne 0 ]; then + echo "adding user failed, exiting.." + exit_1; +fi +./mc admin policy set minio1 consoleAdmin user=foobarx +if [ $? -ne 0 ]; then + echo "adding policy mapping failed, exiting.." + exit_1; +fi +sleep 10 + +# unset policy for foobarx in minio2 +./mc admin policy unset minio2 consoleAdmin user=foobarx +if [ $? -ne 0 ]; then + echo "unset policy mapping failed, exiting.." + exit_1; +fi + +sleep 10 + +# Test whether policy unset replicated to minio1 +policy=$(./mc admin user info minio1 foobarx --json | jq -r .policyName) +if [ "${policy}" != "null" ]; then + echo "expected policy unset to have replicated, exiting..." + exit_1; +fi kill -9 ${site1_pid} # Update tag on minio2/newbucket when minio1 is down ./mc tag set minio2/newbucket "key=val2" + # Restart minio1 instance minio server --config-dir /tmp/minio-internal --address ":9001" /tmp/minio-internal-idp1/{1...4} >/tmp/minio1_1.log 2>&1 & -sleep 10 +sleep 15 + # Test whether most recent tag update on minio2 is replicated to minio1 val=$(./mc tag list minio1/newbucket --json | jq -r .tagset | jq -r .key ) if [ "${val}" != "val2" ]; then