diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 899db7ff3..25448ac18 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -19,6 +19,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" + "google.golang.org/protobuf/proto" ) // Constants from shared package @@ -423,9 +424,16 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { } values := r.PostForm s3cfg := &iam_pb.S3ApiConfiguration{} - if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil && !errors.Is(err, filer_pb.ErrNotFound) { - s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) - return + + // Load configuration from credential manager + config, err := iama.iam.GetCredentialManager().LoadConfiguration(r.Context()) + if err != nil { + if !strings.Contains(err.Error(), filer_pb.ErrNotFound.Error()) { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } + } else { + proto.Merge(s3cfg, config) } glog.V(4).Infof("DoActions: %+v", values) @@ -515,7 +523,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { return } if changed { - err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg) + err := iama.iam.GetCredentialManager().SaveConfiguration(r.Context(), s3cfg) if err != nil { var iamError = IamError{Code: iam.ErrCodeServiceFailureException, Error: err} writeIamErrorResponse(w, r, &iamError) diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go index c1991c703..552bcdb28 100644 --- a/weed/iamapi/iamapi_server.go +++ b/weed/iamapi/iamapi_server.go @@ -15,20 +15,15 @@ import ( "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" - "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" "github.com/seaweedfs/seaweedfs/weed/s3api" "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" . "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" - "github.com/seaweedfs/seaweedfs/weed/util" "github.com/seaweedfs/seaweedfs/weed/wdclient" "google.golang.org/grpc" - "google.golang.org/protobuf/proto" ) type IamS3ApiConfig interface { - GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) - PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) GetPolicies(policies *Policies) (err error) PutPolicies(policies *Policies) (err error) } @@ -134,63 +129,6 @@ func (iama *IamApiServer) Shutdown() { } } -func (iama *IamS3ApiConfigure) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - return iama.GetS3ApiConfigurationFromCredentialManager(s3cfg) -} - -func (iama *IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - return iama.PutS3ApiConfigurationToCredentialManager(s3cfg) -} - -func (iama *IamS3ApiConfigure) GetS3ApiConfigurationFromCredentialManager(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - config, err := iama.credentialManager.LoadConfiguration(context.Background()) - if err != nil { - return fmt.Errorf("failed to load configuration from credential manager: %w", err) - } - // Use proto.Merge to avoid copying the sync.Mutex embedded in the message - proto.Merge(s3cfg, config) - return nil -} - -func (iama *IamS3ApiConfigure) PutS3ApiConfigurationToCredentialManager(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - return iama.credentialManager.SaveConfiguration(context.Background(), s3cfg) -} - -func (iama *IamS3ApiConfigure) GetS3ApiConfigurationFromFiler(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - var buf bytes.Buffer - err = pb.WithOneOfGrpcFilerClients(false, iama.option.Filers, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - if err = filer.ReadEntry(iama.masterClient, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - if buf.Len() > 0 { - if err = filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg); err != nil { - return err - } - } - return nil -} - -func (iama *IamS3ApiConfigure) PutS3ApiConfigurationToFiler(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - buf := bytes.Buffer{} - if err := filer.ProtoToText(&buf, s3cfg); err != nil { - return fmt.Errorf("ProtoToText: %s", err) - } - return pb.WithOneOfGrpcFilerClients(false, iama.option.Filers, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - err = util.Retry("saveIamIdentity", func() error { - return filer.SaveInsideFiler(client, filer.IamConfigDirectory, filer.IamIdentityFile, buf.Bytes()) - }) - if err != nil { - return err - } - return nil - }) -} - func (iama *IamS3ApiConfigure) GetPolicies(policies *Policies) (err error) { var buf bytes.Buffer err = pb.WithOneOfGrpcFilerClients(false, iama.option.Filers, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go index fa04d1ce9..5b4d3c059 100644 --- a/weed/iamapi/iamapi_test.go +++ b/weed/iamapi/iamapi_test.go @@ -1,6 +1,7 @@ package iamapi import ( + "context" "encoding/json" "encoding/xml" "net/http" @@ -14,32 +15,122 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/gorilla/mux" "github.com/jinzhu/copier" + "github.com/seaweedfs/seaweedfs/weed/credential" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" "github.com/seaweedfs/seaweedfs/weed/s3api" "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" + "github.com/seaweedfs/seaweedfs/weed/util" "github.com/stretchr/testify/assert" ) -var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) -var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) var GetPolicies func(policies *Policies) (err error) var PutPolicies func(policies *Policies) (err error) var s3config = iam_pb.S3ApiConfiguration{} var policiesFile = Policies{Policies: make(map[string]policy_engine.PolicyDocument)} -var ias = IamApiServer{s3ApiConfig: iamS3ApiConfigureMock{}} -type iamS3ApiConfigureMock struct{} +// Setup MockCredentialStore +type MockCredentialStore struct{} -func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - _ = copier.Copy(&s3cfg.Identities, &s3config.Identities) +func (m *MockCredentialStore) GetName() credential.CredentialStoreTypeName { return "mock" } +func (m *MockCredentialStore) Initialize(configuration util.Configuration, prefix string) error { return nil } - -func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - _ = copier.Copy(&s3config.Identities, &s3cfg.Identities) +func (m *MockCredentialStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) { + var cfg iam_pb.S3ApiConfiguration + _ = copier.Copy(&cfg, &s3config) + return &cfg, nil +} +func (m *MockCredentialStore) SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error { + _ = copier.Copy(&s3config, config) + return nil +} +func (m *MockCredentialStore) CreateUser(ctx context.Context, identity *iam_pb.Identity) error { return nil } +func (m *MockCredentialStore) GetUser(ctx context.Context, username string) (*iam_pb.Identity, error) { + return nil, nil +} +func (m *MockCredentialStore) UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error { + return nil +} +func (m *MockCredentialStore) DeleteUser(ctx context.Context, username string) error { return nil } +func (m *MockCredentialStore) ListUsers(ctx context.Context) ([]string, error) { return nil, nil } +func (m *MockCredentialStore) GetUserByAccessKey(ctx context.Context, accessKey string) (*iam_pb.Identity, error) { + return nil, nil +} +func (m *MockCredentialStore) CreateAccessKey(ctx context.Context, username string, credential *iam_pb.Credential) error { + return nil +} +func (m *MockCredentialStore) DeleteAccessKey(ctx context.Context, username string, accessKey string) error { + return nil +} +func (m *MockCredentialStore) GetPolicies(ctx context.Context) (map[string]policy_engine.PolicyDocument, error) { + return nil, nil +} +func (m *MockCredentialStore) PutPolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error { + return nil +} +func (m *MockCredentialStore) DeletePolicy(ctx context.Context, name string) error { return nil } +func (m *MockCredentialStore) GetPolicy(ctx context.Context, name string) (*policy_engine.PolicyDocument, error) { + return nil, nil +} +func (m *MockCredentialStore) CreateServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error { + return nil +} +func (m *MockCredentialStore) UpdateServiceAccount(ctx context.Context, id string, sa *iam_pb.ServiceAccount) error { + return nil +} +func (m *MockCredentialStore) DeleteServiceAccount(ctx context.Context, id string) error { return nil } +func (m *MockCredentialStore) GetServiceAccount(ctx context.Context, id string) (*iam_pb.ServiceAccount, error) { + return nil, nil +} +func (m *MockCredentialStore) ListServiceAccounts(ctx context.Context) ([]*iam_pb.ServiceAccount, error) { + return nil, nil +} +func (m *MockCredentialStore) GetServiceAccountByAccessKey(ctx context.Context, accessKey string) (*iam_pb.ServiceAccount, error) { + return nil, nil +} +func (m *MockCredentialStore) Shutdown() {} + +// Initialize ias +var ias IamApiServer + +func init() { + // Infect the credential manager with our mock store + // Since NewIdentityAccessManagementWithStore creates a default one, we need to swap the store inside it + // Accessing unexported field 'credentialManager' in 'IdentityAccessManagement' requires reflection or + // standard setters if available. + // However, GetCredentialManager returns *CredentialManager. + // *CredentialManager has 'store' field which is unexported in credential package. + // But we can create a NEW CredentialManager with our mock store and set it on iam if the field was exported. + // It is NOT exported. + + // Wait, credential.NewCredentialManager is a function. + // Check if is a way to set store. + + // Hack: register our mock store in credential.Stores? + credential.Stores = append(credential.Stores, &MockCredentialStore{}) + + // Initialize ias after registering store + ias = IamApiServer{ + s3ApiConfig: iamS3ApiConfigureMock{}, + iam: s3api.NewIdentityAccessManagementWithStore(&s3api.S3ApiServerOption{}, "mock"), + } + + _, _ = credential.NewCredentialManager("mock", nil, "") + + // Now how to set this 'cm' into 'ias.iam'? + // 'iam.credentialManager' is unexported in 's3api'. + + // Maybe I should use reflection to set it, since this is a test. +} + +// Reflection is messy. +// Alternative: NewIdentityAccessManagementWithStore takes 'explicitStore' string. +// If I register my mock store with name "mock", I can pass "mock" to NewIdentityAccessManagementWithStore. + +type iamS3ApiConfigureMock struct{} func (iam iamS3ApiConfigureMock) GetPolicies(policies *Policies) (err error) { _ = copier.Copy(&policies, &policiesFile) @@ -52,6 +143,10 @@ func (iam iamS3ApiConfigureMock) PutPolicies(policies *Policies) (err error) { } func TestCreateUser(t *testing.T) { + // Re-init ias with mock store properly + credential.Stores = append(credential.Stores, &MockCredentialStore{}) + ias.iam = s3api.NewIdentityAccessManagementWithStore(&s3api.S3ApiServerOption{}, "mock") + userName := aws.String("Test") params := &iam.CreateUserInput{UserName: userName} req, _ := iam.New(session.New()).CreateUserRequest(params)