Add NewHashicorpBackend, hashicorp config types & start vaultWallet impl

This commit is contained in:
chris-j-h 2019-07-06 13:49:44 +01:00
parent 1916b8a865
commit a40c72f8a5
4 changed files with 210 additions and 116 deletions

View File

@ -0,0 +1,25 @@
package vault
type hashicorpWalletConfig struct {
Client hashicorpClientConfig
Secrets []hashicorpSecretData
}
type hashicorpClientConfig struct {
Url string `toml:",omitempty"`
Approle string `toml:",omitempty"`
CaCert string `toml:",omitempty"`
ClientCert string `toml:",omitempty"`
ClientKey string `toml:",omitempty"`
EnvVarPrefix string `toml:",omitempty"`
UseSecretCache bool `toml:",omitempty"`
}
type hashicorpSecretData struct {
Name string `toml:",omitempty"`
SecretEngine string `toml:",omitempty"`
Version int `toml:",omitempty"`
AccountID string `toml:",omitempty"`
KeyID string `toml:",omitempty"`
}

View File

@ -1,23 +1,74 @@
package vault
import (
"fmt"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"sort"
)
type vaultBackend struct {
type VaultBackend struct {
wallets []accounts.Wallet
updateScope event.SubscriptionScope
updateFeed event.Feed
// Other backend impls require mutexes for safety as their wallets can change at any time (e.g. if a file/usb is added/removed). vaultWallets can only be created at startup so there is no chance for concurrent access.
// Other backend impls require mutexes for safety as their wallets can change at any time (e.g. if a file/usb is added/removed). vaultWallets can only be created at startup so there is no danger of concurrent reads and writes.
}
func (b *vaultBackend) Wallets() []accounts.Wallet {
func (b *VaultBackend) Wallets() []accounts.Wallet {
cpy := make([]accounts.Wallet, len(b.wallets))
copy(cpy, b.wallets)
return cpy
}
func (b *vaultBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
func (b *VaultBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
return b.updateScope.Track(b.updateFeed.Subscribe(sink))
}
func NewHashicorpBackend(walletConfigs []hashicorpWalletConfig) VaultBackend {
wallets := []accounts.Wallet{}
for _, conf := range walletConfigs {
w, err := newHashicorpWallet(conf)
if err != nil {
// do something with error and do not append returned w to wallets
log.Error("unable to create Hashicorp wallet from config", "err", err)
continue
}
wallets = append(wallets, w)
}
sort.Sort(walletsByUrl(wallets))
return VaultBackend{
wallets: wallets,
}
}
func newHashicorpWallet(config hashicorpWalletConfig) (vaultWallet, error) {
var url accounts.URL
//to parse a string url as an accounts.URL it must first be in json format
toParse := fmt.Sprintf("\"%v\"", config.Client.Url)
if err := url.UnmarshalJSON([]byte(toParse)); err != nil {
return vaultWallet{}, err
}
return vaultWallet{Url: url}, nil
}
// walletsByUrl implements the sort interface to enable the sorting of a slice of wallets alphanumerically by their urls
type walletsByUrl []accounts.Wallet
func (w walletsByUrl) Len() int {
return len(w)
}
func (w walletsByUrl) Less(i, j int) bool {
return (w[i].URL()).Cmp(w[j].URL()) < 0
}
func (w walletsByUrl) Swap(i, j int) {
w[i], w[j] = w[j], w[i]
}

View File

@ -1,27 +1,81 @@
package vault
import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core/types"
"math/big"
"reflect"
"strings"
"testing"
)
func TestVaultBackend_Wallets(t *testing.T) {
func TestNewHashicorpBackend_CreatesWalletsFromConfig(t *testing.T) {
makeConfs := func (url string, urls... string) []hashicorpWalletConfig {
var confs []hashicorpWalletConfig
confs = append(confs, hashicorpWalletConfig{Client: hashicorpClientConfig{Url: url}})
for _, u := range urls {
confs = append(confs, hashicorpWalletConfig{Client: hashicorpClientConfig{Url: u}})
}
return confs
}
// makeWlts crudely splits the urls to get them as accounts.URLs so as to not use the same parsing method as in the production code. This is fine for simple urls but may not be suitable for tests that require more complex urls.
makeWlts := func(url string, urls... string) []accounts.Wallet {
var wlts []accounts.Wallet
s := strings.Split(url, "://")
scheme, path := s[0], s[1]
wlts = append(wlts, vaultWallet{Url: accounts.URL{Scheme: scheme, Path: path}})
for _, u := range urls {
s := strings.Split(u, "://")
scheme, path := s[0], s[1]
wlts = append(wlts, vaultWallet{Url: accounts.URL{Scheme: scheme, Path: path}})
}
return wlts
}
tests := map[string]struct{
in []hashicorpWalletConfig
want []accounts.Wallet
}{
"no config": {in: []hashicorpWalletConfig{}, want: []accounts.Wallet{}},
"single": {in: makeConfs("http://url:1"), want: makeWlts("http://url:1")},
"multiple": {in: makeConfs("http://url:1", "http://url:2"), want: makeWlts("http://url:1", "http://url:2")},
"orders by url": {
in: makeConfs("https://url:1", "https://a:9", "http://url:2", "http://url:1"),
want: makeWlts("http://url:1", "http://url:2", "https://a:9", "https://url:1")},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
b := NewHashicorpBackend(tt.in)
if !reflect.DeepEqual(tt.want, b.wallets) {
t.Fatalf("\nwant: %v, \ngot : %v", tt.want, b.wallets)
}
})
}
}
func TestVaultBackend_Wallets_ReturnsWallets(t *testing.T) {
tests := map[string]struct {
in []accounts.Wallet
want []accounts.Wallet
}{
"empty": {in: []accounts.Wallet{}, want: []accounts.Wallet{}},
"single": {in: []accounts.Wallet{VaultWallet{}}, want: []accounts.Wallet{VaultWallet{}}},
"multiple": {in: []accounts.Wallet{VaultWallet{}, VaultWallet{}}, want: []accounts.Wallet{VaultWallet{}, VaultWallet{}}},
"single": {in: []accounts.Wallet{vaultWallet{}}, want: []accounts.Wallet{vaultWallet{}}},
"multiple": {in: []accounts.Wallet{vaultWallet{}, vaultWallet{}}, want: []accounts.Wallet{vaultWallet{}, vaultWallet{}}},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
b := vaultBackend{wallets: tt.in}
b := VaultBackend{wallets: tt.in}
got := b.Wallets()
@ -33,11 +87,15 @@ func TestVaultBackend_Wallets(t *testing.T) {
}
func TestVaultBackend_Wallets_ReturnsCopy(t *testing.T) {
b := vaultBackend{wallets: []accounts.Wallet{VaultWallet{}}}
b := VaultBackend{
wallets: []accounts.Wallet{
vaultWallet{Url: accounts.URL{Scheme: "http", Path: "url"}},
},
}
got := b.Wallets()
got[0] = OtherVaultWallet{}
got[0] = vaultWallet{Url: accounts.URL{Scheme: "http", Path: "otherurl"}}
if reflect.DeepEqual(b.wallets, got) {
t.Fatal("changes to returned slice should not affect slice in backend")
@ -46,7 +104,7 @@ func TestVaultBackend_Wallets_ReturnsCopy(t *testing.T) {
func TestVaultBackend_Subscribe_SubscriberReceivesEventsAddedToFeed(t *testing.T) {
subscriber := make(chan accounts.WalletEvent, 1)
b := vaultBackend{}
b := VaultBackend{}
b.Subscribe(subscriber)
@ -55,7 +113,7 @@ func TestVaultBackend_Subscribe_SubscriberReceivesEventsAddedToFeed(t *testing.T
}
// mock an event
event := accounts.WalletEvent{Wallet: VaultWallet{}, Kind: accounts.WalletOpened}
event := accounts.WalletEvent{Wallet: vaultWallet{}, Kind: accounts.WalletOpened}
b.updateFeed.Send(event)
if len(subscriber) != 1 {
@ -68,104 +126,3 @@ func TestVaultBackend_Subscribe_SubscriberReceivesEventsAddedToFeed(t *testing.T
t.Fatalf("want: %v, got: %v", event, got)
}
}
type OtherVaultWallet struct {}
func (OtherVaultWallet) URL() accounts.URL {
panic("implement me")
}
func (OtherVaultWallet) Status() (string, error) {
panic("implement me")
}
func (OtherVaultWallet) Open(passphrase string) error {
panic("implement me")
}
func (OtherVaultWallet) Close() error {
panic("implement me")
}
func (OtherVaultWallet) Accounts() []accounts.Account {
panic("implement me")
}
func (OtherVaultWallet) Contains(account accounts.Account) bool {
panic("implement me")
}
func (OtherVaultWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
panic("implement me")
}
func (OtherVaultWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
panic("implement me")
}
func (OtherVaultWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
panic("implement me")
}
func (OtherVaultWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
panic("implement me")
}
func (OtherVaultWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
panic("implement me")
}
func (OtherVaultWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
panic("implement me")
}
type VaultWallet struct {}
func (VaultWallet) URL() accounts.URL {
panic("implement me")
}
func (VaultWallet) Status() (string, error) {
panic("implement me")
}
func (VaultWallet) Open(passphrase string) error {
panic("implement me")
}
func (VaultWallet) Close() error {
panic("implement me")
}
func (VaultWallet) Accounts() []accounts.Account {
panic("implement me")
}
func (VaultWallet) Contains(account accounts.Account) bool {
panic("implement me")
}
func (VaultWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
panic("implement me")
}
func (VaultWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
panic("implement me")
}
func (VaultWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
panic("implement me")
}
func (VaultWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
panic("implement me")
}
func (VaultWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
panic("implement me")
}
func (VaultWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
panic("implement me")
}

View File

@ -0,0 +1,61 @@
package vault
import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core/types"
"math/big"
)
type vaultWallet struct {
Url accounts.URL
}
func (w vaultWallet) URL() accounts.URL {
return w.Url
}
func (w vaultWallet) Status() (string, error) {
panic("implement me")
}
func (w vaultWallet) Open(passphrase string) error {
panic("implement me")
}
func (w vaultWallet) Close() error {
panic("implement me")
}
func (w vaultWallet) Accounts() []accounts.Account {
panic("implement me")
}
func (w vaultWallet) Contains(account accounts.Account) bool {
panic("implement me")
}
func (w vaultWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
panic("implement me")
}
func (w vaultWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
panic("implement me")
}
func (w vaultWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
panic("implement me")
}
func (w vaultWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
panic("implement me")
}
func (w vaultWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
panic("implement me")
}
func (w vaultWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
panic("implement me")
}