1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api"
8	"github.com/mitchellh/cli"
9	"github.com/posener/complete"
10)
11
12// OperatorKeyringCommand is a Command implementation that handles querying, installing,
13// and removing gossip encryption keys from a keyring.
14type OperatorKeyringCommand struct {
15	Meta
16}
17
18func (c *OperatorKeyringCommand) Help() string {
19	helpText := `
20Usage: nomad operator keyring [options]
21
22  Manages encryption keys used for gossip messages between Nomad servers. Gossip
23  encryption is optional. When enabled, this command may be used to examine
24  active encryption keys in the cluster, add new keys, and remove old ones. When
25  combined, this functionality provides the ability to perform key rotation
26  cluster-wide, without disrupting the cluster.
27
28  All operations performed by this command can only be run against server nodes.
29
30  All variations of the keyring command return 0 if all nodes reply and there
31  are no errors. If any node fails to reply or reports failure, the exit code
32  will be 1.
33
34General Options:
35
36  ` + generalOptionsUsage() + `
37
38Keyring Options:
39
40  -install=<key>            Install a new encryption key. This will broadcast
41                            the new key to all members in the cluster.
42  -list                     List all keys currently in use within the cluster.
43  -remove=<key>             Remove the given key from the cluster. This
44                            operation may only be performed on keys which are
45                            not currently the primary key.
46  -use=<key>                Change the primary encryption key, which is used to
47                            encrypt messages. The key must already be installed
48                            before this operation can succeed.
49`
50	return strings.TrimSpace(helpText)
51}
52
53func (c *OperatorKeyringCommand) Synopsis() string {
54	return "Manages gossip layer encryption keys"
55}
56
57func (c *OperatorKeyringCommand) AutocompleteFlags() complete.Flags {
58	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
59		complete.Flags{
60			"-install": complete.PredictAnything,
61			"-list":    complete.PredictNothing,
62			"-remove":  complete.PredictAnything,
63			"-use":     complete.PredictAnything,
64		})
65}
66func (c *OperatorKeyringCommand) AutocompleteArgs() complete.Predictor {
67	return complete.PredictNothing
68}
69
70func (c *OperatorKeyringCommand) Name() string { return "operator keyring" }
71
72func (c *OperatorKeyringCommand) Run(args []string) int {
73	var installKey, useKey, removeKey string
74	var listKeys bool
75
76	flags := c.Meta.FlagSet("operator-keyring", FlagSetClient)
77	flags.Usage = func() { c.Ui.Output(c.Help()) }
78
79	flags.StringVar(&installKey, "install", "", "install key")
80	flags.StringVar(&useKey, "use", "", "use key")
81	flags.StringVar(&removeKey, "remove", "", "remove key")
82	flags.BoolVar(&listKeys, "list", false, "list keys")
83
84	if err := flags.Parse(args); err != nil {
85		return 1
86	}
87
88	c.Ui = &cli.PrefixedUi{
89		OutputPrefix: "",
90		InfoPrefix:   "==> ",
91		ErrorPrefix:  "",
92		Ui:           c.Ui,
93	}
94
95	// Only accept a single argument
96	found := listKeys
97	for _, arg := range []string{installKey, useKey, removeKey} {
98		if found && len(arg) > 0 {
99			c.Ui.Error("Only a single action is allowed")
100			c.Ui.Error(commandErrorText(c))
101			return 1
102		}
103		found = found || len(arg) > 0
104	}
105
106	// Fail fast if no actionable args were passed
107	if !found {
108		c.Ui.Error("No actionable argument was passed")
109		c.Ui.Error("Either the '-install', '-use', '-remove' or '-list' flag must be set")
110		c.Ui.Error(commandErrorText(c))
111		return 1
112	}
113
114	// All other operations will require a client connection
115	client, err := c.Meta.Client()
116	if err != nil {
117		c.Ui.Error(fmt.Sprintf("Error creating nomad cli client: %s", err))
118		return 1
119	}
120
121	if listKeys {
122		c.Ui.Output("Gathering installed encryption keys...")
123		r, err := client.Agent().ListKeys()
124		if err != nil {
125			c.Ui.Error(fmt.Sprintf("error: %s", err))
126			return 1
127		}
128		c.handleKeyResponse(r)
129		return 0
130	}
131
132	if installKey != "" {
133		c.Ui.Output("Installing new gossip encryption key...")
134		_, err := client.Agent().InstallKey(installKey)
135		if err != nil {
136			c.Ui.Error(fmt.Sprintf("error: %s", err))
137			return 1
138		}
139		return 0
140	}
141
142	if useKey != "" {
143		c.Ui.Output("Changing primary gossip encryption key...")
144		_, err := client.Agent().UseKey(useKey)
145		if err != nil {
146			c.Ui.Error(fmt.Sprintf("error: %s", err))
147			return 1
148		}
149		return 0
150	}
151
152	if removeKey != "" {
153		c.Ui.Output("Removing gossip encryption key...")
154		_, err := client.Agent().RemoveKey(removeKey)
155		if err != nil {
156			c.Ui.Error(fmt.Sprintf("error: %s", err))
157			return 1
158		}
159		return 0
160	}
161
162	// Should never make it here
163	return 0
164}
165
166func (c *OperatorKeyringCommand) handleKeyResponse(resp *api.KeyringResponse) {
167	out := make([]string, len(resp.Keys)+1)
168	out[0] = "Key"
169	i := 1
170	for k := range resp.Keys {
171		out[i] = fmt.Sprintf("%s", k)
172		i = i + 1
173	}
174	c.Ui.Output(formatList(out))
175}
176