Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add KMS functionality #417

Merged
merged 20 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/ostracon/commands/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (
"github.com/line/ostracon/privval"
)

func setupEnv(t *testing.T) {
func setupEnv(t *testing.T) string {
rootDir := t.TempDir()
viper.SetEnvPrefix("OC")
require.NoError(t, viper.BindEnv("HOME"))
require.NoError(t, os.Setenv("OC_HOME", rootDir))
return rootDir
}

func TestResetAllCmd(t *testing.T) {
Expand Down
48 changes: 40 additions & 8 deletions cmd/ostracon/commands/show_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package commands
import (
"fmt"

"github.com/line/ostracon/node"
"github.com/line/ostracon/types"
"github.com/spf13/cobra"

cfg "github.com/line/ostracon/config"
tmjson "github.com/line/ostracon/libs/json"
tmos "github.com/line/ostracon/libs/os"
"github.com/line/ostracon/privval"
Expand All @@ -15,18 +18,31 @@ var ShowValidatorCmd = &cobra.Command{
Use: "show-validator",
Aliases: []string{"show_validator"},
Short: "Show this node's validator info",
RunE: showValidator,
PreRun: deprecateSnakeCase,
RunE: func(cmd *cobra.Command, args []string) error {
Kynea0b marked this conversation as resolved.
Show resolved Hide resolved
return showValidator(cmd, args, config)
},
PreRun: deprecateSnakeCase,
}

func showValidator(cmd *cobra.Command, args []string) error {
keyFilePath := config.PrivValidatorKeyFile()
if !tmos.FileExists(keyFilePath) {
return fmt.Errorf("private validator file %s does not exist", keyFilePath)
func showValidator(cmd *cobra.Command, args []string, config *cfg.Config) error {
tnasu marked this conversation as resolved.
Show resolved Hide resolved
var pv types.PrivValidator
if config.PrivValidatorListenAddr != "" {
chainID, err := loadChainID(config)
if err != nil {
return err
}
pv, err = node.CreateAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, chainID, logger)
tnasu marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
} else {
keyFilePath := config.PrivValidatorKeyFile()
if !tmos.FileExists(keyFilePath) {
return fmt.Errorf("private validator file %s does not exist", keyFilePath)
}
pv = privval.LoadFilePV(keyFilePath, config.PrivValidatorStateFile())
}

pv := privval.LoadFilePV(keyFilePath, config.PrivValidatorStateFile())

pubKey, err := pv.GetPubKey()
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
Expand All @@ -40,3 +56,19 @@ func showValidator(cmd *cobra.Command, args []string) error {
fmt.Println(string(bz))
return nil
}

func loadChainID(config *cfg.Config) (string, error) {
stateDB, err := node.DefaultDBProvider(&node.DBContext{ID: "state", Config: config})
if err != nil {
return "", err
}
defer func() {
var _ = stateDB.Close()
}()
genesisDocProvider := node.DefaultGenesisDocProviderFunc(config)
_, genDoc, err := node.LoadStateFromDBOrGenesisDocProvider(stateDB, genesisDocProvider)
if err != nil {
return "", err
}
return genDoc.ChainID, nil
}
191 changes: 191 additions & 0 deletions cmd/ostracon/commands/show_validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package commands

import (
"bytes"
"io/ioutil"
"os"
"sync"
"testing"

"github.com/line/ostracon/types"

cfg "github.com/line/ostracon/config"
"github.com/line/ostracon/crypto"
tmjson "github.com/line/ostracon/libs/json"
tmos "github.com/line/ostracon/libs/os"
"github.com/line/ostracon/privval"
"github.com/stretchr/testify/require"
)

func TestShowValidator(t *testing.T) {
original := config
defer func() {
config = original
}()

setupEnv(t)
config = cfg.DefaultConfig()
err := RootCmd.PersistentPreRunE(RootCmd, nil)
require.NoError(t, err)
init := NewInitCmd()
err = init.RunE(init, nil)
require.NoError(t, err)
output, err := captureStdout(func() {
err = ShowValidatorCmd.RunE(ShowValidatorCmd, nil)
require.NoError(t, err)
})
require.NoError(t, err)

// output must match the locally stored priv_validator key
privKey := loadFilePVKey(t, config.PrivValidatorKeyFile())
bz, err := tmjson.Marshal(privKey.PubKey)
require.NoError(t, err)
require.Equal(t, string(bz), output)
}

func TestShowValidatorWithoutLocalKeyFile(t *testing.T) {
setupEnv(t)
config := cfg.DefaultConfig()
if tmos.FileExists(config.PrivValidatorKeyFile()) {
err := os.Remove(config.PrivValidatorKeyFile())
require.NoError(t, err)
}
err := showValidator(ShowValidatorCmd, nil, config)
require.Error(t, err)
}

func TestShowValidatorWithKMS(t *testing.T) {
dir := setupEnv(t)
cfg.EnsureRoot(dir)

original := config
defer func() {
config = original
}()

config = cfg.DefaultConfig()
config.SetRoot(dir)
err := RootCmd.PersistentPreRunE(RootCmd, nil)
require.NoError(t, err)
init := NewInitCmd()
err = init.RunE(init, nil)
require.NoError(t, err)

chainID, err := loadChainID(config)
require.NoError(t, err)

if tmos.FileExists(config.PrivValidatorKeyFile()) {
err := os.Remove(config.PrivValidatorKeyFile())
require.NoError(t, err)
}
privval.WithMockKMS(t, dir, chainID, func(addr string, privKey crypto.PrivKey) {
config.PrivValidatorListenAddr = addr
require.NoFileExists(t, config.PrivValidatorKeyFile())
output, err := captureStdout(func() {
err := showValidator(ShowValidatorCmd, nil, config)
require.NoError(t, err)
})
require.NoError(t, err)

// output must contains the KMS public key
bz, err := tmjson.Marshal(privKey.PubKey())
require.NoError(t, err)
expected := string(bz)
require.Contains(t, output, expected)
})
}

func TestShowValidatorWithInefficientKMSAddress(t *testing.T) {
dir := setupEnv(t)
cfg.EnsureRoot(dir)

original := config
defer func() {
config = original
}()

config = cfg.DefaultConfig()
config.SetRoot(dir)
err := RootCmd.PersistentPreRunE(RootCmd, nil)
require.NoError(t, err)
init := NewInitCmd()
err = init.RunE(init, nil)
require.NoError(t, err)

if tmos.FileExists(config.PrivValidatorKeyFile()) {
err := os.Remove(config.PrivValidatorKeyFile())
require.NoError(t, err)
}
config.PrivValidatorListenAddr = "127.0.0.1:inefficient"
err = showValidator(ShowValidatorCmd, nil, config)
require.Error(t, err)
}

func TestLoadChainID(t *testing.T) {
expected := "c57861"
config := cfg.ResetTestRootWithChainID("TestLoadChainID", expected)
defer func() {
var _ = os.RemoveAll(config.RootDir)
}()

require.FileExists(t, config.GenesisFile())
tnasu marked this conversation as resolved.
Show resolved Hide resolved
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
require.NoError(t, err)
require.Equal(t, expected, genDoc.ChainID)

chainID, err := loadChainID(config)
require.NoError(t, err)
require.Equal(t, expected, chainID)
}

func TestLoadChainIDWithoutStateDB(t *testing.T) {
expected := "c34091"
config := cfg.ResetTestRootWithChainID("TestLoadChainID", expected)
defer func() {
var _ = os.RemoveAll(config.RootDir)
}()

config.DBBackend = "goleveldb"
config.DBPath = "/../path with containing chars that cannot be used\\/:*?\"<>|\x00"

_, err := loadChainID(config)
require.Error(t, err)
}

func loadFilePVKey(t *testing.T, file string) privval.FilePVKey {
// output must match the locally stored priv_validator key
keyJSONBytes, err := ioutil.ReadFile(file)
require.NoError(t, err)
privKey := privval.FilePVKey{}
err = tmjson.Unmarshal(keyJSONBytes, &privKey)
require.NoError(t, err)
return privKey
}

var stdoutMutex sync.Mutex

func captureStdout(f func()) (string, error) {
r, w, err := os.Pipe()
if err != nil {
return "", err
}

stdoutMutex.Lock()
original := os.Stdout
defer func() {
stdoutMutex.Lock()
os.Stdout = original
stdoutMutex.Unlock()
}()
os.Stdout = w
stdoutMutex.Unlock()

f()
_ = w.Close()
var buffer bytes.Buffer
if _, err := buffer.ReadFrom(r); err != nil {
return "", err
}
output := buffer.String()
return output[:len(output)-1], nil
}
2 changes: 1 addition & 1 deletion cmd/ostracon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func main() {
// * Provide their own DB implementation
// can copy this file and use something other than the
// DefaultNewNode function
nodeFunc := nm.DefaultNewNode
nodeFunc := nm.NewOstraconNode

// Create & start node
rootCmd.AddCommand(cmd.NewInitCmd())
Expand Down
9 changes: 9 additions & 0 deletions config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ func ResetTestRootWithChainID(testName string, chainID string) *Config {
genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis)
privKeyFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorKey)
privStateFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorState)
nodeKeyFilePath := filepath.Join(rootDir, baseConfig.NodeKey)

// Write default config file if missing.
if !tmos.FileExists(configFilePath) {
Expand All @@ -575,6 +576,7 @@ func ResetTestRootWithChainID(testName string, chainID string) *Config {
// we always overwrite the priv val
tmos.MustWriteFile(privKeyFilePath, []byte(testPrivValidatorKey), 0644)
tmos.MustWriteFile(privStateFilePath, []byte(testPrivValidatorState), 0644)
tmos.MustWriteFile(nodeKeyFilePath, []byte(testNodeKey), 0644)
tnasu marked this conversation as resolved.
Show resolved Hide resolved

config := TestConfig().SetRoot(rootDir)
return config
Expand Down Expand Up @@ -633,6 +635,13 @@ var testPrivValidatorKey = `{
}
}`

var testNodeKey = `{
"priv_key": {
"type": "ostracon/PrivKeyEd25519",
"value": "hICuZLlVwHdzz6pAQOKk07MFn3Hze1EwwTUUhEDIdti9a1cQLR5Co/lxAzeGcyPWS/LuEr7qbgHmDUJT/nxx+Q=="
}
}`

var testPrivValidatorState = `{
"height": "0",
"round": 0,
Expand Down
30 changes: 28 additions & 2 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,32 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) {
)
}

// NewOstraconNode returns an Ostracon node for more safe production environments that don't automatically generate
// critical files. This function doesn't reference local key pair in configurations using KMS.
func NewOstraconNode(config *cfg.Config, logger log.Logger) (*Node, error) {
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
if err != nil {
return nil, fmt.Errorf("failed to load node key %s: %w", config.NodeKeyFile(), err)
}

var privKey types.PrivValidator
if config.PrivValidatorListenAddr == "" {
privKey = privval.LoadFilePV(
config.PrivValidatorKeyFile(),
config.PrivValidatorStateFile())
}
return NewNode(
config,
privKey,
nodeKey,
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
DefaultGenesisDocProviderFunc(config),
DefaultDBProvider,
DefaultMetricsProvider(config.Instrumentation),
logger,
)
}

// MetricsProvider returns a consensus, p2p and mempool Metrics.
type MetricsProvider func(chainID string) (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics)

Expand Down Expand Up @@ -730,7 +756,7 @@ func NewNode(config *cfg.Config,
// external signing process.
if config.PrivValidatorListenAddr != "" {
// FIXME: we should start services inside OnStart
privValidator, err = createAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, genDoc.ChainID, logger)
privValidator, err = CreateAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, genDoc.ChainID, logger)
if err != nil {
return nil, fmt.Errorf("error with private validator socket client: %w", err)
}
Expand Down Expand Up @@ -1438,7 +1464,7 @@ func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) error {
return nil
}

func createAndStartPrivValidatorSocketClient(
func CreateAndStartPrivValidatorSocketClient(
listenAddr,
chainID string,
logger log.Logger,
Expand Down
19 changes: 19 additions & 0 deletions node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ import (
tmtime "github.com/line/ostracon/types/time"
)

func TestNewOstraconNode(t *testing.T) {
config := cfg.ResetTestRootWithChainID("TestNewOstraconNode", "new_ostracon_node")
defer os.RemoveAll(config.RootDir)
require.Equal(t, config.PrivValidatorListenAddr, "")
node, err := NewOstraconNode(config, log.TestingLogger())
require.NoError(t, err)
pubKey, err := node.PrivValidator().GetPubKey()
require.NoError(t, err)
require.NotNil(t, pubKey)
}

func TestNewOstraconNode_WithoutNodeKey(t *testing.T) {
config := cfg.ResetTestRootWithChainID("TestNewOstraconNode", "new_ostracon_node_wo_node_key")
defer os.RemoveAll(config.RootDir)
_ = os.Remove(config.NodeKeyFile())
_, err := NewOstraconNode(config, log.TestingLogger())
require.Error(t, err)
}

func TestNodeStartStop(t *testing.T) {
config := cfg.ResetTestRoot("node_node_test")
defer os.RemoveAll(config.RootDir)
Expand Down
Loading