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

chore: Make keyring non-interactive #3026

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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ crash.log

# Ignore .zip files, such as Lambda Function code slugs.
**.zip

# Ignore .env files containing sensitive information.
.env
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ The following keys are loaded from the keyring on start:
- `peer-key` Ed25519 private key (required)
- `encryption-key` AES-128, AES-192, or AES-256 key (optional)

To randomly generate the required keys, run the following command:
A secret to unlock the keyring is required on start and must be provided via the `DEFRADB_KEYRING_SECRET` environment variable. If a `.env` file is available in the working directory, the secret can be stored there or via a file at a path defined by the `--keyring-secret-file` flag.

The keys will be randomly generated on the inital start of the node if they are not found.

Alternatively, to randomly generate the required keys, run the following command:

```
defradb keyring generate
Expand Down
10 changes: 10 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"path/filepath"
"strings"

"github.com/joho/godotenv"
"github.com/sourcenetwork/corelog"
"github.com/spf13/pflag"
"github.com/spf13/viper"
Expand Down Expand Up @@ -52,6 +53,7 @@
"url": "api.address",
"max-txn-retries": "datastore.maxtxnretries",
"store": "datastore.store",
"no-encryption": "datastore.noencryption",
"valuelogfilesize": "datastore.badger.valuelogfilesize",
"peers": "net.peers",
"p2paddr": "net.p2paddresses",
Expand All @@ -65,6 +67,7 @@
"no-keyring": "keyring.disabled",
"source-hub-address": "acp.sourceHub.address",
"development": "development",
"secret-file": "secretfile",
}

// configDefaults contains default values for config entries.
Expand Down Expand Up @@ -92,6 +95,7 @@
"log.output": "stderr",
"log.source": false,
"log.stacktrace": false,
"secretfile": ".env",
}

// defaultConfig returns a new config with default values.
Expand Down Expand Up @@ -159,6 +163,12 @@
}
}

// load environment variables from .env file if one exists
err = godotenv.Load(cfg.GetString("secretfile"))
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err

Check warning on line 169 in cli/config.go

View check run for this annotation

Codecov / codecov/patch

cli/config.go#L169

Added line #L169 was not covered by tests
}

// set logging config
corelog.SetConfig(corelog.Config{
Level: cfg.GetString("log.level"),
Expand Down
13 changes: 1 addition & 12 deletions cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ import (
"github.com/sourcenetwork/defradb/errors"
)

const errKeyringHelp = `%w

Did you forget to initialize the keyring?

Use the following command to generate the required keys:
defradb keyring generate
`

const (
errInvalidLensConfig string = "invalid lens configuration"
errSchemaVersionNotOfSchema string = "the given schema version is from a different schema"
Expand All @@ -41,6 +33,7 @@ var (
ErrViewAddMissingArgs = errors.New("please provide a base query and output SDL for this view")
ErrPolicyFileArgCanNotBeEmpty = errors.New("policy file argument can not be empty")
ErrPurgeForceFlagRequired = errors.New("run this command again with --force if you really want to purge all data")
ErrMissingKeyringSecret = errors.New("missing keyring secret")
)

func NewErrRequiredFlagEmpty(longName string, shortName string) error {
Expand All @@ -62,7 +55,3 @@ func NewErrSchemaVersionNotOfSchema(schemaRoot string, schemaVersionID string) e
errors.NewKV("SchemaVersionID", schemaVersionID),
)
}

func NewErrKeyringHelp(inner error) error {
return fmt.Errorf(errKeyringHelp, inner)
}
4 changes: 4 additions & 0 deletions cli/keyring_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func MakeKeyringExportCommand() *cobra.Command {
Long: `Export a private key.
Prints the hexadecimal representation of a private key.

The DEFRA_KEYRING_SECRET environment variable must be set to unlock the keyring.
This can also be done with a .env file in the working directory or at a path
defined with the --keyring-secret-file flag.

Example:
defradb keyring export encryption-key`,
Args: cobra.ExactArgs(1),
Expand Down
7 changes: 3 additions & 4 deletions cli/keyring_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@ package cli
import (
"bytes"
"encoding/hex"
"os"
"strings"
"testing"

"github.com/sourcenetwork/defradb/crypto"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestKeyringExport(t *testing.T) {
rootdir := t.TempDir()
readPassword = func(_ *cobra.Command, _ string) ([]byte, error) {
return []byte("secret"), nil
}
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
Copy link
Contributor

Choose a reason for hiding this comment

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

question: don't we want to keep at least one test that uses the old prompt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The prompt has been removed entirely.

require.NoError(t, err)

keyBytes, err := crypto.GenerateAES256()
require.NoError(t, err)
Expand Down
8 changes: 6 additions & 2 deletions cli/keyring_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ func MakeKeyringGenerateCommand() *cobra.Command {
Randomly generate and store private keys in the keyring.
By default peer and encryption keys will be generated.

The DEFRA_KEYRING_SECRET environment variable must be set to unlock the keyring.
This can also be done with a .env file in the working directory or at a path
defined with the --keyring-secret-file flag.

WARNING: This will overwrite existing keys in the keyring.

Example:
defradb keyring generate

Example: with no encryption key
defradb keyring generate --no-encryption-key
defradb keyring generate --no-encryption

Example: with no peer key
defradb keyring generate --no-peer-key
Expand Down Expand Up @@ -69,7 +73,7 @@ Example: with system keyring
return nil
},
}
cmd.Flags().BoolVar(&noEncryptionKey, "no-encryption-key", false,
cmd.Flags().BoolVar(&noEncryptionKey, "no-encryption", false,
"Skip generating an encryption key. Encryption at rest will be disabled")
cmd.Flags().BoolVar(&noPeerKey, "no-peer-key", false,
"Skip generating a peer key.")
Expand Down
25 changes: 11 additions & 14 deletions cli/keyring_generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,23 @@
package cli

import (
"os"
"path/filepath"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestKeyringGenerate(t *testing.T) {
rootdir := t.TempDir()
readPassword = func(_ *cobra.Command, _ string) ([]byte, error) {
return []byte("secret"), nil
}
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
require.NoError(t, err)

cmd := NewDefraCommand()
cmd.SetArgs([]string{"keyring", "generate", "--rootdir", rootdir})

err := cmd.Execute()
err = cmd.Execute()
require.NoError(t, err)

assert.FileExists(t, filepath.Join(rootdir, "keys", encryptionKeyName))
Expand All @@ -37,14 +36,13 @@ func TestKeyringGenerate(t *testing.T) {

func TestKeyringGenerateNoEncryptionKey(t *testing.T) {
rootdir := t.TempDir()
readPassword = func(_ *cobra.Command, _ string) ([]byte, error) {
return []byte("secret"), nil
}
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
require.NoError(t, err)

cmd := NewDefraCommand()
cmd.SetArgs([]string{"keyring", "generate", "--no-encryption-key", "--rootdir", rootdir})
cmd.SetArgs([]string{"keyring", "generate", "--no-encryption", "--rootdir", rootdir})

err := cmd.Execute()
err = cmd.Execute()
require.NoError(t, err)

assert.NoFileExists(t, filepath.Join(rootdir, "keys", encryptionKeyName))
Expand All @@ -53,14 +51,13 @@ func TestKeyringGenerateNoEncryptionKey(t *testing.T) {

func TestKeyringGenerateNoPeerKey(t *testing.T) {
rootdir := t.TempDir()
readPassword = func(_ *cobra.Command, _ string) ([]byte, error) {
return []byte("secret"), nil
}
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
require.NoError(t, err)

cmd := NewDefraCommand()
cmd.SetArgs([]string{"keyring", "generate", "--no-peer-key", "--rootdir", rootdir})

err := cmd.Execute()
err = cmd.Execute()
require.NoError(t, err)

assert.FileExists(t, filepath.Join(rootdir, "keys", encryptionKeyName))
Expand Down
4 changes: 4 additions & 0 deletions cli/keyring_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func MakeKeyringImportCommand() *cobra.Command {
Long: `Import a private key.
Store an externally generated key in the keyring.

The DEFRA_KEYRING_SECRET environment variable must be set to unlock the keyring.
This can also be done with a .env file in the working directory or at a path
defined with the --keyring-secret-file flag.

Example:
defradb keyring import encryption-key 0000000000000000`,
Args: cobra.ExactArgs(2),
Expand Down
7 changes: 3 additions & 4 deletions cli/keyring_import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ package cli

import (
"encoding/hex"
"os"
"path/filepath"
"testing"

"github.com/sourcenetwork/defradb/crypto"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestKeyringImport(t *testing.T) {
rootdir := t.TempDir()
readPassword = func(_ *cobra.Command, _ string) ([]byte, error) {
return []byte("secret"), nil
}
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
require.NoError(t, err)

keyBytes, err := crypto.GenerateAES256()
require.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,9 @@ Start a DefraDB node, interact with a local or remote node, and much more.
cfg.GetString(configFlags["source-hub-address"]),
"The SourceHub address authorized by the client to make SourceHub transactions on behalf of the actor",
)
cmd.PersistentFlags().String(
"secret-file",
cfg.GetString(configFlags["secret-file"]),
"Path to the file containing secrets")
return cmd
}
36 changes: 31 additions & 5 deletions cli/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"github.com/sourcenetwork/immutable"
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/crypto"
"github.com/sourcenetwork/defradb/errors"
"github.com/sourcenetwork/defradb/event"
"github.com/sourcenetwork/defradb/http"
Expand Down Expand Up @@ -97,17 +98,38 @@
if !cfg.GetBool("keyring.disabled") {
kr, err := openKeyring(cmd)
if err != nil {
return NewErrKeyringHelp(err)
return err

Check warning on line 101 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L101

Added line #L101 was not covered by tests
}
// load the required peer key
// load the required peer key or generate one if it doesn't exist
peerKey, err := kr.Get(peerKeyName)
if err != nil {
return NewErrKeyringHelp(err)
if err != nil && errors.Is(err, keyring.ErrNotFound) {
peerKey, err = crypto.GenerateEd25519()
if err != nil {
return err

Check warning on line 108 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L105-L108

Added lines #L105 - L108 were not covered by tests
}
err = kr.Set(peerKeyName, peerKey)
if err != nil {
return err

Check warning on line 112 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L110-L112

Added lines #L110 - L112 were not covered by tests
}
log.Info("generated peer key")
} else if err != nil {
return err

Check warning on line 116 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L114-L116

Added lines #L114 - L116 were not covered by tests
}
opts = append(opts, net.WithPrivateKey(peerKey))

// load the optional encryption key
encryptionKey, err := kr.Get(encryptionKeyName)
if err != nil && !errors.Is(err, keyring.ErrNotFound) {
if err != nil && errors.Is(err, keyring.ErrNotFound) && !cfg.GetBool("datastore.noencryption") {
encryptionKey, err = crypto.GenerateAES256()
if err != nil {
return err

Check warning on line 125 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L122-L125

Added lines #L122 - L125 were not covered by tests
}
err = kr.Set(encryptionKeyName, encryptionKey)
if err != nil {
return err

Check warning on line 129 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L127-L129

Added lines #L127 - L129 were not covered by tests
}
log.Info("generated encryption key")
} else if err != nil && !errors.Is(err, keyring.ErrNotFound) {

Check warning on line 132 in cli/start.go

View check run for this annotation

Codecov / codecov/patch

cli/start.go#L131-L132

Added lines #L131 - L132 were not covered by tests
return err
}
opts = append(opts, node.WithBadgerEncryptionKey(encryptionKey))
Expand Down Expand Up @@ -224,5 +246,9 @@
cfg.GetBool(configFlags["development"]),
"Enables a set of features that make development easier but should not be enabled in production",
)
cmd.Flags().Bool(
"no-encryption",
cfg.GetBool(configFlags["no-encryption"]),
"Skip generating an encryption key. Encryption at rest will be disabled. WARNING: This cannot be undone.")
return cmd
}
19 changes: 5 additions & 14 deletions cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
"encoding/json"
"os"
"path/filepath"
"syscall"
"time"

"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/sourcenetwork/immutable"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"

acpIdentity "github.com/sourcenetwork/defradb/acp/identity"
"github.com/sourcenetwork/defradb/client"
Expand Down Expand Up @@ -58,14 +56,6 @@
authTokenExpiration = time.Minute * 15
)

// readPassword reads a user input password without echoing it to the terminal.
var readPassword = func(cmd *cobra.Command, msg string) ([]byte, error) {
cmd.Print(msg)
pass, err := term.ReadPassword(int(syscall.Stdin))
cmd.Println("")
return pass, err
}

// mustGetContextDB returns the db for the current command context.
//
// If a db is not set in the current context this function panics.
Expand Down Expand Up @@ -214,10 +204,11 @@
if err := os.MkdirAll(path, 0755); err != nil {
return nil, err
}
prompt := keyring.PromptFunc(func(s string) ([]byte, error) {
return readPassword(cmd, s)
})
return keyring.OpenFileKeyring(path, prompt)
secret := []byte(cfg.GetString("keyring.secret"))
if len(secret) == 0 {
return nil, ErrMissingKeyringSecret

Check warning on line 209 in cli/utils.go

View check run for this annotation

Codecov / codecov/patch

cli/utils.go#L209

Added line #L209 was not covered by tests
}
return keyring.OpenFileKeyring(path, secret)
}

func writeJSON(cmd *cobra.Command, out any) error {
Expand Down
8 changes: 8 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ The number of retries to make in the event of a transaction conflict. Defaults t

Currently this is only used within the P2P system and will not affect operations initiated by users.

## `datastore.noencryption`

Skip generating an encryption key. Encryption at rest will be disabled. **WARNING**: This cannot be undone.

## `datastore.badger.path`

The path to the database data file(s). Defaults to `data`.
Expand Down Expand Up @@ -156,3 +160,7 @@ transactions created by the node is stored. Required when using `acp.type`:`sour
The SourceHub address of the actor that client-side actions should permit to make SourceHub actions on
their behalf. This is a client-side only config param. It is required if the client wishes to make
SourceHub ACP requests in order to create protected data.

## `secretfile`

Path to the file containing secrets. Defaults to `.env`.
Loading
Loading