site healing: Skip stale iam asset updates from peer. (#15203)

Allow healing to apply IAM change only when peer
gave the most recent update.
This commit is contained in:
Poorna 2022-07-01 13:19:13 -07:00 committed by GitHub
parent 63ac260bd5
commit 0ea5c9d8e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 489 additions and 349 deletions

View File

@ -160,7 +160,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
err = errSRInvalidRequest(errInvalidArgument) err = errSRInvalidRequest(errInvalidArgument)
case madmin.SRIAMItemPolicy: case madmin.SRIAMItemPolicy:
if item.Policy == nil { if item.Policy == nil {
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil) err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
} else { } else {
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy)) policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
if perr != nil { if perr != nil {
@ -168,21 +168,21 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
return return
} }
if policy.IsEmpty() { if policy.IsEmpty() {
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil) err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
} else { } else {
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy) err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy, item.UpdatedAt)
} }
} }
case madmin.SRIAMItemSvcAcc: case madmin.SRIAMItemSvcAcc:
err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange) err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange, item.UpdatedAt)
case madmin.SRIAMItemPolicyMapping: case madmin.SRIAMItemPolicyMapping:
err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping) err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping, item.UpdatedAt)
case madmin.SRIAMItemSTSAcc: case madmin.SRIAMItemSTSAcc:
err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential) err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential, item.UpdatedAt)
case madmin.SRIAMItemIAMUser: case madmin.SRIAMItemIAMUser:
err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser) err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser, item.UpdatedAt)
case madmin.SRIAMItemGroupInfo: case madmin.SRIAMItemGroupInfo:
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo) err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo, item.UpdatedAt)
} }
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)

View File

@ -72,6 +72,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
AccessKey: accessKey, AccessKey: accessKey,
IsDeleteReq: true, IsDeleteReq: true,
}, },
UpdatedAt: UTCNow(),
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -240,9 +241,9 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return return
} }
var updatedAt time.Time
if updReq.IsRemove { if updReq.IsRemove {
err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) updatedAt, err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members)
} else { } else {
// Check if group already exists // Check if group already exists
if _, gerr := globalIAMSys.GetGroupDescription(updReq.Group); gerr != nil { if _, gerr := globalIAMSys.GetGroupDescription(updReq.Group); gerr != nil {
@ -253,7 +254,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
return return
} }
} }
err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
} }
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 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{ GroupInfo: &madmin.SRGroupInfo{
UpdateReq: updReq, UpdateReq: updReq,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -341,12 +343,15 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
group := vars["group"] group := vars["group"]
status := vars["status"] status := vars["status"]
var err error var (
err error
updatedAt time.Time
)
switch status { switch status {
case statusEnabled: case statusEnabled:
err = globalIAMSys.SetGroupStatus(ctx, group, true) updatedAt, err = globalIAMSys.SetGroupStatus(ctx, group, true)
case statusDisabled: case statusDisabled:
err = globalIAMSys.SetGroupStatus(ctx, group, false) updatedAt, err = globalIAMSys.SetGroupStatus(ctx, group, false)
default: default:
err = errInvalidArgument err = errInvalidArgument
} }
@ -364,6 +369,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
IsRemove: false, IsRemove: false,
}, },
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -391,7 +397,8 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
return 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) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@ -405,6 +412,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
Status: madmin.AccountStatus(status), Status: madmin.AccountStatus(status),
}, },
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -439,8 +447,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return return
} }
userCred, exists := globalIAMSys.GetUser(ctx, accessKey) user, exists := globalIAMSys.GetUser(ctx, accessKey)
if exists && (userCred.IsTemp() || userCred.IsServiceAccount()) { if exists && (user.Credentials.IsTemp() || user.Credentials.IsServiceAccount()) {
// Updating STS credential is not allowed, and this API does not // Updating STS credential is not allowed, and this API does not
// support updating service accounts. // support updating service accounts.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
@ -501,7 +509,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return 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) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@ -513,6 +522,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
IsDeleteReq: false, IsDeleteReq: false,
UserReq: &ureq, UserReq: &ureq,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -674,7 +684,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
} }
opts.sessionPolicy = sp opts.sessionPolicy = sp
newCred, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts) newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -717,6 +727,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
Status: auth.AccountOn, Status: auth.AccountOn,
}, },
}, },
UpdatedAt: updatedAt,
}) })
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
@ -800,7 +811,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
status: updateReq.NewStatus, status: updateReq.NewStatus,
sessionPolicy: sp, sessionPolicy: sp,
} }
err = globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts) updatedAt, err := globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -818,6 +829,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
SessionPolicy: updateReq.NewPolicy, SessionPolicy: updateReq.NewPolicy,
}, },
}, },
UpdatedAt: updatedAt,
}) })
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@ -1057,6 +1069,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
AccessKey: serviceAccount, AccessKey: serviceAccount,
}, },
}, },
UpdatedAt: UTCNow(),
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -1397,6 +1410,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{ if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicy, Type: madmin.SRIAMItemPolicy,
Name: policyName, Name: policyName,
UpdatedAt: UTCNow(),
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -1453,7 +1467,8 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
return 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) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@ -1464,6 +1479,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
Type: madmin.SRIAMItemPolicy, Type: madmin.SRIAMItemPolicy,
Name: policyName, Name: policyName,
Policy: iamPolicyBytes, Policy: iamPolicyBytes,
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return 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) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@ -1510,6 +1527,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
IsGroup: isGroup, IsGroup: isGroup,
Policy: policyName, Policy: policyName,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -1597,22 +1615,22 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case allUsersFile: case allUsersFile:
userCreds := make(map[string]auth.Credentials) userIdentities := make(map[string]UserIdentity)
globalIAMSys.store.rlock() globalIAMSys.store.rlock()
err := globalIAMSys.store.loadUsers(ctx, regUser, userCreds) err := globalIAMSys.store.loadUsers(ctx, regUser, userIdentities)
globalIAMSys.store.runlock() globalIAMSys.store.runlock()
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
userAccounts := make(map[string]madmin.AddOrUpdateUserReq) userAccounts := make(map[string]madmin.AddOrUpdateUserReq)
for u, cred := range userCreds { for u, uid := range userIdentities {
status := madmin.AccountDisabled status := madmin.AccountDisabled
if cred.IsValid() { if uid.Credentials.IsValid() {
status = madmin.AccountEnabled status = madmin.AccountEnabled
} }
userAccounts[u] = madmin.AddOrUpdateUserReq{ userAccounts[u] = madmin.AddOrUpdateUserReq{
SecretKey: cred.SecretKey, SecretKey: uid.Credentials.SecretKey,
Status: status, Status: status,
} }
} }
@ -1646,7 +1664,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case allSvcAcctsFile: case allSvcAcctsFile:
serviceAccounts := make(map[string]auth.Credentials) serviceAccounts := make(map[string]UserIdentity)
globalIAMSys.store.rlock() globalIAMSys.store.rlock()
err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts) err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts)
globalIAMSys.store.runlock() globalIAMSys.store.runlock()
@ -1661,12 +1679,12 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
// site replication is enabled. // site replication is enabled.
continue continue
} }
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey) claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
_, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.AccessKey) _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
@ -1681,13 +1699,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
} }
} }
svcAccts[user] = madmin.SRSvcAccCreate{ svcAccts[user] = madmin.SRSvcAccCreate{
Parent: acc.ParentUser, Parent: acc.Credentials.ParentUser,
AccessKey: user, AccessKey: user,
SecretKey: acc.SecretKey, SecretKey: acc.Credentials.SecretKey,
Groups: acc.Groups, Groups: acc.Credentials.Groups,
Claims: claims, Claims: claims,
SessionPolicy: json.RawMessage(policyJSON), 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() { if policy.IsEmpty() {
err = globalIAMSys.DeletePolicy(ctx, policyName, true) err = globalIAMSys.DeletePolicy(ctx, policyName, true)
} else { } else {
err = globalIAMSys.SetPolicy(ctx, policyName, policy) _, err = globalIAMSys.SetPolicy(ctx, policyName, policy)
} }
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, importError(ctx, err, allPoliciesFile, policyName), r.URL) 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 return
} }
userCred, exists := globalIAMSys.GetUser(ctx, accessKey) user, exists := globalIAMSys.GetUser(ctx, accessKey)
if exists && (userCred.IsTemp() || userCred.IsServiceAccount()) { if exists && (user.Credentials.IsTemp() || user.Credentials.IsServiceAccount()) {
// Updating STS credential is not allowed, and this API does not // Updating STS credential is not allowed, and this API does not
// support updating service accounts. // support updating service accounts.
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL) 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) writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
return 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) writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, toAdminAPIErrCode(ctx, err), err, allUsersFile, accessKey), r.URL)
return return
} }
@ -1949,7 +1967,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
return 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, allGroupsFile, group), r.URL)
return return
} }
@ -2018,7 +2036,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
status: svcAcctReq.Status, status: svcAcctReq.Status,
sessionPolicy: sp, sessionPolicy: sp,
} }
err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts) _, err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL) writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
return return
@ -2044,7 +2062,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
opts.claims[ldapUser] = targetUser // username DN 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
return 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) writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, userPolicyMappingsFile, u), r.URL)
return 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, userPolicyMappingsFile, u), r.URL)
return return
} }
@ -2113,7 +2131,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
for g, pm := range grpPolicyMap { 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, g), r.URL)
return 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) writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL)
return 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL)
return return
} }
@ -2181,7 +2199,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
for g, pm := range grpPolicyMap { 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) writeErrorResponseJSON(ctx, w, importError(ctx, err, stsGroupPolicyMappingsFile, g), r.URL)
return return
} }

View File

@ -20,8 +20,6 @@ package cmd
import ( import (
"context" "context"
"sync" "sync"
"github.com/minio/minio/internal/auth"
) )
type iamDummyStore struct { type iamDummyStore struct {
@ -79,7 +77,7 @@ func (ids *iamDummyStore) loadPolicyDocs(ctx context.Context, m map[string]Polic
return nil 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] u, ok := ids.iamUsersMap[user]
if !ok { if !ok {
return errNoSuchUser return errNoSuchUser
@ -88,7 +86,7 @@ func (ids *iamDummyStore) loadUser(ctx context.Context, user string, userType IA
return nil 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 { for k, v := range ids.iamUsersMap {
m[k] = v m[k] = v
} }

View File

@ -336,7 +336,7 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]Policy
return nil 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 var u UserIdentity
err := getIAMConfig(&u, userkv.Value, string(userkv.Key)) err := getIAMConfig(&u, userkv.Value, string(userkv.Key))
if err != nil { 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) 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() { if u.Credentials.IsExpired() {
// Delete expired identity. // Delete expired identity.
deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, userType)) 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 == "" { if u.Credentials.AccessKey == "" {
u.Credentials.AccessKey = user u.Credentials.AccessKey = user
} }
m[user] = u.Credentials m[user] = u
return nil 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 var u UserIdentity
err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil { 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) 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 var basePrefix string
switch userType { switch userType {
case svcUser: case svcUser:

View File

@ -287,7 +287,7 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]Po
return nil 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 var u UserIdentity
err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil { if err != nil {
@ -308,11 +308,11 @@ func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType
u.Credentials.AccessKey = user u.Credentials.AccessKey = user
} }
m[user] = u.Credentials m[user] = u
return nil 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 var basePrefix string
switch userType { switch userType {
case svcUser: case svcUser:

View File

@ -249,7 +249,7 @@ type iamCache struct {
// map of policy names to policy definitions // map of policy names to policy definitions
iamPolicyDocsMap map[string]PolicyDoc iamPolicyDocsMap map[string]PolicyDoc
// map of usernames to credentials // map of usernames to credentials
iamUsersMap map[string]auth.Credentials iamUsersMap map[string]UserIdentity
// map of group names to group info // map of group names to group info
iamGroupsMap map[string]GroupInfo iamGroupsMap map[string]GroupInfo
// map of user names to groups they are a member of // map of user names to groups they are a member of
@ -263,7 +263,7 @@ type iamCache struct {
func newIamCache() *iamCache { func newIamCache() *iamCache {
return &iamCache{ return &iamCache{
iamPolicyDocsMap: map[string]PolicyDoc{}, iamPolicyDocsMap: map[string]PolicyDoc{},
iamUsersMap: map[string]auth.Credentials{}, iamUsersMap: map[string]UserIdentity{},
iamGroupsMap: map[string]GroupInfo{}, iamGroupsMap: map[string]GroupInfo{},
iamUserGroupMemberships: map[string]set.StringSet{}, iamUserGroupMemberships: map[string]set.StringSet{},
iamUserPolicyMap: map[string]MappedPolicy{}, iamUserPolicyMap: map[string]MappedPolicy{},
@ -346,16 +346,16 @@ func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]
var parentName string var parentName string
u, ok := c.iamUsersMap[name] u, ok := c.iamUsersMap[name]
if ok { if ok {
if !u.IsValid() { if !u.Credentials.IsValid() {
return nil, time.Time{}, nil return nil, time.Time{}, nil
} }
parentName = u.ParentUser parentName = u.Credentials.ParentUser
} }
mp, ok := c.iamUserPolicyMap[name] mp, ok := c.iamUserPolicyMap[name]
if !ok { if !ok {
// Service accounts with root credentials, inherit parent permissions // 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 // even if this is set, the claims present in the service
// accounts apply the final permissions if any. // accounts apply the final permissions if any.
return []string{"consoleAdmin"}, mp.UpdatedAt, nil return []string{"consoleAdmin"}, mp.UpdatedAt, nil
@ -395,8 +395,8 @@ type IAMStorageAPI interface {
getUsersSysType() UsersSysType getUsersSysType() UsersSysType
loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error
loadPolicyDocs(ctx context.Context, 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 loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error
loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
loadGroups(ctx context.Context, 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 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. // 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() cache := store.rlock()
defer store.runlock() defer store.runlock()
c, ok := cache.iamUsersMap[user] u, ok := cache.iamUsersMap[user]
return c, ok return u, ok
} }
// GetMappedPolicy - fetches mapped policy from memory. // 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. // 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 == "" { if group == "" {
return errInvalidArgument return updatedAt, errInvalidArgument
} }
cache := store.lock() cache := store.lock()
@ -624,12 +624,13 @@ func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, mem
// Validate that all members exist. // Validate that all members exist.
for _, member := range members { for _, member := range members {
cr, ok := cache.iamUsersMap[member] u, ok := cache.iamUsersMap[member]
if !ok { if !ok {
return errNoSuchUser return updatedAt, errNoSuchUser
} }
cr := u.Credentials
if cr.IsTemp() || cr.IsServiceAccount() { 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) gi = newGroupInfo(members)
} else { } else {
gi.Members = set.CreateStringSet(append(gi.Members, members...)...).ToSlice() gi.Members = set.CreateStringSet(append(gi.Members, members...)...).ToSlice()
gi.UpdatedAt = UTCNow()
} }
if err := store.saveGroupInfo(ctx, group, gi); err != nil { if err := store.saveGroupInfo(ctx, group, gi); err != nil {
return err return updatedAt, err
} }
cache.iamGroupsMap[group] = gi cache.iamGroupsMap[group] = gi
@ -660,16 +662,15 @@ func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, mem
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return gi.UpdatedAt, nil
return nil
} }
// helper function - does not take any locks. Updates only cache if // helper function - does not take any locks. Updates only cache if
// updateCacheOnly is set. // 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] gi, ok := cache.iamGroupsMap[group]
if !ok { if !ok {
return errNoSuchGroup return updatedAt, errNoSuchGroup
} }
s := set.CreateStringSet(gi.Members...) s := set.CreateStringSet(gi.Members...)
@ -679,9 +680,10 @@ func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamC
if !updateCacheOnly { if !updateCacheOnly {
err := store.saveGroupInfo(ctx, group, gi) err := store.saveGroupInfo(ctx, group, gi)
if err != nil { if err != nil {
return err return updatedAt, err
} }
} }
gi.UpdatedAt = UTCNow()
cache.iamGroupsMap[group] = gi cache.iamGroupsMap[group] = gi
// update user-group membership map // update user-group membership map
@ -695,13 +697,13 @@ func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamC
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return gi.UpdatedAt, nil
} }
// RemoveUsersFromGroup - removes users from group, deleting it if it is empty. // 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 == "" { if group == "" {
return errInvalidArgument return updatedAt, errInvalidArgument
} }
cache := store.lock() cache := store.lock()
@ -709,23 +711,24 @@ func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string
// Validate that all members exist. // Validate that all members exist.
for _, member := range members { for _, member := range members {
cr, ok := cache.iamUsersMap[member] u, ok := cache.iamUsersMap[member]
if !ok { if !ok {
return errNoSuchUser return updatedAt, errNoSuchUser
} }
cr := u.Credentials
if cr.IsTemp() || cr.IsServiceAccount() { if cr.IsTemp() || cr.IsServiceAccount() {
return errIAMActionNotAllowed return updatedAt, errIAMActionNotAllowed
} }
} }
gi, ok := cache.iamGroupsMap[group] gi, ok := cache.iamGroupsMap[group]
if !ok { if !ok {
return errNoSuchGroup return updatedAt, errNoSuchGroup
} }
// Check if attempting to delete a non-empty group. // Check if attempting to delete a non-empty group.
if len(members) == 0 && len(gi.Members) != 0 { if len(members) == 0 && len(gi.Members) != 0 {
return errGroupNotEmpty return updatedAt, errGroupNotEmpty
} }
if len(members) == 0 { 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 // Remove the group from storage. First delete the
// mapped policy. No-mapped-policy case is ignored. // mapped policy. No-mapped-policy case is ignored.
if err := store.deleteMappedPolicy(ctx, group, regUser, true); err != nil && err != errNoSuchPolicy { 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 { if err := store.deleteGroupInfo(ctx, group); err != nil && err != errNoSuchGroup {
return err return updatedAt, err
} }
// Delete from server memory // Delete from server memory
delete(cache.iamGroupsMap, group) delete(cache.iamGroupsMap, group)
delete(cache.iamGroupPolicyMap, group) delete(cache.iamGroupPolicyMap, group)
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return cache.updatedAt, nil
} }
return removeMembersFromGroup(ctx, store, cache, group, members, false) return removeMembersFromGroup(ctx, store, cache, group, members, false)
} }
// SetGroupStatus - updates group status // 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 == "" { if group == "" {
return errInvalidArgument return updatedAt, errInvalidArgument
} }
cache := store.lock() cache := store.lock()
@ -761,7 +764,7 @@ func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enab
gi, ok := cache.iamGroupsMap[group] gi, ok := cache.iamGroupsMap[group]
if !ok { if !ok {
return errNoSuchGroup return updatedAt, errNoSuchGroup
} }
if enabled { if enabled {
@ -769,15 +772,15 @@ func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enab
} else { } else {
gi.Status = statusDisabled gi.Status = statusDisabled
} }
gi.UpdatedAt = UTCNow()
if err := store.saveGroupInfo(ctx, group, gi); err != nil { if err := store.saveGroupInfo(ctx, group, gi); err != nil {
return err return gi.UpdatedAt, err
} }
cache.iamGroupsMap[group] = gi cache.iamGroupsMap[group] = gi
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return gi.UpdatedAt, nil
} }
// GetGroupDescription - builds up group description // 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 // PolicyDBSet - update the policy mapping for the given user or group in
// storage and in cache. // 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 == "" { if name == "" {
return errInvalidArgument return updatedAt, errInvalidArgument
} }
cache := store.lock() cache := store.lock()
@ -863,11 +866,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
if !isGroup { if !isGroup {
if _, ok := cache.iamUsersMap[name]; !ok { if _, ok := cache.iamUsersMap[name]; !ok {
return errNoSuchUser return updatedAt, errNoSuchUser
} }
} else { } else {
if _, ok := cache.iamGroupsMap[name]; !ok { 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) err := store.deleteMappedPolicy(ctx, name, userType, isGroup)
if err != nil && err != errNoSuchPolicy { if err != nil && err != errNoSuchPolicy {
return err return updatedAt, err
} }
if !isGroup { if !isGroup {
delete(cache.iamUserPolicyMap, name) delete(cache.iamUserPolicyMap, name)
@ -890,8 +893,7 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
delete(cache.iamGroupPolicyMap, name) delete(cache.iamGroupPolicyMap, name)
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return cache.updatedAt, nil
return nil
} }
// Handle policy mapping set/update // Handle policy mapping set/update
@ -899,12 +901,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
for _, p := range mp.toSlice() { for _, p := range mp.toSlice() {
if _, found := cache.iamPolicyDocsMap[p]; !found { if _, found := cache.iamPolicyDocsMap[p]; !found {
logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, p)) 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 { if err := store.saveMappedPolicy(ctx, name, userType, isGroup, mp); err != nil {
return err return updatedAt, err
} }
if !isGroup { if !isGroup {
cache.iamUserPolicyMap[name] = mp cache.iamUserPolicyMap[name] = mp
@ -912,7 +914,7 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
cache.iamGroupPolicyMap[name] = mp cache.iamGroupPolicyMap[name] = mp
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return mp.UpdatedAt, nil
} }
// PolicyNotificationHandler - loads given policy from storage. If not present, // 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. // 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 == "" { if policy.IsEmpty() || name == "" {
return errInvalidArgument return time.Time{}, errInvalidArgument
} }
cache := store.lock() 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 { if err := store.savePolicyDoc(ctx, name, d); err != nil {
return err return d.UpdateDate, err
} }
cache.iamPolicyDocsMap[name] = d cache.iamPolicyDocsMap[name] = d
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return d.UpdateDate, nil
} }
// ListPolicies - fetches all policies from storage and updates cache as well. // 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{} result := map[string]madmin.UserInfo{}
for k, v := range cache.iamUsersMap { for k, v := range cache.iamUsersMap {
if v.IsTemp() || v.IsServiceAccount() { c := v.Credentials
if c.IsTemp() || c.IsServiceAccount() {
continue continue
} }
var policies []string var policies []string
@ -1216,7 +1219,7 @@ func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserI
result[k] = madmin.UserInfo{ result[k] = madmin.UserInfo{
PolicyName: matchedPolicies, PolicyName: matchedPolicies,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
if v.IsValid() { if c.IsValid() {
return madmin.AccountEnabled return madmin.AccountEnabled
} }
return madmin.AccountDisabled return madmin.AccountDisabled
@ -1235,7 +1238,9 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
defer store.runlock() defer store.runlock()
result := map[string]madmin.UserInfo{} 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() { if v.IsTemp() || v.IsServiceAccount() {
continue continue
} }
@ -1281,8 +1286,8 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
// return that info. Otherwise we return error. // return that info. Otherwise we return error.
var groups []string var groups []string
for _, v := range cache.iamUsersMap { for _, v := range cache.iamUsersMap {
if v.ParentUser == name { if v.Credentials.ParentUser == name {
groups = v.Groups groups = v.Credentials.Groups
break break
} }
} }
@ -1297,11 +1302,11 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
}, nil }, nil
} }
cred, found := cache.iamUsersMap[name] ui, found := cache.iamUsersMap[name]
if !found { if !found {
return u, errNoSuchUser return u, errNoSuchUser
} }
cred := ui.Credentials
if cred.IsTemp() || cred.IsServiceAccount() { if cred.IsTemp() || cred.IsServiceAccount() {
return u, errIAMActionNotAllowed return u, errIAMActionNotAllowed
} }
@ -1363,7 +1368,7 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice()
for _, group := range memberOf { 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 { if removeErr == errNoSuchGroup {
removeErr = nil removeErr = nil
} }
@ -1376,11 +1381,11 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
// 2. Remove any derived credentials from memory // 2. Remove any derived credentials from memory
if userType == regUser { if userType == regUser {
for _, u := range cache.iamUsersMap { for _, u := range cache.iamUsersMap {
if u.IsServiceAccount() && u.ParentUser == accessKey { if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey {
delete(cache.iamUsersMap, u.AccessKey) delete(cache.iamUsersMap, u.Credentials.AccessKey)
} }
if u.IsTemp() && u.ParentUser == accessKey { if u.Credentials.IsTemp() && u.Credentials.ParentUser == accessKey {
delete(cache.iamUsersMap, u.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 // This mapping is necessary to ensure that valid credentials
// have necessary ParentUser present - this is mainly for only // have necessary ParentUser present - this is mainly for only
// webIdentity based STS tokens. // webIdentity based STS tokens.
cred, ok := cache.iamUsersMap[accessKey] u, ok := cache.iamUsersMap[accessKey]
if ok { if ok {
cred := u.Credentials
if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey { if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey {
if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok { if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok {
cache.iamUserPolicyMap[cred.ParentUser] = cache.iamUserPolicyMap[accessKey] 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 { if store.getUsersSysType() == MinIOUsersSysType && userType == regUser {
memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice()
for _, group := range memberOf { 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 { if removeErr != nil {
return removeErr 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 // Delete any STS and service account derived from this credential
// first. // first.
if userType == regUser { if userType == regUser {
for _, u := range cache.iamUsersMap { for _, ui := range cache.iamUsersMap {
u := ui.Credentials
if u.IsServiceAccount() && u.ParentUser == accessKey { if u.IsServiceAccount() && u.ParentUser == accessKey {
_ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser) _ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser)
delete(cache.iamUsersMap, u.AccessKey) 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 // 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 // policy name is given, it is associated with the parent user specified in the
// credential. // 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 == "" { if accessKey == "" || !cred.IsTemp() || cred.IsExpired() || cred.ParentUser == "" {
return errInvalidArgument return time.Time{}, errInvalidArgument
} }
ttl := int64(cred.Expiration.Sub(UTCNow()).Seconds()) 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, "") _, combinedPolicyStmt := filterPolicies(cache, mp.Policies, "")
if combinedPolicyStmt.IsEmpty() { 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}) err := store.saveMappedPolicy(ctx, cred.ParentUser, stsUser, false, mp, options{ttl: ttl})
if err != nil { if err != nil {
return err return time.Time{}, err
} }
cache.iamUserPolicyMap[cred.ParentUser] = mp cache.iamUserPolicyMap[cred.ParentUser] = mp
@ -1512,14 +1519,14 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
u := newUserIdentity(cred) u := newUserIdentity(cred)
err := store.saveUserIdentity(ctx, accessKey, stsUser, u, options{ttl: ttl}) err := store.saveUserIdentity(ctx, accessKey, stsUser, u, options{ttl: ttl})
if err != nil { if err != nil {
return err return time.Time{}, err
} }
cache.iamUsersMap[accessKey] = cred cache.iamUsersMap[accessKey] = u
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return u.UpdatedAt, nil
} }
// DeleteUsers - given a set of users or access keys, deletes them along with // 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 var deleted bool
usersToDelete := set.CreateStringSet(users...) usersToDelete := set.CreateStringSet(users...)
for user, cred := range cache.iamUsersMap { for user, ui := range cache.iamUsersMap {
userType := regUser userType := regUser
cred := ui.Credentials
if cred.IsServiceAccount() { if cred.IsServiceAccount() {
userType = svcUser userType = svcUser
} else if cred.IsTemp() { } else if cred.IsTemp() {
@ -1575,7 +1584,8 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
defer store.runlock() defer store.runlock()
res := map[string]ParentUserInfo{} 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 // Only consider service account or STS credentials with
// non-empty session tokens. // non-empty session tokens.
if !(cred.IsServiceAccount() || cred.IsTemp()) || if !(cred.IsServiceAccount() || cred.IsTemp()) ||
@ -1633,21 +1643,22 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
} }
// SetUserStatus - sets current user status. // 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 { if accessKey != "" && status != madmin.AccountEnabled && status != madmin.AccountDisabled {
return errInvalidArgument return updatedAt, errInvalidArgument
} }
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
cred, ok := cache.iamUsersMap[accessKey] ui, ok := cache.iamUsersMap[accessKey]
if !ok { if !ok {
return errNoSuchUser return updatedAt, errNoSuchUser
} }
cred := ui.Credentials
if cred.IsTemp() || cred.IsServiceAccount() { if cred.IsTemp() || cred.IsServiceAccount() {
return errIAMActionNotAllowed return updatedAt, errIAMActionNotAllowed
} }
uinfo := newUserIdentity(auth.Credentials{ 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 { 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() cache.updatedAt = time.Now()
return nil return uinfo.UpdatedAt, nil
} }
// AddServiceAccount - add a new service account // 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() cache := store.lock()
defer store.unlock() 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 - // Found newly requested service account, to be an existing account -
// reject such operation (updates to the service account are handled in // reject such operation (updates to the service account are handled in
// a different API). // a different API).
if scred, found := cache.iamUsersMap[accessKey]; found { if su, found := cache.iamUsersMap[accessKey]; found {
scred := su.Credentials
if scred.ParentUser != parentUser { if scred.ParentUser != parentUser {
return errIAMServiceAccountUsed return updatedAt, errIAMServiceAccountUsed
} }
return errIAMServiceAccount return updatedAt, errIAMServiceAccount
} }
// Parent user must not be a service account. // Parent user must not be a service account.
if cr, found := cache.iamUsersMap[parentUser]; found && cr.IsServiceAccount() { if u, found := cache.iamUsersMap[parentUser]; found && u.Credentials.IsServiceAccount() {
return errIAMServiceAccount return updatedAt, errIAMServiceAccount
} }
u := newUserIdentity(cred) u := newUserIdentity(cred)
err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u) err = store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u)
if err != nil { 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() cache.updatedAt = time.Now()
return nil return u.UpdatedAt, nil
} }
// UpdateServiceAccount - updates a service account on storage. // 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() cache := store.lock()
defer store.unlock() defer store.unlock()
cr, ok := cache.iamUsersMap[accessKey] ui, ok := cache.iamUsersMap[accessKey]
if !ok || !cr.IsServiceAccount() { if !ok || !ui.Credentials.IsServiceAccount() {
return errNoSuchServiceAccount return updatedAt, errNoSuchServiceAccount
} }
cr := ui.Credentials
currentSecretKey := cr.SecretKey currentSecretKey := cr.SecretKey
if opts.secretKey != "" { if opts.secretKey != "" {
if !auth.IsSecretKeyValid(opts.secretKey) { if !auth.IsSecretKeyValid(opts.secretKey) {
return auth.ErrInvalidSecretKeyLength return updatedAt, auth.ErrInvalidSecretKeyLength
} }
cr.SecretKey = opts.secretKey cr.SecretKey = opts.secretKey
} }
@ -1736,12 +1748,12 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
case auth.AccountOn, auth.AccountOff: case auth.AccountOn, auth.AccountOff:
cr.Status = opts.status cr.Status = opts.status
default: default:
return errors.New("unknown account status value") return updatedAt, errors.New("unknown account status value")
} }
m, err := getClaimsFromTokenWithSecret(cr.SessionToken, currentSecretKey) m, err := getClaimsFromTokenWithSecret(cr.SessionToken, currentSecretKey)
if err != nil { 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 // 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 opts.sessionPolicy != nil {
if err := opts.sessionPolicy.Validate(); err != nil { if err := opts.sessionPolicy.Validate(); err != nil {
return err return updatedAt, err
} }
policyBuf, err := json.Marshal(opts.sessionPolicy) policyBuf, err := json.Marshal(opts.sessionPolicy)
if err != nil { if err != nil {
return err return updatedAt, err
} }
if len(policyBuf) > 16*humanize.KiByte { 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. // 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) cr.SessionToken, err = auth.JWTSignWithAccessKey(accessKey, m, cr.SecretKey)
if err != nil { if err != nil {
return err return updatedAt, err
} }
u := newUserIdentity(cr) u := newUserIdentity(cr)
if err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u); err != nil { 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() cache.updatedAt = time.Now()
return nil return u.UpdatedAt, nil
} }
// ListTempAccounts - lists only temporary accounts from the cache. // 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() cache := store.rlock()
defer store.runlock() defer store.runlock()
userExists := false userExists := false
var tempAccounts []auth.Credentials var tempAccounts []UserIdentity
for _, v := range cache.iamUsersMap { for _, v := range cache.iamUsersMap {
isDerived := false isDerived := false
if v.IsServiceAccount() || v.IsTemp() { if v.Credentials.IsServiceAccount() || v.Credentials.IsTemp() {
isDerived = true isDerived = true
} }
if !isDerived && v.AccessKey == accessKey { if !isDerived && v.Credentials.AccessKey == accessKey {
userExists = true userExists = true
} else if isDerived && v.ParentUser == accessKey { } else if isDerived && v.Credentials.ParentUser == accessKey {
userExists = true userExists = true
if v.IsTemp() { if v.Credentials.IsTemp() {
// Hide secret key & session key here // Hide secret key & session key here
v.SecretKey = "" v.Credentials.SecretKey = ""
v.SessionToken = "" v.Credentials.SessionToken = ""
tempAccounts = append(tempAccounts, v) tempAccounts = append(tempAccounts, v)
} }
} }
@ -1830,8 +1842,9 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
userExists := false userExists := false
var serviceAccounts []auth.Credentials var serviceAccounts []auth.Credentials
for _, v := range cache.iamUsersMap { for _, u := range cache.iamUsersMap {
isDerived := false isDerived := false
v := u.Credentials
if v.IsServiceAccount() || v.IsTemp() { if v.IsServiceAccount() || v.IsTemp() {
isDerived = true isDerived = true
} }
@ -1857,17 +1870,17 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
} }
// AddUser - adds/updates long term user account to storage. // 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() cache := store.lock()
defer store.unlock() defer store.unlock()
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
cr, ok := cache.iamUsersMap[accessKey] ui, ok := cache.iamUsersMap[accessKey]
// It is not possible to update an STS account. // It is not possible to update an STS account.
if ok && cr.IsTemp() { if ok && ui.Credentials.IsTemp() {
return errIAMActionNotAllowed return updatedAt, errIAMActionNotAllowed
} }
u := newUserIdentity(auth.Credentials{ 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 { 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. // UpdateUserSecretKey - sets user secret key to storage.
@ -1898,18 +1911,18 @@ func (store *IAMStoreSys) UpdateUserSecretKey(ctx context.Context, accessKey, se
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
cred, ok := cache.iamUsersMap[accessKey] ui, ok := cache.iamUsersMap[accessKey]
if !ok { if !ok {
return errNoSuchUser return errNoSuchUser
} }
cred := ui.Credentials
cred.SecretKey = secretKey cred.SecretKey = secretKey
u := newUserIdentity(cred) u := newUserIdentity(cred)
if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil { if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil {
return err return err
} }
cache.iamUsersMap[accessKey] = cred cache.iamUsersMap[accessKey] = u
return nil return nil
} }
@ -1919,7 +1932,8 @@ func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials {
defer store.runlock() defer store.runlock()
var res []auth.Credentials var res []auth.Credentials
for _, cred := range cache.iamUsersMap { for _, u := range cache.iamUsersMap {
cred := u.Credentials
if cred.IsTemp() || cred.IsServiceAccount() { if cred.IsTemp() || cred.IsServiceAccount() {
res = append(res, cred) res = append(res, cred)
} }
@ -1940,13 +1954,13 @@ func (store *IAMStoreSys) UpdateUserIdentity(ctx context.Context, cred auth.Cred
} else if cred.IsTemp() { } else if cred.IsTemp() {
userType = stsUser userType = stsUser
} }
ui := newUserIdentity(cred)
// Overwrite the user identity here. As store should be // Overwrite the user identity here. As store should be
// atomic, it shouldn't cause any corruption. // 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 return err
} }
cache.iamUsersMap[cred.AccessKey] = cred cache.iamUsersMap[cred.AccessKey] = ui
return nil return nil
} }
@ -1969,9 +1983,9 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
if svc, found := cache.iamUsersMap[accessKey]; found { if svc, found := cache.iamUsersMap[accessKey]; found {
// Load parent user and mapped policies. // Load parent user and mapped policies.
if store.getUsersSysType() == MinIOUsersSysType { 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 { } else {
// check for STS account // check for STS account
store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap) store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap)

View File

@ -599,14 +599,14 @@ func (sys *IAMSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[s
} }
// SetPolicy - sets a new named policy. // 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() { 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 { if err != nil {
return err return updatedAt, err
} }
if !sys.HasWatcher() { 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). // 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 // elsewhere), the AssumeRole case (because the parent user is real and their
// policy is associated via policy-set API) and the AssumeRoleWithLDAP case // policy is associated via policy-set API) and the AssumeRoleWithLDAP case
// (because the policy association is made via policy-set API). // (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() { if !sys.Initialized() {
return errServerNotInitialized return time.Time{}, errServerNotInitialized
} }
if newGlobalAuthZPluginFn() != nil { if newGlobalAuthZPluginFn() != nil {
@ -717,14 +717,14 @@ func (sys *IAMSys) SetTempUser(ctx context.Context, accessKey string, cred auth.
policyName = "" policyName = ""
} }
err := sys.store.SetTempUser(ctx, accessKey, cred, policyName) updatedAt, err := sys.store.SetTempUser(ctx, accessKey, cred, policyName)
if err != nil { if err != nil {
return err return time.Time{}, err
} }
sys.notifyForUser(ctx, cred.AccessKey, true) sys.notifyForUser(ctx, cred.AccessKey, true)
return nil return updatedAt, nil
} }
// ListBucketUsers - list all users who can access this 'bucket' // 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 return false, "", errServerNotInitialized
} }
cred, found := sys.store.GetUser(name) u, found := sys.store.GetUser(name)
if !found { if !found {
return false, "", errNoSuchUser return false, "", errNoSuchUser
} }
cred := u.Credentials
if cred.IsTemp() { if cred.IsTemp() {
return true, cred.ParentUser, nil return true, cred.ParentUser, nil
} }
@ -800,11 +800,11 @@ func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) {
return false, "", errServerNotInitialized return false, "", errServerNotInitialized
} }
cred, found := sys.store.GetUser(name) u, found := sys.store.GetUser(name)
if !found { if !found {
return false, "", errNoSuchUser return false, "", errNoSuchUser
} }
cred := u.Credentials
if cred.IsServiceAccount() { if cred.IsServiceAccount() {
return true, cred.ParentUser, nil 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. // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if sys.usersSysType != MinIOUsersSysType { 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 { if err != nil {
return err return
} }
sys.notifyForUser(ctx, accessKey, false) sys.notifyForUser(ctx, accessKey, false)
return nil return updatedAt, nil
} }
func (sys *IAMSys) notifyForServiceAccount(ctx context.Context, accessKey string) { func (sys *IAMSys) notifyForServiceAccount(ctx context.Context, accessKey string) {
@ -867,34 +867,34 @@ type newServiceAccountOpts struct {
} }
// NewServiceAccount - create a new service account // 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() { if !sys.Initialized() {
return auth.Credentials{}, errServerNotInitialized return auth.Credentials{}, time.Time{}, errServerNotInitialized
} }
if parentUser == "" { if parentUser == "" {
return auth.Credentials{}, errInvalidArgument return auth.Credentials{}, time.Time{}, errInvalidArgument
} }
var policyBuf []byte var policyBuf []byte
if opts.sessionPolicy != nil { if opts.sessionPolicy != nil {
err := opts.sessionPolicy.Validate() err := opts.sessionPolicy.Validate()
if err != nil { if err != nil {
return auth.Credentials{}, err return auth.Credentials{}, time.Time{}, err
} }
policyBuf, err = json.Marshal(opts.sessionPolicy) policyBuf, err = json.Marshal(opts.sessionPolicy)
if err != nil { if err != nil {
return auth.Credentials{}, err return auth.Credentials{}, time.Time{}, err
} }
if len(policyBuf) > 16*humanize.KiByte { 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 // found newly requested service account, to be same as
// parentUser, reject such operations. // parentUser, reject such operations.
if parentUser == opts.accessKey { if parentUser == opts.accessKey {
return auth.Credentials{}, errIAMActionNotAllowed return auth.Credentials{}, time.Time{}, errIAMActionNotAllowed
} }
m := make(map[string]interface{}) m := make(map[string]interface{})
@ -922,24 +922,24 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
} else { } else {
accessKey, secretKey, err = auth.GenerateCredentials() accessKey, secretKey, err = auth.GenerateCredentials()
if err != nil { if err != nil {
return auth.Credentials{}, err return auth.Credentials{}, time.Time{}, err
} }
} }
cred, err := auth.CreateNewCredentialsWithMetadata(accessKey, secretKey, m, secretKey) cred, err := auth.CreateNewCredentialsWithMetadata(accessKey, secretKey, m, secretKey)
if err != nil { if err != nil {
return auth.Credentials{}, err return auth.Credentials{}, time.Time{}, err
} }
cred.ParentUser = parentUser cred.ParentUser = parentUser
cred.Groups = groups cred.Groups = groups
cred.Status = string(auth.AccountOn) cred.Status = string(auth.AccountOn)
err = sys.store.AddServiceAccount(ctx, cred) updatedAt, err := sys.store.AddServiceAccount(ctx, cred)
if err != nil { if err != nil {
return auth.Credentials{}, err return auth.Credentials{}, time.Time{}, err
} }
sys.notifyForServiceAccount(ctx, cred.AccessKey) sys.notifyForServiceAccount(ctx, cred.AccessKey)
return cred, nil return cred, updatedAt, nil
} }
type updateServiceAccountOpts struct { type updateServiceAccountOpts struct {
@ -949,18 +949,18 @@ type updateServiceAccountOpts struct {
} }
// UpdateServiceAccount - edit a service account // 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() { 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 { if err != nil {
return err return updatedAt, err
} }
sys.notifyForServiceAccount(ctx, accessKey) sys.notifyForServiceAccount(ctx, accessKey)
return nil return updatedAt, nil
} }
// ListServiceAccounts - lists all services accounts associated to a specific user // 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 // 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() { if !sys.Initialized() {
return nil, errServerNotInitialized 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) { func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) {
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey) sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
if err != nil { if err != nil {
return sa, embeddedPolicy, err return auth.Credentials{}, embeddedPolicy, err
} }
// Hide secret & session keys // Hide secret & session keys
sa.SecretKey = "" sa.Credentials.SecretKey = ""
sa.SessionToken = "" sa.Credentials.SessionToken = ""
return sa, embeddedPolicy, nil return sa.Credentials, embeddedPolicy, nil
} }
// getServiceAccount - gets information about a service account // 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() { if !sys.Initialized() {
return auth.Credentials{}, nil, errServerNotInitialized return u, nil, errServerNotInitialized
} }
sa, ok := sys.store.GetUser(accessKey) sa, ok := sys.store.GetUser(accessKey)
if !ok || !sa.IsServiceAccount() { if !ok || !sa.Credentials.IsServiceAccount() {
return auth.Credentials{}, nil, errNoSuchServiceAccount return u, nil, errNoSuchServiceAccount
} }
var embeddedPolicy *iampolicy.Policy 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 { if err != nil {
jwtClaims, err = auth.ExtractClaims(sa.SessionToken, globalActiveCred.SecretKey) jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey)
if err != nil { if err != nil {
return auth.Credentials{}, nil, err return u, nil, err
} }
} }
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA()) pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
@ -1028,11 +1028,11 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (aut
if ptok && spok && pt == embeddedPolicyType { if ptok && spok && pt == embeddedPolicyType {
policyBytes, err := base64.StdEncoding.DecodeString(sp) policyBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil { if err != nil {
return auth.Credentials{}, nil, err return u, nil, err
} }
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes))
if err != nil { 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) sa, ok := sys.store.GetUser(accessKey)
if !ok || !sa.IsServiceAccount() { if !ok || !sa.Credentials.IsServiceAccount() {
return nil, errNoSuchServiceAccount return nil, errNoSuchServiceAccount
} }
jwtClaims, err := auth.ExtractClaims(sa.SessionToken, sa.SecretKey) jwtClaims, err := auth.ExtractClaims(sa.Credentials.SessionToken, sa.Credentials.SecretKey)
if err != nil { if err != nil {
jwtClaims, err = auth.ExtractClaims(sa.SessionToken, globalActiveCred.SecretKey) jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1071,7 +1071,7 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string, n
} }
sa, ok := sys.store.GetUser(accessKey) sa, ok := sys.store.GetUser(accessKey)
if !ok || !sa.IsServiceAccount() { if !ok || !sa.Credentials.IsServiceAccount() {
return nil 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 // CreateUser - create new user credentials and policy, if user already exists
// they shall be rewritten with new inputs. // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if sys.usersSysType != MinIOUsersSysType { if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed return updatedAt, errIAMActionNotAllowed
} }
if !auth.IsAccessKeyValid(accessKey) { if !auth.IsAccessKeyValid(accessKey) {
return auth.ErrInvalidAccessKeyLength return updatedAt, auth.ErrInvalidAccessKeyLength
} }
if !auth.IsSecretKeyValid(ureq.SecretKey) { 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 { if err != nil {
return err return updatedAt, err
} }
sys.notifyForUser(ctx, accessKey, false) sys.notifyForUser(ctx, accessKey, false)
return nil return updatedAt, nil
} }
// SetUserSecretKey - sets user secret key // SetUserSecretKey - sets user secret key
@ -1291,9 +1291,9 @@ func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) {
} }
// GetUser - get user credentials // 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() { if !sys.Initialized() {
return cred, false return u, false
} }
fallback := false fallback := false
@ -1304,7 +1304,7 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (cred auth.Cre
fallback = true fallback = true
} }
cred, ok = sys.store.GetUser(accessKey) u, ok = sys.store.GetUser(accessKey)
if !ok && !fallback { if !ok && !fallback {
// accessKey not found, also // accessKey not found, also
// IAM store is not in fallback mode // 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 // exists now. If it doesn't proceed to
// fail. // fail.
sys.store.LoadUser(ctx, accessKey) 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. // 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 // AddUsersToGroup - adds users to a group, creating the group if
// needed. No error if user(s) already are in the group. // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if sys.usersSysType != MinIOUsersSysType { 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 { if err != nil {
return err return updatedAt, err
} }
sys.notifyForGroup(ctx, group) sys.notifyForGroup(ctx, group)
return nil return updatedAt, nil
} }
// RemoveUsersFromGroup - remove users from group. If no users are // RemoveUsersFromGroup - remove users from group. If no users are
// given, and the group is empty, deletes the group as well. // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if sys.usersSysType != MinIOUsersSysType { 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 { if err != nil {
return err return updatedAt, err
} }
sys.notifyForGroup(ctx, group) sys.notifyForGroup(ctx, group)
return nil return updatedAt, nil
} }
// SetGroupStatus - enable/disabled a group // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if sys.usersSysType != MinIOUsersSysType { 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 { if err != nil {
return err return updatedAt, err
} }
sys.notifyForGroup(ctx, group) sys.notifyForGroup(ctx, group)
return nil return updatedAt, nil
} }
// GetGroupDescription - builds up group description // 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. // 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() { if !sys.Initialized() {
return errServerNotInitialized return updatedAt, errServerNotInitialized
} }
// Determine user-type based on IDP mode. // Determine user-type based on IDP mode.
@ -1425,9 +1425,9 @@ func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, isGroup
userType = stsUser userType = stsUser
} }
err := sys.store.PolicyDBSet(ctx, name, policy, userType, isGroup) updatedAt, err = sys.store.PolicyDBSet(ctx, name, policy, userType, isGroup)
if err != nil { if err != nil {
return err return
} }
// Notify all other MinIO peers to reload policy // 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 // PolicyDBGet - gets policy set on a user or group. If a list of groups is

View File

@ -63,11 +63,11 @@ func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (st
func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt time.Time) (string, error) { func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt time.Time) (string, error) {
serverCred := globalActiveCred serverCred := globalActiveCred
if serverCred.AccessKey != credentials.AccessKey { if serverCred.AccessKey != credentials.AccessKey {
var ok bool u, ok := globalIAMSys.GetUser(context.TODO(), credentials.AccessKey)
serverCred, ok = globalIAMSys.GetUser(context.TODO(), credentials.AccessKey)
if !ok { if !ok {
return "", errInvalidAccessKeyID return "", errInvalidAccessKeyID
} }
serverCred = u.Credentials
} }
if !serverCred.Equal(credentials) { if !serverCred.Equal(credentials) {
@ -145,10 +145,11 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
if claims.AccessKey == globalActiveCred.AccessKey { if claims.AccessKey == globalActiveCred.AccessKey {
return []byte(globalActiveCred.SecretKey), nil return []byte(globalActiveCred.SecretKey), nil
} }
cred, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey) u, ok := globalIAMSys.GetUser(req.Context(), claims.AccessKey)
if !ok { if !ok {
return nil, errInvalidAccessKeyID return nil, errInvalidAccessKeyID
} }
cred := u.Credentials
return []byte(cred.SecretKey), nil return []byte(cred.SecretKey), nil
}); err != nil { }); err != nil {
return claims, nil, false, errAuthentication return claims, nil, false, errAuthentication
@ -157,11 +158,11 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
var groups []string var groups []string
if globalActiveCred.AccessKey != claims.AccessKey { if globalActiveCred.AccessKey != claims.AccessKey {
// Check if the access key is part of users credentials. // 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 { if !ok {
return nil, nil, false, errInvalidAccessKeyID return nil, nil, false, errInvalidAccessKeyID
} }
ucred := u.Credentials
// get embedded claims // get embedded claims
eclaims, s3Err := checkClaimsFromToken(req, ucred) eclaims, s3Err := checkClaimsFromToken(req, ucred)
if s3Err != ErrNone { if s3Err != ErrNone {

View File

@ -152,16 +152,16 @@ func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, A
cred := globalActiveCred cred := globalActiveCred
if cred.AccessKey != accessKey { if cred.AccessKey != accessKey {
// Check if the access key is part of users credentials. // 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 { if !ok {
// Credentials will be invalid but and disabled // Credentials will be invalid but and disabled
// return a different error in such a scenario. // 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, ErrAccessKeyDisabled
} }
return cred, false, ErrInvalidAccessKeyID return cred, false, ErrInvalidAccessKeyID
} }
cred = ucred cred = u.Credentials
} }
claims, s3Err := checkClaimsFromToken(r, cred) claims, s3Err := checkClaimsFromToken(r, cred)

View File

@ -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. // Generate a secret key for the service account if not created already.
var secretKey string var secretKey string
svcCred, _, err := globalIAMSys.getServiceAccount(ctx, siteReplicatorSvcAcc) var svcCred auth.Credentials
sa, _, err := globalIAMSys.getServiceAccount(ctx, siteReplicatorSvcAcc)
switch { switch {
case err == errNoSuchServiceAccount: case err == errNoSuchServiceAccount:
_, secretKey, err = auth.GenerateCredentials() _, secretKey, err = auth.GenerateCredentials()
if err != nil { if err != nil {
return madmin.ReplicateAddStatus{}, errSRServiceAccount(fmt.Errorf("unable to create local service account: %w", err)) 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, accessKey: siteReplicatorSvcAcc,
secretKey: secretKey, 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)) return madmin.ReplicateAddStatus{}, errSRServiceAccount(fmt.Errorf("unable to create local service account: %w", err))
} }
case err == nil: case err == nil:
svcCred = sa.Credentials
secretKey = svcCred.SecretKey secretKey = svcCred.SecretKey
default: default:
return madmin.ReplicateAddStatus{}, errSRBackendIssue(err) 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) _, _, err := globalIAMSys.GetServiceAccount(ctx, arg.SvcAcctAccessKey)
if err == errNoSuchServiceAccount { if err == errNoSuchServiceAccount {
_, err = globalIAMSys.NewServiceAccount(ctx, arg.SvcAcctParent, nil, newServiceAccountOpts{ _, _, err = globalIAMSys.NewServiceAccount(ctx, arg.SvcAcctParent, nil, newServiceAccountOpts{
accessKey: arg.SvcAcctAccessKey, accessKey: arg.SvcAcctAccessKey,
secretKey: arg.SvcAcctSecretKey, 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, // PeerAddPolicyHandler - copies IAM policy to local. A nil policy argument,
// causes the named policy to be deleted. // 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 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 { if p == nil {
err = globalIAMSys.DeletePolicy(ctx, policyName, true) err = globalIAMSys.DeletePolicy(ctx, policyName, true)
} else { } else {
err = globalIAMSys.SetPolicy(ctx, policyName, *p) _, err = globalIAMSys.SetPolicy(ctx, policyName, *p)
} }
if err != nil { if err != nil {
return wrapSRErr(err) return wrapSRErr(err)
@ -1036,10 +1044,17 @@ func (c *SiteReplicationSys) PeerAddPolicyHandler(ctx context.Context, policyNam
} }
// PeerIAMUserChangeHandler - copies IAM user to local. // 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 { if change == nil {
return errSRInvalidRequest(errInvalidArgument) 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 var err error
if change.IsDeleteReq { if change.IsDeleteReq {
err = globalIAMSys.DeleteUser(ctx, change.AccessKey, true) err = globalIAMSys.DeleteUser(ctx, change.AccessKey, true)
@ -1051,9 +1066,9 @@ func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, chang
if userReq.Status != "" && userReq.SecretKey == "" { if userReq.Status != "" && userReq.SecretKey == "" {
// Status is set without secretKey updates means we are // Status is set without secretKey updates means we are
// only changing the account status. // only changing the account status.
err = globalIAMSys.SetUserStatus(ctx, change.AccessKey, userReq.Status) _, err = globalIAMSys.SetUserStatus(ctx, change.AccessKey, userReq.Status)
} else { } else {
err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq) _, err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq)
} }
} }
if err != nil { if err != nil {
@ -1063,19 +1078,27 @@ func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, chang
} }
// PeerGroupInfoChangeHandler - copies group changes to local. // 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 { if change == nil {
return errSRInvalidRequest(errInvalidArgument) return errSRInvalidRequest(errInvalidArgument)
} }
updReq := change.UpdateReq updReq := change.UpdateReq
var err error 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 { if updReq.IsRemove {
err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members) _, err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members)
} else { } else {
if updReq.Status != "" && len(updReq.Members) == 0 { 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 { } else {
err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) _, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
} }
} }
if err != nil { if err != nil {
@ -1085,7 +1108,7 @@ func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, cha
} }
// PeerSvcAccChangeHandler - copies service-account change to local. // 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 { if change == nil {
return errSRInvalidRequest(errInvalidArgument) return errSRInvalidRequest(errInvalidArgument)
} }
@ -1099,14 +1122,19 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
return wrapSRErr(err) 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{ opts := newServiceAccountOpts{
accessKey: change.Create.AccessKey, accessKey: change.Create.AccessKey,
secretKey: change.Create.SecretKey, secretKey: change.Create.SecretKey,
sessionPolicy: sp, sessionPolicy: sp,
claims: change.Create.Claims, 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 { if err != nil {
return wrapSRErr(err) return wrapSRErr(err)
} }
@ -1120,20 +1148,31 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
return wrapSRErr(err) 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{ opts := updateServiceAccountOpts{
secretKey: change.Update.SecretKey, secretKey: change.Update.SecretKey,
status: change.Update.Status, status: change.Update.Status,
sessionPolicy: sp, sessionPolicy: sp,
} }
err = globalIAMSys.UpdateServiceAccount(ctx, change.Update.AccessKey, opts) _, err = globalIAMSys.UpdateServiceAccount(ctx, change.Update.AccessKey, opts)
if err != nil { if err != nil {
return wrapSRErr(err) return wrapSRErr(err)
} }
case change.Delete != nil: case change.Delete != nil:
err := globalIAMSys.DeleteServiceAccount(ctx, change.Delete.AccessKey, true) // skip overwrite of local update if peer sent stale info
if err != nil { 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) return wrapSRErr(err)
} }
@ -1143,11 +1182,19 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
} }
// PeerPolicyMappingHandler - copies policy mapping to local. // 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 { if mapping == nil {
return errSRInvalidRequest(errInvalidArgument) 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 { if err != nil {
return wrapSRErr(err) return wrapSRErr(err)
} }
@ -1155,10 +1202,19 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi
} }
// PeerSTSAccHandler - replicates STS credential locally. // 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 { if stsCred == nil {
return errSRInvalidRequest(errInvalidArgument) 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 // Verify the session token of the stsCred
claims, err := auth.ExtractClaims(stsCred.SessionToken, globalActiveCred.SecretKey) 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. // 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) 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) { 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 { if !ok {
return nil, errors.New("site replication service account not found") 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 // listBuckets returns a consistent common view of latest unique buckets across
@ -1606,13 +1662,13 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
// Policies should be synced first. // Policies should be synced first.
{ {
// Replicate IAM policies on local to all peers. // Replicate IAM policies on local to all peers.
allPolicies, err := globalIAMSys.ListPolicies(ctx, "") allPolicyDocs, err := globalIAMSys.ListPolicyDocs(ctx, "")
if err != nil { if err != nil {
return errSRBackendIssue(err) return errSRBackendIssue(err)
} }
for pname, policy := range allPolicies { for pname, pdoc := range allPolicyDocs {
policyJSON, err := json.Marshal(policy) policyJSON, err := json.Marshal(pdoc.Policy)
if err != nil { if err != nil {
return wrapSRErr(err) return wrapSRErr(err)
} }
@ -1620,6 +1676,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
Type: madmin.SRIAMItemPolicy, Type: madmin.SRIAMItemPolicy,
Name: pname, Name: pname,
Policy: policyJSON, Policy: policyJSON,
UpdatedAt: pdoc.UpdateDate,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) 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 // Next should be userAccounts those are local users, OIDC and LDAP will not
// may not have any local users. // may not have any local users.
{ {
userAccounts := make(map[string]auth.Credentials) userAccounts := make(map[string]UserIdentity)
globalIAMSys.store.rlock() globalIAMSys.store.rlock()
err := globalIAMSys.store.loadUsers(ctx, regUser, userAccounts) err := globalIAMSys.store.loadUsers(ctx, regUser, userAccounts)
globalIAMSys.store.runlock() globalIAMSys.store.runlock()
@ -1642,13 +1699,14 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
if err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ if err := c.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemIAMUser, Type: madmin.SRIAMItemIAMUser,
IAMUser: &madmin.SRIAMUser{ IAMUser: &madmin.SRIAMUser{
AccessKey: acc.AccessKey, AccessKey: acc.Credentials.AccessKey,
IsDeleteReq: false, IsDeleteReq: false,
UserReq: &madmin.AddOrUpdateUserReq{ UserReq: &madmin.AddOrUpdateUserReq{
SecretKey: acc.SecretKey, SecretKey: acc.Credentials.SecretKey,
Status: madmin.AccountStatus(acc.Status), Status: madmin.AccountStatus(acc.Credentials.Status),
}, },
}, },
UpdatedAt: acc.UpdatedAt,
}); err != nil { }); err != nil {
return errSRIAMError(err) return errSRIAMError(err)
} }
@ -1678,6 +1736,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
IsRemove: false, IsRemove: false,
}, },
}, },
UpdatedAt: group.UpdatedAt,
}); err != nil { }); err != nil {
return errSRIAMError(err) 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 // Service accounts are the static accounts that should be synced with
// valid claims. // valid claims.
{ {
serviceAccounts := make(map[string]auth.Credentials) serviceAccounts := make(map[string]UserIdentity)
globalIAMSys.store.rlock() globalIAMSys.store.rlock()
err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts) err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts)
globalIAMSys.store.runlock() globalIAMSys.store.runlock()
@ -1702,12 +1761,12 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
continue continue
} }
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey) claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey)
if err != nil { if err != nil {
return errSRBackendIssue(err) return errSRBackendIssue(err)
} }
_, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.AccessKey) _, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey)
if err != nil { if err != nil {
return errSRBackendIssue(err) return errSRBackendIssue(err)
} }
@ -1724,15 +1783,16 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
Type: madmin.SRIAMItemSvcAcc, Type: madmin.SRIAMItemSvcAcc,
SvcAccChange: &madmin.SRSvcAccChange{ SvcAccChange: &madmin.SRSvcAccChange{
Create: &madmin.SRSvcAccCreate{ Create: &madmin.SRSvcAccCreate{
Parent: acc.ParentUser, Parent: acc.Credentials.ParentUser,
AccessKey: user, AccessKey: user,
SecretKey: acc.SecretKey, SecretKey: acc.Credentials.SecretKey,
Groups: acc.Groups, Groups: acc.Credentials.Groups,
Claims: claims, Claims: claims,
SessionPolicy: json.RawMessage(policyJSON), SessionPolicy: json.RawMessage(policyJSON),
Status: acc.Status, Status: acc.Credentials.Status,
}, },
}, },
UpdatedAt: acc.UpdatedAt,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
@ -1761,6 +1821,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
IsGroup: false, IsGroup: false,
Policy: mp.Policies, Policy: mp.Policies,
}, },
UpdatedAt: mp.UpdatedAt,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
@ -1779,6 +1840,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
IsGroup: true, IsGroup: true,
Policy: mp.Policies, Policy: mp.Policies,
}, },
UpdatedAt: mp.UpdatedAt,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
@ -1807,6 +1869,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
IsGroup: false, IsGroup: false,
Policy: mp.Policies, Policy: mp.Policies,
}, },
UpdatedAt: mp.UpdatedAt,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
@ -1825,6 +1888,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
IsGroup: true, IsGroup: true,
Policy: mp.Policies, Policy: mp.Policies,
}, },
UpdatedAt: mp.UpdatedAt,
}) })
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
@ -3244,8 +3308,8 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
return info, errSRBackendIssue(err) return info, errSRBackendIssue(err)
} }
for _, tempAcct := range tempAccts { for _, tempAcct := range tempAccts {
info.UserInfoMap[tempAcct.AccessKey] = madmin.UserInfo{ info.UserInfoMap[tempAcct.Credentials.AccessKey] = madmin.UserInfo{
Status: madmin.AccountStatus(tempAcct.Status), Status: madmin.AccountStatus(tempAcct.Credentials.Status),
} }
} }
} }
@ -4166,6 +4230,7 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye
Type: madmin.SRIAMItemPolicy, Type: madmin.SRIAMItemPolicy,
Name: policy, Name: policy,
Policy: latestPolicyStat.policy.Policy, Policy: latestPolicyStat.policy.Policy,
UpdatedAt: lastUpdate,
}) })
if err != nil { if err != nil {
logger.LogIf(ctx, fmt.Errorf("Error healing IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err)) 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, IsGroup: false,
Policy: latestUserStat.userPolicy.Policy, Policy: latestUserStat.userPolicy.Policy,
}, },
UpdatedAt: lastUpdate,
}) })
if err != nil { 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)) 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, IsGroup: true,
Policy: latestGroupStat.groupPolicy.Policy, Policy: latestGroupStat.groupPolicy.Policy,
}, },
UpdatedAt: lastUpdate,
}) })
if err != nil { 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)) 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 peerName := info.Sites[dID].Name
creds, ok := globalIAMSys.GetUser(ctx, user) u, ok := globalIAMSys.GetUser(ctx, user)
if !ok { if !ok {
continue continue
} }
creds := u.Credentials
// heal only the user accounts that are local users // heal only the user accounts that are local users
if creds.IsServiceAccount() { if creds.IsServiceAccount() {
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey) claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
@ -4381,6 +4448,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
Status: creds.Status, Status: creds.Status,
}, },
}, },
UpdatedAt: lastUpdate,
}); err != nil { }); err != nil {
logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) 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, ParentUser: creds.ParentUser,
ParentPolicyMapping: u.PolicyName, ParentPolicyMapping: u.PolicyName,
}, },
UpdatedAt: lastUpdate,
}); err != nil { }); err != nil {
logger.LogIf(ctx, fmt.Errorf("Error healing temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) 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, Status: latestUserStat.userInfo.Status,
}, },
}, },
UpdatedAt: lastUpdate,
}); err != nil { }); err != nil {
logger.LogIf(ctx, fmt.Errorf("Error healing user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err)) 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, IsRemove: false,
}, },
}, },
UpdatedAt: lastUpdate,
}); err != nil { }); err != nil {
logger.LogIf(ctx, fmt.Errorf("Error healing group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err)) logger.LogIf(ctx, fmt.Errorf("Error healing group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
} }

View File

@ -275,7 +275,8 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
cred.ParentUser = user.AccessKey cred.ParentUser = user.AccessKey
// Set the newly generated credentials. // 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) writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return return
} }
@ -290,6 +291,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
SessionToken: cred.SessionToken, SessionToken: cred.SessionToken,
ParentUser: cred.ParentUser, ParentUser: cred.ParentUser,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
@ -469,7 +471,8 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
} }
// Set the newly generated credentials. // 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) writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return return
} }
@ -484,6 +487,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
ParentUser: cred.ParentUser, ParentUser: cred.ParentUser,
ParentPolicyMapping: policyName, ParentPolicyMapping: policyName,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
logger.LogIf(ctx, err) 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 // Set the newly generated credentials, policyName is empty on purpose
// LDAP policies are applied automatically using their ldapUser, ldapGroups // LDAP policies are applied automatically using their ldapUser, ldapGroups
// mapping. // 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) writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return return
} }
@ -653,6 +658,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
SessionToken: cred.SessionToken, SessionToken: cred.SessionToken,
ParentUser: cred.ParentUser, ParentUser: cred.ParentUser,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
@ -797,7 +803,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
tmpCredentials.ParentUser = parentUser tmpCredentials.ParentUser = parentUser
policyName := certificate.Subject.CommonName policyName := certificate.Subject.CommonName
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName) updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName)
if err != nil { if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return return
@ -813,6 +819,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
ParentUser: tmpCredentials.ParentUser, ParentUser: tmpCredentials.ParentUser,
ParentPolicyMapping: policyName, ParentPolicyMapping: policyName,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
@ -918,7 +925,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h
} }
tmpCredentials.ParentUser = parentUser tmpCredentials.ParentUser = parentUser
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "") updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "")
if err != nil { if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return return
@ -933,6 +940,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h
SessionToken: tmpCredentials.SessionToken, SessionToken: tmpCredentials.SessionToken,
ParentUser: tmpCredentials.ParentUser, ParentUser: tmpCredentials.ParentUser,
}, },
UpdatedAt: updatedAt,
}); err != nil { }); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return

View File

@ -285,13 +285,43 @@ if [ "${val}" != "val1" ]; then
echo "expected bucket tag to have replicated, exiting..." echo "expected bucket tag to have replicated, exiting..."
exit_1; exit_1;
fi 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} kill -9 ${site1_pid}
# Update tag on minio2/newbucket when minio1 is down # Update tag on minio2/newbucket when minio1 is down
./mc tag set minio2/newbucket "key=val2" ./mc tag set minio2/newbucket "key=val2"
# Restart minio1 instance # Restart minio1 instance
minio server --config-dir /tmp/minio-internal --address ":9001" /tmp/minio-internal-idp1/{1...4} >/tmp/minio1_1.log 2>&1 & 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 # 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 ) val=$(./mc tag list minio1/newbucket --json | jq -r .tagset | jq -r .key )
if [ "${val}" != "val2" ]; then if [ "${val}" != "val2" ]; then