Skip to content

Commit

Permalink
feat: Keyring (sourcenetwork#2557)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves sourcenetwork#2556 

## Description

This PR adds a keyring to the cli.

Notable changes:
- created `keyring` package
  - file backed keystore uses a password based key generation to encrypt
  - system backed keystore uses the OS managed keyring 
- added `keyring` root command
  - `--no-keyring` disables the keyring and generates ephemeral keys
  - `--keyring-backend` flag sets the keyring backend (file or system)
- `--keyring-path` flag sets the keyring directory when using file
backend
- `--keyring-namespace` flag sets the service name when using system
backend
- added `keyring generate` generates required keys
- `--no-encryption-key` skips generating encryption key (disables
datastore encryption)
- added `keyring import` import external keys  (hexadecimal for now)
- added `keyring export` export existing key (hexadecimal for now)
- moved key generation to `crypto` package

## Tasks

- [x] I made sure the code is well commented, particularly
hard-to-understand areas.
- [x] I made sure the repository-held documentation is changed
accordingly.
- [x] I made sure the pull request title adheres to the conventional
commit style (the subset used in the project can be found in
[tools/configs/chglog/config.yml](tools/configs/chglog/config.yml)).
- [x] I made sure to discuss its limitations such as threats to
validity, vulnerability to mistake and misuse, robustness to
invalidation of assumptions, resource requirements, ...

## How has this been tested?

Manually tested on MacOS.

Specify the platform(s) on which this was tested:
- MacOS
  • Loading branch information
nasdf authored May 10, 2024
1 parent a3d035b commit 01e1b66
Show file tree
Hide file tree
Showing 87 changed files with 1,259 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/start-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:

- name: Attempt to start binary
run: |
./build/defradb start &
./build/defradb start --no-keyring &
sleep 5
- name: Check if binary is still running
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/test-and-upload-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,34 @@ jobs:
database-type: [badger-file, badger-memory]
mutation-type: [gql, collection-named, collection-save]
detect-changes: [false]
database-encryption: [false]
include:
- os: ubuntu-latest
client-type: go
database-type: badger-memory
mutation-type: collection-save
detect-changes: true
database-encryption: false
- os: ubuntu-latest
client-type: go
database-type: badger-memory
mutation-type: collection-save
detect-changes: false
database-encryption: true
- os: macos-latest
client-type: go
database-type: badger-memory
mutation-type: collection-save
detect-changes: false
database-encryption: false
## TODO: https://github.com/sourcenetwork/defradb/issues/2080
## Uncomment the lines below to Re-enable the windows build once this todo is resolved.
## - os: windows-latest
## client-type: go
## database-type: badger-memory
## mutation-type: collection-save
## detect-changes: false
## database-encryption: false

runs-on: ${{ matrix.os }}

Expand All @@ -68,6 +78,7 @@ jobs:
DEFRA_CLIENT_CLI: ${{ matrix.client-type == 'cli' }}
DEFRA_BADGER_MEMORY: ${{ matrix.database-type == 'badger-memory' }}
DEFRA_BADGER_FILE: ${{ matrix.database-type == 'badger-file' }}
DEFRA_BADGER_ENCRYPTION: ${{ matrix.database-encryption }}
DEFRA_MUTATION_TYPE: ${{ matrix.mutation-type }}

steps:
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Read the documentation on [docs.source.network](https://docs.source.network/).
## Table of Contents

- [Install](#install)
- [Key Management](#key-management)
- [Start](#start)
- [Configuration](#configuration)
- [External port binding](#external-port-binding)
Expand Down Expand Up @@ -58,6 +59,33 @@ export PATH=$PATH:$(go env GOPATH)/bin

We recommend experimenting with queries using a native GraphQL client. GraphiQL is a popular option - [download and install it](https://altairgraphql.dev/#download).

## Key Management

DefraDB has a built in keyring that can be used to store private keys securely.

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:

```
defradb keyring generate
```

To import externally generated keys, run the following command:

```
defradb keyring import <name> <private-key-hex>
```

To learn more about the available options:

```
defradb keyring --help
```

## Start

Start a node by executing `defradb start`. Keep the node running while going through the following examples.
Expand Down
8 changes: 8 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@ func NewDefraCommand() *cobra.Command {
collection,
)

keyring := MakeKeyringCommand()
keyring.AddCommand(
MakeKeyringGenerateCommand(),
MakeKeyringImportCommand(),
MakeKeyringExportCommand(),
)

root := MakeRootCommand()
root.AddCommand(
client,
keyring,
MakeStartCommand(),
MakeServerDumpCmd(),
MakeVersionCommand(),
Expand Down
5 changes: 5 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var configPaths = []string{
"datastore.badger.path",
"api.pubkeypath",
"api.privkeypath",
"keyring.path",
}

// configFlags is a mapping of config keys to cli flags to bind to.
Expand All @@ -57,6 +58,10 @@ var configFlags = map[string]string{
"api.allowed-origins": "allowed-origins",
"api.pubkeypath": "pubkeypath",
"api.privkeypath": "privkeypath",
"keyring.namespace": "keyring-namespace",
"keyring.backend": "keyring-backend",
"keyring.path": "keyring-path",
"keyring.disabled": "no-keyring",
}

// defaultConfig returns a new config with default values.
Expand Down
6 changes: 5 additions & 1 deletion cli/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func TestLoadConfigNotExist(t *testing.T) {
require.NoError(t, err)

assert.Equal(t, 5, cfg.GetInt("datastore.maxtxnretries"))

assert.Equal(t, filepath.Join(rootdir, "data"), cfg.GetString("datastore.badger.path"))
assert.Equal(t, 1<<30, cfg.GetInt("datastore.badger.valuelogfilesize"))
assert.Equal(t, "badger", cfg.GetString("datastore.store"))
Expand All @@ -59,4 +58,9 @@ func TestLoadConfigNotExist(t *testing.T) {
assert.Equal(t, false, cfg.GetBool("log.source"))
assert.Equal(t, "", cfg.GetString("log.overrides"))
assert.Equal(t, false, cfg.GetBool("log.nocolor"))

assert.Equal(t, filepath.Join(rootdir, "keys"), cfg.GetString("keyring.path"))
assert.Equal(t, false, cfg.GetBool("keyring.disabled"))
assert.Equal(t, "defradb", cfg.GetString("keyring.namespace"))
assert.Equal(t, "file", cfg.GetString("keyring.backend"))
}
12 changes: 12 additions & 0 deletions cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ 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 Down Expand Up @@ -53,3 +61,7 @@ func NewErrSchemaVersionNotOfSchema(schemaRoot string, schemaVersionID string) e
errors.NewKV("SchemaVersionID", schemaVersionID),
)
}

func NewErrKeyringHelp(inner error) error {
return fmt.Errorf(errKeyringHelp, inner)
}
25 changes: 25 additions & 0 deletions cli/keyring.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import (
"github.com/spf13/cobra"
)

func MakeKeyringCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "keyring",
Short: "Manage DefraDB private keys",
Long: `Manage DefraDB private keys.
Generate, import, and export private keys.`,
}
return cmd
}
41 changes: 41 additions & 0 deletions cli/keyring_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import (
"github.com/spf13/cobra"
)

func MakeKeyringExportCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "export <name>",
Short: "Export a private key",
Long: `Export a private key.
Prints the hexadecimal representation of a private key.
Example:
defradb keyring export encryption-key`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
keyring, err := openKeyring(cmd)
if err != nil {
return err
}
keyBytes, err := keyring.Get(args[0])
if err != nil {
return err
}
cmd.Printf("%x\n", keyBytes)
return nil
},
}
return cmd
}
51 changes: 51 additions & 0 deletions cli/keyring_export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import (
"bytes"
"encoding/hex"
"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
}

keyBytes, err := crypto.GenerateAES256()
require.NoError(t, err)
keyHex := hex.EncodeToString(keyBytes)

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

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

var output bytes.Buffer
cmd.SetOut(&output)
cmd.SetArgs([]string{"keyring", "export", "--rootdir", rootdir, encryptionKeyName})

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

actualKeyHex := strings.TrimSpace(output.String())
assert.Equal(t, keyHex, actualKeyHex)
}
63 changes: 63 additions & 0 deletions cli/keyring_generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import (
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/crypto"
)

func MakeKeyringGenerateCommand() *cobra.Command {
var noEncryption bool
var cmd = &cobra.Command{
Use: "generate",
Short: "Generate private keys",
Long: `Generate private keys.
Randomly generate and store private keys in the keyring.
WARNING: This will overwrite existing keys in the keyring.
Example:
defradb keyring generate
Example: with no encryption key
defradb keyring generate --no-encryption-key
Example: with system keyring
defradb keyring generate --keyring-backend system`,
RunE: func(cmd *cobra.Command, args []string) error {
keyring, err := openKeyring(cmd)
if err != nil {
return err
}
if !noEncryption {
// generate optional encryption key
encryptionKey, err := crypto.GenerateAES256()
if err != nil {
return err
}
err = keyring.Set(encryptionKeyName, encryptionKey)
if err != nil {
return err
}
}
peerKey, err := crypto.GenerateEd25519()
if err != nil {
return err
}
return keyring.Set(peerKeyName, peerKey)
},
}
cmd.Flags().BoolVar(&noEncryption, "no-encryption-key", false,
"Skip generating an encryption. Encryption at rest will be disabled")
return cmd
}
Loading

0 comments on commit 01e1b66

Please sign in to comment.