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

Generate database encryption keys in bootstrap tooling #1340

Merged
merged 8 commits into from
Sep 29, 2021
52 changes: 52 additions & 0 deletions cmd/bootstrap/cmd/db_encryption_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cmd

import (
"fmt"
"path"

"github.com/spf13/cobra"

"github.com/onflow/flow-go/cmd/bootstrap/utils"
model "github.com/onflow/flow-go/model/bootstrap"
)

// dbEncryptionKyCmd adds a command to the bootstrap utility which generates an
// AES-256 key for encrypting the secrets database, and writes it to the default
// path.
var dbEncryptionKyCmd = &cobra.Command{
Use: "db-encryption-key",
Short: "Generates encryption key for secrets database and writes it to the default path within the bootstrap directory",
Run: dbEncryptionKeyRun,
}

func init() {
rootCmd.AddCommand(dbEncryptionKyCmd)
}

func dbEncryptionKeyRun(_ *cobra.Command, _ []string) {

// read nodeID written to boostrap dir by `bootstrap key`
nodeID, err := readNodeID()
if err != nil {
log.Fatal().Err(err).Msg("could not read node id")
}

// check if the key already exists
dbEncryptionKeyPath := fmt.Sprintf(model.PathSecretsEncryptionKey, nodeID)
exists, err := pathExists(path.Join(flagOutdir, dbEncryptionKeyPath))
if err != nil {
log.Fatal().Err(err).Msg("could not check if db encryption key already exists")
}
if exists {
log.Warn().Msg("DB encryption key already exists, exiting...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to print the path

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it to the logger f958aea

return
}

dbEncryptionKey, err := utils.GenerateSecretsDBEncryptionKey()
if err != nil {
log.Fatal().Err(err).Msg("could not generate db encryption key")
}
log.Info().Msg("generated db encryption key")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to print path


writeText(dbEncryptionKeyPath, dbEncryptionKey)
}
109 changes: 109 additions & 0 deletions cmd/bootstrap/cmd/db_encryption_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/model/bootstrap"
ioutils "github.com/onflow/flow-go/utils/io"
"github.com/onflow/flow-go/utils/unittest"
)

// Test that attempting to generate a db encryption key is a no-op if a
// key file already exists.
func TestDBEncryptionKeyFileExists(t *testing.T) {
unittest.RunWithTempDir(t, func(bootDir string) {

var keyFileExistsRegex = regexp.MustCompile(`^DB encryption key already exists`)

flagOutdir = bootDir
flagRole = "verification"
flagAddress = "189.123.123.42:3869"

// generate all bootstrapping files
keyCmdRun(nil, nil)
require.DirExists(t, filepath.Join(flagOutdir, bootstrap.DirnamePublicBootstrap))
require.DirExists(t, filepath.Join(flagOutdir, bootstrap.DirPrivateRoot))

nodeIDPath := filepath.Join(flagOutdir, bootstrap.PathNodeID)
require.FileExists(t, nodeIDPath)
b, err := ioutils.ReadFile(nodeIDPath)
require.NoError(t, err)
nodeID := strings.TrimSpace(string(b))

// make sure file exists
encryptionKeyPath := filepath.Join(flagOutdir, fmt.Sprintf(bootstrap.PathSecretsEncryptionKey, nodeID))
require.FileExists(t, encryptionKeyPath)

// read the file
keyFileBefore, err := ioutils.ReadFile(encryptionKeyPath)
require.NoError(t, err)

// create a hooked logger
var hook unittest.LoggerHook
log, hook = unittest.HookedLogger()

// run the encryption key gen tool
dbEncryptionKeyRun(nil, nil)

// ensure regex matches
require.Regexp(t, keyFileExistsRegex, hook.Logs())

// ensure the existing file is unmodified
keyFileAfter, err := ioutils.ReadFile(encryptionKeyPath)
require.NoError(t, err)
assert.Equal(t, keyFileBefore, keyFileAfter)
})
}

// Test that we can generate a key file if none exists.
func TestDBEncryptionKeyFileCreated(t *testing.T) {
unittest.RunWithTempDir(t, func(bootDir string) {

var keyFileCreatedRegex = `^generated db encryption key` +
`wrote file ` + bootDir + `/private-root-information/private-node-info_\S+/secretsdb-key`

flagOutdir = bootDir
flagRole = "verification"
flagAddress = "189.123.123.42:3869"

// generate all bootstrapping files
keyCmdRun(nil, nil)
require.DirExists(t, filepath.Join(flagOutdir, bootstrap.DirnamePublicBootstrap))
require.DirExists(t, filepath.Join(flagOutdir, bootstrap.DirPrivateRoot))

nodeIDPath := filepath.Join(flagOutdir, bootstrap.PathNodeID)
require.FileExists(t, nodeIDPath)
b, err := ioutils.ReadFile(nodeIDPath)
require.NoError(t, err)
nodeID := strings.TrimSpace(string(b))

// delete db encryption key file
dbEncryptionKeyPath := filepath.Join(flagOutdir, fmt.Sprintf(bootstrap.PathSecretsEncryptionKey, nodeID))
err = os.Remove(dbEncryptionKeyPath)
require.NoError(t, err)

// confirm file was removed
require.NoFileExists(t, dbEncryptionKeyPath)

// create a hooked logger
var hook unittest.LoggerHook
log, hook = unittest.HookedLogger()

// run the encryption key gen tool
dbEncryptionKeyRun(nil, nil)

// ensure regex matches
require.Regexp(t, keyFileCreatedRegex, hook.Logs())

// make sure file exists (regex checks this too)
require.FileExists(t, dbEncryptionKeyPath)
})
}
18 changes: 13 additions & 5 deletions cmd/bootstrap/cmd/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func keyCmdRun(_ *cobra.Command, _ []string) {
validateAddressFormat(flagAddress)

// generate staking and network keys
networkKey, stakingKey, err := generateKeys()
networkKey, stakingKey, secretsDBKey, err := generateKeys()
if err != nil {
log.Fatal().Err(err).Msg("could not generate staking or network keys")
}
Expand All @@ -88,6 +88,7 @@ func keyCmdRun(_ *cobra.Command, _ []string) {
// write files
writeText(model.PathNodeID, []byte(nodeInfo.NodeID.String()))
writeJSON(fmt.Sprintf(model.PathNodeInfoPriv, nodeInfo.NodeID), private)
writeText(fmt.Sprintf(model.PathSecretsEncryptionKey, nodeInfo.NodeID), secretsDBKey)
writeJSON(fmt.Sprintf(model.PathNodeInfoPub, nodeInfo.NodeID), nodeInfo.Public())

// write machine account info
Expand All @@ -106,25 +107,32 @@ func keyCmdRun(_ *cobra.Command, _ []string) {
}
}

func generateKeys() (crypto.PrivateKey, crypto.PrivateKey, error) {
func generateKeys() (crypto.PrivateKey, crypto.PrivateKey, []byte, error) {

log.Debug().Msg("will generate networking key")
networkSeed := validateSeed(flagNetworkSeed)
networkKey, err := utils.GenerateNetworkingKey(networkSeed)
if err != nil {
return nil, nil, fmt.Errorf("could not generate networking key: %w", err)
return nil, nil, nil, fmt.Errorf("could not generate networking key: %w", err)
}
log.Info().Msg("generated networking key")

log.Debug().Msg("will generate staking key")
stakingSeed := validateSeed(flagStakingSeed)
stakingKey, err := utils.GenerateStakingKey(stakingSeed)
if err != nil {
return nil, nil, fmt.Errorf("could not generate staking key: %w", err)
return nil, nil, nil, fmt.Errorf("could not generate staking key: %w", err)
}
log.Info().Msg("generated staking key")

return networkKey, stakingKey, nil
log.Debug().Msg("will generate db encryption key")
secretsDBKey, err := utils.GenerateSecretsDBEncryptionKey()
if err != nil {
return nil, nil, nil, fmt.Errorf("could not generate secrets db encryption key: %w", err)
}
log.Info().Msg("generated db encryption key")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.Info().Msg("generated db encryption key")
log.Info().Msg("saved db encryption key")

otherwise, we are printing the same log twice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two logs are for two separate commands. One is for setting up all of your keys at once (key.go) the other is only for creating the encryption key (db_encryption_key.go), so we wouldn't see these logs twice running either command.


return networkKey, stakingKey, secretsDBKey, nil
}

func generateMachineAccountKey() (crypto.PrivateKey, error) {
Expand Down
12 changes: 10 additions & 2 deletions cmd/bootstrap/cmd/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,34 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

model "github.com/onflow/flow-go/model/bootstrap"
)

var happyPathRegex = `^will generate networking key` +
`generated networking key` +
`will generate staking key` +
`generated staking key` +
`will generate db encryption key` +
`generated db encryption key` +
`assembling node information` +
`encoded public staking and network keys` +
`wrote file /tmp/%s/public-root-information/node-id` +
`wrote file /tmp/%s/private-root-information/private-node-info_\S+/node-info.priv.json` +
`wrote file /tmp/%s/private-root-information/private-node-info_\S+/` + model.FilenameSecretsEncryptionKey +
`wrote file /tmp/%s/public-root-information/node-info.pub.\S+.json`

var happyPathWithMachineAccountRegex = `^will generate networking key` +
`generated networking key` +
`will generate staking key` +
`generated staking key` +
`will generate db encryption key` +
`generated db encryption key` +
`assembling node information` +
`encoded public staking and network keys` +
`wrote file /tmp/%s/public-root-information/node-id` +
`wrote file /tmp/%s/private-root-information/private-node-info_\S+/node-info.priv.json` +
`wrote file /tmp/%s/private-root-information/private-node-info_\S+/` + model.FilenameSecretsEncryptionKey +
`wrote file /tmp/%s/public-root-information/node-info.pub.\S+.json` +
`will generate machine account key` +
`generated machine account key` +
Expand All @@ -42,7 +50,7 @@ var happyPathWithMachineAccountRegex = `^will generate networking key` +

func TestHappyPath(t *testing.T) {
dirName := strconv.FormatInt(time.Now().UnixNano(), 10)
regex := regexp.MustCompile(fmt.Sprintf(happyPathRegex, dirName, dirName, dirName))
regex := regexp.MustCompile(fmt.Sprintf(happyPathRegex, dirName, dirName, dirName, dirName))
flagOutdir = "/tmp/" + dirName
flagRole = "access"
flagAddress = "189.123.123.42:3869"
Expand All @@ -60,7 +68,7 @@ func TestHappyPath(t *testing.T) {

func TestHappyPathMachineAccount(t *testing.T) {
dirName := strconv.FormatInt(time.Now().UnixNano(), 10)
regex := regexp.MustCompile(fmt.Sprintf(happyPathWithMachineAccountRegex, dirName, dirName, dirName, dirName))
regex := regexp.MustCompile(fmt.Sprintf(happyPathWithMachineAccountRegex, dirName, dirName, dirName, dirName, dirName))
flagOutdir = "/tmp/" + dirName
flagRole = "consensus"
flagAddress = "189.123.123.42:3869"
Expand Down
18 changes: 15 additions & 3 deletions cmd/bootstrap/cmd/keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,36 @@ var keygenCmd = &cobra.Command{
log.Info().Msg("")

// write key files
writeFile := func(relativePath string, val interface{}) error {
writeJSONFile := func(relativePath string, val interface{}) error {
writeJSON(relativePath, val)
return nil
}
writeFile := func(relativePath string, data []byte) error {
writeText(relativePath, data)
return nil
}

log.Info().Msg("writing internal private key files")
err = utils.WriteStakingNetworkingKeyFiles(nodes, writeFile)
err = utils.WriteStakingNetworkingKeyFiles(nodes, writeJSONFile)
if err != nil {
log.Fatal().Err(err).Msg("failed to write internal private key files")
}
log.Info().Msg("")

log.Info().Msg("writing internal db encryption key files")
err = utils.WriteSecretsDBEncryptionKeyFiles(nodes, writeFile)
if err != nil {
log.Fatal().Err(err).Msg("failed to write internal db encryption key files")
}
log.Info().Msg("")

// if specified, write machine account key files
// this should be only be used on non-production networks and immediately after
// bootstrapping from a fresh execution state (eg. benchnet)
if flagDefaultMachineAccount {
chainID := parseChainID(flagRootChain)
log.Info().Msg("writing default machine account files")
err = utils.WriteMachineAccountFiles(chainID, nodes, writeFile)
err = utils.WriteMachineAccountFiles(chainID, nodes, writeJSONFile)
if err != nil {
log.Fatal().Err(err).Msg("failed to write machine account key files")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/bootstrap/cmd/machine_account_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func machineAccountKeyRun(_ *cobra.Command, _ []string) {
log.Fatal().Err(err).Msg("could not check if node-machine-account-key.priv.json exists")
}
if keyExists {
log.Warn().Msg("machine account private key already exists")
log.Warn().Msg("machine account private key already exists, exiting...")
return
}

Expand Down