From d236300e5e14e800b08386dc58723115b5ab886e Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Mon, 21 Nov 2022 11:55:01 -0800 Subject: [PATCH 1/3] improve kv CLI to remove data or custom metadata using kv patch --- command/kv_metadata_patch.go | 39 ++++++++++++++++++------ command/kv_metadata_patch_test.go | 23 ++++++++++++++ command/kv_patch.go | 33 ++++++++++++++++---- vault/external_tests/kv/kv_patch_test.go | 6 ++++ 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/command/kv_metadata_patch.go b/command/kv_metadata_patch.go index b63da2369a2c..11ffdb4bea26 100644 --- a/command/kv_metadata_patch.go +++ b/command/kv_metadata_patch.go @@ -20,12 +20,13 @@ var ( type KVMetadataPatchCommand struct { *BaseCommand - flagMaxVersions int - flagCASRequired BoolPtr - flagDeleteVersionAfter time.Duration - flagCustomMetadata map[string]string - flagMount string - testStdin io.Reader // for tests + flagMaxVersions int + flagCASRequired BoolPtr + flagDeleteVersionAfter time.Duration + flagCustomMetadata map[string]string + flagRemoveCustomMetadata []string + flagMount string + testStdin io.Reader // for tests } func (c *KVMetadataPatchCommand) Synopsis() string { @@ -65,6 +66,10 @@ Usage: vault kv metadata patch [options] KEY $ vault kv metadata patch -mount=secret -custom-metadata=foo=abc -custom-metadata=bar=123 foo + To remove custom meta data from the corresponding path in the key-value store, kv metadata patch can be used. + + $ vault kv metadata patch -mount=secret -remove-custom-metadata=bar foo + Additional flags and more advanced use cases are detailed below. ` + c.Flags().Help() @@ -111,6 +116,13 @@ func (c *KVMetadataPatchCommand) Flags() *FlagSets { This can be specified multiple times to add multiple pieces of metadata.`, }) + f.StringSliceVar(&StringSliceVar{ + Name: "remove-custom-metadata", + Target: &c.flagRemoveCustomMetadata, + Default: []string{}, + Usage: "Key to remove from custom metadata. To specify multiple values, specify this flag multiple times.", + }) + f.StringVar(&StringVar{ Name: "mount", Target: &c.flagMount, @@ -198,7 +210,7 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata") - data := map[string]interface{}{} + data := make(map[string]interface{}, 0) if c.flagMaxVersions >= 0 { data["max_versions"] = c.flagMaxVersions @@ -212,10 +224,19 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { data["delete_version_after"] = c.flagDeleteVersionAfter.String() } - if len(c.flagCustomMetadata) > 0 { - data["custom_metadata"] = c.flagCustomMetadata + customMetadata := make(map[string]interface{}) + + for key, value := range c.flagCustomMetadata { + customMetadata[key] = value + } + + for _, key := range c.flagRemoveCustomMetadata { + // A null in a JSON merge patch payload will remove the associated key + customMetadata[key] = nil } + data["custom_metadata"] = customMetadata + 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)) diff --git a/command/kv_metadata_patch_test.go b/command/kv_metadata_patch_test.go index 40b74dc8d9ee..3b15c520294c 100644 --- a/command/kv_metadata_patch_test.go +++ b/command/kv_metadata_patch_test.go @@ -122,6 +122,29 @@ func TestKvMetadataPatchCommand_Flags(t *testing.T) { }, }, }, + { + "remove-custom_metadata", + []string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo"}, + "Success!", + 0, + map[string]interface{}{ + "custom_metadata": map[string]interface{}{ + "bar": "def", + "baz": "ghi", + }, + }, + }, + { + "remove-custom_metadata-multiple", + []string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo", "-remove-custom-metadata=bar"}, + "Success!", + 0, + map[string]interface{}{ + "custom_metadata": map[string]interface{}{ + "baz": "ghi", + }, + }, + }, { "delete_version_after_success", []string{"-delete-version-after=5s"}, diff --git a/command/kv_patch.go b/command/kv_patch.go index 6a736fa247f1..b51888a4e8af 100644 --- a/command/kv_patch.go +++ b/command/kv_patch.go @@ -21,10 +21,11 @@ var ( type KVPatchCommand struct { *BaseCommand - flagCAS int - flagMethod string - flagMount string - testStdin io.Reader // for tests + flagCAS int + flagMethod string + flagMount string + testStdin io.Reader // for tests + flagRemoveData []string } func (c *KVPatchCommand) Synopsis() string { @@ -76,6 +77,10 @@ Usage: vault kv patch [options] KEY [DATA] $ vault kv patch -mount=secret -method=rw foo bar=baz + To remove data from the corresponding path in the key-value store, kv patch can be used. + + $ vault kv patch -mount=secret -remove-data=bar foo + Additional flags and more advanced use cases are detailed below. ` + c.Flags().Help() @@ -117,6 +122,13 @@ func (c *KVPatchCommand) Flags() *FlagSets { v2 secrets.`, }) + f.StringSliceVar(&StringSliceVar{ + Name: "remove-data", + Target: &c.flagRemoveData, + Default: []string{}, + Usage: "Key to remove from data. To specify multiple values, specify this flag multiple times.", + }) + return set } @@ -147,8 +159,8 @@ func (c *KVPatchCommand) Run(args []string) int { 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("Must supply data") + case len(c.flagRemoveData) == 0 && len(args) == 1: + c.UI.Error(fmt.Sprintf("Must supply data %v", args)) return 1 } @@ -211,6 +223,15 @@ func (c *KVPatchCommand) Run(args []string) int { return 2 } + // collecting data to be removed + if newData == nil { + newData = make(map[string]interface{}) + } + + for _, key := range c.flagRemoveData { + newData[key] = nil + } + // Check the method and behave accordingly var secret *api.Secret var code int diff --git a/vault/external_tests/kv/kv_patch_test.go b/vault/external_tests/kv/kv_patch_test.go index 52e60215dd74..2228097cac42 100644 --- a/vault/external_tests/kv/kv_patch_test.go +++ b/vault/external_tests/kv/kv_patch_test.go @@ -249,6 +249,7 @@ func TestKV_Patch_RootToken(t *testing.T) { data := map[string]interface{}{ "data": map[string]interface{}{ "bar": "baz", + "foo": "qux", }, } @@ -263,6 +264,7 @@ func TestKV_Patch_RootToken(t *testing.T) { data := map[string]interface{}{ "data": map[string]interface{}{ "bar": "quux", + "foo": nil, }, } return client.Logical().JSONMergePatch(context.Background(), "kv/data/foo", data) @@ -288,4 +290,8 @@ func TestKV_Patch_RootToken(t *testing.T) { if bar != "quux" { t.Fatalf("expected bar to be quux but it was %q", bar) } + + if _, ok := secret.Data["data"].(map[string]interface{})["foo"]; ok { + t.Fatalf("expected data not to include foo") + } } From 2ce01a7ccb6feb61c7fa1afdc5c2db5a596fda7b Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Mon, 21 Nov 2022 11:58:42 -0800 Subject: [PATCH 2/3] CL --- changelog/18067.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/18067.txt diff --git a/changelog/18067.txt b/changelog/18067.txt new file mode 100644 index 000000000000..2c86305d96d5 --- /dev/null +++ b/changelog/18067.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli/kv: improve kv CLI to remove data or custom metadata using kv patch +``` From f6db4f142c3c9204e061e4fbcd1b45912415225f Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Mon, 21 Nov 2022 12:56:46 -0800 Subject: [PATCH 3/3] adding a comment --- command/kv_patch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/command/kv_patch.go b/command/kv_patch.go index b51888a4e8af..3d0f3456c2b3 100644 --- a/command/kv_patch.go +++ b/command/kv_patch.go @@ -160,7 +160,7 @@ func (c *KVPatchCommand) Run(args []string) int { c.UI.Error(fmt.Sprintf("Not enough arguments (expected >1, got %d)", len(args))) return 1 case len(c.flagRemoveData) == 0 && len(args) == 1: - c.UI.Error(fmt.Sprintf("Must supply data %v", args)) + c.UI.Error("Must supply data") return 1 } @@ -229,6 +229,7 @@ func (c *KVPatchCommand) Run(args []string) int { } for _, key := range c.flagRemoveData { + // A null in a JSON merge patch payload will remove the associated key newData[key] = nil }