Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output full secret path in certain kv commands #14301

Merged
merged 10 commits into from
Mar 8, 2022
Merged
3 changes: 3 additions & 0 deletions changelog/14301.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/kv: add full secret path output to table-formatted responses
```
4 changes: 4 additions & 0 deletions command/kv_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ func (c *KVGetCommand) Run(args []string) int {
tf.printWarnings(c.UI, secret)
}

if v2 {
outputPath(c.UI, path, "Secret Path")
}

if metadata, ok := secret.Data["metadata"]; ok && metadata != nil {
c.UI.Info(getHeaderForMap("Metadata", metadata.(map[string]interface{})))
OutputData(c.UI, metadata)
Expand Down
30 changes: 21 additions & 9 deletions command/kv_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/cli"
)

func kvReadRequest(client *api.Client, path string, params map[string]string) (*api.Secret, error) {
Expand Down Expand Up @@ -141,6 +142,26 @@ func getHeaderForMap(header string, data map[string]interface{}) string {
// 4 for the column spaces and 5 for the len("value")
totalLen := maxKey + 4 + 5

return padEqualSigns(header, totalLen)
}

func kvParseVersionsFlags(versions []string) []string {
versionsOut := make([]string, 0, len(versions))
for _, v := range versions {
versionsOut = append(versionsOut, strutil.ParseStringSlice(v, ",")...)
}

return versionsOut
}

func outputPath(ui cli.Ui, path string, title string) {
ui.Info(padEqualSigns(title, len(path)))
ui.Info(path)
ui.Info("")
}

// Pad the table header with equal signs on each side
func padEqualSigns(header string, totalLen int) string {
equalSigns := totalLen - (len(header) + 2)

// If we have zero or fewer equal signs bump it back up to two on either
Expand All @@ -156,12 +177,3 @@ func getHeaderForMap(header string, data map[string]interface{}) string {

return fmt.Sprintf("%s %s %s", strings.Repeat("=", equalSigns/2), header, strings.Repeat("=", equalSigns/2))
}

func kvParseVersionsFlags(versions []string) []string {
versionsOut := make([]string, 0, len(versions))
for _, v := range versions {
versionsOut = append(versionsOut, strutil.ParseStringSlice(v, ",")...)
}

return versionsOut
}
2 changes: 2 additions & 0 deletions command/kv_metadata_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ func (c *KVMetadataGetCommand) Run(args []string) int {

delete(secret.Data, "versions")

outputPath(c.UI, path, "Metadata Path")

c.UI.Info(getHeaderForMap("Metadata", secret.Data))
OutputSecret(c.UI, secret)

Expand Down
7 changes: 7 additions & 0 deletions command/kv_patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ func (c *KVPatchCommand) Run(args []string) int {
return code
}

if Format(c.UI) == "table" {
outputPath(c.UI, path, "Secret Path")
metadata := secret.Data
c.UI.Info(getHeaderForMap("Metadata", metadata))
return OutputData(c.UI, metadata)
}

return OutputSecret(c.UI, secret)
}

Expand Down
19 changes: 19 additions & 0 deletions command/kv_put.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,24 @@ func (c *KVPutCommand) Run(args []string) int {
return PrintRawField(c.UI, secret, c.flagField)
}

// if Format(c.UI) != "table" {
// return OutputSecret(c.UI, secret)
// }

// outputPath(c.UI, path, "Secret Path")

// metadata := secret.Data
// c.UI.Info(getHeaderForMap("Metadata", metadata))
// OutputData(c.UI, metadata)

// return 0

if Format(c.UI) == "table" {
outputPath(c.UI, path, "Secret Path")
metadata := secret.Data
c.UI.Info(getHeaderForMap("Metadata", metadata))
return OutputData(c.UI, metadata)
}

return OutputSecret(c.UI, secret)
}
117 changes: 95 additions & 22 deletions command/kv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ func TestKVPutCommand(t *testing.T) {
v2ExpectedFields,
0,
},
{
"v2_secret_path",
[]string{"kv/write/foo", "foo=bar"},
[]string{"== Secret Path ==", "kv/data/write/foo"},
0,
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -441,6 +447,12 @@ func TestKVGetCommand(t *testing.T) {
append(baseV2ExpectedFields, "foo"),
0,
},
{
"v2_secret_path",
[]string{"kv/read/foo"},
[]string{"== Secret Path ==", "kv/data/read/foo"},
0,
},
}

t.Run("validations", func(t *testing.T) {
Expand Down Expand Up @@ -560,6 +572,12 @@ func TestKVMetadataGetCommand(t *testing.T) {
append(expectedTopLevelFields, expectedVersionFields[:]...),
0,
},
{
"path_exists",
[]string{"kv/foo"},
[]string{"== Metadata Path ==", "kv/metadata/foo"},
0,
},
}

t.Run("validations", func(t *testing.T) {
Expand Down Expand Up @@ -872,6 +890,13 @@ func TestKVPatchCommand_RWMethodSucceeds(t *testing.T) {
}
}

// Test that full path was output
for _, str := range []string{"== Secret Path ==", "kv/data/patch/foo"} {
if !strings.Contains(combined, str) {
t.Errorf("expected %q to contain %q", combined, str)
}
}

// Test multi value
args = []string{"-method", "rw", "kv/patch/foo", "foo=aaa", "bar=bbb"}
code, combined = kvPatchWithRetry(t, client, args, nil)
Expand Down Expand Up @@ -1106,28 +1131,6 @@ func TestKVPatchCommand_403Fallback(t *testing.T) {
}
}

func createTokenForPolicy(t *testing.T, client *api.Client, policy string) (*api.SecretAuth, error) {
t.Helper()

if err := client.Sys().PutPolicy("policy", policy); err != nil {
return nil, err
}

secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
Policies: []string{"policy"},
TTL: "30m",
})
if err != nil {
return nil, err
}

if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
return nil, fmt.Errorf("missing auth data: %#v", secret)
}

return secret.Auth, err
}

func TestKVPatchCommand_RWMethodPolicyVariations(t *testing.T) {
cases := []struct {
name string
Expand Down Expand Up @@ -1205,3 +1208,73 @@ func TestKVPatchCommand_RWMethodPolicyVariations(t *testing.T) {
})
}
}

func TestPadEqualSigns(t *testing.T) {
t.Parallel()

header := "Test Header"

cases := []struct {
name string
totalPathLen int
expectedCount int
}{
{
name: "path with even length",
totalPathLen: 20,
expectedCount: 4,
},
{
name: "path with odd length",
totalPathLen: 19,
expectedCount: 3,
},
{
name: "smallest possible path",
totalPathLen: 8,
expectedCount: 2,
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

padded := padEqualSigns(header, tc.totalPathLen)

signs := strings.Split(padded, fmt.Sprintf(" %s ", header))
if len(signs[0]) != len(signs[1]) {
t.Fatalf("expected an equal number of equal signs on both sides")
}
for _, sign := range signs {
count := strings.Count(sign, "=")
if count != tc.expectedCount {
t.Fatalf("expected %d equal signs but there were %d", tc.expectedCount, count)
}
}
})
}
}

func createTokenForPolicy(t *testing.T, client *api.Client, policy string) (*api.SecretAuth, error) {
t.Helper()

if err := client.Sys().PutPolicy("policy", policy); err != nil {
return nil, err
}

secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
Policies: []string{"policy"},
TTL: "30m",
})
if err != nil {
return nil, err
}

if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
return nil, fmt.Errorf("missing auth data: %#v", secret)
}

return secret.Auth, err
}
digivava marked this conversation as resolved.
Show resolved Hide resolved