Skip to content

Commit

Permalink
feat: Add 'keyring list' command to CLI (#3331)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves #2756

## Description

I am adding support for a new feature in the CLI. By running `defradb
keyring list`, the names of the keys in the fileKeyring will be listed
out.

## 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?

Specify the platform(s) on which this was tested:
- Windows
  • Loading branch information
ChrisBQu authored Jan 1, 2025
1 parent 9306e7d commit f7b996c
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 3 deletions.
1 change: 1 addition & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func NewDefraCommand() *cobra.Command {
MakeKeyringGenerateCommand(),
MakeKeyringImportCommand(),
MakeKeyringExportCommand(),
MakeKeyringListCommand(),
)

identity := MakeIdentityCommand()
Expand Down
54 changes: 54 additions & 0 deletions cli/keyring_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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"
)

// MakeKeyringListCommand creates a new command to list all keys in the keyring.
func MakeKeyringListCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "list",
Short: "List all keys in the keyring",
Long: `List all keys 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 --secret-file flag.
Example:
defradb keyring list`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
keyring, err := openKeyring(cmd)
if err != nil {
return err
}

keyNames, err := keyring.List()
if err != nil {
return err
}

if len(keyNames) == 0 {
cmd.Println("No keys found in the keyring.")
return nil
}

cmd.Println("Keys in the keyring:")
for _, keyName := range keyNames {
cmd.Println("- " + keyName)
}
return nil
},
}
return cmd
}
66 changes: 66 additions & 0 deletions cli/keyring_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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"
"os"
"regexp"
"testing"

"github.com/sourcenetwork/defradb/crypto"

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

func TestKeyringList(t *testing.T) {
rootdir := t.TempDir()

err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
require.NoError(t, err)

keyNames := []string{"keyname1", "keyname2", "keyname3"}

// Insert the keys into the keyring
for _, keyName := range keyNames {
keyBytes, err := crypto.GenerateAES256()
require.NoError(t, err)
keyHex := hex.EncodeToString(keyBytes)
cmd := NewDefraCommand()
cmd.SetArgs([]string{"keyring", "import", "--rootdir", rootdir, keyName, keyHex})
err = cmd.Execute()
require.NoError(t, err)
}

// Run the 'keyring list' command, and require no error on the output
var output bytes.Buffer
cmd := NewDefraCommand()
cmd.SetOut(&output)
cmd.SetArgs([]string{"keyring", "list", "--rootdir", rootdir})
err = cmd.Execute()
require.NoError(t, err)

outputString := output.String()

// Use regex to extract the keys, and compare with the expected values
// We know what the format the output should be, which is:
// "Keys in the keyring:\n- keyname1\n- keyname2\n- keyname3\n"
re := regexp.MustCompile(`-\s([^\n]+)`)
matches := re.FindAllStringSubmatch(outputString, -1)
var extractedKeys []string
for _, match := range matches {
extractedKeys = append(extractedKeys, match[1])
}

assert.ElementsMatch(t, keyNames, extractedKeys, "The listed keys do not match the expected keys.")
}
1 change: 1 addition & 0 deletions docs/website/references/cli/defradb_keyring.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ To learn more about the available options:
* [defradb keyring export](defradb_keyring_export.md) - Export a private key
* [defradb keyring generate](defradb_keyring_generate.md) - Generate private keys
* [defradb keyring import](defradb_keyring_import.md) - Import a private key
* [defradb keyring list](defradb_keyring_list.md) - List all keys in the keyring

48 changes: 48 additions & 0 deletions docs/website/references/cli/defradb_keyring_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## defradb keyring list

List all keys in the keyring

### Synopsis

List all keys 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 --secret-file flag.

Example:
defradb keyring list

```
defradb keyring list [flags]
```

### Options

```
-h, --help help for list
```

### Options inherited from parent commands

```
--keyring-backend string Keyring backend to use. Options are file or system (default "file")
--keyring-namespace string Service name to use when using the system backend (default "defradb")
--keyring-path string Path to store encrypted keys when using the file backend (default "keys")
--log-format string Log format to use. Options are text or json (default "text")
--log-level string Log level to use. Options are debug, info, error, fatal (default "info")
--log-output string Log output path. Options are stderr or stdout. (default "stderr")
--log-overrides string Logger config overrides. Format <name>,<key>=<val>,...;<name>,...
--log-source Include source location in logs
--log-stacktrace Include stacktrace in error and fatal logs
--no-keyring Disable the keyring and generate ephemeral keys
--no-log-color Disable colored log output
--rootdir string Directory for persistent data (default: $HOME/.defradb)
--secret-file string Path to the file containing secrets (default ".env")
--source-hub-address string The SourceHub address authorized by the client to make SourceHub transactions on behalf of the actor
--url string URL of HTTP endpoint to listen on or connect to (default "127.0.0.1:9181")
```

### SEE ALSO

* [defradb keyring](defradb_keyring.md) - Manage DefraDB private keys

13 changes: 10 additions & 3 deletions keyring/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@

package keyring

import "github.com/zalando/go-keyring"
import (
"github.com/zalando/go-keyring"

// ErrNotFound is returned when a keyring item is not found.
var ErrNotFound = keyring.ErrNotFound
"github.com/sourcenetwork/defradb/errors"
)

var (
// ErrNotFound is returned when a keyring item is not found.
ErrNotFound = keyring.ErrNotFound
ErrSystemKeyringListInvoked = errors.New("listing keys is not supported by OS keyring")
)
17 changes: 17 additions & 0 deletions keyring/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,20 @@ func (f *fileKeyring) Delete(user string) error {
}
return err
}

func (f *fileKeyring) List() ([]string, error) {
files, err := os.ReadDir(f.dir)
if err != nil {
return nil, err
}

// File names are key names
var keyNames []string
for _, file := range files {
if !file.IsDir() {
keyNames = append(keyNames, file.Name())
}
}

return keyNames, nil
}
2 changes: 2 additions & 0 deletions keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ type Keyring interface {
//
// If a key with that name does not exist `ErrNotFound` is returned.
Delete(name string) error
// List returns a list of all keys in the keyring, used by the CLI 'keyring list' command
List() ([]string, error)
}
7 changes: 7 additions & 0 deletions keyring/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@ func (s *systemKeyring) Get(name string) ([]byte, error) {
func (s *systemKeyring) Delete(user string) error {
return keyring.Delete(s.service, user)
}

func (s *systemKeyring) List() ([]string, error) {
// The OS keyring does not support listing keys
// This function is a stub for now because the Keyring interface requires it
// Currently, the 'defradb keyring list' command uses only fileKeyring
return nil, ErrSystemKeyringListInvoked
}
27 changes: 27 additions & 0 deletions keyring/system_keyring_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 keyring

import (
"testing"

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

func TestSystemKeyringListThrowsError(t *testing.T) {
service := "test-service"
systemKeyring := OpenSystemKeyring(service)

keys, err := systemKeyring.List()

require.Nil(t, keys, "keys should be nil when List is called")
require.ErrorIs(t, err, ErrSystemKeyringListInvoked, "function should throw ErrSystemKeyringListInvoked error")
}

0 comments on commit f7b996c

Please sign in to comment.