Skip to content

Commit

Permalink
feat: Add source namespace to migration endpoint (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
darioAnongba authored Oct 13, 2021
1 parent 86746f4 commit 98f5d7b
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/service/ethereum/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/service/formatters/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
VersionLabel = "version"
CreatedAtLabel = "created_at"
UpdatedAtLabel = "updated_at"
SourceNamespace = "source_namespace"

NamespaceHeader = "X-Vault-Namespace"
)
Expand Down
18 changes: 14 additions & 4 deletions src/service/migrations/eth-to-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
20 changes: 18 additions & 2 deletions src/service/migrations/migrations.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
},
Expand Down
4 changes: 2 additions & 2 deletions src/vault/use-cases/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
66 changes: 45 additions & 21 deletions src/vault/use-cases/migrations/eth_to_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 &ethToKeysUseCase{
ethUseCases: ethUseCases,
Expand All @@ -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)
Expand All @@ -43,40 +48,59 @@ 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
return
}

// 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)
Expand All @@ -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),
Expand All @@ -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
}
16 changes: 8 additions & 8 deletions src/vault/use-cases/mocks/migrations.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 98f5d7b

Please sign in to comment.