diff --git a/changelog/17679.txt b/changelog/17679.txt new file mode 100644 index 000000000..b77f631ea --- /dev/null +++ b/changelog/17679.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: Fix issue preventing kv commands from executing properly when the mount path provided by `-mount` flag and secret key path are the same. +``` diff --git a/command/kv_delete.go b/command/kv_delete.go index 65e092763..5555b9c71 100644 --- a/command/kv_delete.go +++ b/command/kv_delete.go @@ -117,7 +117,7 @@ func (c *KVDeleteCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -129,12 +129,15 @@ func (c *KVDeleteCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_destroy.go b/command/kv_destroy.go index 33f5452a7..45cbca025 100644 --- a/command/kv_destroy.go +++ b/command/kv_destroy.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "path" "strings" "github.com/mitchellh/cli" @@ -115,7 +116,7 @@ func (c *KVDestroyCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -127,12 +128,15 @@ func (c *KVDestroyCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_get.go b/command/kv_get.go index 391efaf88..057a787c4 100644 --- a/command/kv_get.go +++ b/command/kv_get.go @@ -113,7 +113,7 @@ func (c *KVGetCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -126,12 +126,15 @@ func (c *KVGetCommand) Run(args []string) int { // Parse the paths and grab the KV version if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_metadata_delete.go b/command/kv_metadata_delete.go index 911f00117..cff16f21c 100644 --- a/command/kv_metadata_delete.go +++ b/command/kv_metadata_delete.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "path" "strings" "github.com/mitchellh/cli" @@ -97,7 +98,7 @@ func (c *KVMetadataDeleteCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -109,12 +110,15 @@ func (c *KVMetadataDeleteCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_metadata_get.go b/command/kv_metadata_get.go index 08e401374..892034075 100644 --- a/command/kv_metadata_get.go +++ b/command/kv_metadata_get.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "path" "sort" "strconv" "strings" @@ -99,7 +100,7 @@ func (c *KVMetadataGetCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -111,12 +112,15 @@ func (c *KVMetadataGetCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_metadata_patch.go b/command/kv_metadata_patch.go index d3550e645..b63da2369 100644 --- a/command/kv_metadata_patch.go +++ b/command/kv_metadata_patch.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "path" "strings" "time" @@ -159,7 +160,7 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -171,12 +172,15 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_metadata_put.go b/command/kv_metadata_put.go index 57aa3ebd9..5196b1c79 100644 --- a/command/kv_metadata_put.go +++ b/command/kv_metadata_put.go @@ -3,6 +3,7 @@ package command import ( "fmt" "io" + "path" "strings" "time" @@ -158,7 +159,7 @@ func (c *KVMetadataPutCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -170,12 +171,15 @@ func (c *KVMetadataPutCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_patch.go b/command/kv_patch.go index 5368d9e0d..6a736fa24 100644 --- a/command/kv_patch.go +++ b/command/kv_patch.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "path" "strings" "github.com/hashicorp/vault/api" @@ -167,7 +168,7 @@ func (c *KVPatchCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -179,12 +180,15 @@ func (c *KVPatchCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_put.go b/command/kv_put.go index fe991711a..5cc7b6fbc 100644 --- a/command/kv_put.go +++ b/command/kv_put.go @@ -143,7 +143,7 @@ func (c *KVPutCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -155,12 +155,15 @@ func (c *KVPutCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_rollback.go b/command/kv_rollback.go index e69dba6f6..0d782619a 100644 --- a/command/kv_rollback.go +++ b/command/kv_rollback.go @@ -3,6 +3,7 @@ package command import ( "flag" "fmt" + "path" "strings" "github.com/mitchellh/cli" @@ -123,7 +124,7 @@ func (c *KVRollbackCommand) Run(args []string) int { // If true, we're working with "-mount=secret foo" syntax. // If false, we're using "secret/foo" syntax. - mountFlagSyntax := (c.flagMount != "") + mountFlagSyntax := c.flagMount != "" var ( mountPath string @@ -135,12 +136,15 @@ func (c *KVRollbackCommand) Run(args []string) int { if mountFlagSyntax { // In this case, this arg is the secret path (e.g. "foo"). partialPath = sanitizePath(args[0]) - mountPath = sanitizePath(c.flagMount) - _, v2, err = isKVv2(mountPath, client) + 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") diff --git a/command/kv_test.go b/command/kv_test.go index eba591043..830c9cc30 100644 --- a/command/kv_test.go +++ b/command/kv_test.go @@ -129,6 +129,12 @@ func TestKVPutCommand(t *testing.T) { []string{"Success!"}, 0, }, + { + "v1_mount_flag_syntax_key_same_as_mount", + []string{"-mount", "secret", "secret", "foo=bar"}, + []string{"Success!"}, + 0, + }, { "v2_single_value", []string{"kv/write/foo", "foo=bar"}, @@ -153,6 +159,12 @@ func TestKVPutCommand(t *testing.T) { v2ExpectedFields, 0, }, + { + "v2_mount_flag_syntax_key_same_as_mount", + []string{"-mount", "kv", "kv", "foo=bar"}, + v2ExpectedFields, + 0, + }, { "v2_single_value_backslash", []string{"kv/write/foo", "foo=\\"}, @@ -464,6 +476,18 @@ func TestKVGetCommand(t *testing.T) { append(baseV2ExpectedFields, "foo"), 0, }, + { + "v1_mount_flag_syntax_key_same_as_mount", + []string{"-mount", "kv", "kv"}, + append(baseV2ExpectedFields, "foo"), + 0, + }, + { + "v2_mount_flag_syntax_key_same_as_mount", + []string{"-mount", "kv", "kv"}, + append(baseV2ExpectedFields, "foo"), + 0, + }, { "v2_not_found", []string{"kv/nope/not/once/never"}, @@ -524,6 +548,21 @@ func TestKVGetCommand(t *testing.T) { t.Fatal(err) } + // create KV entries to test -mount flag where secret key is same as mount path + if _, err := client.Logical().Write("secret/secret", map[string]interface{}{ + "foo": "bar", + }); err != nil { + t.Fatal(err) + } + + if _, err := client.Logical().Write("kv/data/kv", map[string]interface{}{ + "data": map[string]interface{}{ + "foo": "bar", + }, + }); err != nil { + t.Fatal(err) + } + ui, cmd := testKVGetCommand(t) cmd.client = client @@ -613,6 +652,12 @@ func TestKVMetadataGetCommand(t *testing.T) { expectedTopLevelFields, 0, }, + { + "mount_flag_syntax_key_same_as_mount", + []string{"-mount", "kv", "kv"}, + expectedTopLevelFields, + 0, + }, } t.Run("validations", func(t *testing.T) { @@ -643,6 +688,15 @@ func TestKVMetadataGetCommand(t *testing.T) { t.Fatal(err) } + // create KV entry to test -mount flag where secret key is same as mount path + if _, err := client.Logical().Write("kv/data/kv", map[string]interface{}{ + "data": map[string]interface{}{ + "foo": "bar", + }, + }); err != nil { + t.Fatal(err) + } + ui, cmd := testKVMetadataGetCommand(t) cmd.client = client @@ -973,6 +1027,7 @@ func TestKVPatchCommand_RWMethodSucceeds(t *testing.T) { func TestKVPatchCommand_CAS(t *testing.T) { cases := []struct { name string + key string args []string expected string outStrings []string @@ -980,6 +1035,7 @@ func TestKVPatchCommand_CAS(t *testing.T) { }{ { "right version", + "foo", []string{"-cas", "1", "kv/foo", "bar=quux"}, "quux", expectedPatchFields(), @@ -987,6 +1043,7 @@ func TestKVPatchCommand_CAS(t *testing.T) { }, { "wrong version", + "foo", []string{"-cas", "2", "kv/foo", "bar=wibble"}, "baz", []string{"check-and-set parameter did not match the current version"}, @@ -994,11 +1051,20 @@ func TestKVPatchCommand_CAS(t *testing.T) { }, { "mount_flag_syntax", + "foo", []string{"-mount", "kv", "-cas", "1", "foo", "bar=quux"}, "quux", expectedPatchFields(), 0, }, + { + "v2_mount_flag_syntax_key_same_as_mount", + "kv", + []string{"-mount", "kv", "-cas", "1", "kv", "bar=quux"}, + "quux", + expectedPatchFields(), + 0, + }, } for _, tc := range cases { @@ -1030,7 +1096,11 @@ func TestKVPatchCommand_CAS(t *testing.T) { kvClient.SetToken(secretAuth.ClientToken) - _, err = kvClient.Logical().Write("kv/data/foo", map[string]interface{}{"data": map[string]interface{}{"bar": "baz"}}) + data := map[string]interface{}{ + "bar": "baz", + } + + _, err = kvClient.Logical().Write("kv/data/"+tc.key, map[string]interface{}{"data": data}) if err != nil { t.Fatal(err) } @@ -1047,7 +1117,7 @@ func TestKVPatchCommand_CAS(t *testing.T) { } } - secret, err := kvClient.Logical().ReadWithContext(context.Background(), "kv/data/foo") + secret, err := kvClient.Logical().ReadWithContext(context.Background(), "kv/data/"+tc.key) if err != nil { t.Fatal(err) }