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.
This commit is contained in:
Tim Gross 2022-06-20 15:21:19 -04:00
parent 4a8463595b
commit 4d011d4c53
9 changed files with 501 additions and 29 deletions

View File

@ -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,

View File

@ -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 <key>
Install an encryption key from backup:
$ nomad operator gossip keyring install <key>
Use an already-installed encryption key:
$ nomad operator gossip keyring use <key>
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
}

View File

@ -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 {

View File

@ -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] <key>
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: <key>")
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
}

View File

@ -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))
}

View File

@ -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] <key>
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: <key>")
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
}

View File

@ -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)

View File

@ -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] <key>
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: <key>")
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
}

View File

@ -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: "==> ",