mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-14 15:58:52 +01:00
credential: migrate the legacy IAM file without overwriting newer entries
The migration wrote every identity, service account and group from the snapshot the load read, over whatever the per-entry files held by then, so a revoke or delete landing in between was undone for good. It now migrates only the legacy file's own entries, creates each per-entry file only when none exists, and waits for the next load when the legacy file changed after the snapshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a576398662
commit
6f10339dd2
1 file changed
+63
-13
@@ -1,10 +1,14 @@
|
||||
package filer_etc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
@@ -54,7 +58,7 @@ func (store *FilerEtcStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3Ap
|
||||
// This ensures that all identities (including legacy ones) are written to individual files
|
||||
// and the legacy file is renamed.
|
||||
if foundLegacy {
|
||||
if err := store.migrateToMultiFile(ctx, s3cfg); err != nil {
|
||||
if err := store.migrateToMultiFile(ctx, content); err != nil {
|
||||
glog.Errorf("Failed to migrate IAM configuration to multi-file layout: %v", err)
|
||||
return s3cfg, nil
|
||||
}
|
||||
@@ -137,26 +141,43 @@ func (store *FilerEtcStore) loadFromMultiFile(ctx context.Context, s3cfg *iam_pb
|
||||
return hasIdentities, nil
|
||||
}
|
||||
|
||||
func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, s3cfg *iam_pb.S3ApiConfiguration) error {
|
||||
func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyContent []byte) error {
|
||||
current, found, err := store.readInsideFiler(ctx, filer.IamConfigDirectory, IamLegacyIdentityFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || !bytes.Equal(current, legacyContent) {
|
||||
glog.V(0).Infof("%s changed during the IAM load, migrating it at the next load", IamLegacyIdentityFile)
|
||||
return nil
|
||||
}
|
||||
legacy := &iam_pb.S3ApiConfiguration{}
|
||||
if err := filer.ParseS3ConfigurationFromBytes(current, legacy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.Infof("Migrating IAM configuration to multi-file layout...")
|
||||
|
||||
// 1. Save all identities
|
||||
for _, identity := range s3cfg.Identities {
|
||||
if err := store.saveIdentity(ctx, identity); err != nil {
|
||||
// An existing per-entry file is newer than the legacy copy, and may have
|
||||
// been rewritten or removed after this load read it: never overwrite it.
|
||||
for _, identity := range legacy.Identities {
|
||||
if err := store.createIfAbsent(ctx, IamIdentitiesDirectory, identity.Name, identity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Save all service accounts
|
||||
for _, sa := range s3cfg.ServiceAccounts {
|
||||
if err := store.saveServiceAccount(ctx, sa); err != nil {
|
||||
for _, sa := range legacy.ServiceAccounts {
|
||||
if err := validateServiceAccountId(sa.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := store.createIfAbsent(ctx, IamServiceAccountsDirectory, sa.Id, sa); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Save all groups
|
||||
for _, g := range s3cfg.Groups {
|
||||
if err := store.saveGroup(ctx, g); err != nil {
|
||||
for _, g := range legacy.Groups {
|
||||
g.Name = strings.TrimSpace(g.Name)
|
||||
if g.Name == "" {
|
||||
return fmt.Errorf("group name is required")
|
||||
}
|
||||
if err := store.createIfAbsent(ctx, IamGroupsDirectory, g.Name, g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -487,6 +508,35 @@ func (store *FilerEtcStore) saveIdentity(ctx context.Context, identity *iam_pb.I
|
||||
})
|
||||
}
|
||||
|
||||
func (store *FilerEtcStore) createIfAbsent(ctx context.Context, subDir, name string, v any) error {
|
||||
data, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contentMd5 := md5.Sum(data)
|
||||
return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
err := filer_pb.CreateEntry(ctx, client, &filer_pb.CreateEntryRequest{
|
||||
Directory: filer.IamConfigDirectory + "/" + subDir,
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: name + ".json",
|
||||
Content: data,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(0644),
|
||||
FileSize: uint64(len(data)),
|
||||
Md5: contentMd5[:],
|
||||
},
|
||||
},
|
||||
OExcl: true,
|
||||
})
|
||||
if errors.Is(err, filer_pb.ErrEntryAlreadyExists) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *FilerEtcStore) readInsideFiler(ctx context.Context, dir string, name string) ([]byte, bool, error) {
|
||||
var content []byte
|
||||
found := false
|
||||
|
||||
Reference in new issue
Block a user