keyring command line (#13169)

Co-authored-by: Charlie Voiselle <464492+angrycub@users.noreply.github.com>
This commit is contained in:
Tim Gross
2022-06-07 16:06:44 -04:00
parent a7522d5c48
commit caa02dfce1
3 changed files with 224 additions and 2 deletions

View File

@@ -80,9 +80,9 @@ func (k *Keyring) Rotate(opts *KeyringRotateOptions, w *WriteOptions) (*RootKeyM
qp.Set("full", "true")
}
}
resp := &RootKeyMeta{}
resp := &struct{ Key *RootKeyMeta }{}
wm, err := k.client.write("/v1/operator/keyring/rotate?"+qp.Encode(), nil, resp, w)
return resp, wm, err
return resp.Key, wm, err
}
// KeyringRotateOptions are parameters for the Rotate API

View File

@@ -591,6 +591,11 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"operator secure-variables keyring": func() (cli.Command, error) {
return &OperatorSecureVariablesKeyringCommand{
Meta: meta,
}, nil
},
"operator snapshot": func() (cli.Command, error) {
return &OperatorSnapshotCommand{
Meta: meta,

View File

@@ -0,0 +1,217 @@
package command
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/posener/complete"
"github.com/hashicorp/nomad/api"
)
// OperatorSecureVariablesKeyringCommand is a Command implementation
// that handles querying, installing, and removing secure variables
// encryption keys from a keyring.
type OperatorSecureVariablesKeyringCommand struct {
Meta
}
func (c *OperatorSecureVariablesKeyringCommand) Help() string {
helpText := `
Usage: nomad operator secure-variables keyring [options]
Manages encryption keys used for storing secure variables. This command may be
used to examine active encryption keys in the cluster, rotate keys, add new
keys from backups, or remove unused keys.
If ACLs are enabled, this command requires a management token.
General Options:
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
Keyring Options:
-install=<file> Install a new encryption key from file. The key file must
be a JSON file previously written by Nomad to the keystore.
-list List the currently installed keys. This list returns key
metadata and not sensitive key material.
-remove=<key> Remove the given key from the cluster. This operation may
only be performed on keys that are not the active key.
-rotate Generate a new encryption key for all future variables.
Allows the use of the -full flag.
-full When used with -rotate, decrypt all variables and
re-encrypt with the new key. The command will immediately
return and the re-encryption process will run asynchronously
on the leader.
-verbose Show full information.
`
return strings.TrimSpace(helpText)
}
func (c *OperatorSecureVariablesKeyringCommand) Synopsis() string {
return "Manages secure variables encryption keys"
}
func (c *OperatorSecureVariablesKeyringCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-install": complete.PredictFiles("*.json"),
"-list": complete.PredictNothing,
"-remove": complete.PredictAnything,
"-rotate": complete.PredictNothing,
"-full": complete.PredictNothing,
"-verbose": complete.PredictNothing,
})
}
func (c *OperatorSecureVariablesKeyringCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *OperatorSecureVariablesKeyringCommand) Name() string { return "secure-variables keyring" }
func (c *OperatorSecureVariablesKeyringCommand) Run(args []string) int {
var installKey, removeKey string
var listKeys, rotateKey, rotateFull, verbose bool
flags := c.Meta.FlagSet("secure-variables keyring", FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.StringVar(&installKey, "install", "", "install key")
flags.BoolVar(&listKeys, "list", false, "list keys")
flags.StringVar(&removeKey, "remove", "", "remove key")
flags.BoolVar(&rotateKey, "rotate", false, "rotate key")
flags.BoolVar(&rotateFull, "full", false, "full key rotation")
flags.BoolVar(&verbose, "verbose", false, "")
if err := flags.Parse(args); err != nil {
return 1
}
// Only accept a single argument
if rotateKey && listKeys {
c.Ui.Error("Only a single action is allowed")
c.Ui.Error(commandErrorText(c))
return 1
}
found := listKeys || rotateKey
for _, arg := range []string{installKey, removeKey} {
if found && len(arg) > 0 {
c.Ui.Error("Only a single action is allowed")
c.Ui.Error(commandErrorText(c))
return 1
}
found = found || len(arg) > 0
}
// Fail fast if no actionable args were passed
if !found {
c.Ui.Error("No actionable argument was passed")
c.Ui.Error("One of '-install', '-list', '-remove' or '-rotate' flags must be set")
c.Ui.Error(commandErrorText(c))
return 1
}
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error creating nomad cli client: %s", err))
return 1
}
if listKeys {
resp, _, err := client.Keyring().List(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
c.handleKeysResponse(resp, verbose)
return 0
}
if rotateKey {
resp, _, err := client.Keyring().Rotate(
&api.KeyringRotateOptions{Full: rotateFull}, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
c.handleKeysResponse([]*api.RootKeyMeta{resp}, verbose)
return 0
}
if removeKey != "" {
_, err := client.Keyring().Delete(&api.KeyringDeleteOptions{
KeyID: removeKey,
}, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Removed encryption key %s", removeKey))
return 0
}
if installKey != "" {
if fi, err := os.Stat(installKey); (installKey == "-" || err == nil) && !fi.IsDir() {
var buf []byte
if installKey == "-" {
buf, err = ioutil.ReadAll(os.Stdin)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read stdin: %v", err))
return 1
}
} else {
buf, err = ioutil.ReadFile(installKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read file: %v", err))
return 1
}
}
key := &api.RootKey{}
dec := json.NewDecoder(bytes.NewBuffer(buf))
if err := dec.Decode(key); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse key file: %v", err))
return 1
}
_, err := client.Keyring().Update(key, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Installed encryption key %s", key.Meta.KeyID))
return 0
}
}
// Should never make it here
return 0
}
func (c *OperatorSecureVariablesKeyringCommand) handleKeysResponse(keys []*api.RootKeyMeta, verbose bool) {
length := fullId
if !verbose {
length = 8
}
out := make([]string, len(keys)+1)
out[0] = "Key|Active|Create Time"
i := 1
for _, k := range keys {
out[i] = fmt.Sprintf("%s|%v|%s",
k.KeyID[:length], k.Active, formatTime(k.CreateTime))
i = i + 1
}
c.Ui.Output(formatList(out))
}