fix(iam): protect stale creates and cache loads

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-08 14:19:44 +08:00
parent 7eed95bc8e
commit 07ce95dfda
4 changed files with 144 additions and 1 deletions
+25
View File
@@ -60,6 +60,15 @@ type iamIdentityEdit struct {
unchanged bool
}
// An identity create is checked against the record under the same revision
// lock as the write. Local callers may recreate a deleted access key, but may
// never replace a live stored service account that their cache did not contain.
// Replicated creates retain the normal revision ordering below and may replace
// an older live snapshot when their source revision wins.
type iamIdentityCreate struct {
UserIdentity
}
var (
errIAMIdentityGone = errors.New("IAM identity is absent or deleted in storage")
errIAMIdentityUnchanged = errors.New("IAM identity edit changes nothing")
@@ -272,6 +281,22 @@ func saveIAMRevision(ctx context.Context, store IAMStorageAPI, path string, item
if err != nil {
return err
}
if op, ok := item.(*iamIdentityCreate); ok {
if _, replicated := iamReplicationTime(ctx); !replicated {
var current UserIdentity
err := store.loadIAMConfig(ctx, &current, path)
switch {
case err == nil && !current.Deleted:
return fmt.Errorf("%w: access key already exists in storage", errIAMServiceAccountNotAllowed)
case err == nil, errors.Is(err, errConfigNotFound):
// A tombstone or an absent record can be recreated. The
// existing revision path below retains its revocation boundary.
default:
return err
}
}
item = &op.UserIdentity
}
if op, ok := item.(*iamIdentityEdit); ok {
var current UserIdentity
err := store.loadIAMConfig(ctx, &current, path)