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:
+51
-30
@@ -1311,22 +1311,62 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
|
||||
return UserIdentity{}, nil, errNoSuchServiceAccount
|
||||
}
|
||||
|
||||
var embeddedPolicy *policy.Policy
|
||||
embeddedPolicy, err := embeddedServiceAccountPolicy(jwtClaims)
|
||||
if err != nil {
|
||||
return UserIdentity{}, nil, err
|
||||
}
|
||||
return sa, embeddedPolicy, nil
|
||||
}
|
||||
|
||||
// embeddedServiceAccountPolicy returns the policy embedded in the token of a
|
||||
// service account, or nil when the account has the policies of its parent.
|
||||
func embeddedServiceAccountPolicy(jwtClaims *jwt.MapClaims) (*policy.Policy, error) {
|
||||
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
|
||||
sp, spok := jwtClaims.Lookup(policy.SessionPolicyName)
|
||||
if ptok && spok && pt == embeddedPolicyType {
|
||||
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||
if err != nil {
|
||||
return UserIdentity{}, nil, err
|
||||
}
|
||||
embeddedPolicy, err = policy.ParseConfig(bytes.NewReader(policyBytes))
|
||||
if err != nil {
|
||||
return UserIdentity{}, nil, err
|
||||
if !ptok || !spok || pt != embeddedPolicyType {
|
||||
return nil, nil
|
||||
}
|
||||
policyBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy.ParseConfig(bytes.NewReader(policyBytes))
|
||||
}
|
||||
|
||||
// serviceAccountSnapshot returns service account u, a record loaded from
|
||||
// storage, as the create that replication, the first sync, heal and export
|
||||
// carry. Its claims and its embedded policy are read from the token of that
|
||||
// record, so that every part of the snapshot is of one revision: a server's
|
||||
// cache may hold the account as it was before an edit made through another
|
||||
// server.
|
||||
func serviceAccountSnapshot(u UserIdentity) (madmin.SRSvcAccCreate, error) {
|
||||
jwtClaims, err := extractJWTClaims(u)
|
||||
if err != nil {
|
||||
return madmin.SRSvcAccCreate{}, err
|
||||
}
|
||||
embeddedPolicy, err := embeddedServiceAccountPolicy(jwtClaims)
|
||||
if err != nil {
|
||||
return madmin.SRSvcAccCreate{}, err
|
||||
}
|
||||
var policyJSON []byte
|
||||
if embeddedPolicy != nil {
|
||||
if policyJSON, err = json.Marshal(embeddedPolicy); err != nil {
|
||||
return madmin.SRSvcAccCreate{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return sa, embeddedPolicy, nil
|
||||
cr := u.Credentials
|
||||
return madmin.SRSvcAccCreate{
|
||||
Parent: cr.ParentUser,
|
||||
AccessKey: cr.AccessKey,
|
||||
SecretKey: cr.SecretKey,
|
||||
Groups: cr.Groups,
|
||||
Claims: jwtClaims.Map(),
|
||||
SessionPolicy: policyJSON,
|
||||
Status: cr.Status,
|
||||
Name: cr.Name,
|
||||
Description: cr.Description,
|
||||
Expiration: &cr.Expiration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTemporaryAccount - wrapper method to get information about a temporary account
|
||||
@@ -1398,25 +1438,6 @@ func (sys *IAMSys) getAccountWithClaims(ctx context.Context, accessKey string) (
|
||||
return acc, jwtClaims, nil
|
||||
}
|
||||
|
||||
// GetClaimsForSvcAcc - gets the claims associated with the service account.
|
||||
func (sys *IAMSys) GetClaimsForSvcAcc(ctx context.Context, accessKey string) (map[string]any, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
}
|
||||
|
||||
sa, ok := sys.store.GetUser(accessKey)
|
||||
if !ok || !sa.Credentials.IsServiceAccount() {
|
||||
return nil, errNoSuchServiceAccount
|
||||
}
|
||||
|
||||
jwtClaims, err := extractJWTClaims(sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwtClaims.Map(), nil
|
||||
}
|
||||
|
||||
// DeleteServiceAccount - delete a service account
|
||||
func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string, notifyPeers bool) error {
|
||||
if !sys.Initialized() {
|
||||
|
||||
Reference in New Issue
Block a user