Skip to content

Commit f7b996c

Browse files
authoredJan 1, 2025
feat: Add 'keyring list' command to CLI (sourcenetwork#3331)
## Relevant issue(s) Resolves sourcenetwork#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
1 parent 9306e7d commit f7b996c

10 files changed

+233
-3
lines changed
 

‎cli/cli.go

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func NewDefraCommand() *cobra.Command {
137137
MakeKeyringGenerateCommand(),
138138
MakeKeyringImportCommand(),
139139
MakeKeyringExportCommand(),
140+
MakeKeyringListCommand(),
140141
)
141142

142143
identity := MakeIdentityCommand()

‎cli/keyring_list.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2024 Democratized Data Foundation
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package cli
12+
13+
import (
14+
"github.com/spf13/cobra"
15+
)
16+
17+
// MakeKeyringListCommand creates a new command to list all keys in the keyring.
18+
func MakeKeyringListCommand() *cobra.Command {
19+
var cmd = &cobra.Command{
20+
Use: "list",
21+
Short: "List all keys in the keyring",
22+
Long: `List all keys in the keyring.
23+
The DEFRA_KEYRING_SECRET environment variable must be set to unlock the keyring.
24+
This can also be done with a .env file in the working directory or at a path
25+
defined with the --secret-file flag.
26+
27+
Example:
28+
defradb keyring list`,
29+
Args: cobra.NoArgs,
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
keyring, err := openKeyring(cmd)
32+
if err != nil {
33+
return err
34+
}
35+
36+
keyNames, err := keyring.List()
37+
if err != nil {
38+
return err
39+
}
40+
41+
if len(keyNames) == 0 {
42+
cmd.Println("No keys found in the keyring.")
43+
return nil
44+
}
45+
46+
cmd.Println("Keys in the keyring:")
47+
for _, keyName := range keyNames {
48+
cmd.Println("- " + keyName)
49+
}
50+
return nil
51+
},
52+
}
53+
return cmd
54+
}

‎cli/keyring_list_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2024 Democratized Data Foundation
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package cli
12+
13+
import (
14+
"bytes"
15+
"encoding/hex"
16+
"os"
17+
"regexp"
18+
"testing"
19+
20+
"github.com/sourcenetwork/defradb/crypto"
21+
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestKeyringList(t *testing.T) {
27+
rootdir := t.TempDir()
28+
29+
err := os.Setenv("DEFRA_KEYRING_SECRET", "password")
30+
require.NoError(t, err)
31+
32+
keyNames := []string{"keyname1", "keyname2", "keyname3"}
33+
34+
// Insert the keys into the keyring
35+
for _, keyName := range keyNames {
36+
keyBytes, err := crypto.GenerateAES256()
37+
require.NoError(t, err)
38+
keyHex := hex.EncodeToString(keyBytes)
39+
cmd := NewDefraCommand()
40+
cmd.SetArgs([]string{"keyring", "import", "--rootdir", rootdir, keyName, keyHex})
41+
err = cmd.Execute()
42+
require.NoError(t, err)
43+
}
44+
45+
// Run the 'keyring list' command, and require no error on the output
46+
var output bytes.Buffer
47+
cmd := NewDefraCommand()
48+
cmd.SetOut(&output)
49+
cmd.SetArgs([]string{"keyring", "list", "--rootdir", rootdir})
50+
err = cmd.Execute()
51+
require.NoError(t, err)
52+
53+
outputString := output.String()
54+
55+
// Use regex to extract the keys, and compare with the expected values
56+
// We know what the format the output should be, which is:
57+
// "Keys in the keyring:\n- keyname1\n- keyname2\n- keyname3\n"
58+
re := regexp.MustCompile(`-\s([^\n]+)`)
59+
matches := re.FindAllStringSubmatch(outputString, -1)
60+
var extractedKeys []string
61+
for _, match := range matches {
62+
extractedKeys = append(extractedKeys, match[1])
63+
}
64+
65+
assert.ElementsMatch(t, keyNames, extractedKeys, "The listed keys do not match the expected keys.")
66+
}

‎docs/website/references/cli/defradb_keyring.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ To learn more about the available options:
5353
* [defradb keyring export](defradb_keyring_export.md) - Export a private key
5454
* [defradb keyring generate](defradb_keyring_generate.md) - Generate private keys
5555
* [defradb keyring import](defradb_keyring_import.md) - Import a private key
56+
* [defradb keyring list](defradb_keyring_list.md) - List all keys in the keyring
5657

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## defradb keyring list
2+
3+
List all keys in the keyring
4+
5+
### Synopsis
6+
7+
List all keys in the keyring.
8+
The DEFRA_KEYRING_SECRET environment variable must be set to unlock the keyring.
9+
This can also be done with a .env file in the working directory or at a path
10+
defined with the --secret-file flag.
11+
12+
Example:
13+
defradb keyring list
14+
15+
```
16+
defradb keyring list [flags]
17+
```
18+
19+
### Options
20+
21+
```
22+
-h, --help help for list
23+
```
24+
25+
### Options inherited from parent commands
26+
27+
```
28+
--keyring-backend string Keyring backend to use. Options are file or system (default "file")
29+
--keyring-namespace string Service name to use when using the system backend (default "defradb")
30+
--keyring-path string Path to store encrypted keys when using the file backend (default "keys")
31+
--log-format string Log format to use. Options are text or json (default "text")
32+
--log-level string Log level to use. Options are debug, info, error, fatal (default "info")
33+
--log-output string Log output path. Options are stderr or stdout. (default "stderr")
34+
--log-overrides string Logger config overrides. Format <name>,<key>=<val>,...;<name>,...
35+
--log-source Include source location in logs
36+
--log-stacktrace Include stacktrace in error and fatal logs
37+
--no-keyring Disable the keyring and generate ephemeral keys
38+
--no-log-color Disable colored log output
39+
--rootdir string Directory for persistent data (default: $HOME/.defradb)
40+
--secret-file string Path to the file containing secrets (default ".env")
41+
--source-hub-address string The SourceHub address authorized by the client to make SourceHub transactions on behalf of the actor
42+
--url string URL of HTTP endpoint to listen on or connect to (default "127.0.0.1:9181")
43+
```
44+
45+
### SEE ALSO
46+
47+
* [defradb keyring](defradb_keyring.md) - Manage DefraDB private keys
48+

‎keyring/errors.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010

1111
package keyring
1212

13-
import "github.com/zalando/go-keyring"
13+
import (
14+
"github.com/zalando/go-keyring"
1415

15-
// ErrNotFound is returned when a keyring item is not found.
16-
var ErrNotFound = keyring.ErrNotFound
16+
"github.com/sourcenetwork/defradb/errors"
17+
)
18+
19+
var (
20+
// ErrNotFound is returned when a keyring item is not found.
21+
ErrNotFound = keyring.ErrNotFound
22+
ErrSystemKeyringListInvoked = errors.New("listing keys is not supported by OS keyring")
23+
)

‎keyring/file.go

+17
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,20 @@ func (f *fileKeyring) Delete(user string) error {
6464
}
6565
return err
6666
}
67+
68+
func (f *fileKeyring) List() ([]string, error) {
69+
files, err := os.ReadDir(f.dir)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
// File names are key names
75+
var keyNames []string
76+
for _, file := range files {
77+
if !file.IsDir() {
78+
keyNames = append(keyNames, file.Name())
79+
}
80+
}
81+
82+
return keyNames, nil
83+
}

‎keyring/keyring.go

+2
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ type Keyring interface {
2424
//
2525
// If a key with that name does not exist `ErrNotFound` is returned.
2626
Delete(name string) error
27+
// List returns a list of all keys in the keyring, used by the CLI 'keyring list' command
28+
List() ([]string, error)
2729
}

‎keyring/system.go

+7
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,10 @@ func (s *systemKeyring) Get(name string) ([]byte, error) {
5353
func (s *systemKeyring) Delete(user string) error {
5454
return keyring.Delete(s.service, user)
5555
}
56+
57+
func (s *systemKeyring) List() ([]string, error) {
58+
// The OS keyring does not support listing keys
59+
// This function is a stub for now because the Keyring interface requires it
60+
// Currently, the 'defradb keyring list' command uses only fileKeyring
61+
return nil, ErrSystemKeyringListInvoked
62+
}

‎keyring/system_keyring_list_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2024 Democratized Data Foundation
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package keyring
12+
13+
import (
14+
"testing"
15+
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestSystemKeyringListThrowsError(t *testing.T) {
20+
service := "test-service"
21+
systemKeyring := OpenSystemKeyring(service)
22+
23+
keys, err := systemKeyring.List()
24+
25+
require.Nil(t, keys, "keys should be nil when List is called")
26+
require.ErrorIs(t, err, ErrSystemKeyringListInvoked, "function should throw ErrSystemKeyringListInvoked error")
27+
}

0 commit comments

Comments
 (0)
Please sign in to comment.