From e1190b3224638616cf4e1318ddcba0b1575f2130 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Tue, 3 Nov 2020 21:45:56 +0500 Subject: [PATCH 1/7] load S3 config from filer https://github.com/chrislusf/seaweedfs/issues/1500 --- weed/s3api/auth_credentials.go | 15 ++-- weed/s3api/auto_signature_v4_test.go | 6 +- weed/s3api/filer_util.go | 105 ++++++++++++++++++++++++++- weed/s3api/s3api_server.go | 2 +- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index 2b7666345..55cc9f727 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -43,15 +43,17 @@ type Credential struct { SecretKey string } -func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement { +func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement { iam := &IdentityAccessManagement{ - domain: domain, + domain: option.DomainName, } - if fileName == "" { - return iam + if err := loadS3config(iam, option); err != nil { + glog.Warningf("fail to load config %v", err) } - if err := iam.loadS3ApiConfiguration(fileName); err != nil { - glog.Fatalf("fail to load config file %s: %v", fileName, err) + if len(iam.identities) == 0 && option.Config != "" { + if err := iam.loadS3ApiConfiguration(option.Config); err != nil { + glog.Fatalf("fail to load config file %s: %v", option.Config, err) + } } return iam } @@ -59,7 +61,6 @@ func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccess func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error { s3ApiConfiguration := &iam_pb.S3ApiConfiguration{} - rawData, readErr := ioutil.ReadFile(fileName) if readErr != nil { glog.Warningf("fail to read %s : %v", fileName, readErr) diff --git a/weed/s3api/auto_signature_v4_test.go b/weed/s3api/auto_signature_v4_test.go index 8f1c9b470..4c8255768 100644 --- a/weed/s3api/auto_signature_v4_test.go +++ b/weed/s3api/auto_signature_v4_test.go @@ -57,7 +57,8 @@ func TestIsRequestPresignedSignatureV4(t *testing.T) { // Tests is requested authenticated function, tests replies for s3 errors. func TestIsReqAuthenticated(t *testing.T) { - iam := NewIdentityAccessManagement("", "") + option := S3ApiServerOption{} + iam := NewIdentityAccessManagement(&option) iam.identities = []*Identity{ { Name: "someone", @@ -92,7 +93,8 @@ func TestIsReqAuthenticated(t *testing.T) { } func TestCheckAdminRequestAuthType(t *testing.T) { - iam := NewIdentityAccessManagement("", "") + option := S3ApiServerOption{} + iam := NewIdentityAccessManagement(&option) iam.identities = []*Identity{ { Name: "someone", diff --git a/weed/s3api/filer_util.go b/weed/s3api/filer_util.go index ebdbe8245..f6cfed785 100644 --- a/weed/s3api/filer_util.go +++ b/weed/s3api/filer_util.go @@ -3,10 +3,13 @@ package s3api import ( "context" "fmt" - "strings" - "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + proto "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "strings" ) func (s3a *S3ApiServer) mkdir(parentDirectoryPath string, dirName string, fn func(entry *filer_pb.Entry)) error { @@ -75,6 +78,104 @@ func (s3a *S3ApiServer) exists(parentDirectoryPath string, entryName string, isD } +func loadS3config(iam *IdentityAccessManagement, option *S3ApiServerOption) error { + return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { + client := filer_pb.NewSeaweedFilerClient(grpcConnection) + resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{ + Directory: "/.configs", + Name: "s3identities", + }) + if err != nil { + return err + } + for name, ident := range resp.Entry.Extended { + t := &Identity{ + Name: name, + Credentials: nil, + Actions: nil, + } + identity := &iam_pb.Identity{} + if err := proto.Unmarshal(ident, identity); err != nil { + return err + } + for _, action := range identity.Actions { + t.Actions = append(t.Actions, Action(action)) + } + for _, cred := range identity.Credentials { + t.Credentials = append(t.Credentials, &Credential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + }) + glog.V(0).Infof("AccessKey %s, SecretKey: %s", cred.AccessKey, cred.SecretKey) + } + iam.identities = append(iam.identities, t) + } + return nil + }, option.FilerGrpcAddress, option.GrpcDialOption) +} + +/* testing save +func saveS3config(iam *IdentityAccessManagement, option *S3ApiServerOption) (error) { + return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { + client := filer_pb.NewSeaweedFilerClient(grpcConnection) + entry := &filer_pb.Entry{ + Name: "s3identities", + IsDirectory: false, + Attributes: &filer_pb.FuseAttributes{ + Mtime: time.Now().Unix(), + Crtime: time.Now().Unix(), + FileMode: uint32(0644), + Collection: "", + Replication: "", + }, + Extended: make(map[string][]byte), + } + for _, identity := range iam.identities { + glog.V(0).Infof("get iam identities %s", identity.Name) + i := &iam_pb.Identity{ + Name: identity.Name, + Credentials: []*iam_pb.Credential{}, + Actions: []string{}, + } + for _, cred := range identity.Credentials { + i.Credentials = append(i.Credentials, &iam_pb.Credential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + }) + } + for _, action := range identity.Actions { + i.Actions = append(i.Actions, string(action)) + } + ident, err := proto.Marshal(i) + if err != nil { + return err + } + entry.Extended[identity.Name] = ident + } + _, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{ + Directory: "/.configs", + Name: "s3identities", + }) + if err == filer_pb.ErrNotFound { + err = filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{ + Directory: "/.configs", + Entry: entry, + IsFromOtherCluster: false, + Signatures: nil, + }) + } else { + err = filer_pb.UpdateEntry(client, &filer_pb.UpdateEntryRequest{ + Directory: "/.configs", + Entry: entry, + IsFromOtherCluster: false, + Signatures: nil, + }) + } + return err + },option.FilerGrpcAddress, option.GrpcDialOption) +} +*/ + func objectKey(key *string) *string { if strings.HasPrefix(*key, "/") { t := (*key)[1:] diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index b1e1cfe80..850a02171 100644 --- a/weed/s3api/s3api_server.go +++ b/weed/s3api/s3api_server.go @@ -27,7 +27,7 @@ type S3ApiServer struct { func NewS3ApiServer(router *mux.Router, option *S3ApiServerOption) (s3ApiServer *S3ApiServer, err error) { s3ApiServer = &S3ApiServer{ option: option, - iam: NewIdentityAccessManagement(option.Config, option.DomainName), + iam: NewIdentityAccessManagement(option), } s3ApiServer.registerRouter(router) From 6206737df2ee5b6a999c9869fc48d9583c3f8e31 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 25 Nov 2020 21:02:31 +0500 Subject: [PATCH 2/7] s3 configure --- weed/s3api/filer_util.go | 87 +++++++++++----- weed/shell/command_s3_configure.go | 160 +++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 weed/shell/command_s3_configure.go diff --git a/weed/s3api/filer_util.go b/weed/s3api/filer_util.go index e4d7eb04f..7e61aa46d 100644 --- a/weed/s3api/filer_util.go +++ b/weed/s3api/filer_util.go @@ -3,16 +3,20 @@ package s3api import ( "context" "fmt" - "strings" - + "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/util" + "strings" + proto "github.com/golang/protobuf/proto" "google.golang.org/grpc" - "github.com/chrislusf/seaweedfs/weed/util" ) +const S3ConfName = "s3.conf" + func (s3a *S3ApiServer) mkdir(parentDirectoryPath string, dirName string, fn func(entry *filer_pb.Entry)) error { return filer_pb.Mkdir(s3a, parentDirectoryPath, dirName, fn) @@ -84,37 +88,68 @@ func (s3a *S3ApiServer) getEntry(parentDirectoryPath, entryName string) (entry * return filer_pb.GetEntry(s3a, fullPath) } +func LoadS3configFromEntryExtended(extended *map[string][]byte, identities *[]*Identity) (err error) { + for name, ident := range *extended { + t := &Identity{ + Name: name, + Credentials: nil, + Actions: nil, + } + identity := &iam_pb.Identity{} + if err := proto.Unmarshal(ident, identity); err != nil { + return err + } + for _, action := range identity.Actions { + t.Actions = append(t.Actions, Action(action)) + } + for _, cred := range identity.Credentials { + t.Credentials = append(t.Credentials, &Credential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + }) + } + *identities = append(*identities, t) + } + return nil +} + +func SaveS3configToEntryExtended(extended *map[string][]byte, identities *[]*Identity) (err error) { + for _, identity := range *identities { + i := &iam_pb.Identity{ + Name: identity.Name, + Credentials: []*iam_pb.Credential{}, + Actions: []string{}, + } + for _, cred := range identity.Credentials { + i.Credentials = append(i.Credentials, &iam_pb.Credential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + }) + } + for _, action := range identity.Actions { + i.Actions = append(i.Actions, string(action)) + } + ident, err := proto.Marshal(i) + if err != nil { + return err + } + (*extended)[identity.Name] = ident + } + return nil +} + func loadS3config(iam *IdentityAccessManagement, option *S3ApiServerOption) error { return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { client := filer_pb.NewSeaweedFilerClient(grpcConnection) resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{ - Directory: "/.configs", - Name: "s3identities", + Directory: filer.DirectoryEtc, + Name: S3ConfName, }) if err != nil { return err } - for name, ident := range resp.Entry.Extended { - t := &Identity{ - Name: name, - Credentials: nil, - Actions: nil, - } - identity := &iam_pb.Identity{} - if err := proto.Unmarshal(ident, identity); err != nil { - return err - } - for _, action := range identity.Actions { - t.Actions = append(t.Actions, Action(action)) - } - for _, cred := range identity.Credentials { - t.Credentials = append(t.Credentials, &Credential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - }) - glog.V(0).Infof("AccessKey %s, SecretKey: %s", cred.AccessKey, cred.SecretKey) - } - iam.identities = append(iam.identities, t) + if err = LoadS3configFromEntryExtended(&resp.Entry.Extended, &iam.identities); err != nil { + return err } return nil }, option.FilerGrpcAddress, option.GrpcDialOption) diff --git a/weed/shell/command_s3_configure.go b/weed/shell/command_s3_configure.go new file mode 100644 index 000000000..a4d45a4f9 --- /dev/null +++ b/weed/shell/command_s3_configure.go @@ -0,0 +1,160 @@ +package shell + +import ( + "flag" + "fmt" + "github.com/chrislusf/seaweedfs/weed/s3api" + "io" + "sort" + "strings" + + "github.com/chrislusf/seaweedfs/weed/filer" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" +) + +func init() { + Commands = append(Commands, &commandS3Configure{}) +} + +type commandS3Configure struct { +} + +func (c *commandS3Configure) Name() string { + return "s3.configure" +} + +func (c *commandS3Configure) Help() string { + return `configure and apply s3 options for each bucket + # see the current configuration file content + s3.configure + ` +} + +func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) { + s3ConfigureCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + actions := s3ConfigureCommand.String("actions", "", "actions names") + user := s3ConfigureCommand.String("user", "", "user name") + buckets := s3ConfigureCommand.String("buckets", "", "bucket name") + accessKey := s3ConfigureCommand.String("access_key", "", "specify the access key") + secretKey := s3ConfigureCommand.String("secret_key", "", "specify the secret key") + isDelete := s3ConfigureCommand.Bool("delete", false, "delete users, actions or access keys") + apply := s3ConfigureCommand.Bool("apply", false, "update and apply s3 configuration") + + if err = s3ConfigureCommand.Parse(args); err != nil { + return nil + } + + var identities []*s3api.Identity + if err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + request := &filer_pb.LookupDirectoryEntryRequest{ + Directory: filer.DirectoryEtc, + Name: s3api.S3ConfName, + } + respLookupEntry, err := filer_pb.LookupEntry(client, request) + if err != nil { + return err + } + if err = s3api.LoadS3configFromEntryExtended(&respLookupEntry.Entry.Extended, &identities); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + idx := 0 + changed := false + if *user != "" && *buckets != "" { + for i, identity := range identities { + if *user == identity.Name { + idx = i + changed = true + break + } + } + } + cmdActions := []s3api.Action{} + for _, bucket := range strings.Split(*buckets, ",") { + for _, action := range strings.Split(*actions, ",") { + cmdActions = append(cmdActions, s3api.Action(fmt.Sprintf("%s:%s", action, bucket))) + } + } + cmdCredential := &s3api.Credential{ + AccessKey: *accessKey, + SecretKey: *secretKey, + } + + if changed { + if *isDelete { + exists := []int{} + for _, cmdAction := range cmdActions { + for i, currentAction := range identities[idx].Actions { + if cmdAction == currentAction { + exists = append(exists, i) + } + } + } + sort.Sort(sort.Reverse(sort.IntSlice(exists))) + for _, i := range exists { + identities[idx].Actions = append(identities[idx].Actions[:i], identities[idx].Actions[i+1:]...) + } + if *accessKey != "" { + exists = []int{} + for i, credential := range identities[idx].Credentials { + if credential.AccessKey == *accessKey { + exists = append(exists, i) + } + } + sort.Sort(sort.Reverse(sort.IntSlice(exists))) + for _, i := range exists { + identities[idx].Credentials = append(identities[idx].Credentials[:i], identities[idx].Credentials[:i+1]...) + } + + } + if *actions == "" && *accessKey == "" { + identities = append(identities[:idx], identities[idx+1:]...) + } + } else { + identities[idx].Actions = append(identities[idx].Actions, cmdActions...) + identities[idx].Credentials = append(identities[idx].Credentials, &s3api.Credential{ + AccessKey: *accessKey, + SecretKey: *secretKey, + }) + } + } else { + identity := s3api.Identity{ + Name: *user, + Actions: cmdActions, + } + identity.Credentials = append(identity.Credentials, &s3api.Credential{ + AccessKey: *accessKey, + SecretKey: *secretKey, + }) + identities = append(identities, &identity) + } + + fmt.Fprintf(writer, fmt.Sprintf("%+v\n", identities)) + fmt.Fprintln(writer) + + if !*apply { + return nil + } + + if err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + request := &filer_pb.LookupDirectoryEntryRequest{ + Directory: filer.DirectoryEtc, + Name: s3api.S3ConfName, + } + respLookupEntry, err := filer_pb.LookupEntry(client, request) + if err != nil { + return err + } + if err = s3api.SaveS3configToEntryExtended(&respLookupEntry.Entry.Extended, &identities); err != nil { + return err + } + return nil + }); err != nil { + return err + } + return nil +} From a26f1b2040762903c4b700a0508c0b4c66d4cad5 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 26 Nov 2020 01:30:11 +0500 Subject: [PATCH 3/7] new pkg s3iam --- weed/s3api/auth_credentials.go | 33 +++++-- weed/s3api/filer_util.go | 137 ----------------------------- weed/s3iam/s3iam_filer_store.go | 104 ++++++++++++++++++++++ weed/shell/command_s3_configure.go | 81 +++++++---------- 4 files changed, 163 insertions(+), 192 deletions(-) create mode 100644 weed/s3iam/s3iam_filer_store.go diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index cc259645d..de1a0e3a1 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -3,11 +3,15 @@ package s3api import ( "bytes" "fmt" + "github.com/chrislusf/seaweedfs/weed/pb" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "google.golang.org/grpc" "io/ioutil" "net/http" xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + "github.com/chrislusf/seaweedfs/weed/s3iam" "github.com/golang/protobuf/jsonpb" "github.com/chrislusf/seaweedfs/weed/glog" @@ -48,19 +52,33 @@ func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManag iam := &IdentityAccessManagement{ domain: option.DomainName, } - if err := loadS3config(iam, option); err != nil { + if err := iam.loadS3ApiConfigurationFromFiler(option); err != nil { glog.Warningf("fail to load config %v", err) } if len(iam.identities) == 0 && option.Config != "" { - if err := iam.loadS3ApiConfiguration(option.Config); err != nil { + if err := iam.loadS3ApiConfigurationFromFile(option.Config); err != nil { glog.Fatalf("fail to load config file %s: %v", option.Config, err) } } return iam } -func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error { +func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3ApiServerOption) error { + s3ApiConfiguration := &iam_pb.S3ApiConfiguration{} + return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { + client := filer_pb.NewSeaweedFilerClient(grpcConnection) + store := s3iam.NewIAMFilerStore(&client) + if err := store.LoadIAMConfig(s3ApiConfiguration); err != nil { + return nil + } + if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil { + return err + } + return nil + }, option.FilerGrpcAddress, option.GrpcDialOption) +} +func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error { s3ApiConfiguration := &iam_pb.S3ApiConfiguration{} rawData, readErr := ioutil.ReadFile(fileName) if readErr != nil { @@ -73,8 +91,14 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) err glog.Warningf("unmarshal error: %v", err) return fmt.Errorf("unmarshal %s error: %v", fileName, err) } + if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil { + return err + } + return nil +} - for _, ident := range s3ApiConfiguration.Identities { +func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error { + for _, ident := range config.Identities { t := &Identity{ Name: ident.Name, Credentials: nil, @@ -91,7 +115,6 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) err } iam.identities = append(iam.identities, t) } - return nil } diff --git a/weed/s3api/filer_util.go b/weed/s3api/filer_util.go index 7e61aa46d..3626ece98 100644 --- a/weed/s3api/filer_util.go +++ b/weed/s3api/filer_util.go @@ -3,20 +3,12 @@ package s3api import ( "context" "fmt" - "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" - "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" - "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/chrislusf/seaweedfs/weed/util" "strings" - - proto "github.com/golang/protobuf/proto" - "google.golang.org/grpc" ) -const S3ConfName = "s3.conf" - func (s3a *S3ApiServer) mkdir(parentDirectoryPath string, dirName string, fn func(entry *filer_pb.Entry)) error { return filer_pb.Mkdir(s3a, parentDirectoryPath, dirName, fn) @@ -88,135 +80,6 @@ func (s3a *S3ApiServer) getEntry(parentDirectoryPath, entryName string) (entry * return filer_pb.GetEntry(s3a, fullPath) } -func LoadS3configFromEntryExtended(extended *map[string][]byte, identities *[]*Identity) (err error) { - for name, ident := range *extended { - t := &Identity{ - Name: name, - Credentials: nil, - Actions: nil, - } - identity := &iam_pb.Identity{} - if err := proto.Unmarshal(ident, identity); err != nil { - return err - } - for _, action := range identity.Actions { - t.Actions = append(t.Actions, Action(action)) - } - for _, cred := range identity.Credentials { - t.Credentials = append(t.Credentials, &Credential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - }) - } - *identities = append(*identities, t) - } - return nil -} - -func SaveS3configToEntryExtended(extended *map[string][]byte, identities *[]*Identity) (err error) { - for _, identity := range *identities { - i := &iam_pb.Identity{ - Name: identity.Name, - Credentials: []*iam_pb.Credential{}, - Actions: []string{}, - } - for _, cred := range identity.Credentials { - i.Credentials = append(i.Credentials, &iam_pb.Credential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - }) - } - for _, action := range identity.Actions { - i.Actions = append(i.Actions, string(action)) - } - ident, err := proto.Marshal(i) - if err != nil { - return err - } - (*extended)[identity.Name] = ident - } - return nil -} - -func loadS3config(iam *IdentityAccessManagement, option *S3ApiServerOption) error { - return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { - client := filer_pb.NewSeaweedFilerClient(grpcConnection) - resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{ - Directory: filer.DirectoryEtc, - Name: S3ConfName, - }) - if err != nil { - return err - } - if err = LoadS3configFromEntryExtended(&resp.Entry.Extended, &iam.identities); err != nil { - return err - } - return nil - }, option.FilerGrpcAddress, option.GrpcDialOption) -} - -/* testing save -func saveS3config(iam *IdentityAccessManagement, option *S3ApiServerOption) (error) { - return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error { - client := filer_pb.NewSeaweedFilerClient(grpcConnection) - entry := &filer_pb.Entry{ - Name: "s3identities", - IsDirectory: false, - Attributes: &filer_pb.FuseAttributes{ - Mtime: time.Now().Unix(), - Crtime: time.Now().Unix(), - FileMode: uint32(0644), - Collection: "", - Replication: "", - }, - Extended: make(map[string][]byte), - } - for _, identity := range iam.identities { - glog.V(0).Infof("get iam identities %s", identity.Name) - i := &iam_pb.Identity{ - Name: identity.Name, - Credentials: []*iam_pb.Credential{}, - Actions: []string{}, - } - for _, cred := range identity.Credentials { - i.Credentials = append(i.Credentials, &iam_pb.Credential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - }) - } - for _, action := range identity.Actions { - i.Actions = append(i.Actions, string(action)) - } - ident, err := proto.Marshal(i) - if err != nil { - return err - } - entry.Extended[identity.Name] = ident - } - _, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{ - Directory: "/.configs", - Name: "s3identities", - }) - if err == filer_pb.ErrNotFound { - err = filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{ - Directory: "/.configs", - Entry: entry, - IsFromOtherCluster: false, - Signatures: nil, - }) - } else { - err = filer_pb.UpdateEntry(client, &filer_pb.UpdateEntryRequest{ - Directory: "/.configs", - Entry: entry, - IsFromOtherCluster: false, - Signatures: nil, - }) - } - return err - },option.FilerGrpcAddress, option.GrpcDialOption) -} -*/ - func objectKey(key *string) *string { if strings.HasPrefix(*key, "/") { t := (*key)[1:] diff --git a/weed/s3iam/s3iam_filer_store.go b/weed/s3iam/s3iam_filer_store.go new file mode 100644 index 000000000..22bce2bc4 --- /dev/null +++ b/weed/s3iam/s3iam_filer_store.go @@ -0,0 +1,104 @@ +package s3iam + +import ( + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "time" + + proto "github.com/golang/protobuf/proto" +) + +const ( + iamConfigPrefix = "/etc/iam" + iamIdentityFile = "identity.json" +) + +type IAMFilerStore struct { + client *filer_pb.SeaweedFilerClient +} + +func NewIAMFilerStore(client *filer_pb.SeaweedFilerClient) *IAMFilerStore { + return &IAMFilerStore{client: client} +} + +func (ifs *IAMFilerStore) getIAMConfigRequest() *filer_pb.LookupDirectoryEntryRequest { + return &filer_pb.LookupDirectoryEntryRequest{ + Directory: iamConfigPrefix, + Name: iamIdentityFile, + } +} + +func (ifs *IAMFilerStore) LoadIAMConfig(config *iam_pb.S3ApiConfiguration) error { + resp, err := filer_pb.LookupEntry(*ifs.client, ifs.getIAMConfigRequest()) + if err != nil { + return err + } + err = ifs.loadIAMConfigFromEntryExtended(&resp.Entry.Extended, config) + if err != nil { + return err + } + return nil +} + +func (ifs *IAMFilerStore) SaveIAMConfig(config *iam_pb.S3ApiConfiguration) error { + entry := &filer_pb.Entry{ + Name: iamIdentityFile, + IsDirectory: false, + Attributes: &filer_pb.FuseAttributes{ + Mtime: time.Now().Unix(), + Crtime: time.Now().Unix(), + FileMode: uint32(0644), + Collection: "", + Replication: "", + }, + Extended: make(map[string][]byte), + } + + err := ifs.saveIAMConfigToEntryExtended(&entry.Extended, config) + if err != nil { + return err + } + + _, err = filer_pb.LookupEntry(*ifs.client, ifs.getIAMConfigRequest()) + if err == filer_pb.ErrNotFound { + err = filer_pb.CreateEntry(*ifs.client, &filer_pb.CreateEntryRequest{ + Directory: iamConfigPrefix, + Entry: entry, + IsFromOtherCluster: false, + Signatures: nil, + }) + } else { + err = filer_pb.UpdateEntry(*ifs.client, &filer_pb.UpdateEntryRequest{ + Directory: iamConfigPrefix, + Entry: entry, + IsFromOtherCluster: false, + Signatures: nil, + }) + } + if err != nil { + return err + } + return nil +} + +func (ifs *IAMFilerStore) loadIAMConfigFromEntryExtended(extended *map[string][]byte, config *iam_pb.S3ApiConfiguration) error { + for _, ident := range *extended { + identity := &iam_pb.Identity{} + if err := proto.Unmarshal(ident, identity); err != nil { + return err + } + config.Identities = append(config.Identities, identity) + } + return nil +} + +func (ifs *IAMFilerStore) saveIAMConfigToEntryExtended(extended *map[string][]byte, config *iam_pb.S3ApiConfiguration) error { + for _, identity := range config.Identities { + ident, err := proto.Marshal(identity) + if err != nil { + return err + } + (*extended)[identity.Name] = ident + } + return nil +} diff --git a/weed/shell/command_s3_configure.go b/weed/shell/command_s3_configure.go index a4d45a4f9..340d20894 100644 --- a/weed/shell/command_s3_configure.go +++ b/weed/shell/command_s3_configure.go @@ -3,13 +3,13 @@ package shell import ( "flag" "fmt" - "github.com/chrislusf/seaweedfs/weed/s3api" "io" "sort" "strings" - "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/s3iam" ) func init() { @@ -44,18 +44,12 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io return nil } - var identities []*s3api.Identity + s3cfg := &iam_pb.S3ApiConfiguration{} + ifs := &s3iam.IAMFilerStore{} if err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { - request := &filer_pb.LookupDirectoryEntryRequest{ - Directory: filer.DirectoryEtc, - Name: s3api.S3ConfName, - } - respLookupEntry, err := filer_pb.LookupEntry(client, request) - if err != nil { - return err - } - if err = s3api.LoadS3configFromEntryExtended(&respLookupEntry.Entry.Extended, &identities); err != nil { - return err + ifs = s3iam.NewIAMFilerStore(&client) + if err := ifs.LoadIAMConfig(s3cfg); err != nil { + return nil } return nil }); err != nil { @@ -65,7 +59,7 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io idx := 0 changed := false if *user != "" && *buckets != "" { - for i, identity := range identities { + for i, identity := range s3cfg.Identities { if *user == identity.Name { idx = i changed = true @@ -73,22 +67,17 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io } } } - cmdActions := []s3api.Action{} + var cmdActions []string for _, bucket := range strings.Split(*buckets, ",") { for _, action := range strings.Split(*actions, ",") { - cmdActions = append(cmdActions, s3api.Action(fmt.Sprintf("%s:%s", action, bucket))) + cmdActions = append(cmdActions, fmt.Sprintf("%s:%s", action, bucket)) } } - cmdCredential := &s3api.Credential{ - AccessKey: *accessKey, - SecretKey: *secretKey, - } - if changed { if *isDelete { - exists := []int{} + var exists []int for _, cmdAction := range cmdActions { - for i, currentAction := range identities[idx].Actions { + for i, currentAction := range s3cfg.Identities[idx].Actions { if cmdAction == currentAction { exists = append(exists, i) } @@ -96,65 +85,57 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io } sort.Sort(sort.Reverse(sort.IntSlice(exists))) for _, i := range exists { - identities[idx].Actions = append(identities[idx].Actions[:i], identities[idx].Actions[i+1:]...) + s3cfg.Identities[idx].Actions = append( + s3cfg.Identities[idx].Actions[:i], + s3cfg.Identities[idx].Actions[i+1:]..., + ) } if *accessKey != "" { exists = []int{} - for i, credential := range identities[idx].Credentials { + for i, credential := range s3cfg.Identities[idx].Credentials { if credential.AccessKey == *accessKey { exists = append(exists, i) } } sort.Sort(sort.Reverse(sort.IntSlice(exists))) for _, i := range exists { - identities[idx].Credentials = append(identities[idx].Credentials[:i], identities[idx].Credentials[:i+1]...) + s3cfg.Identities[idx].Credentials = append( + s3cfg.Identities[idx].Credentials[:i], + s3cfg.Identities[idx].Credentials[:i+1]..., + ) } } if *actions == "" && *accessKey == "" { - identities = append(identities[:idx], identities[idx+1:]...) + s3cfg.Identities = append(s3cfg.Identities[:idx], s3cfg.Identities[idx+1:]...) } } else { - identities[idx].Actions = append(identities[idx].Actions, cmdActions...) - identities[idx].Credentials = append(identities[idx].Credentials, &s3api.Credential{ + s3cfg.Identities[idx].Actions = append(s3cfg.Identities[idx].Actions, cmdActions...) + s3cfg.Identities[idx].Credentials = append(s3cfg.Identities[idx].Credentials, &iam_pb.Credential{ AccessKey: *accessKey, SecretKey: *secretKey, }) } } else { - identity := s3api.Identity{ + identity := iam_pb.Identity{ Name: *user, Actions: cmdActions, } - identity.Credentials = append(identity.Credentials, &s3api.Credential{ + identity.Credentials = append(identity.Credentials, &iam_pb.Credential{ AccessKey: *accessKey, SecretKey: *secretKey, }) - identities = append(identities, &identity) + s3cfg.Identities = append(s3cfg.Identities, &identity) } - fmt.Fprintf(writer, fmt.Sprintf("%+v\n", identities)) + fmt.Fprintf(writer, fmt.Sprintf("%+v\n", s3cfg.Identities)) fmt.Fprintln(writer) - if !*apply { - return nil - } - - if err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { - request := &filer_pb.LookupDirectoryEntryRequest{ - Directory: filer.DirectoryEtc, - Name: s3api.S3ConfName, - } - respLookupEntry, err := filer_pb.LookupEntry(client, request) - if err != nil { - return err - } - if err = s3api.SaveS3configToEntryExtended(&respLookupEntry.Entry.Extended, &identities); err != nil { + if *apply { + if err := ifs.SaveIAMConfig(s3cfg); err != nil { return err } - return nil - }); err != nil { - return err } + return nil } From 52c8f2fc9a99e72cc8f0df32534b4d06f11cdbdd Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 26 Nov 2020 02:26:45 +0500 Subject: [PATCH 4/7] s3iam test --- weed/s3iam/s3iam_filer_store_test.go | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 weed/s3iam/s3iam_filer_store_test.go diff --git a/weed/s3iam/s3iam_filer_store_test.go b/weed/s3iam/s3iam_filer_store_test.go new file mode 100644 index 000000000..b87dd4f17 --- /dev/null +++ b/weed/s3iam/s3iam_filer_store_test.go @@ -0,0 +1,63 @@ +package s3iam + +import ( + "testing" + + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + + "github.com/stretchr/testify/assert" +) + +const ( + ACTION_READ = "Read" + ACTION_WRITE = "Write" + ACTION_ADMIN = "Admin" + ACTION_TAGGING = "Tagging" + ACTION_LIST = "List" +) + +func TestS3Conf(t *testing.T) { + ifs := &IAMFilerStore{} + s3Conf := &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "some_name", + Credentials: []*iam_pb.Credential{ + { + AccessKey: "some_access_key1", + SecretKey: "some_secret_key1", + }, + }, + Actions: []string{ + ACTION_ADMIN, + ACTION_READ, + ACTION_WRITE, + }, + }, + { + Name: "some_read_only_user", + Credentials: []*iam_pb.Credential{ + { + AccessKey: "some_access_key2", + SecretKey: "some_secret_key2", + }, + }, + Actions: []string{ + ACTION_READ, + ACTION_TAGGING, + ACTION_LIST, + }, + }, + }, + } + s3ConfSaved := &iam_pb.S3ApiConfiguration{} + extended := make(map[string][]byte) + _ = ifs.saveIAMConfigToEntryExtended(&extended, s3Conf) + _ = ifs.loadIAMConfigFromEntryExtended(&extended, s3ConfSaved) + + assert.Equal(t, "some_name", s3ConfSaved.Identities[0].Name) + assert.Equal(t, "some_read_only_user", s3ConfSaved.Identities[1].Name) + assert.Equal(t, "some_access_key1", s3ConfSaved.Identities[0].Credentials[0].AccessKey) + assert.Equal(t, "some_secret_key2", s3ConfSaved.Identities[1].Credentials[0].SecretKey) + +} From 4e55baf5b109cfe5cf9f65c44cd92c542b4acf5e Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 26 Nov 2020 03:50:53 +0500 Subject: [PATCH 5/7] s3 config changes --- weed/shell/command_s3_configure.go | 69 ++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/weed/shell/command_s3_configure.go b/weed/shell/command_s3_configure.go index 340d20894..c1ac1ce74 100644 --- a/weed/shell/command_s3_configure.go +++ b/weed/shell/command_s3_configure.go @@ -58,7 +58,7 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io idx := 0 changed := false - if *user != "" && *buckets != "" { + if *user != "" { for i, identity := range s3cfg.Identities { if *user == identity.Name { idx = i @@ -68,9 +68,13 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io } } var cmdActions []string - for _, bucket := range strings.Split(*buckets, ",") { - for _, action := range strings.Split(*actions, ",") { - cmdActions = append(cmdActions, fmt.Sprintf("%s:%s", action, bucket)) + for _, action := range strings.Split(*actions, ",") { + if *buckets == "" { + cmdActions = append(cmdActions, action) + } else { + for _, bucket := range strings.Split(*buckets, ",") { + cmdActions = append(cmdActions, fmt.Sprintf("%s:%s", action, bucket)) + } } } if changed { @@ -106,29 +110,58 @@ func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io } } - if *actions == "" && *accessKey == "" { + if *actions == "" && *accessKey == "" && *buckets == "" { s3cfg.Identities = append(s3cfg.Identities[:idx], s3cfg.Identities[idx+1:]...) } } else { - s3cfg.Identities[idx].Actions = append(s3cfg.Identities[idx].Actions, cmdActions...) - s3cfg.Identities[idx].Credentials = append(s3cfg.Identities[idx].Credentials, &iam_pb.Credential{ - AccessKey: *accessKey, - SecretKey: *secretKey, - }) + if *actions != "" { + for _, cmdAction := range cmdActions { + found := false + for _, action := range s3cfg.Identities[idx].Actions { + if cmdAction == action { + found = true + break + } + } + if !found { + s3cfg.Identities[idx].Actions = append(s3cfg.Identities[idx].Actions, cmdAction) + } + } + } + if *accessKey != "" && *user != "anonymous" { + found := false + for _, credential := range s3cfg.Identities[idx].Credentials { + if credential.AccessKey == *accessKey { + found = true + credential.SecretKey = *secretKey + break + } + } + if !found { + s3cfg.Identities[idx].Credentials = append(s3cfg.Identities[idx].Credentials, &iam_pb.Credential{ + AccessKey: *accessKey, + SecretKey: *secretKey, + }) + } + } } - } else { + } else if *user != "" && *actions != "" { identity := iam_pb.Identity{ - Name: *user, - Actions: cmdActions, + Name: *user, + Actions: cmdActions, + Credentials: []*iam_pb.Credential{}, + } + if *user != "anonymous" { + identity.Credentials = append(identity.Credentials, + &iam_pb.Credential{AccessKey: *accessKey, SecretKey: *secretKey}) } - identity.Credentials = append(identity.Credentials, &iam_pb.Credential{ - AccessKey: *accessKey, - SecretKey: *secretKey, - }) s3cfg.Identities = append(s3cfg.Identities, &identity) } - fmt.Fprintf(writer, fmt.Sprintf("%+v\n", s3cfg.Identities)) + for _, identity := range s3cfg.Identities { + fmt.Fprintf(writer, fmt.Sprintf("%+v\n", identity)) + } + fmt.Fprintln(writer) if *apply { From a3d4b50a491237052c2b4ab3048aae53568d0887 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 2 Dec 2020 16:12:13 +0500 Subject: [PATCH 6/7] use entry content filed --- weed/s3iam/s3iam_filer_store.go | 29 ++++++++++------------------ weed/s3iam/s3iam_filer_store_test.go | 7 +++---- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/weed/s3iam/s3iam_filer_store.go b/weed/s3iam/s3iam_filer_store.go index 22bce2bc4..045681a99 100644 --- a/weed/s3iam/s3iam_filer_store.go +++ b/weed/s3iam/s3iam_filer_store.go @@ -33,7 +33,7 @@ func (ifs *IAMFilerStore) LoadIAMConfig(config *iam_pb.S3ApiConfiguration) error if err != nil { return err } - err = ifs.loadIAMConfigFromEntryExtended(&resp.Entry.Extended, config) + err = ifs.loadIAMConfigFromEntry(resp.Entry.Content, config) if err != nil { return err } @@ -51,14 +51,12 @@ func (ifs *IAMFilerStore) SaveIAMConfig(config *iam_pb.S3ApiConfiguration) error Collection: "", Replication: "", }, - Extended: make(map[string][]byte), + Content: []byte{}, } - - err := ifs.saveIAMConfigToEntryExtended(&entry.Extended, config) + err := ifs.saveIAMConfigToEntry(entry.Content, config) if err != nil { return err } - _, err = filer_pb.LookupEntry(*ifs.client, ifs.getIAMConfigRequest()) if err == filer_pb.ErrNotFound { err = filer_pb.CreateEntry(*ifs.client, &filer_pb.CreateEntryRequest{ @@ -81,24 +79,17 @@ func (ifs *IAMFilerStore) SaveIAMConfig(config *iam_pb.S3ApiConfiguration) error return nil } -func (ifs *IAMFilerStore) loadIAMConfigFromEntryExtended(extended *map[string][]byte, config *iam_pb.S3ApiConfiguration) error { - for _, ident := range *extended { - identity := &iam_pb.Identity{} - if err := proto.Unmarshal(ident, identity); err != nil { - return err - } - config.Identities = append(config.Identities, identity) +func (ifs *IAMFilerStore) loadIAMConfigFromEntry(content []byte, config *iam_pb.S3ApiConfiguration) error { + if err := proto.Unmarshal(content, config); err != nil { + return err } return nil } -func (ifs *IAMFilerStore) saveIAMConfigToEntryExtended(extended *map[string][]byte, config *iam_pb.S3ApiConfiguration) error { - for _, identity := range config.Identities { - ident, err := proto.Marshal(identity) - if err != nil { - return err - } - (*extended)[identity.Name] = ident +func (ifs *IAMFilerStore) saveIAMConfigToEntry(content []byte, config *iam_pb.S3ApiConfiguration) error { + content, err := proto.Marshal(config) + if err != nil { + return err } return nil } diff --git a/weed/s3iam/s3iam_filer_store_test.go b/weed/s3iam/s3iam_filer_store_test.go index b87dd4f17..fca83ee92 100644 --- a/weed/s3iam/s3iam_filer_store_test.go +++ b/weed/s3iam/s3iam_filer_store_test.go @@ -50,14 +50,13 @@ func TestS3Conf(t *testing.T) { }, }, } + content := []byte{} + _ = ifs.saveIAMConfigToEntry(content, s3Conf) s3ConfSaved := &iam_pb.S3ApiConfiguration{} - extended := make(map[string][]byte) - _ = ifs.saveIAMConfigToEntryExtended(&extended, s3Conf) - _ = ifs.loadIAMConfigFromEntryExtended(&extended, s3ConfSaved) + _ = ifs.loadIAMConfigFromEntry(content, s3ConfSaved) assert.Equal(t, "some_name", s3ConfSaved.Identities[0].Name) assert.Equal(t, "some_read_only_user", s3ConfSaved.Identities[1].Name) assert.Equal(t, "some_access_key1", s3ConfSaved.Identities[0].Credentials[0].AccessKey) assert.Equal(t, "some_secret_key2", s3ConfSaved.Identities[1].Credentials[0].SecretKey) - } From 14699dfcef11493b6823503be948841f6d2921bb Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 2 Dec 2020 17:19:05 +0500 Subject: [PATCH 7/7] use content field of entry --- weed/s3iam/s3iam_filer_store.go | 12 ++++++------ weed/s3iam/s3iam_filer_store_test.go | 9 ++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/weed/s3iam/s3iam_filer_store.go b/weed/s3iam/s3iam_filer_store.go index 045681a99..4f84a0e54 100644 --- a/weed/s3iam/s3iam_filer_store.go +++ b/weed/s3iam/s3iam_filer_store.go @@ -33,7 +33,7 @@ func (ifs *IAMFilerStore) LoadIAMConfig(config *iam_pb.S3ApiConfiguration) error if err != nil { return err } - err = ifs.loadIAMConfigFromEntry(resp.Entry.Content, config) + err = ifs.loadIAMConfigFromEntry(resp.Entry, config) if err != nil { return err } @@ -53,7 +53,7 @@ func (ifs *IAMFilerStore) SaveIAMConfig(config *iam_pb.S3ApiConfiguration) error }, Content: []byte{}, } - err := ifs.saveIAMConfigToEntry(entry.Content, config) + err := ifs.saveIAMConfigToEntry(entry, config) if err != nil { return err } @@ -79,15 +79,15 @@ func (ifs *IAMFilerStore) SaveIAMConfig(config *iam_pb.S3ApiConfiguration) error return nil } -func (ifs *IAMFilerStore) loadIAMConfigFromEntry(content []byte, config *iam_pb.S3ApiConfiguration) error { - if err := proto.Unmarshal(content, config); err != nil { +func (ifs *IAMFilerStore) loadIAMConfigFromEntry(entry *filer_pb.Entry, config *iam_pb.S3ApiConfiguration) error { + if err := proto.Unmarshal(entry.Content, config); err != nil { return err } return nil } -func (ifs *IAMFilerStore) saveIAMConfigToEntry(content []byte, config *iam_pb.S3ApiConfiguration) error { - content, err := proto.Marshal(config) +func (ifs *IAMFilerStore) saveIAMConfigToEntry(entry *filer_pb.Entry, config *iam_pb.S3ApiConfiguration) (err error) { + entry.Content, err = proto.Marshal(config) if err != nil { return err } diff --git a/weed/s3iam/s3iam_filer_store_test.go b/weed/s3iam/s3iam_filer_store_test.go index fca83ee92..6c595134e 100644 --- a/weed/s3iam/s3iam_filer_store_test.go +++ b/weed/s3iam/s3iam_filer_store_test.go @@ -3,6 +3,7 @@ package s3iam import ( "testing" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/stretchr/testify/assert" @@ -50,10 +51,12 @@ func TestS3Conf(t *testing.T) { }, }, } - content := []byte{} - _ = ifs.saveIAMConfigToEntry(content, s3Conf) + entry := filer_pb.Entry{} + err := ifs.saveIAMConfigToEntry(&entry, s3Conf) + assert.Equal(t, err, nil) s3ConfSaved := &iam_pb.S3ApiConfiguration{} - _ = ifs.loadIAMConfigFromEntry(content, s3ConfSaved) + err = ifs.loadIAMConfigFromEntry(&entry, s3ConfSaved) + assert.Equal(t, err, nil) assert.Equal(t, "some_name", s3ConfSaved.Identities[0].Name) assert.Equal(t, "some_read_only_user", s3ConfSaved.Identities[1].Name)