From 98f5d7b33a5c42dadeac1b0f421308aa5197e051 Mon Sep 17 00:00:00 2001 From: Dario Anongba Varela Date: Wed, 13 Oct 2021 11:49:30 +0200 Subject: [PATCH] feat: Add source namespace to migration endpoint (#8) --- src/service/ethereum/import.go | 2 +- src/service/formatters/fields.go | 1 + src/service/migrations/eth-to-keys.go | 18 +++-- src/service/migrations/migrations.go | 20 +++++- src/vault/use-cases/migrations.go | 4 +- src/vault/use-cases/migrations/eth_to_keys.go | 66 +++++++++++++------ src/vault/use-cases/mocks/migrations.go | 16 ++--- 7 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/service/ethereum/import.go b/src/service/ethereum/import.go index 51ae818..11f2de1 100644 --- a/src/service/ethereum/import.go +++ b/src/service/ethereum/import.go @@ -41,7 +41,7 @@ func (c *controller) importHandler() framework.OperationFunc { namespace := formatters.GetRequestNamespace(req) if privateKeyString == "" { - return logical.ErrorResponse("privateKey must be provided"), nil + return logical.ErrorResponse("private_key must be provided"), nil } ctx = log.Context(ctx, c.logger) diff --git a/src/service/formatters/fields.go b/src/service/formatters/fields.go index 21d0b3c..b1d6651 100644 --- a/src/service/formatters/fields.go +++ b/src/service/formatters/fields.go @@ -26,6 +26,7 @@ const ( VersionLabel = "version" CreatedAtLabel = "created_at" UpdatedAtLabel = "updated_at" + SourceNamespace = "source_namespace" NamespaceHeader = "X-Vault-Namespace" ) diff --git a/src/service/migrations/eth-to-keys.go b/src/service/migrations/eth-to-keys.go index 714e49a..38393ad 100644 --- a/src/service/migrations/eth-to-keys.go +++ b/src/service/migrations/eth-to-keys.go @@ -48,10 +48,15 @@ func (c *controller) NewEthereumToKeysStatusOperation() *framework.PathOperation func (c *controller) ethToKeysHandler() framework.OperationFunc { return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - namespace := formatters.GetRequestNamespace(req) + sourceNamespace := data.Get(formatters.SourceNamespace).(string) + destinationNamespace := formatters.GetRequestNamespace(req) + + if sourceNamespace == "" { + return logical.ErrorResponse("%s must be provided", formatters.SourceNamespace), nil + } ctx = log.Context(ctx, c.logger) - err := c.useCases.EthereumToKeys().Execute(ctx, req.Storage, namespace) + err := c.useCases.EthereumToKeys().Execute(ctx, req.Storage, sourceNamespace, destinationNamespace) if err != nil { return errors.ParseHTTPError(err) } @@ -62,10 +67,15 @@ func (c *controller) ethToKeysHandler() framework.OperationFunc { func (c *controller) ethToKeysStatusHandler() framework.OperationFunc { return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - namespace := formatters.GetRequestNamespace(req) + sourceNamespace := data.Get(formatters.SourceNamespace).(string) + destinationNamespace := formatters.GetRequestNamespace(req) + + if sourceNamespace == "" { + return logical.ErrorResponse("%s must be provided", formatters.SourceNamespace), nil + } ctx = log.Context(ctx, c.logger) - status, err := c.useCases.EthereumToKeys().Status(ctx, namespace) + status, err := c.useCases.EthereumToKeys().Status(ctx, sourceNamespace, destinationNamespace) if err != nil { return errors.ParseHTTPError(err) } diff --git a/src/service/migrations/migrations.go b/src/service/migrations/migrations.go index 331da00..c5d1997 100644 --- a/src/service/migrations/migrations.go +++ b/src/service/migrations/migrations.go @@ -1,7 +1,9 @@ package migrations import ( + "fmt" "github.com/consensys/quorum-hashicorp-vault-plugin/src/pkg/log" + "github.com/consensys/quorum-hashicorp-vault-plugin/src/service/formatters" usecases "github.com/consensys/quorum-hashicorp-vault-plugin/src/vault/use-cases" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" @@ -35,8 +37,15 @@ func (c *controller) Paths() []*framework.Path { func (c *controller) pathEthereumToKeys() *framework.Path { return &framework.Path{ - Pattern: "migrations/ethereum-to-keys/migrate/?", + Pattern: "migrations/ethereum-to-keys/migrate", HelpSynopsis: "Migrates the current Ethereum accounts to the keys namespace", + Fields: map[string]*framework.FieldSchema{ + formatters.SourceNamespace: { + Type: framework.TypeString, + Description: "Namespace from which to migrate. Use * for all namespaces", + Required: true, + }, + }, Operations: map[logical.Operation]framework.OperationHandler{ logical.CreateOperation: c.NewEthereumToKeysOperation(), logical.UpdateOperation: c.NewEthereumToKeysOperation(), @@ -46,8 +55,15 @@ func (c *controller) pathEthereumToKeys() *framework.Path { func (c *controller) pathEthereumToKeysStatus() *framework.Path { return &framework.Path{ - Pattern: "migrations/ethereum-to-keys/status/?", + Pattern: fmt.Sprintf("migrations/ethereum-to-keys/status/%s", framework.GenericNameRegex(formatters.SourceNamespace)), HelpSynopsis: "Checks the status of the migration", + Fields: map[string]*framework.FieldSchema{ + formatters.SourceNamespace: { + Type: framework.TypeString, + Description: "Namespace from which to check the status", + Required: true, + }, + }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: c.NewEthereumToKeysStatusOperation(), }, diff --git a/src/vault/use-cases/migrations.go b/src/vault/use-cases/migrations.go index a42ed2b..5ddbb94 100644 --- a/src/vault/use-cases/migrations.go +++ b/src/vault/use-cases/migrations.go @@ -13,6 +13,6 @@ type MigrationsUseCases interface { } type EthereumToKeysUseCase interface { - Execute(ctx context.Context, storage logical.Storage, namespace string) error - Status(ctx context.Context, namespace string) (*entities.MigrationStatus, error) + Execute(ctx context.Context, storage logical.Storage, sourceNamespace, destinationNamespace string) error + Status(ctx context.Context, sourceNamespace, destinationNamespace string) (*entities.MigrationStatus, error) } diff --git a/src/vault/use-cases/migrations/eth_to_keys.go b/src/vault/use-cases/migrations/eth_to_keys.go index 483d6f2..ce674c2 100644 --- a/src/vault/use-cases/migrations/eth_to_keys.go +++ b/src/vault/use-cases/migrations/eth_to_keys.go @@ -21,6 +21,11 @@ type ethToKeysUseCase struct { mux sync.RWMutex } +type migrationAccount struct { + address string + namespace string +} + func NewEthToKeysUseCase(ethUseCases usecases.ETHUseCases, keysUseCases usecases.KeysUseCases) usecases.EthereumToKeysUseCase { return ðToKeysUseCase{ ethUseCases: ethUseCases, @@ -30,10 +35,10 @@ func NewEthToKeysUseCase(ethUseCases usecases.ETHUseCases, keysUseCases usecases } } -func (uc *ethToKeysUseCase) Status(ctx context.Context, namespace string) (*entities.MigrationStatus, error) { - logger := log.FromContext(ctx).With("namespace", namespace) +func (uc *ethToKeysUseCase) Status(ctx context.Context, sourceNamespace, destinationNamespace string) (*entities.MigrationStatus, error) { + logger := log.FromContext(ctx).With("source_namespace", sourceNamespace, "destination_namespace", destinationNamespace) - status := uc.status[namespace] + status := uc.status[sourceNamespace+destinationNamespace] if status == nil { errMessage := "migration could not be found" logger.Warn(errMessage) @@ -43,32 +48,51 @@ func (uc *ethToKeysUseCase) Status(ctx context.Context, namespace string) (*enti return status, nil } -func (uc *ethToKeysUseCase) Execute(ctx context.Context, storage logical.Storage, namespace string) error { - logger := log.FromContext(ctx).With("namespace", namespace) +func (uc *ethToKeysUseCase) Execute(ctx context.Context, storage logical.Storage, sourceNamespace, destinationNamespace string) error { + logger := log.FromContext(ctx).With("source_namespace", sourceNamespace, "destination_namespace", destinationNamespace) - if uc.getStatus(namespace) != nil && uc.getStatus(namespace).Status == "pending" { + if uc.getStatus(sourceNamespace, destinationNamespace) != nil && uc.getStatus(sourceNamespace, destinationNamespace).Status == "pending" { errMessage := "migration is currently running, please check its status" logger.Warn(errMessage) return errors.AlreadyExistsError(errMessage) } - addresses, err := uc.ethUseCases.ListAccounts().WithStorage(storage).Execute(ctx, namespace) - if err != nil { - return err + namespaces := []string{sourceNamespace} + var err error + if sourceNamespace == "*" { + namespaces, err = uc.ethUseCases.ListNamespaces().WithStorage(storage).Execute(ctx) + if err != nil { + return err + } + } + + var accounts []migrationAccount + for _, namespace := range namespaces { + currAddresses, err := uc.ethUseCases.ListAccounts().WithStorage(storage).Execute(ctx, namespace) + if err != nil { + return err + } + + for _, address := range currAddresses { + accounts = append(accounts, migrationAccount{ + address: address, + namespace: namespace, + }) + } } status := &entities.MigrationStatus{ Status: "pending", StartTime: time.Now(), - Total: len(addresses), + Total: len(accounts), } - uc.writeStatus(namespace, status) + uc.writeStatus(sourceNamespace, destinationNamespace, status) go func() { newCtx := log.Context(context.Background(), logger) - for _, address := range addresses { - account, der := uc.ethUseCases.GetAccount().WithStorage(storage).Execute(newCtx, address, namespace) + for _, acc := range accounts { + retrievedAccount, der := uc.ethUseCases.GetAccount().WithStorage(storage).Execute(newCtx, acc.address, acc.namespace) if der != nil { status.Status = "failure" status.Error = der @@ -76,7 +100,7 @@ func (uc *ethToKeysUseCase) Execute(ctx context.Context, storage logical.Storage } // Private keys are stored in hex format without "0x" prefix, they must be transformed to base64 - privKey, der := hex.DecodeString(account.PrivateKey) + privKey, der := hex.DecodeString(retrievedAccount.PrivateKey) if der != nil { errMessage := "failed to decode private key" logger.With("error", err).Error(errMessage) @@ -88,8 +112,8 @@ func (uc *ethToKeysUseCase) Execute(ctx context.Context, storage logical.Storage // The ID of the key is the address of the ETH account _, der = uc.keysUseCases.CreateKey().WithStorage(storage).Execute( newCtx, - namespace, - address, + destinationNamespace, + acc.address, entities.ECDSA, entities.Secp256k1, base64.URLEncoding.EncodeToString(privKey), @@ -108,20 +132,20 @@ func (uc *ethToKeysUseCase) Execute(ctx context.Context, storage logical.Storage status.EndTime = time.Now() }() - logger.With("total", len(addresses)).Info("migration from ethereum to keys namespace initiated") + logger.With("total", len(accounts)).Info("migration from ethereum to keys namespace initiated") return nil } -func (uc *ethToKeysUseCase) getStatus(namespace string) *entities.MigrationStatus { +func (uc *ethToKeysUseCase) getStatus(sourceNamespace, destinationNamespace string) *entities.MigrationStatus { uc.mux.RLock() defer uc.mux.RUnlock() - return uc.status[namespace] + return uc.status[sourceNamespace+destinationNamespace] } -func (uc *ethToKeysUseCase) writeStatus(namespace string, status *entities.MigrationStatus) { +func (uc *ethToKeysUseCase) writeStatus(sourceNamespace, destinationNamespace string, status *entities.MigrationStatus) { uc.mux.Lock() defer uc.mux.Unlock() - uc.status[namespace] = status + uc.status[sourceNamespace+destinationNamespace] = status } diff --git a/src/vault/use-cases/mocks/migrations.go b/src/vault/use-cases/mocks/migrations.go index 94f2ce4..193c27b 100644 --- a/src/vault/use-cases/mocks/migrations.go +++ b/src/vault/use-cases/mocks/migrations.go @@ -74,30 +74,30 @@ func (m *MockEthereumToKeysUseCase) EXPECT() *MockEthereumToKeysUseCaseMockRecor } // Execute mocks base method -func (m *MockEthereumToKeysUseCase) Execute(ctx context.Context, storage logical.Storage, namespace string) error { +func (m *MockEthereumToKeysUseCase) Execute(ctx context.Context, storage logical.Storage, sourceNamespace, destinationNamespace string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", ctx, storage, namespace) + ret := m.ctrl.Call(m, "Execute", ctx, storage, sourceNamespace, destinationNamespace) ret0, _ := ret[0].(error) return ret0 } // Execute indicates an expected call of Execute -func (mr *MockEthereumToKeysUseCaseMockRecorder) Execute(ctx, storage, namespace interface{}) *gomock.Call { +func (mr *MockEthereumToKeysUseCaseMockRecorder) Execute(ctx, storage, sourceNamespace, destinationNamespace interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockEthereumToKeysUseCase)(nil).Execute), ctx, storage, namespace) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockEthereumToKeysUseCase)(nil).Execute), ctx, storage, sourceNamespace, destinationNamespace) } // Status mocks base method -func (m *MockEthereumToKeysUseCase) Status(ctx context.Context, namespace string) (*entities.MigrationStatus, error) { +func (m *MockEthereumToKeysUseCase) Status(ctx context.Context, sourceNamespace, destinationNamespace string) (*entities.MigrationStatus, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Status", ctx, namespace) + ret := m.ctrl.Call(m, "Status", ctx, sourceNamespace, destinationNamespace) ret0, _ := ret[0].(*entities.MigrationStatus) ret1, _ := ret[1].(error) return ret0, ret1 } // Status indicates an expected call of Status -func (mr *MockEthereumToKeysUseCaseMockRecorder) Status(ctx, namespace interface{}) *gomock.Call { +func (mr *MockEthereumToKeysUseCaseMockRecorder) Status(ctx, sourceNamespace, destinationNamespace interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockEthereumToKeysUseCase)(nil).Status), ctx, namespace) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockEthereumToKeysUseCase)(nil).Status), ctx, sourceNamespace, destinationNamespace) }