add validation on config (#3350)

This commit is contained in:
frank 2023-03-29 23:51:01 +08:00 committed by GitHub
parent 8dd8b1ae45
commit bb6139aef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 259 additions and 60 deletions

View File

@ -1 +1 @@
0.142.0
0.142.1

View File

@ -239,6 +239,10 @@ func setupSendingClient(backend *api.GethStatusBackend, cs, configJSON string) (
if err != nil {
return nil, err
}
err = validateAndVerifyPassword(conf, conf.SenderConfig)
if err != nil {
return nil, err
}
conf.SenderConfig.DB = backend.GetMultiaccountDB()
@ -437,6 +441,10 @@ func setupReceivingClient(backend *api.GethStatusBackend, cs, configJSON string)
if err != nil {
return nil, err
}
err = validateAndVerifyNodeConfig(conf, conf.ReceiverConfig)
if err != nil {
return nil, err
}
conf.ReceiverConfig.DB = backend.GetMultiaccountDB()

View File

@ -1,13 +1,51 @@
package pairing
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"gopkg.in/go-playground/validator.v9"
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/keystore"
)
func newValidate() (*validator.Validate, error) {
var validate = validator.New()
var keyUIDPattern = regexp.MustCompile(`^0x[0-9a-fA-F]{64}$`)
if err := validate.RegisterValidation("keyuid", func(fl validator.FieldLevel) bool {
return keyUIDPattern.MatchString(fl.Field().String())
}); err != nil {
return nil, err
}
if err := validate.RegisterValidation("keystorepath", func(fl validator.FieldLevel) bool {
keyUIDField := fl.Parent()
if keyUIDField.Kind() == reflect.Ptr {
keyUIDField = keyUIDField.Elem()
}
keyUID := keyUIDField.FieldByName("KeyUID").String()
return strings.HasSuffix(fl.Field().String(), keyUID)
}); err != nil {
return nil, err
}
if err := validate.RegisterValidation("not_end_keyuid", func(fl validator.FieldLevel) bool {
keystorePath := fl.Field().String()
return len(keystorePath) <= 66 || !keyUIDPattern.MatchString(keystorePath[len(keystorePath)-66:])
}); err != nil {
return nil, err
}
return validate, nil
}
func validateKeys(keys map[string][]byte, password string) error {
for _, key := range keys {
k, err := keystore.DecryptKey(key, password)
@ -24,6 +62,83 @@ func validateKeys(keys map[string][]byte, password string) error {
return nil
}
func loadKeys(keys map[string][]byte, keyStorePath string) error {
fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
return nil
}
rawKeyFile, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("invalid account key file: %v", err)
}
keys[dirEntry.Name()] = rawKeyFile
return nil
}
err := filepath.WalkDir(keyStorePath, fileWalker)
if err != nil {
return fmt.Errorf("cannot traverse key store folder: %v", err)
}
return nil
}
func validate(s interface{}) error {
v, err := newValidate()
if err != nil {
return err
}
return v.Struct(s)
}
func validateAndVerifyPassword(s interface{}, senderConfig *SenderConfig) error {
err := validate(s)
if err != nil {
return err
}
keys := make(map[string][]byte)
err = loadKeys(keys, senderConfig.KeystorePath)
if err != nil {
return err
}
return validateKeys(keys, senderConfig.Password)
}
func validateAndVerifyNodeConfig(s interface{}, receiverConfig *ReceiverConfig) error {
if receiverConfig.NodeConfig != nil {
// we should update with defaults before validation
err := receiverConfig.NodeConfig.UpdateWithDefaults()
if err != nil {
return err
}
}
err := validate(s)
if err != nil {
return err
}
if receiverConfig.NodeConfig == nil {
return fmt.Errorf("node config is required for receiver config")
}
if receiverConfig.NodeConfig.RootDataDir == "" {
return fmt.Errorf("root data dir is required for node config")
}
return receiverConfig.NodeConfig.Validate()
}
func emptyDir(dir string) error {
// Open the directory
d, err := os.Open(dir)

View File

@ -10,26 +10,29 @@ import (
type SenderConfig struct {
// SenderConfig.KeystorePath must end with keyUID
KeystorePath string `json:"keystorePath"`
KeystorePath string `json:"keystorePath" validate:"required,keystorepath"`
// DeviceType SendPairInstallation need this information
DeviceType string `json:"deviceType"`
DeviceType string `json:"deviceType" validate:"required"`
KeyUID string `json:"keyUID"`
Password string `json:"password"`
KeyUID string `json:"keyUID" validate:"required,keyuid"`
Password string `json:"password" validate:"required"`
DB *multiaccounts.Database `json:"-"`
}
type ReceiverConfig struct {
// nodeConfig is required, but we'll validate it separately
NodeConfig *params.NodeConfig `json:"nodeConfig"`
// ReceiverConfig.KeystorePath must not end with keyUID (because keyUID is not known yet)
KeystorePath string `json:"keystorePath"`
KeystorePath string `json:"keystorePath" validate:"required,not_end_keyuid"`
// DeviceType SendPairInstallation need this information
DeviceType string `json:"deviceType"`
KDFIterations int `json:"kdfIterations"`
DeviceType string `json:"deviceType" validate:"required"`
KDFIterations int `json:"kdfIterations" validate:"gte=0"`
// SettingCurrentNetwork corresponding to field current_network from table settings, so that we can override current network from sender
SettingCurrentNetwork string `json:"settingCurrentNetwork"`
SettingCurrentNetwork string `json:"settingCurrentNetwork" validate:"required"`
DB *multiaccounts.Database `json:"-"`
LoggedInKeyUID string `json:"-"`
@ -37,7 +40,7 @@ type ReceiverConfig struct {
type ServerConfig struct {
// Timeout the number of milliseconds after which the pairing server will automatically terminate
Timeout uint `json:"timeout"`
Timeout uint `json:"timeout" validate:"omitempty,gte=0"`
// Connection fields, not json (un)marshalled
// Required for the server, but MUST NOT come from client
@ -51,23 +54,23 @@ type ServerConfig struct {
type ClientConfig struct{}
type SenderServerConfig struct {
SenderConfig *SenderConfig `json:"senderConfig"`
ServerConfig *ServerConfig `json:"serverConfig"`
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
}
type SenderClientConfig struct {
SenderConfig *SenderConfig `json:"senderConfig"`
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
ClientConfig *ClientConfig `json:"clientConfig"`
}
type ReceiverClientConfig struct {
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
ClientConfig *ClientConfig `json:"clientConfig"`
}
type ReceiverServerConfig struct {
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
ServerConfig *ServerConfig `json:"serverConfig"`
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
}
func NewSenderServerConfig() *SenderServerConfig {

View File

@ -0,0 +1,103 @@
package pairing
import (
"testing"
"github.com/stretchr/testify/suite"
"gopkg.in/go-playground/validator.v9"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigTestSuite(t *testing.T) {
suite.Run(t, new(ConfigTestSuite))
}
type ConfigTestSuite struct {
suite.Suite
validate *validator.Validate
}
func (s *ConfigTestSuite) SetupTest() {
var err error
s.validate, err = newValidate()
require.NoError(s.T(), err, "newValidate should not return error")
}
func (s *ConfigTestSuite) TestValidationKeystorePath() {
s.T().Run("Valid keystore path with keyUID", func(t *testing.T) {
sc := &SenderConfig{
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
DeviceType: "phone",
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
Password: "password",
}
assert.NoError(t, s.validate.Struct(sc), "SenderConfig validation should pass")
})
s.T().Run("Invalid keystore path without keyUID", func(t *testing.T) {
sc := &SenderConfig{
KeystorePath: "some/path/",
DeviceType: "phone",
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
Password: "password",
}
assert.Error(t, s.validate.Struct(sc), "SenderConfig validation should fail")
})
}
func (s *ConfigTestSuite) TestValidationKeyUID() {
s.T().Run("Valid keyUID", func(t *testing.T) {
sc := &SenderConfig{
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
DeviceType: "phone",
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
Password: "password",
}
assert.NoError(t, s.validate.Struct(sc), "SenderConfig validation should pass")
})
s.T().Run("Invalid keyUID", func(t *testing.T) {
sc := &SenderConfig{
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
DeviceType: "phone",
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54",
Password: "password",
}
assert.Error(t, s.validate.Struct(sc), "SenderConfig validation should fail")
})
}
func (s *ConfigTestSuite) TestValidationNotEndKeyUID() {
nodeConfig, err := defaultNodeConfig(uuid.New().String(), "")
nodeConfig.RootDataDir = "/tmp"
require.NoError(s.T(), err, "defaultNodeConfig should not return error")
s.T().Run("Valid keystore path without keyUID", func(t *testing.T) {
r := &ReceiverConfig{
NodeConfig: nodeConfig,
KeystorePath: "some/path/",
DeviceType: "phone",
KDFIterations: 1,
SettingCurrentNetwork: "mainnet",
}
assert.NoError(t, validateAndVerifyNodeConfig(r, r), "ReceiverConfig validation should pass")
})
s.T().Run("Invalid keystore path with keyUID", func(t *testing.T) {
r := &ReceiverConfig{
NodeConfig: nodeConfig,
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
DeviceType: "phone",
KDFIterations: 1,
SettingCurrentNetwork: "mainnet",
}
assert.Error(t, validateAndVerifyNodeConfig(r, r), "ReceiverConfig validation should fail")
})
}

View File

@ -1,11 +1,6 @@
package pairing
import (
"fmt"
"io/fs"
"io/ioutil"
"path/filepath"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
@ -126,7 +121,8 @@ func NewAccountPayloadLoader(p *AccountPayload, config *SenderConfig) (*AccountP
}
func (apl *AccountPayloadLoader) Load() error {
err := apl.loadKeys(apl.keystorePath)
apl.keys = make(map[string][]byte)
err := loadKeys(apl.keys, apl.keystorePath)
if err != nil {
return err
}
@ -144,36 +140,6 @@ func (apl *AccountPayloadLoader) Load() error {
return nil
}
func (apl *AccountPayloadLoader) loadKeys(keyStorePath string) error {
apl.keys = make(map[string][]byte)
fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
return nil
}
rawKeyFile, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("invalid account key file: %v", err)
}
apl.keys[dirEntry.Name()] = rawKeyFile
return nil
}
err := filepath.WalkDir(keyStorePath, fileWalker)
if err != nil {
return fmt.Errorf("cannot traverse key store folder: %v", err)
}
return nil
}
/*
|--------------------------------------------------------------------------
| RawMessagePayload

View File

@ -172,6 +172,10 @@ func StartUpSenderServer(backend *api.GethStatusBackend, configJSON string) (str
if err != nil {
return "", err
}
err = validateAndVerifyPassword(conf, conf.SenderConfig)
if err != nil {
return "", err
}
ps, err := MakeFullSenderServer(backend, conf)
if err != nil {
@ -268,6 +272,10 @@ func StartUpReceiverServer(backend *api.GethStatusBackend, configJSON string) (s
if err != nil {
return "", err
}
err = validateAndVerifyNodeConfig(conf, conf.ReceiverConfig)
if err != nil {
return "", err
}
ps, err := MakeFullReceiverServer(backend, conf)
if err != nil {

View File

@ -363,13 +363,9 @@ func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derive
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
settings.WalletVisibleTokens = &visibleTokenJSONRaw
networks := make([]map[string]string, 0)
networksJSON, err := json.Marshal(networks)
if err != nil {
return nil, err
}
networkRawMessage := json.RawMessage(networksJSON)
settings.Networks = &networkRawMessage
networks := `[{"id":"goerli_rpc","chain-explorer-link":"https://goerli.etherscan.io/address/","name":"Goerli with upstream RPC","config":{"NetworkId":5,"DataDir":"/ethereum/goerli_rpc","UpstreamConfig":{"Enabled":true,"URL":"https://goerli-archival.gateway.pokt.network/v1/lb/3ef2018191814b7e1009b8d9"}}},{"id":"mainnet_rpc","chain-explorer-link":"https://etherscan.io/address/","name":"Mainnet with upstream RPC","config":{"NetworkId":1,"DataDir":"/ethereum/mainnet_rpc","UpstreamConfig":{"Enabled":true,"URL":"https://eth-archival.gateway.pokt.network/v1/lb/3ef2018191814b7e1009b8d9"}}}]`
var networksRawMessage json.RawMessage = []byte(networks)
settings.Networks = &networksRawMessage
settings.CurrentNetwork = currentNetwork
return settings, nil
@ -407,7 +403,7 @@ func defaultNodeConfig(installationID, keyUID string) (*params.NodeConfig, error
}
nodeConfig.ShhextConfig = params.ShhextConfig{
BackupDisabledDataDir: "",
BackupDisabledDataDir: nodeConfig.DataDir,
InstallationID: installationID,
MaxMessageDeliveryAttempts: 6,
MailServerConfirmations: true,