From 4d011d4c537360e98b887ff6df3eae4c844d504d Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Mon, 20 Jun 2022 15:21:19 -0400 Subject: [PATCH] move gossip keyring command to their own subcommands (#13383) Move all the gossip keyring and key generation commands under `operator gossip keyring` subcommands to align with the new `operator secure-variables keyring` subcommands. Deprecate the `operator keyring` and `operator keygen` commands. --- command/commands.go | 72 ++++++++++---- command/operator_gossip_keyring.go | 72 ++++++++++++++ ...go => operator_gossip_keyring_generate.go} | 19 ++-- command/operator_gossip_keyring_install.go | 87 ++++++++++++++++ command/operator_gossip_keyring_list.go | 98 +++++++++++++++++++ command/operator_gossip_keyring_remove.go | 87 ++++++++++++++++ ...est.go => operator_gossip_keyring_test.go} | 4 +- command/operator_gossip_keyring_use.go | 87 ++++++++++++++++ command/operator_keyring.go | 4 + 9 files changed, 501 insertions(+), 29 deletions(-) create mode 100644 command/operator_gossip_keyring.go rename command/{operator_keygen.go => operator_gossip_keyring_generate.go} (55%) create mode 100644 command/operator_gossip_keyring_install.go create mode 100644 command/operator_gossip_keyring_list.go create mode 100644 command/operator_gossip_keyring_remove.go rename command/{operator_keygen_test.go => operator_gossip_keyring_test.go} (78%) create mode 100644 command/operator_gossip_keyring_use.go diff --git a/command/commands.go b/command/commands.go index d2f88413e..7175332f4 100644 --- a/command/commands.go +++ b/command/commands.go @@ -44,7 +44,7 @@ func (c *DeprecatedCommand) Run(args []string) int { func (c *DeprecatedCommand) warn() { c.Ui.Warn(wrapAtLength(fmt.Sprintf( "WARNING! The \"nomad %s\" command is deprecated. Please use \"nomad %s\" "+ - "instead. This command will be removed in Nomad 0.10 (or later).", + "instead. This command will be removed a later version of Nomad.", c.Old, c.New))) c.Ui.Warn("") @@ -305,16 +305,6 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "keygen": func() (cli.Command, error) { - return &OperatorKeygenCommand{ - Meta: meta, - }, nil - }, - "keyring": func() (cli.Command, error) { - return &OperatorKeyringCommand{ - Meta: meta, - }, nil - }, "job": func() (cli.Command, error) { return &JobCommand{ Meta: meta, @@ -529,16 +519,49 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "operator keygen": func() (cli.Command, error) { - return &OperatorKeygenCommand{ - Meta: meta, - }, nil - }, + + // COMPAT(1.4.0): deprecated, remove in Nomad 1.5.0 + // Note: we can't just put this in the DeprecatedCommand list + // because the flags have changed too. So we've provided the + // deprecation warning in the original command and when it's + // time to remove it we can remove the entire command "operator keyring": func() (cli.Command, error) { return &OperatorKeyringCommand{ Meta: meta, }, nil }, + + "operator gossip keyring": func() (cli.Command, error) { + return &OperatorGossipKeyringCommand{ + Meta: meta, + }, nil + }, + "operator gossip keyring install": func() (cli.Command, error) { + return &OperatorGossipKeyringInstallCommand{ + Meta: meta, + }, nil + }, + "operator gossip keyring use": func() (cli.Command, error) { + return &OperatorGossipKeyringUseCommand{ + Meta: meta, + }, nil + }, + "operator gossip keyring list": func() (cli.Command, error) { + return &OperatorGossipKeyringListCommand{ + Meta: meta, + }, nil + }, + "operator gossip keyring remove": func() (cli.Command, error) { + return &OperatorGossipKeyringRemoveCommand{ + Meta: meta, + }, nil + }, + "operator gossip keyring generate": func() (cli.Command, error) { + return &OperatorGossipKeyringGenerateCommand{ + Meta: meta, + }, nil + }, + "operator metrics": func() (cli.Command, error) { return &OperatorMetricsCommand{ Meta: meta, @@ -961,9 +984,20 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { "keygen": func() (cli.Command, error) { return &DeprecatedCommand{ Old: "keygen", - New: "operator keygen", + New: "operator gossip keyring generate", Meta: meta, - Command: &OperatorKeygenCommand{ + Command: &OperatorGossipKeyringGenerateCommand{ + Meta: meta, + }, + }, nil + }, + + "operator keygen": func() (cli.Command, error) { + return &DeprecatedCommand{ + Old: "operator keygen", + New: "operator gossip keyring generate", + Meta: meta, + Command: &OperatorGossipKeyringGenerateCommand{ Meta: meta, }, }, nil @@ -972,7 +1006,7 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { "keyring": func() (cli.Command, error) { return &DeprecatedCommand{ Old: "keyring", - New: "operator keyring", + New: "operator gossip keyring", Meta: meta, Command: &OperatorKeyringCommand{ Meta: meta, diff --git a/command/operator_gossip_keyring.go b/command/operator_gossip_keyring.go new file mode 100644 index 000000000..5d3b71da1 --- /dev/null +++ b/command/operator_gossip_keyring.go @@ -0,0 +1,72 @@ +package command + +import ( + "strings" + + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +// OperatorGossipKeyringCommand is a Command implementation that +// handles querying, installing, and removing gossip encryption keys +// from a keyring. +type OperatorGossipKeyringCommand struct { + Meta +} + +func (c *OperatorGossipKeyringCommand) Help() string { + helpText := ` +Usage: nomad operator gossip keyring [options] + + Manages encryption keys used for gossip messages between Nomad servers. Gossip + encryption is optional. When enabled, this command may be used to examine + active encryption keys in the cluster, add new keys, and remove old ones. When + combined, this functionality provides the ability to perform key rotation + cluster-wide, without disrupting the cluster. + + Generate an encryption key: + + $ nomad operator gossip keyring generate + + List all gossip encryption keys: + + $ nomad operator gossip keyring list + + Remove an encryption key from the keyring: + + $ nomad operator gossip keyring remove + + Install an encryption key from backup: + + $ nomad operator gossip keyring install + + Use an already-installed encryption key: + + $ nomad operator gossip keyring use + + Please see individual subcommand help for detailed usage information. + +General Options: + + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + + return strings.TrimSpace(helpText) +} + +func (c *OperatorGossipKeyringCommand) Synopsis() string { + return "Manages gossip layer encryption keys" +} + +func (c *OperatorGossipKeyringCommand) AutocompleteFlags() complete.Flags { + return c.Meta.AutocompleteFlags(FlagSetClient) +} + +func (c *OperatorGossipKeyringCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *OperatorGossipKeyringCommand) Name() string { return "operator gossip keyring" } + +func (c *OperatorGossipKeyringCommand) Run(args []string) int { + return cli.RunResultHelp +} diff --git a/command/operator_keygen.go b/command/operator_gossip_keyring_generate.go similarity index 55% rename from command/operator_keygen.go rename to command/operator_gossip_keyring_generate.go index aa77904ab..ab4016201 100644 --- a/command/operator_keygen.go +++ b/command/operator_gossip_keyring_generate.go @@ -7,19 +7,20 @@ import ( "strings" ) -// OperatorKeygenCommand is a Command implementation that generates an encryption -// key for use in `nomad agent`. -type OperatorKeygenCommand struct { +// OperatorGossipKeyringGenerateCommand is a Command implementation that +// generates an encryption key for use in `nomad agent`. +type OperatorGossipKeyringGenerateCommand struct { Meta } -func (c *OperatorKeygenCommand) Synopsis() string { +func (c *OperatorGossipKeyringGenerateCommand) Synopsis() string { return "Generates a new encryption key" } -func (c *OperatorKeygenCommand) Help() string { +func (c *OperatorGossipKeyringGenerateCommand) Help() string { helpText := ` -Usage: nomad operator keygen +Usage: nomad operator gossip keying generate +Alias: nomad operator keygen Generates a new 32-byte encryption key that can be used to configure the agent to encrypt traffic. The output of this command is already @@ -28,9 +29,11 @@ Usage: nomad operator keygen return strings.TrimSpace(helpText) } -func (c *OperatorKeygenCommand) Name() string { return "operator keygen" } +func (c *OperatorGossipKeyringGenerateCommand) Name() string { + return "operator gossip keyring generate" +} -func (c *OperatorKeygenCommand) Run(_ []string) int { +func (c *OperatorGossipKeyringGenerateCommand) Run(_ []string) int { key := make([]byte, 32) n, err := rand.Reader.Read(key) if err != nil { diff --git a/command/operator_gossip_keyring_install.go b/command/operator_gossip_keyring_install.go new file mode 100644 index 000000000..8fd31680c --- /dev/null +++ b/command/operator_gossip_keyring_install.go @@ -0,0 +1,87 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +// OperatorGossipKeyringInstallCommand is a Command implementation +// that handles installing a gossip encryption key from a keyring +type OperatorGossipKeyringInstallCommand struct { + Meta +} + +func (c *OperatorGossipKeyringInstallCommand) Help() string { + helpText := ` +Usage: nomad operator gossip keyring install [options] + + Install a new encryption key used for gossip. This will broadcast the new key + to all members in the cluster. + + This command can only be run against server nodes. It returns 0 if all nodes + reply and there are no errors. If any node fails to reply or reports failure, + the exit code will be 1. + + If ACLs are enabled, this command requires a token with the 'agent:write' + capability. + +General Options: + + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + + return strings.TrimSpace(helpText) +} + +func (c *OperatorGossipKeyringInstallCommand) Synopsis() string { + return "Install a gossip encryption key" +} + +func (c *OperatorGossipKeyringInstallCommand) AutocompleteFlags() complete.Flags { + return c.Meta.AutocompleteFlags(FlagSetClient) +} + +func (c *OperatorGossipKeyringInstallCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictAnything +} + +func (c *OperatorGossipKeyringInstallCommand) Name() string { return "operator gossip keyring install" } + +func (c *OperatorGossipKeyringInstallCommand) Run(args []string) int { + flags := c.Meta.FlagSet("operator-gossip-keyring-install", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + c.Ui = &cli.PrefixedUi{ + OutputPrefix: "", + InfoPrefix: "==> ", + ErrorPrefix: "", + Ui: c.Ui, + } + + args = flags.Args() + if len(args) != 1 { + c.Ui.Error("This command requires one argument: ") + c.Ui.Error(commandErrorText(c)) + return 1 + } + installKey := args[0] + + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error creating nomad cli client: %s", err)) + return 1 + } + + c.Ui.Output("Installing new gossip encryption key...") + _, err = client.Agent().InstallKey(installKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 + } + return 0 +} diff --git a/command/operator_gossip_keyring_list.go b/command/operator_gossip_keyring_list.go new file mode 100644 index 000000000..5bb86e181 --- /dev/null +++ b/command/operator_gossip_keyring_list.go @@ -0,0 +1,98 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/hashicorp/nomad/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +// OperatorGossipKeyringListCommand is a Command implementation +// that handles removing a gossip encryption key from a keyring +type OperatorGossipKeyringListCommand struct { + Meta +} + +func (c *OperatorGossipKeyringListCommand) Help() string { + helpText := ` +Usage: nomad operator gossip keyring list [options] + + List all gossip keys currently in use within the cluster. + + This command can only be run against server nodes. It returns 0 if all nodes + reply and there are no errors. If any node fails to reply or reports failure, + the exit code will be 1. + + If ACLs are enabled, this command requires a token with the 'agent:write' + capability. + +General Options: + + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + + return strings.TrimSpace(helpText) +} + +func (c *OperatorGossipKeyringListCommand) Synopsis() string { + return "List gossip encryption keys" +} + +func (c *OperatorGossipKeyringListCommand) AutocompleteFlags() complete.Flags { + return c.Meta.AutocompleteFlags(FlagSetClient) +} + +func (c *OperatorGossipKeyringListCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictAnything +} + +func (c *OperatorGossipKeyringListCommand) Name() string { return "operator gossip keyring list" } + +func (c *OperatorGossipKeyringListCommand) Run(args []string) int { + flags := c.Meta.FlagSet("operator-gossip-keyring-list", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + c.Ui = &cli.PrefixedUi{ + OutputPrefix: "", + InfoPrefix: "==> ", + ErrorPrefix: "", + Ui: c.Ui, + } + + args = flags.Args() + if len(args) != 0 { + c.Ui.Error("This command requires no arguments") + 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 + } + + c.Ui.Output("Gathering installed encryption keys...") + r, err := client.Agent().ListKeys() + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 + } + c.handleKeyResponse(r) + return 0 +} + +func (c *OperatorGossipKeyringListCommand) handleKeyResponse(resp *api.KeyringResponse) { + out := make([]string, len(resp.Keys)+1) + out[0] = "Key" + i := 1 + for k := range resp.Keys { + out[i] = k + i = i + 1 + } + c.Ui.Output(formatList(out)) +} diff --git a/command/operator_gossip_keyring_remove.go b/command/operator_gossip_keyring_remove.go new file mode 100644 index 000000000..70e89728d --- /dev/null +++ b/command/operator_gossip_keyring_remove.go @@ -0,0 +1,87 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +// OperatorGossipKeyringRemoveCommand is a Command implementation +// that handles removing a gossip encryption key from a keyring +type OperatorGossipKeyringRemoveCommand struct { + Meta +} + +func (c *OperatorGossipKeyringRemoveCommand) Help() string { + helpText := ` +Usage: nomad operator gossip keyring remove [options] + + Remove the given key from the cluster. This operation may only be performed + on keys which are not currently the primary key. + + This command can only be run against server nodes. It returns 0 if all nodes + reply and there are no errors. If any node fails to reply or reports failure, + the exit code will be 1. + + If ACLs are enabled, this command requires a token with the 'agent:write' + capability. + +General Options: + + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + + return strings.TrimSpace(helpText) +} + +func (c *OperatorGossipKeyringRemoveCommand) Synopsis() string { + return "Remove a gossip encryption key" +} + +func (c *OperatorGossipKeyringRemoveCommand) AutocompleteFlags() complete.Flags { + return c.Meta.AutocompleteFlags(FlagSetClient) +} + +func (c *OperatorGossipKeyringRemoveCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictAnything +} + +func (c *OperatorGossipKeyringRemoveCommand) Name() string { return "operator gossip keyring remove" } + +func (c *OperatorGossipKeyringRemoveCommand) Run(args []string) int { + flags := c.Meta.FlagSet("operator-gossip-keyring-remove", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + c.Ui = &cli.PrefixedUi{ + OutputPrefix: "", + InfoPrefix: "==> ", + ErrorPrefix: "", + Ui: c.Ui, + } + + args = flags.Args() + if len(args) != 1 { + c.Ui.Error("This command requires one argument: ") + c.Ui.Error(commandErrorText(c)) + return 1 + } + removeKey := args[0] + + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error creating nomad cli client: %s", err)) + return 1 + } + + c.Ui.Output("Removing gossip encryption key...") + _, err = client.Agent().RemoveKey(removeKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 + } + return 0 +} diff --git a/command/operator_keygen_test.go b/command/operator_gossip_keyring_test.go similarity index 78% rename from command/operator_keygen_test.go rename to command/operator_gossip_keyring_test.go index d003c7402..b6535e06b 100644 --- a/command/operator_keygen_test.go +++ b/command/operator_gossip_keyring_test.go @@ -8,11 +8,11 @@ import ( "github.com/mitchellh/cli" ) -func TestKeygenCommand(t *testing.T) { +func TestGossipKeyringGenerateCommand(t *testing.T) { ci.Parallel(t) ui := cli.NewMockUi() - c := &OperatorKeygenCommand{Meta: Meta{Ui: ui}} + c := &OperatorGossipKeyringGenerateCommand{Meta: Meta{Ui: ui}} code := c.Run(nil) if code != 0 { t.Fatalf("bad: %d", code) diff --git a/command/operator_gossip_keyring_use.go b/command/operator_gossip_keyring_use.go new file mode 100644 index 000000000..2b5d7d5c0 --- /dev/null +++ b/command/operator_gossip_keyring_use.go @@ -0,0 +1,87 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +// OperatorGossipKeyringUseCommand is a Command implementation that +// handles setting the gossip encryption key from a keyring +type OperatorGossipKeyringUseCommand struct { + Meta +} + +func (c *OperatorGossipKeyringUseCommand) Help() string { + helpText := ` +Usage: nomad operator gossip keyring use [options] + + Change the encryption key used for gossip. The key must already be installed + before this operator can succeed. + + This command can only be run against server nodes. It returns 0 if all nodes + reply and there are no errors. If any node fails to reply or reports failure, + the exit code will be 1. + + If ACLs are enabled, this command requires a token with the 'agent:write' + capability. + +General Options: + + ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + + return strings.TrimSpace(helpText) +} + +func (c *OperatorGossipKeyringUseCommand) Synopsis() string { + return "Change the gossip encryption key" +} + +func (c *OperatorGossipKeyringUseCommand) AutocompleteFlags() complete.Flags { + return c.Meta.AutocompleteFlags(FlagSetClient) +} + +func (c *OperatorGossipKeyringUseCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictAnything +} + +func (c *OperatorGossipKeyringUseCommand) Name() string { return "operator gossip keyring use" } + +func (c *OperatorGossipKeyringUseCommand) Run(args []string) int { + flags := c.Meta.FlagSet("operator-gossip-keyring-use", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + c.Ui = &cli.PrefixedUi{ + OutputPrefix: "", + InfoPrefix: "==> ", + ErrorPrefix: "", + Ui: c.Ui, + } + + args = flags.Args() + if len(args) != 1 { + c.Ui.Error("This command requires one argument: ") + c.Ui.Error(commandErrorText(c)) + return 1 + } + useKey := args[0] + + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error creating nomad cli client: %s", err)) + return 1 + } + + c.Ui.Output("Changing primary gossip encryption key...") + _, err = client.Agent().UseKey(useKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 + } + return 0 +} diff --git a/command/operator_keyring.go b/command/operator_keyring.go index d19dfc628..dfdc60244 100644 --- a/command/operator_keyring.go +++ b/command/operator_keyring.go @@ -88,6 +88,10 @@ func (c *OperatorKeyringCommand) Run(args []string) int { return 1 } + c.Ui.Warn(wrapAtLength("WARNING! The \"nomad operator keyring\" command " + + "is deprecated. Please use \"nomad operator gossip keyring\" instead. " + + "This command will be removed in Nomad 1.5.0.")) + c.Ui.Warn("") c.Ui = &cli.PrefixedUi{ OutputPrefix: "", InfoPrefix: "==> ",