mirror of
https://github.com/pgsty/minio.git
synced 2026-11-11 10:05:34 +03:00
fix(iam): replicate a service account as the record that is stored
Site replication sent an edit of a service account as the fields the request changed. A peer orders the items of an account by revision and drops an older one, which is right for a whole account and loses data for a part of one: when an edit did not reach a peer, or reached it after a later edit, the peer kept the old secret key, status or inline policy under the revision of the later edit, and healing took the sites for equal. Two requests at one site sufficed, as the sender dropped the item of the first when the second was already stored. The first sync of a new site, healing and IAM export put an account together from two places: the revision from storage, and the claims and the inline policy from the cache of the server; healing took the secret key and the status from the cache as well. A server whose cache had not heard of an edit made through another server sent the account from before the edit under the revision of the edit, and a peer kept that. replicationItem now sends a create or an edit as the account stored at that time: the whole record, read once, with its revision, as it does for a group. serviceAccountSnapshot builds it from the record and the token in it; IAM export uses the same function, and sync, heal and the update handler only name the account. The wire types and the way a peer applies an item do not change: a peer on 20261109 or 20261026 takes such an edit as it takes a healed account, which also means that it keeps the inline policy on an edit that names none. A site that is not upgraded still sends its edits as before. The hook reads what it sends before it takes the lock of the replication configuration: loading an identity takes that lock for reading, and a read lock asked for again behind a waiting writer is never granted. An account whose stored token cannot be read is an error, as it was for sync and heal, and is not taken for deleted. A new service account of a user that was deleted and created again now reaches the peers without waiting for healing: the create carries the claims of the token, among them the parent revocation a peer asks for. An edit gives every site the whole account of the site that took it. Accounts that already differ between sites under one revision are not repaired by this change, only by their next edit. Signed-off-by: Feng Ruohang <rh@vonng.com> Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -914,21 +914,13 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// Call site replication hook - non-root user accounts are replicated.
|
||||
// The hook sends the account as it is stored after this edit, not the
|
||||
// fields the request changed (see replicationItem).
|
||||
if svcAccount.ParentUser != globalActiveCred.AccessKey {
|
||||
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
Update: &madmin.SRSvcAccUpdate{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: opts.secretKey,
|
||||
Status: opts.status,
|
||||
Name: opts.name,
|
||||
Description: opts.description,
|
||||
SessionPolicy: madmin.SRSessionPolicy(updateReq.NewPolicy),
|
||||
Expiration: updateReq.NewExpiration,
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{Update: &madmin.SRSvcAccUpdate{AccessKey: accessKey}},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2399,37 +2391,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
// skip site-replication service account.
|
||||
continue
|
||||
}
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey)
|
||||
// The stored record only: this server's cache may not have
|
||||
// heard of an edit made through another server.
|
||||
svcAccts[user], err = serviceAccountSnapshot(acc)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
sa, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var policyJSON []byte
|
||||
if policy != nil {
|
||||
policyJSON, err = json.Marshal(policy)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
svcAccts[user] = madmin.SRSvcAccCreate{
|
||||
Parent: acc.Credentials.ParentUser,
|
||||
AccessKey: user,
|
||||
SecretKey: acc.Credentials.SecretKey,
|
||||
Groups: acc.Credentials.Groups,
|
||||
Claims: claims,
|
||||
SessionPolicy: policyJSON,
|
||||
Status: acc.Credentials.Status,
|
||||
Name: sa.Name,
|
||||
Description: sa.Description,
|
||||
Expiration: &sa.Expiration,
|
||||
}
|
||||
}
|
||||
|
||||
svcAccData, err := json.Marshal(svcAccts)
|
||||
|
||||
Reference in New Issue
Block a user