Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Secure secrets storage 2 #33

Merged
merged 12 commits into from
Feb 14, 2024
91 changes: 82 additions & 9 deletions command/polybftsecrets/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"strings"

"github.com/0xPolygon/polygon-edge/command"
bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer"
"github.com/0xPolygon/polygon-edge/consensus/polybft/wallet"
"github.com/0xPolygon/polygon-edge/secrets"
"github.com/0xPolygon/polygon-edge/secrets/helper"
"github.com/0xPolygon/polygon-edge/types"
"github.com/spf13/cobra"
ethWallet "github.com/umbracle/ethgo/wallet"
)

const (
Expand All @@ -21,6 +23,9 @@ const (
numFlag = "num"
outputFlag = "output"
chainIDFlag = "chain-id"
networkKeyFlag = "network-key"
ecdsaKeyFlag = "ecdsa-key"
blsKeyFlag = "bls-key"

// maxInitNum is the maximum value for "num" flag
maxInitNum = 30
Expand All @@ -42,6 +47,10 @@ type initParams struct {
output bool

chainID int64

networkKey string
ecdsaKey string
blsKey string
}

func (ip *initParams) validateFlags() error {
Expand Down Expand Up @@ -78,12 +87,26 @@ func (ip *initParams) setFlags(cmd *cobra.Command) {
"the flag indicating how many secrets should be created, only for the local FS",
)

// Don't accept data-dir and config flags because they are related to different secrets managers.
// data-dir is about the local FS as secrets storage, config is about remote secrets manager.
cmd.MarkFlagsMutuallyExclusive(AccountDirFlag, AccountConfigFlag)
cmd.Flags().StringVar(
&ip.networkKey,
networkKeyFlag,
"",
"the flag providing already created network key to be used",
)

// num flag should be used with data-dir flag only so it should not be used with config flag.
cmd.MarkFlagsMutuallyExclusive(numFlag, AccountConfigFlag)
cmd.Flags().StringVar(
&ip.ecdsaKey,
ecdsaKeyFlag,
"",
"the flag providing already created ecdsa key to be used",
)

cmd.Flags().StringVar(
&ip.blsKey,
blsKeyFlag,
"",
"the flag providing already created bls key to be used",
)

cmd.Flags().BoolVar(
&ip.generatesAccount,
Expand Down Expand Up @@ -126,6 +149,26 @@ func (ip *initParams) setFlags(cmd *cobra.Command) {
command.DefaultChainID,
"the ID of the chain",
)

// Don't accept data-dir and config flags because they are related to different secrets managers.
// data-dir is about the local FS as secrets storage, config is about remote secrets manager.
cmd.MarkFlagsMutuallyExclusive(AccountDirFlag, AccountConfigFlag)

// num flag should be used with data-dir flag only so it should not be used with config flag.
cmd.MarkFlagsMutuallyExclusive(numFlag, AccountConfigFlag)

// Encryptedlocal secrets manager with preset keys can be setup for a single bunch of secrets only, so num flag is not allowed.
cmd.MarkFlagsMutuallyExclusive(numFlag, networkKeyFlag)
cmd.MarkFlagsMutuallyExclusive(numFlag, blsKeyFlag)
cmd.MarkFlagsMutuallyExclusive(numFlag, ecdsaKeyFlag)

// network-key, ecdsa-key and bls-key flags should be used with data-dir flag only because they are related to local FS.
cmd.MarkFlagsMutuallyExclusive(AccountConfigFlag, networkKeyFlag)
cmd.MarkFlagsMutuallyExclusive(AccountConfigFlag, blsKeyFlag)
cmd.MarkFlagsMutuallyExclusive(AccountConfigFlag, ecdsaKeyFlag)

// Currently we handle ecdsaKey and blsKey generation from flag together only.
cmd.MarkFlagsRequiredTogether(ecdsaKeyFlag, blsKeyFlag)
}

func (ip *initParams) Execute() (Results, error) {
Expand Down Expand Up @@ -171,11 +214,15 @@ func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) ([]string,

if ip.generatesNetwork {
if !secretsManager.HasSecret(secrets.NetworkKey) {
if _, err := helper.InitNetworkingPrivateKey(secretsManager); err != nil {
if _, err := helper.InitNetworkingPrivateKey(secretsManager, []byte(ip.networkKey)); err != nil {
return generated, fmt.Errorf("error initializing network-key: %w", err)
}

generated = append(generated, secrets.NetworkKey)
} else {
if ip.networkKey != "" {
return generated, fmt.Errorf("network-key already exists")
}
}
}

Expand All @@ -186,9 +233,31 @@ func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) ([]string,
)

if !secretsManager.HasSecret(secrets.ValidatorKey) && !secretsManager.HasSecret(secrets.ValidatorBLSKey) {
a, err = wallet.GenerateAccount()
if err != nil {
return generated, fmt.Errorf("error generating account: %w", err)
if ip.ecdsaKey != "" && ip.blsKey != "" {
blsKey, err := bls.UnmarshalPrivateKey([]byte(ip.blsKey))
if err != nil {
return generated, fmt.Errorf("failed to retrieve bls key: %w", err)
}

ecdsaRaw, err := hex.DecodeString(ip.ecdsaKey)
if err != nil {
return generated, fmt.Errorf("failed to retrieve ecdsa key: %w", err)
}

key, err := ethWallet.NewWalletFromPrivKey(ecdsaRaw)
if err != nil {
return generated, fmt.Errorf("failed to retrieve ecdsa key: %w", err)
}

a = &wallet.Account{
Ecdsa: key,
Bls: blsKey,
}
} else {
a, err = wallet.GenerateAccount()
if err != nil {
return generated, fmt.Errorf("error generating account: %w", err)
}
}

if err = a.Save(secretsManager); err != nil {
Expand All @@ -197,6 +266,10 @@ func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) ([]string,

generated = append(generated, secrets.ValidatorKey, secrets.ValidatorBLSKey)
} else {
if ip.ecdsaKey != "" || ip.blsKey != "" {
return generated, fmt.Errorf("ecdsa-key or bls-key already exists")
}

a, err = wallet.NewAccountFromSecret(secretsManager)
if err != nil {
return generated, fmt.Errorf("error loading account: %w", err)
Expand Down
7 changes: 3 additions & 4 deletions command/polybftsecrets/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ func GetSecretsManager(dataPath, configPath string, insecureLocalStore bool) (se
return helper.InitCloudSecretsManager(secretsConfig)
}

// Storing secrets on a local file system should only be allowed with --insecure flag,
// to raise awareness that it should be only used in development/testing environments.
// Production setups should use one of the supported secrets managers
// Storing secrets on the local file system should be allowed only when files are encrypted with password.
// For plaintext storage of the secrets --insecure flag must be added
if !insecureLocalStore {
return nil, ErrSecureLocalStoreNotImplemented
return helper.SetupEncryptedLocalSecretsManager(dataPath)
}

return helper.SetupLocalSecretsManager(dataPath)
Expand Down
2 changes: 1 addition & 1 deletion command/secrets/init/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (ip *initParams) initValidatorKey() error {

func (ip *initParams) initNetworkingKey() error {
if ip.generatesNetwork {
if _, err := helper.InitNetworkingPrivateKey(ip.secretsManager); err != nil {
if _, err := helper.InitNetworkingPrivateKey(ip.secretsManager, nil); err != nil {
return err
}
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ require (
github.com/umbracle/ethgo v0.1.4-0.20231006072852-6b068360fc97
github.com/valyala/fastjson v1.6.3 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/tools v0.13.0
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.1 // indirect
Expand All @@ -69,6 +69,7 @@ require (
github.com/quasilyte/go-ruleguard/dsl v0.3.22
github.com/sethvargo/go-retry v0.2.4
golang.org/x/sync v0.4.0
golang.org/x/term v0.16.0
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98
gopkg.in/DataDog/dd-trace-go.v1 v1.55.0
pgregory.net/rapid v1.1.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -908,12 +908,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
139 changes: 139 additions & 0 deletions secrets/encryptedlocal/encryptedlocal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package encryptedlocal

import (
"errors"

"github.com/0xPolygon/polygon-edge/secrets"
"github.com/0xPolygon/polygon-edge/secrets/local"
"github.com/hashicorp/go-hclog"
)

// EncryptedLocalSecretsManager is a SecretsManager that
// stores secrets encrypted locally on disk
type EncryptedLocalSecretsManager struct {
prompt *Prompt
logger hclog.Logger
*local.LocalSecretsManager
encryption Encryption
pwd []byte
}

// SecretsManagerFactory implements the factory method
func SecretsManagerFactory(
_ *secrets.SecretsManagerConfig,
params *secrets.SecretsManagerParams,
) (secrets.SecretsManager, error) {
baseSM, err := local.SecretsManagerFactory(
nil, // Local secrets manager doesn't require a config
params)
if err != nil {
return nil, err
}

localSM, ok := baseSM.(*local.LocalSecretsManager)
if !ok {
return nil, errors.New("invalid type assertion")
}

prompt := NewPrompt()
encryption := NewEncryption()
logger := params.Logger.Named(string(secrets.EncryptedLocal))
esm := &EncryptedLocalSecretsManager{
prompt,
logger,
localSM,
encryption,
nil,
}

return esm, nil
}

func (esm *EncryptedLocalSecretsManager) SetSecret(name string, value []byte) error {
esm.logger.Info("Configuring secret", "name", name)
onSetHandler, ok := onSetHandlers[name]
if ok {
res, err := onSetHandler(esm, name, value)
if err != nil {
return err
}

value = res
}

return esm.LocalSecretsManager.SetSecret(name, value)
}

func (esm *EncryptedLocalSecretsManager) GetSecret(name string) ([]byte, error) {
value, err := esm.LocalSecretsManager.GetSecret(name)
if err != nil {
return nil, err
}

onGetHandler, ok := onGetHandlers[name]
if ok {
value, err = onGetHandler(esm, name, value)
if err != nil {
return nil, err
}
}

return value, nil
}

// --------- Custom Additional handlers ---------

type AdditionalHandlerFunc func(esm *EncryptedLocalSecretsManager, name string, value []byte) ([]byte, error)

var onSetHandlers = map[string]AdditionalHandlerFunc{
secrets.NetworkKey: baseOnSetHandler,
secrets.ValidatorBLSKey: baseOnSetHandler,
secrets.ValidatorKey: baseOnSetHandler,
}

func baseOnSetHandler(esm *EncryptedLocalSecretsManager, name string, value []byte) ([]byte, error) {
esm.logger.Info("Here is the raw hex value of your secret. \nPlease copy it and store it in a safe place.", name, string(value))
confirmValue, err := esm.prompt.DefaultPrompt("Please rewrite the secret value to confirm that you have copied it down correctly.", "")
if err != nil {
return nil, err
}

if confirmValue != string(value) {
esm.logger.Error("The secret value you entered does not match the original value. Please try again.")
return nil, errors.New("secret value mismatch")
} else {
esm.logger.Info("The secret value you entered matches the original value. Continuing.")
}

if esm.pwd == nil || len(esm.pwd) == 0 {
esm.pwd, err = esm.prompt.GeneratePassword()
if err != nil {
return nil, err
}
}

encryptedValue, err := esm.encryption.Encrypt(value, esm.pwd)
if err != nil {
return nil, err
}

return encryptedValue, nil
}

var onGetHandlers = map[string]AdditionalHandlerFunc{
secrets.NetworkKey: baseOnGetHandler,
secrets.ValidatorBLSKey: baseOnGetHandler,
secrets.ValidatorKey: baseOnGetHandler,
}

func baseOnGetHandler(esm *EncryptedLocalSecretsManager, name string, value []byte) ([]byte, error) {
if esm.pwd == nil || len(esm.pwd) == 0 {
var err error
esm.pwd, err = esm.prompt.InputPassword(false)
if err != nil {
return nil, err
}
}

return esm.encryption.Decrypt(value, esm.pwd)
}
Loading
Loading