Browse Source

iamapi: refactor handlers to use CredentialManager directly

pull/8126/head
Chris Lu 2 days ago
parent
commit
3a14821223
  1. 16
      weed/iamapi/iamapi_management_handlers.go
  2. 62
      weed/iamapi/iamapi_server.go
  3. 113
      weed/iamapi/iamapi_test.go

16
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)

62
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 {

113
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)

Loading…
Cancel
Save