Add HTTP PATCH support for KV key metadata (#13215)
* go get vault-plugin-secrets-kv@vault-4290-patch-metadata * add kv metadata patch command * add changelog entry * success tests for kv metadata patch flags * add more kv metadata patch flags tests * add kv metadata patch cas warning test * add kv-v2 key metadata patch API docs * add kv metadata patch to docs * prevent unintentional field overwriting in kv metadata put cmd * like create/update ops, prevent patch to paths ending in / * fix kv metadata patch cmd in docs * fix flag defaults for kv metadata put * go get vault-plugin-secrets-kv@vault-4290-patch-metadata * fix TestKvMetadataPatchCommand_Flags test * doc fixes * go get vault-plugin-secrets-kv@master; go mod tidy
This commit is contained in:
parent
2adf4df7d7
commit
d52d69e4bb
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secrets/kv: add patch support for KVv2 key metadata
|
||||||
|
```
|
|
@ -718,6 +718,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"kv metadata patch": func() (cli.Command, error) {
|
||||||
|
return &KVMetadataPatchCommand{
|
||||||
|
BaseCommand: getBaseCommand(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"kv metadata get": func() (cli.Command, error) {
|
"kv metadata get": func() (cli.Command, error) {
|
||||||
return &KVMetadataGetCommand{
|
return &KVMetadataGetCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"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
|
||||||
|
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 secret/foo
|
||||||
|
|
||||||
|
Set a max versions setting on the key:
|
||||||
|
|
||||||
|
$ vault kv metadata patch -max-versions=5 secret/foo
|
||||||
|
|
||||||
|
Set delete-version-after on the key:
|
||||||
|
|
||||||
|
$ vault kv metadata patch -delete-version-after=3h25m19s secret/foo
|
||||||
|
|
||||||
|
Require Check-and-Set for this key:
|
||||||
|
|
||||||
|
$ vault kv metadata patch -cas-required secret/foo
|
||||||
|
|
||||||
|
Set custom metadata on the key:
|
||||||
|
|
||||||
|
$ vault kv metadata patch -custom-metadata=foo=abc -custom-metadata=bar=123 secret/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.`,
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
path := sanitizePath(args[0])
|
||||||
|
|
||||||
|
mountPath, v2, err := isKVv2(path, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
path = addPrefixToVKVPath(path, 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(), path, data)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, 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", path))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutputSecret(c.UI, secret)
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-test/deep"
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testKVMetadataPatchCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPatchCommand) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
return ui, &KVMetadataPatchCommand{
|
||||||
|
BaseCommand: &BaseCommand{
|
||||||
|
UI: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func kvMetadataPatchWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return retryKVCommand(t, func() (int, string) {
|
||||||
|
ui, cmd := testKVMetadataPatchCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
if stdin != nil {
|
||||||
|
cmd.testStdin = stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
|
|
||||||
|
return code, combined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func kvMetadataPutWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return retryKVCommand(t, func() (int, string) {
|
||||||
|
ui, cmd := testKVMetadataPutCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
if stdin != nil {
|
||||||
|
cmd.testStdin = stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
|
|
||||||
|
return code, combined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKvMetadataPatchCommand_EmptyArgs(t *testing.T) {
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("kv/", &api.MountInput{
|
||||||
|
Type: "kv-v2",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]string, 0)
|
||||||
|
code, combined := kvMetadataPatchWithRetry(t, client, args, nil)
|
||||||
|
|
||||||
|
expectedCode := 1
|
||||||
|
expectedOutput := "Not enough arguments"
|
||||||
|
|
||||||
|
if code != expectedCode {
|
||||||
|
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", expectedCode, code, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(combined, expectedOutput) {
|
||||||
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKvMetadataPatchCommand_Flags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
out string
|
||||||
|
code int
|
||||||
|
expectedUpdates map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"cas_required_success",
|
||||||
|
[]string{"-cas-required=true"},
|
||||||
|
"Success!",
|
||||||
|
0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"cas_required": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cas_required_invalid",
|
||||||
|
[]string{"-cas-required=12345"},
|
||||||
|
"invalid boolean value",
|
||||||
|
1,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"custom_metadata_success",
|
||||||
|
[]string{"-custom-metadata=baz=ghi"},
|
||||||
|
"Success!",
|
||||||
|
0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"custom_metadata": map[string]interface{}{
|
||||||
|
"foo": "abc",
|
||||||
|
"bar": "def",
|
||||||
|
"baz": "ghi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"delete_version_after_success",
|
||||||
|
[]string{"-delete-version-after=5s"},
|
||||||
|
"Success!",
|
||||||
|
0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"delete_version_after": "5s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"delete_version_after_invalid",
|
||||||
|
[]string{"-delete-version-after=false"},
|
||||||
|
"invalid duration",
|
||||||
|
1,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"max_versions_success",
|
||||||
|
[]string{"-max-versions=10"},
|
||||||
|
"Success!",
|
||||||
|
0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"max_versions": json.Number("10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"max_versions_invalid",
|
||||||
|
[]string{"-max-versions=false"},
|
||||||
|
"invalid syntax",
|
||||||
|
1,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple_flags_success",
|
||||||
|
[]string{"-max-versions=20", "-custom-metadata=baz=123"},
|
||||||
|
"Success!",
|
||||||
|
0,
|
||||||
|
map[string]interface{}{
|
||||||
|
"max_versions": json.Number("20"),
|
||||||
|
"custom_metadata": map[string]interface{}{
|
||||||
|
"foo": "abc",
|
||||||
|
"bar": "def",
|
||||||
|
"baz": "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
basePath := t.Name() + "/"
|
||||||
|
secretPath := basePath + "my-secret"
|
||||||
|
metadataPath := basePath + "metadata/" + "my-secret"
|
||||||
|
|
||||||
|
if err := client.Sys().Mount(basePath, &api.MountInput{
|
||||||
|
Type: "kv-v2",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
putArgs := []string{"-cas-required=true", "-custom-metadata=foo=abc", "-custom-metadata=bar=def", secretPath}
|
||||||
|
code, combined := kvMetadataPutWithRetry(t, client, putArgs, nil)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("initial metadata put failed, code: %d, output: %s", code, combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialMetadata, err := client.Logical().Read(metadataPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("metadata read failed, err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchArgs := append(tc.args, secretPath)
|
||||||
|
|
||||||
|
code, combined = kvMetadataPatchWithRetry(t, client, patchArgs, nil)
|
||||||
|
|
||||||
|
if !strings.Contains(combined, tc.out) {
|
||||||
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", tc.out, combined, patchArgs)
|
||||||
|
}
|
||||||
|
if code != tc.code {
|
||||||
|
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", tc.code, code, patchArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchedMetadata, err := client.Logical().Read(metadataPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("metadata read failed, err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range patchedMetadata.Data {
|
||||||
|
var expectedVal interface{}
|
||||||
|
|
||||||
|
if inputVal, ok := tc.expectedUpdates[k]; ok {
|
||||||
|
expectedVal = inputVal
|
||||||
|
} else {
|
||||||
|
expectedVal = initialMetadata.Data[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := deep.Equal(expectedVal, v); len(diff) > 0 {
|
||||||
|
t.Fatalf("patched %q mismatch, diff: %#v", k, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKvMetadataPatchCommand_CasWarning(t *testing.T) {
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
basePath := "kv/"
|
||||||
|
if err := client.Sys().Mount(basePath, &api.MountInput{
|
||||||
|
Type: "kv-v2",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretPath := basePath + "my-secret"
|
||||||
|
|
||||||
|
args := []string{"-cas-required=true", secretPath}
|
||||||
|
code, combined := kvMetadataPutWithRetry(t, client, args, nil)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("metadata put failed, code: %d, output: %s", code, combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
casConfig := map[string]interface{}{
|
||||||
|
"cas_required": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Logical().Write(basePath + "config", casConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("config write failed, err: #%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = []string{"-cas-required=false", secretPath}
|
||||||
|
code, combined = kvMetadataPatchWithRetry(t, client, args, nil)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v", code, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedOutput := "\"cas_required\" set to false, but is mandated by backend config"
|
||||||
|
if !strings.Contains(combined, expectedOutput) {
|
||||||
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type KVMetadataPutCommand struct {
|
||||||
*BaseCommand
|
*BaseCommand
|
||||||
|
|
||||||
flagMaxVersions int
|
flagMaxVersions int
|
||||||
flagCASRequired bool
|
flagCASRequired BoolPtr
|
||||||
flagDeleteVersionAfter time.Duration
|
flagDeleteVersionAfter time.Duration
|
||||||
flagCustomMetadata map[string]string
|
flagCustomMetadata map[string]string
|
||||||
testStdin io.Reader // for tests
|
testStdin io.Reader // for tests
|
||||||
|
@ -71,14 +71,13 @@ func (c *KVMetadataPutCommand) Flags() *FlagSets {
|
||||||
f.IntVar(&IntVar{
|
f.IntVar(&IntVar{
|
||||||
Name: "max-versions",
|
Name: "max-versions",
|
||||||
Target: &c.flagMaxVersions,
|
Target: &c.flagMaxVersions,
|
||||||
Default: 0,
|
Default: -1,
|
||||||
Usage: `The number of versions to keep. If not set, the backend’s configured max version is used.`,
|
Usage: `The number of versions to keep. If not set, the backend’s configured max version is used.`,
|
||||||
})
|
})
|
||||||
|
|
||||||
f.BoolVar(&BoolVar{
|
f.BoolPtrVar(&BoolPtrVar{
|
||||||
Name: "cas-required",
|
Name: "cas-required",
|
||||||
Target: &c.flagCASRequired,
|
Target: &c.flagCASRequired,
|
||||||
Default: false,
|
|
||||||
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.`,
|
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.`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -151,16 +150,24 @@ func (c *KVMetadataPutCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
path = addPrefixToVKVPath(path, mountPath, "metadata")
|
path = addPrefixToVKVPath(path, mountPath, "metadata")
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{}
|
||||||
"max_versions": c.flagMaxVersions,
|
|
||||||
"cas_required": c.flagCASRequired,
|
if c.flagMaxVersions >= 0 {
|
||||||
"custom_metadata": c.flagCustomMetadata,
|
data["max_versions"] = c.flagMaxVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.flagDeleteVersionAfter >= 0 {
|
if c.flagDeleteVersionAfter >= 0 {
|
||||||
data["delete_version_after"] = c.flagDeleteVersionAfter.String()
|
data["delete_version_after"] = c.flagDeleteVersionAfter.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.flagCASRequired.IsSet() {
|
||||||
|
data["cas_required"] = c.flagCASRequired.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.flagCustomMetadata) > 0 {
|
||||||
|
data["custom_metadata"] = c.flagCustomMetadata
|
||||||
|
}
|
||||||
|
|
||||||
secret, err := client.Logical().Write(path, data)
|
secret, err := client.Logical().Write(path, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))
|
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func testKVMetadataPutCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPutCommand) {
|
func testKVMetadataPutCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPutCommand) {
|
||||||
|
@ -19,7 +21,7 @@ func testKVMetadataPutCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPutCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKvMetadataPutCommandDeleteVersionAfter(t *testing.T) {
|
func TestKvMetadataPutCommand_DeleteVersionAfter(t *testing.T) {
|
||||||
client, closer := testVaultServer(t)
|
client, closer := testVaultServer(t)
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
|
@ -78,7 +80,7 @@ func TestKvMetadataPutCommandDeleteVersionAfter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKvMetadataPutCommandCustomMetadata(t *testing.T) {
|
func TestKvMetadataPutCommand_CustomMetadata(t *testing.T) {
|
||||||
client, closer := testVaultServer(t)
|
client, closer := testVaultServer(t)
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
|
@ -154,3 +156,47 @@ func TestKvMetadataPutCommandCustomMetadata(t *testing.T) {
|
||||||
t.Fatal(diff)
|
t.Fatal(diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKvMetadataPutCommand_UnprovidedFlags(t *testing.T) {
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
basePath := t.Name() + "/"
|
||||||
|
secretPath := basePath + "my-secret"
|
||||||
|
|
||||||
|
if err := client.Sys().Mount(basePath, &api.MountInput{
|
||||||
|
Type: "kv-v2",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, cmd := testKVMetadataPutCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
args := []string{"-cas-required=true", "-max-versions=10", secretPath}
|
||||||
|
code, _ := kvMetadataPutWithRetry(t, client, args, nil)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("expected 0 exit status but received %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = []string{"-custom-metadata=foo=bar", secretPath}
|
||||||
|
code, _ = kvMetadataPutWithRetry(t, client, args, nil)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("expected 0 exit status but received %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := client.Logical().Read(basePath + "metadata/" + "my-secret")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret.Data["cas_required"] != true {
|
||||||
|
t.Fatalf("expected cas_required to be true but received %#v", secret.Data["cas_required"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret.Data["max_versions"] != json.Number("10") {
|
||||||
|
t.Fatalf("expected max_versions to be 10 but received %#v", secret.Data["max_versions"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -108,7 +108,7 @@ require (
|
||||||
github.com/hashicorp/vault-plugin-secrets-azure v0.11.2
|
github.com/hashicorp/vault-plugin-secrets-azure v0.11.2
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcp v0.11.1
|
github.com/hashicorp/vault-plugin-secrets-gcp v0.11.1
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0
|
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20211123171606-16933c88368a
|
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20220112155832-c2eb38b5f5b6
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1
|
||||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.6.0
|
github.com/hashicorp/vault-plugin-secrets-openldap v0.6.0
|
||||||
github.com/hashicorp/vault-plugin-secrets-terraform v0.3.0
|
github.com/hashicorp/vault-plugin-secrets-terraform v0.3.0
|
||||||
|
@ -116,7 +116,7 @@ require (
|
||||||
github.com/hashicorp/vault/api v1.3.1
|
github.com/hashicorp/vault/api v1.3.1
|
||||||
github.com/hashicorp/vault/api/auth/approle v0.1.0
|
github.com/hashicorp/vault/api/auth/approle v0.1.0
|
||||||
github.com/hashicorp/vault/api/auth/userpass v0.1.0
|
github.com/hashicorp/vault/api/auth/userpass v0.1.0
|
||||||
github.com/hashicorp/vault/sdk v0.3.0
|
github.com/hashicorp/vault/sdk v0.3.1-0.20220112143259-b48602fdb885
|
||||||
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
|
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
|
||||||
github.com/jcmturner/gokrb5/v8 v8.4.2
|
github.com/jcmturner/gokrb5/v8 v8.4.2
|
||||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -965,8 +965,8 @@ github.com/hashicorp/vault-plugin-secrets-gcp v0.11.1 h1:v8XfuZVrgP4pIwaZe/GgrPC
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcp v0.11.1/go.mod h1:ndpmRkIPHW5UYqv2nn2AJNVZsucJ8lY2bp5i5Ngvhuc=
|
github.com/hashicorp/vault-plugin-secrets-gcp v0.11.1/go.mod h1:ndpmRkIPHW5UYqv2nn2AJNVZsucJ8lY2bp5i5Ngvhuc=
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0 h1:0Vi5WEIpZctk/ZoRClodV9WCnM/lCzw9XekMhRZdo8k=
|
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0 h1:0Vi5WEIpZctk/ZoRClodV9WCnM/lCzw9XekMhRZdo8k=
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0/go.mod h1:6DPwGu8oGR1sZRpjwkcAnrQZWQuAJ/Ph+rQHfUo1Yf4=
|
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.10.0/go.mod h1:6DPwGu8oGR1sZRpjwkcAnrQZWQuAJ/Ph+rQHfUo1Yf4=
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20211123171606-16933c88368a h1:GVA3sY+FRhQrMexWGMCsIfVVMgcdru36WMKvDtKed5I=
|
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20220112155832-c2eb38b5f5b6 h1:Z3NnaIBragxW6iTW7OnvklRzZSZdaidxjs/vkCneGAg=
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20211123171606-16933c88368a/go.mod h1:TNPRoB53Twd9tYvlhqqEhMsQPiVN604kZw9jr2zUzDk=
|
github.com/hashicorp/vault-plugin-secrets-kv v0.5.7-0.20220112155832-c2eb38b5f5b6/go.mod h1:9V2Ecim3m/qw+YAQelUeFADqZ1GVo8xwoLqfKsqh9pI=
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1 h1:Maewon4nu0KL1ALBOvL6Rsj+Qyr9hdULWflyMz7+9nk=
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1 h1:Maewon4nu0KL1ALBOvL6Rsj+Qyr9hdULWflyMz7+9nk=
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1/go.mod h1:PLx2vxXukfsKsDRo/PlG4fxmJ1d+H2h82wT3vf4buuI=
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.5.1/go.mod h1:PLx2vxXukfsKsDRo/PlG4fxmJ1d+H2h82wT3vf4buuI=
|
||||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.6.0 h1:d6N/aMlklMfEacyiIuu5ZnTlADhGkGZkDrOtQXBRuhI=
|
github.com/hashicorp/vault-plugin-secrets-openldap v0.6.0 h1:d6N/aMlklMfEacyiIuu5ZnTlADhGkGZkDrOtQXBRuhI=
|
||||||
|
|
|
@ -464,7 +464,8 @@ func (c *Core) handleCancelableRequest(ctx context.Context, req *logical.Request
|
||||||
// backends. Basically, it's all just terrible, so don't allow it.
|
// backends. Basically, it's all just terrible, so don't allow it.
|
||||||
if strings.HasSuffix(req.Path, "/") &&
|
if strings.HasSuffix(req.Path, "/") &&
|
||||||
(req.Operation == logical.UpdateOperation ||
|
(req.Operation == logical.UpdateOperation ||
|
||||||
req.Operation == logical.CreateOperation) {
|
req.Operation == logical.CreateOperation ||
|
||||||
|
req.Operation == logical.PatchOperation) {
|
||||||
return logical.ErrorResponse("cannot write to a path ending in '/'"), nil
|
return logical.ErrorResponse("cannot write to a path ending in '/'"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ key-value store.
|
||||||
|
|
||||||
- `max_versions` `(int: 0)` – The number of versions to keep per key. This value
|
- `max_versions` `(int: 0)` – The number of versions to keep per key. This value
|
||||||
applies to all keys, but a key's metadata setting can overwrite this value.
|
applies to all keys, but a key's metadata setting can overwrite this value.
|
||||||
Once a key has more than the configured allowed versions the oldest version
|
Once a key has more than the configured allowed versions, the oldest version
|
||||||
will be permanently deleted. When 0 is used or the value is unset, Vault
|
will be permanently deleted. When 0 is used or the value is unset, Vault
|
||||||
will keep 10 versions.
|
will keep 10 versions.
|
||||||
|
|
||||||
|
@ -519,10 +519,10 @@ It does not create a new version.
|
||||||
|
|
||||||
- `max_versions` `(int: 0)` – The number of versions to keep per key. If not
|
- `max_versions` `(int: 0)` – The number of versions to keep per key. If not
|
||||||
set, the backend’s configured max version is used. Once a key has more than
|
set, the backend’s configured max version is used. Once a key has more than
|
||||||
the configured allowed versions the oldest version will be permanently
|
the configured allowed versions, the oldest version will be permanently
|
||||||
deleted.
|
deleted.
|
||||||
|
|
||||||
- `cas_required` `(bool: false)` – If true the key will require the cas
|
- `cas_required` `(bool: false)` – If true, the key will require the `cas`
|
||||||
parameter to be set on all write requests. If false, the backend’s
|
parameter to be set on all write requests. If false, the backend’s
|
||||||
configuration will be used.
|
configuration will be used.
|
||||||
|
|
||||||
|
@ -561,6 +561,60 @@ $ curl \
|
||||||
https://127.0.0.1:8200/v1/secret/metadata/my-secret
|
https://127.0.0.1:8200/v1/secret/metadata/my-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Patch Metadata
|
||||||
|
This endpoint patches an existing metadata entry of a secret at the specified
|
||||||
|
location. The calling token must have an ACL policy granting the `patch`
|
||||||
|
capability. Currently, only JSON merge patch is supported and must be specified
|
||||||
|
using a `Content-Type` header value of `application/merge-patch+json`. It does
|
||||||
|
not create a new version.
|
||||||
|
|
||||||
|
| Method | Path |
|
||||||
|
| :------ | :----------------------- |
|
||||||
|
| `PATCH` | `/secret/metadata/:path` |
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- `max_versions` `(int: 0)` – The number of versions to keep per key. If not
|
||||||
|
set, the backend’s configured max version is used. Once a key has more than
|
||||||
|
the configured allowed versions, the oldest version will be permanently
|
||||||
|
deleted.
|
||||||
|
|
||||||
|
- `cas_required` `(bool: false)` – 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.
|
||||||
|
|
||||||
|
- `delete_version_after` `(string:"0s")` – Set the `delete_version_after` value
|
||||||
|
to a duration to specify the `deletion_time` for all new versions
|
||||||
|
written to this key. If not set, the backend's `delete_version_after` will be
|
||||||
|
used. If the value is greater than the backend's `delete_version_after`, the
|
||||||
|
backend's `delete_version_after` will be used. Accepts [Go duration
|
||||||
|
format string][duration-godoc].
|
||||||
|
|
||||||
|
- `custom_metadata` `(map<string|string>: nil)` - A map of arbitrary string to string valued user-provided metadata meant
|
||||||
|
to describe the secret.
|
||||||
|
|
||||||
|
### Sample Payload
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"max_versions": 5,
|
||||||
|
"custom_metadata": {
|
||||||
|
"bar": "123",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample Request
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ curl \
|
||||||
|
--header "X-Vault-Token: ..." \
|
||||||
|
--header "Content-Type: application/merge-patch+json"
|
||||||
|
--request PATCH \
|
||||||
|
--data @payload.json \
|
||||||
|
https://127.0.0.1:8200/v1/secret/metadata/my-secret
|
||||||
|
```
|
||||||
|
|
||||||
## Delete Metadata and All Versions
|
## Delete Metadata and All Versions
|
||||||
|
|
||||||
This endpoint permanently deletes the key metadata and all version data for the
|
This endpoint permanently deletes the key metadata and all version data for the
|
||||||
|
|
|
@ -169,7 +169,7 @@ allows for writing keys with arbitrary values.
|
||||||
|
|
||||||
1. Write arbitrary data:
|
1. Write arbitrary data:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv put secret/my-secret foo=a bar=b
|
$ vault kv put secret/my-secret foo=a bar=b
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -182,7 +182,7 @@ allows for writing keys with arbitrary values.
|
||||||
|
|
||||||
1. Read arbitrary data:
|
1. Read arbitrary data:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv get secret/my-secret
|
$ vault kv get secret/my-secret
|
||||||
====== Metadata ======
|
====== Metadata ======
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -206,7 +206,7 @@ allows for writing keys with arbitrary values.
|
||||||
allowed if the key’s current version matches the version specified in the
|
allowed if the key’s current version matches the version specified in the
|
||||||
cas parameter.
|
cas parameter.
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv put -cas=1 secret/my-secret foo=aa bar=bb
|
$ vault kv put -cas=1 secret/my-secret foo=aa bar=bb
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -219,7 +219,7 @@ allows for writing keys with arbitrary values.
|
||||||
|
|
||||||
1. Reading now will return the newest version of the data:
|
1. Reading now will return the newest version of the data:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv get secret/my-secret
|
$ vault kv get secret/my-secret
|
||||||
====== Metadata ======
|
====== Metadata ======
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -249,7 +249,7 @@ allows for writing keys with arbitrary values.
|
||||||
read-then-write flow will use the `version` value from the secret returned by
|
read-then-write flow will use the `version` value from the secret returned by
|
||||||
the read to perform a check-and-set operation in the subsequent write.
|
the read to perform a check-and-set operation in the subsequent write.
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv patch -cas=2 secret/my-secret bar=bbb
|
$ vault kv patch -cas=2 secret/my-secret bar=bbb
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -266,7 +266,7 @@ allows for writing keys with arbitrary values.
|
||||||
|
|
||||||
Perform a patch using the `patch` method:
|
Perform a patch using the `patch` method:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv patch -method=patch -cas=2 secret/my-secret bar=bbb
|
$ vault kv patch -method=patch -cas=2 secret/my-secret bar=bbb
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -278,7 +278,7 @@ allows for writing keys with arbitrary values.
|
||||||
```
|
```
|
||||||
|
|
||||||
Perform a patch using the read-then-write method:
|
Perform a patch using the read-then-write method:
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv patch -method=rw secret/my-secret bar=bbb
|
$ vault kv patch -method=rw secret/my-secret bar=bbb
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -292,7 +292,7 @@ allows for writing keys with arbitrary values.
|
||||||
1. Reading after a patch will return the newest version of the data in which
|
1. Reading after a patch will return the newest version of the data in which
|
||||||
only the specified fields were updated:
|
only the specified fields were updated:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv get secret/my-secret
|
$ vault kv get secret/my-secret
|
||||||
====== Metadata ======
|
====== Metadata ======
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -312,7 +312,7 @@ allows for writing keys with arbitrary values.
|
||||||
|
|
||||||
1. Previous versions can be accessed with the `-version` flag:
|
1. Previous versions can be accessed with the `-version` flag:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv get -version=1 secret/my-secret
|
$ vault kv get -version=1 secret/my-secret
|
||||||
====== Metadata ======
|
====== Metadata ======
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -349,14 +349,14 @@ See the commands below for more information:
|
||||||
1. The latest version of a key can be deleted with the delete command, this also
|
1. The latest version of a key can be deleted with the delete command, this also
|
||||||
takes a `-versions` flag to delete prior versions:
|
takes a `-versions` flag to delete prior versions:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv delete secret/my-secret
|
$ vault kv delete secret/my-secret
|
||||||
t
|
Success! Data deleted (if it existed) at: secret/my-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Versions can be undeleted:
|
1. Versions can be undeleted:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv undelete -versions=2 secret/my-secret
|
$ vault kv undelete -versions=2 secret/my-secret
|
||||||
Success! Data written to: secret/undelete/my-secret
|
Success! Data written to: secret/undelete/my-secret
|
||||||
|
|
||||||
|
@ -378,7 +378,7 @@ See the commands below for more information:
|
||||||
|
|
||||||
1. Destroying a version permanently deletes the underlying data:
|
1. Destroying a version permanently deletes the underlying data:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv destroy -versions=2 secret/my-secret
|
$ vault kv destroy -versions=2 secret/my-secret
|
||||||
Success! Data written to: secret/destroy/my-secret
|
Success! Data written to: secret/destroy/my-secret
|
||||||
```
|
```
|
||||||
|
@ -393,7 +393,7 @@ See the commands below for more information:
|
||||||
|
|
||||||
1. All metadata and versions for a key can be viewed:
|
1. All metadata and versions for a key can be viewed:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv metadata get secret/my-secret
|
$ vault kv metadata get secret/my-secret
|
||||||
========== Metadata ==========
|
========== Metadata ==========
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -424,7 +424,7 @@ See the commands below for more information:
|
||||||
|
|
||||||
1. The metadata settings for a key can be configured:
|
1. The metadata settings for a key can be configured:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv metadata put -max-versions 2 -delete-version-after="3h25m19s" secret/my-secret
|
$ vault kv metadata put -max-versions 2 -delete-version-after="3h25m19s" secret/my-secret
|
||||||
Success! Data written to: secret/metadata/my-secret
|
Success! Data written to: secret/metadata/my-secret
|
||||||
```
|
```
|
||||||
|
@ -432,7 +432,7 @@ See the commands below for more information:
|
||||||
Delete-version-after settings will apply only to new versions. Max versions
|
Delete-version-after settings will apply only to new versions. Max versions
|
||||||
changes will be applied on next write:
|
changes will be applied on next write:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv put secret/my-secret my-value=newer-s3cr3t
|
$ vault kv put secret/my-secret my-value=newer-s3cr3t
|
||||||
Key Value
|
Key Value
|
||||||
--- -----
|
--- -----
|
||||||
|
@ -446,7 +446,7 @@ See the commands below for more information:
|
||||||
Once a key has more versions than max versions the oldest versions
|
Once a key has more versions than max versions the oldest versions
|
||||||
are cleaned up:
|
are cleaned up:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv metadata get secret/my-secret
|
$ vault kv metadata get secret/my-secret
|
||||||
========== Metadata ==========
|
========== Metadata ==========
|
||||||
Key Value
|
Key Value
|
||||||
|
@ -476,17 +476,25 @@ See the commands below for more information:
|
||||||
```
|
```
|
||||||
|
|
||||||
A secret's key metadata can contain custom metadata used to describe the secret. The
|
A secret's key metadata can contain custom metadata used to describe the secret. The
|
||||||
data will be stored as string-to-string key-value pairs. If the `-custom-metadata` flag
|
data will be stored as string-to-string key-value pairs. The `-custom-metadata`
|
||||||
is set, the value of `custom_metadata` will be fully overwritten. The `-custom-metadata`
|
flag can be repeated to add multiple key-value pairs.
|
||||||
flag can be repeated to add multiple key-value pairs:
|
|
||||||
|
|
||||||
```text
|
The `vault kv metadata put` command can be used to fully overwrite the value of `custom_metadata`:
|
||||||
vault kv metadata put -custom-metadata=foo=abc -custom-metadata=bar=123 secret/my-secret
|
|
||||||
|
```shell-session
|
||||||
|
$ vault kv metadata put -custom-metadata=foo=abc -custom-metadata=bar=123 secret/my-secret
|
||||||
|
```
|
||||||
|
|
||||||
|
The `vault kv metadata patch` command can be used to partially overwrite the value of `custom_metadata`.
|
||||||
|
The following invocation will update `custom_metadata` sub-field `foo` but leave `bar` untouched:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ vault kv metadata patch -custom-metadata=foo=def secret/my-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Permanently delete all metadata and versions for a key:
|
1. Permanently delete all metadata and versions for a key:
|
||||||
|
|
||||||
```text
|
```shell-session
|
||||||
$ vault kv metadata delete secret/my-secret
|
$ vault kv metadata delete secret/my-secret
|
||||||
Success! Data deleted (if it existed) at: secret/metadata/my-secret
|
Success! Data deleted (if it existed) at: secret/metadata/my-secret
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in New Issue