-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
kv_metadata_patch.go
238 lines (187 loc) · 6.17 KB
/
kv_metadata_patch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package command
import (
"context"
"fmt"
"io"
"path"
"strings"
"time"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var (
_ cli.Command = (*KVMetadataPutCommand)(nil)
_ cli.CommandAutocomplete = (*KVMetadataPutCommand)(nil)
)
type KVMetadataPatchCommand struct {
*BaseCommand
flagMaxVersions int
flagCASRequired BoolPtr
flagDeleteVersionAfter time.Duration
flagCustomMetadata map[string]string
flagMount string
testStdin io.Reader // for tests
}
func (c *KVMetadataPatchCommand) Synopsis() string {
return "Patches key settings in the KV store"
}
func (c *KVMetadataPatchCommand) Help() string {
helpText := `
Usage: vault metadata kv patch [options] KEY
This command can be used to create a blank key in the key-value store or to
update key configuration for a specified key.
Create a key in the key-value store with no data:
$ vault kv metadata patch -mount=secret foo
The deprecated path-like syntax can also be used, but this should be avoided
for KV v2, as the fact that it is not actually the full API path to
the secret (secret/metadata/foo) can cause confusion:
$ vault kv metadata patch secret/foo
Set a max versions setting on the key:
$ vault kv metadata patch -mount=secret -max-versions=5 foo
Set delete-version-after on the key:
$ vault kv metadata patch -mount=secret -delete-version-after=3h25m19s foo
Require Check-and-Set for this key:
$ vault kv metadata patch -mount=secret -cas-required foo
Set custom metadata on the key:
$ vault kv metadata patch -mount=secret -custom-metadata=foo=abc -custom-metadata=bar=123 foo
Additional flags and more advanced use cases are detailed below.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *KVMetadataPatchCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
// Common Options
f := set.NewFlagSet("Common Options")
f.IntVar(&IntVar{
Name: "max-versions",
Target: &c.flagMaxVersions,
Default: -1,
Usage: `The number of versions to keep. If not set, the backend’s configured max version is used.`,
})
f.BoolPtrVar(&BoolPtrVar{
Name: "cas-required",
Target: &c.flagCASRequired,
Usage: `If true the key will require the cas parameter to be set on all write requests. If false, the backend’s configuration will be used.`,
})
f.DurationVar(&DurationVar{
Name: "delete-version-after",
Target: &c.flagDeleteVersionAfter,
Default: -1,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: `Specifies the length of time before a version is deleted.
If not set, the backend's configured delete-version-after is used. Cannot be
greater than the backend's delete-version-after. The delete-version-after is
specified as a numeric string with a suffix like "30s" or
"3h25m19s".`,
})
f.StringMapVar(&StringMapVar{
Name: "custom-metadata",
Target: &c.flagCustomMetadata,
Default: map[string]string{},
Usage: `Specifies arbitrary version-agnostic key=value metadata meant to describe a secret.
This can be specified multiple times to add multiple pieces of metadata.`,
})
f.StringVar(&StringVar{
Name: "mount",
Target: &c.flagMount,
Default: "", // no default, because the handling of the next arg is determined by whether this flag has a value
Usage: `Specifies the path where the KV backend is mounted. If specified,
the next argument will be interpreted as the secret path. If this flag is
not specified, the next argument will be interpreted as the combined mount
path and secret path, with /metadata/ automatically appended between KV
v2 secrets.`,
})
return set
}
func (c *KVMetadataPatchCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *KVMetadataPatchCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *KVMetadataPatchCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
switch {
case len(args) < 1:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
return 1
case len(args) > 1:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
return 1
}
client, err := c.Client()
if err != nil {
c.UI.Error(err.Error())
return 2
}
// If true, we're working with "-mount=secret foo" syntax.
// If false, we're using "secret/foo" syntax.
mountFlagSyntax := c.flagMount != ""
var (
mountPath string
partialPath string
v2 bool
)
// Parse the paths and grab the KV version
if mountFlagSyntax {
// In this case, this arg is the secret path (e.g. "foo").
partialPath = sanitizePath(args[0])
mountPath, v2, err = isKVv2(sanitizePath(c.flagMount), client)
if err != nil {
c.UI.Error(err.Error())
return 2
}
if v2 {
partialPath = path.Join(mountPath, partialPath)
}
} else {
// In this case, this arg is a path-like combination of mountPath/secretPath.
// (e.g. "secret/foo")
partialPath = sanitizePath(args[0])
mountPath, v2, err = isKVv2(partialPath, client)
if err != nil {
c.UI.Error(err.Error())
return 2
}
}
if !v2 {
c.UI.Error("Metadata not supported on KV Version 1")
return 1
}
fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata")
data := map[string]interface{}{}
if c.flagMaxVersions >= 0 {
data["max_versions"] = c.flagMaxVersions
}
if c.flagCASRequired.IsSet() {
data["cas_required"] = c.flagCASRequired.Get()
}
if c.flagDeleteVersionAfter >= 0 {
data["delete_version_after"] = c.flagDeleteVersionAfter.String()
}
if len(c.flagCustomMetadata) > 0 {
data["custom_metadata"] = c.flagCustomMetadata
}
secret, err := client.Logical().JSONMergePatch(context.Background(), fullPath, data)
if err != nil {
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", fullPath, err))
if secret != nil {
OutputSecret(c.UI, secret)
}
return 2
}
if secret == nil {
// Don't output anything unless using the "table" format
if Format(c.UI) == "table" {
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", fullPath))
}
return 0
}
return OutputSecret(c.UI, secret)
}