Mount flag syntax to mitigate confusion from KV-v2 path discrepancies (#14807)
* Add explanation to help text and flag usage text * KV get with new mount flag * Clearer naming * KV Put, Patch, Metadata Get + corresponding tests * KV Delete, Destroy, Rollback, Undelete, MetadataDelete, MetadataPatch, MetadataPut * Update KV-v2 docs to use mount flag syntax * Add changelog * Run make fmt * Clarify deprecation message in help string * Address style comments
This commit is contained in:
parent
7393bc173d
commit
2113ae1021
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
cli: Alternative flag-based syntax for KV to mitigate confusion from automatically appended /data
|
||||
```
|
|
@ -27,19 +27,25 @@ Usage: vault kv <subcommand> [options] [args]
|
|||
Create or update the key named "foo" in the "secret" mount with the value
|
||||
"bar=baz":
|
||||
|
||||
$ vault kv put secret/foo bar=baz
|
||||
$ vault kv put -mount=secret foo bar=baz
|
||||
|
||||
Read this value back:
|
||||
|
||||
$ vault kv get secret/foo
|
||||
$ vault kv get -mount=secret foo
|
||||
|
||||
Get metadata for the key:
|
||||
|
||||
$ vault kv metadata get secret/foo
|
||||
$ vault kv metadata get -mount=secret foo
|
||||
|
||||
Get a specific version of the key:
|
||||
|
||||
$ vault kv get -version=1 secret/foo
|
||||
$ vault kv get -mount=secret -version=1 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/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv get secret/foo
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
|
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
|
@ -18,6 +19,7 @@ type KVDeleteCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) Synopsis() string {
|
||||
|
@ -34,11 +36,17 @@ Usage: vault kv delete [options] PATH
|
|||
|
||||
To delete the latest version of the key "foo":
|
||||
|
||||
$ vault kv delete -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/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv delete secret/foo
|
||||
|
||||
To delete version 3 of key foo:
|
||||
|
||||
$ vault kv delete -versions=3 secret/foo
|
||||
$ vault kv delete -mount=secret -versions=3 foo
|
||||
|
||||
To delete all versions and metadata, see the "vault kv metadata" subcommand.
|
||||
|
||||
|
@ -61,6 +69,17 @@ func (c *KVDeleteCommand) Flags() *FlagSets {
|
|||
Usage: `Specifies the version numbers to delete.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -96,22 +115,54 @@ func (c *KVDeleteCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
var secret *api.Secret
|
||||
var fullPath string
|
||||
if v2 {
|
||||
secret, err = c.deleteV2(path, mountPath, client)
|
||||
secret, err = c.deleteV2(partialPath, mountPath, client)
|
||||
fullPath = addPrefixToKVPath(partialPath, mountPath, "data")
|
||||
} else {
|
||||
secret, err = client.Logical().Delete(path)
|
||||
// v1
|
||||
if mountFlagSyntax {
|
||||
fullPath = path.Join(mountPath, partialPath)
|
||||
} else {
|
||||
fullPath = partialPath
|
||||
}
|
||||
secret, err = client.Logical().Delete(fullPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", path, err))
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", fullPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
@ -121,7 +172,7 @@ func (c *KVDeleteCommand) Run(args []string) int {
|
|||
if secret == nil {
|
||||
// Don't output anything unless using the "table" format
|
||||
if Format(c.UI) == "table" {
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", path))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", fullPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@ -139,22 +190,12 @@ func (c *KVDeleteCommand) deleteV2(path, mountPath string, client *api.Client) (
|
|||
switch {
|
||||
case len(c.flagVersions) > 0:
|
||||
path = addPrefixToKVPath(path, mountPath, "delete")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"versions": kvParseVersionsFlags(c.flagVersions),
|
||||
}
|
||||
|
||||
secret, err = client.Logical().Write(path, data)
|
||||
default:
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err = client.Logical().Delete(path)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ type KVDestroyCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) Synopsis() string {
|
||||
|
@ -32,6 +33,12 @@ Usage: vault kv destroy [options] KEY
|
|||
|
||||
To destroy version 3 of key foo:
|
||||
|
||||
$ vault kv destroy -mount=secret -versions=3 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/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv destroy -versions=3 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
@ -53,6 +60,17 @@ func (c *KVDestroyCommand) Flags() *FlagSets {
|
|||
Usage: `Specifies the version numbers to destroy.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -86,8 +104,8 @@ func (c *KVDestroyCommand) Run(args []string) int {
|
|||
c.UI.Error("No versions provided, use the \"-versions\" flag to specify the version to destroy.")
|
||||
return 1
|
||||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
|
@ -95,16 +113,42 @@ func (c *KVDestroyCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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("Destroy not supported on KV Version 1")
|
||||
return 1
|
||||
}
|
||||
path = addPrefixToKVPath(path, mountPath, "destroy")
|
||||
destroyPath := addPrefixToKVPath(partialPath, mountPath, "destroy")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
|
@ -114,9 +158,9 @@ func (c *KVDestroyCommand) Run(args []string) int {
|
|||
"versions": kvParseVersionsFlags(c.flagVersions),
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(path, data)
|
||||
secret, err := client.Logical().Write(destroyPath, data)
|
||||
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", destroyPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
@ -125,7 +169,7 @@ func (c *KVDestroyCommand) Run(args []string) int {
|
|||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", destroyPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -17,6 +18,7 @@ type KVGetCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagVersion int
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) Synopsis() string {
|
||||
|
@ -31,12 +33,18 @@ Usage: vault kv get [options] KEY
|
|||
key exists with that name, an error is returned. If a key exists with that
|
||||
name but has no data, nothing is returned.
|
||||
|
||||
$ vault kv get -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/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv get secret/foo
|
||||
|
||||
To view the given key name at a specific version in time, specify the "-version"
|
||||
flag:
|
||||
|
||||
$ vault kv get -version=1 secret/foo
|
||||
$ vault kv get -mount=secret -version=1 foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
|
@ -57,6 +65,17 @@ func (c *KVGetCommand) Flags() *FlagSets {
|
|||
Usage: `If passed, the value at the version number will be returned.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -92,35 +111,67 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
var versionParam map[string]string
|
||||
|
||||
var fullPath string
|
||||
// Add /data to v2 paths only
|
||||
if v2 {
|
||||
path = addPrefixToKVPath(path, mountPath, "data")
|
||||
fullPath = addPrefixToKVPath(partialPath, mountPath, "data")
|
||||
|
||||
if c.flagVersion > 0 {
|
||||
versionParam = map[string]string{
|
||||
"version": fmt.Sprintf("%d", c.flagVersion),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// v1
|
||||
if mountFlagSyntax {
|
||||
fullPath = path.Join(mountPath, partialPath)
|
||||
} else {
|
||||
fullPath = partialPath
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := kvReadRequest(client, path, versionParam)
|
||||
secret, err := kvReadRequest(client, fullPath, versionParam)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err))
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", fullPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if secret == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", fullPath))
|
||||
return 2
|
||||
}
|
||||
|
||||
|
@ -140,7 +191,7 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
}
|
||||
return PrintRawField(c.UI, data, c.flagField)
|
||||
} else {
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s", fullPath))
|
||||
return 2
|
||||
}
|
||||
} else {
|
||||
|
@ -159,7 +210,7 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if v2 {
|
||||
outputPath(c.UI, path, "Secret Path")
|
||||
outputPath(c.UI, fullPath, "Secret Path")
|
||||
}
|
||||
|
||||
if metadata, ok := secret.Data["metadata"]; ok && metadata != nil {
|
||||
|
|
|
@ -26,16 +26,22 @@ Usage: vault kv metadata <subcommand> [options] [args]
|
|||
|
||||
Create or update a metadata entry for a key:
|
||||
|
||||
$ vault kv metadata put -max-versions=5 -delete-version-after=3h25m19s secret/foo
|
||||
$ vault kv metadata put -mount=secret -max-versions=5 -delete-version-after=3h25m19s foo
|
||||
|
||||
Get the metadata for a key, this provides information about each existing
|
||||
version:
|
||||
|
||||
$ vault kv metadata get secret/foo
|
||||
$ vault kv metadata get -mount=secret foo
|
||||
|
||||
Delete a key and all existing versions:
|
||||
|
||||
$ vault kv metadata delete secret/foo
|
||||
$ vault kv metadata delete -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 get secret/foo
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
|
|
|
@ -15,6 +15,7 @@ var (
|
|||
|
||||
type KVMetadataDeleteCommand struct {
|
||||
*BaseCommand
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Synopsis() string {
|
||||
|
@ -27,6 +28,12 @@ Usage: vault kv metadata delete [options] PATH
|
|||
|
||||
Deletes all versions and metadata for the provided key.
|
||||
|
||||
$ vault kv metadata delete -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 delete secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
@ -37,7 +44,23 @@ Usage: vault kv metadata delete [options] PATH
|
|||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Flags() *FlagSets {
|
||||
return c.flagSet(FlagSetHTTP)
|
||||
set := c.flagSet(FlagSetHTTP)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
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 *KVMetadataDeleteCommand) AutocompleteArgs() complete.Predictor {
|
||||
|
@ -72,26 +95,51 @@ func (c *KVMetadataDeleteCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "metadata")
|
||||
if secret, err := client.Logical().Delete(path); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", path, err))
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata")
|
||||
if secret, err := client.Logical().Delete(fullPath); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", fullPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", path))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", fullPath))
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ var (
|
|||
|
||||
type KVMetadataGetCommand struct {
|
||||
*BaseCommand
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) Synopsis() string {
|
||||
|
@ -30,6 +31,12 @@ Usage: vault kv metadata get [options] KEY
|
|||
Retrieves the metadata from Vault's key-value store at the given key name. If no
|
||||
key exists with that name, an error is returned.
|
||||
|
||||
$ vault kv metadata get -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 get secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
@ -41,6 +48,20 @@ Usage: vault kv metadata get [options] KEY
|
|||
func (c *KVMetadataGetCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -76,25 +97,50 @@ func (c *KVMetadataGetCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "metadata")
|
||||
secret, err := client.Logical().Read(path)
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata")
|
||||
secret, err := client.Logical().Read(fullPath)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err))
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", fullPath, err))
|
||||
return 2
|
||||
}
|
||||
if secret == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", fullPath))
|
||||
return 2
|
||||
}
|
||||
|
||||
|
@ -109,7 +155,7 @@ func (c *KVMetadataGetCommand) Run(args []string) int {
|
|||
|
||||
versionsRaw, ok := secret.Data["versions"]
|
||||
if !ok || versionsRaw == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", fullPath))
|
||||
OutputSecret(c.UI, secret)
|
||||
return 2
|
||||
}
|
||||
|
@ -117,7 +163,7 @@ func (c *KVMetadataGetCommand) Run(args []string) int {
|
|||
|
||||
delete(secret.Data, "versions")
|
||||
|
||||
outputPath(c.UI, path, "Metadata Path")
|
||||
outputPath(c.UI, fullPath, "Metadata Path")
|
||||
|
||||
c.UI.Info(getHeaderForMap("Metadata", secret.Data))
|
||||
OutputSecret(c.UI, secret)
|
||||
|
|
|
@ -23,6 +23,7 @@ type KVMetadataPatchCommand struct {
|
|||
flagCASRequired BoolPtr
|
||||
flagDeleteVersionAfter time.Duration
|
||||
flagCustomMetadata map[string]string
|
||||
flagMount string
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
|
@ -39,23 +40,29 @@ Usage: vault metadata kv patch [options] 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 -max-versions=5 secret/foo
|
||||
$ vault kv metadata patch -mount=secret -max-versions=5 foo
|
||||
|
||||
Set delete-version-after on the key:
|
||||
|
||||
$ vault kv metadata patch -delete-version-after=3h25m19s secret/foo
|
||||
$ vault kv metadata patch -mount=secret -delete-version-after=3h25m19s foo
|
||||
|
||||
Require Check-and-Set for this key:
|
||||
|
||||
$ vault kv metadata patch -cas-required secret/foo
|
||||
$ vault kv metadata patch -mount=secret -cas-required foo
|
||||
|
||||
Set custom metadata on the key:
|
||||
|
||||
$ vault kv metadata patch -custom-metadata=foo=abc -custom-metadata=bar=123 secret/foo
|
||||
$ 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.
|
||||
|
||||
|
@ -103,6 +110,17 @@ func (c *KVMetadataPatchCommand) Flags() *FlagSets {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -139,19 +157,42 @@ func (c *KVMetadataPatchCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
// If true, we're working with "-mount=secret foo" syntax.
|
||||
// If false, we're using "secret/foo" syntax.
|
||||
mountFlagSyntax := (c.flagMount != "")
|
||||
|
||||
mountPath, v2, err := isKVv2(path, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "metadata")
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata")
|
||||
|
||||
data := map[string]interface{}{}
|
||||
|
||||
|
@ -171,9 +212,9 @@ func (c *KVMetadataPatchCommand) Run(args []string) int {
|
|||
data["custom_metadata"] = c.flagCustomMetadata
|
||||
}
|
||||
|
||||
secret, err := client.Logical().JSONMergePatch(context.Background(), path, data)
|
||||
secret, err := client.Logical().JSONMergePatch(context.Background(), fullPath, data)
|
||||
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", fullPath, err))
|
||||
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
|
@ -184,7 +225,7 @@ func (c *KVMetadataPatchCommand) Run(args []string) int {
|
|||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", fullPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ type KVMetadataPutCommand struct {
|
|||
flagCASRequired BoolPtr
|
||||
flagDeleteVersionAfter time.Duration
|
||||
flagCustomMetadata map[string]string
|
||||
flagMount string
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
|
@ -38,23 +39,29 @@ Usage: vault metadata kv put [options] KEY
|
|||
|
||||
Create a key in the key-value store with no data:
|
||||
|
||||
$ vault kv metadata put -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 put secret/foo
|
||||
|
||||
Set a max versions setting on the key:
|
||||
|
||||
$ vault kv metadata put -max-versions=5 secret/foo
|
||||
$ vault kv metadata put -mount=secret -max-versions=5 foo
|
||||
|
||||
Set delete-version-after on the key:
|
||||
|
||||
$ vault kv metadata put -delete-version-after=3h25m19s secret/foo
|
||||
$ vault kv metadata put -mount=secret -delete-version-after=3h25m19s foo
|
||||
|
||||
Require Check-and-Set for this key:
|
||||
|
||||
$ vault kv metadata put -cas-required secret/foo
|
||||
$ vault kv metadata put -mount=secret -cas-required foo
|
||||
|
||||
Set custom metadata on the key:
|
||||
|
||||
$ vault kv metadata put -custom-metadata=foo=abc -custom-metadata=bar=123 secret/foo
|
||||
$ vault kv metadata put -mount=secret -custom-metadata=foo=abc -custom-metadata=bar=123 foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
|
@ -102,6 +109,17 @@ func (c *KVMetadataPutCommand) Flags() *FlagSets {
|
|||
"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
|
||||
}
|
||||
|
||||
|
@ -138,18 +156,43 @@ func (c *KVMetadataPutCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "metadata")
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata")
|
||||
data := map[string]interface{}{}
|
||||
|
||||
if c.flagMaxVersions >= 0 {
|
||||
|
@ -168,9 +211,9 @@ func (c *KVMetadataPutCommand) Run(args []string) int {
|
|||
data["custom_metadata"] = c.flagCustomMetadata
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(path, data)
|
||||
secret, err := client.Logical().Write(fullPath, data)
|
||||
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", fullPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
@ -179,7 +222,7 @@ func (c *KVMetadataPutCommand) Run(args []string) int {
|
|||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", fullPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ type KVPatchCommand struct {
|
|||
|
||||
flagCAS int
|
||||
flagMethod string
|
||||
flagMount string
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
|
@ -35,25 +36,31 @@ Usage: vault kv patch [options] KEY [DATA]
|
|||
|
||||
*NOTE*: This is only supported for KV v2 engine mounts.
|
||||
|
||||
Writes the data to the given path in the key-value store. The data can be of
|
||||
Writes the data to the corresponding path in the key-value store. The data can be of
|
||||
any type.
|
||||
|
||||
$ vault kv patch -mount=secret foo bar=baz
|
||||
|
||||
The deprecated path-like syntax can also be used, but this should be avoided,
|
||||
as the fact that it is not actually the full API path to
|
||||
the secret (secret/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv patch secret/foo bar=baz
|
||||
|
||||
The data can also be consumed from a file on disk by prefixing with the "@"
|
||||
symbol. For example:
|
||||
|
||||
$ vault kv patch secret/foo @data.json
|
||||
$ vault kv patch -mount=secret foo @data.json
|
||||
|
||||
Or it can be read from stdin using the "-" symbol:
|
||||
|
||||
$ echo "abcd1234" | vault kv patch secret/foo bar=-
|
||||
$ echo "abcd1234" | vault kv patch -mount=secret foo bar=-
|
||||
|
||||
To perform a Check-And-Set operation, specify the -cas flag with the
|
||||
appropriate version number corresponding to the key you want to perform
|
||||
the CAS operation on:
|
||||
|
||||
$ vault kv patch -cas=1 secret/foo bar=baz
|
||||
$ vault kv patch -mount=secret -cas=1 foo bar=baz
|
||||
|
||||
By default, this operation will attempt an HTTP PATCH operation. If your
|
||||
policy does not allow that, it will fall back to a read/local update/write approach.
|
||||
|
@ -61,12 +68,12 @@ Usage: vault kv patch [options] KEY [DATA]
|
|||
with the -method flag. When -method=patch is specified, only an HTTP PATCH
|
||||
operation will be tried. If it fails, the entire command will fail.
|
||||
|
||||
$ vault kv patch -method=patch secret/foo bar=baz
|
||||
$ vault kv patch -mount=secret -method=patch foo bar=baz
|
||||
|
||||
When -method=rw is specified, only a read/local update/write approach will be tried.
|
||||
This was the default behavior previous to Vault 1.9.
|
||||
|
||||
$ vault kv patch -method=rw secret/foo bar=baz
|
||||
$ vault kv patch -mount=secret -method=rw foo bar=baz
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
|
@ -98,6 +105,17 @@ func (c *KVPatchCommand) Flags() *FlagSets {
|
|||
performed, then a local update, followed by a remote update.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -134,7 +152,6 @@ func (c *KVPatchCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
|
@ -148,10 +165,35 @@ func (c *KVPatchCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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 {
|
||||
|
@ -159,7 +201,7 @@ func (c *KVPatchCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "data")
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "data")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
|
@ -171,11 +213,11 @@ func (c *KVPatchCommand) Run(args []string) int {
|
|||
|
||||
switch c.flagMethod {
|
||||
case "rw":
|
||||
secret, code = c.readThenWrite(client, path, newData)
|
||||
secret, code = c.readThenWrite(client, fullPath, newData)
|
||||
case "patch":
|
||||
secret, code = c.mergePatch(client, path, newData, false)
|
||||
secret, code = c.mergePatch(client, fullPath, newData, false)
|
||||
case "":
|
||||
secret, code = c.mergePatch(client, path, newData, true)
|
||||
secret, code = c.mergePatch(client, fullPath, newData, true)
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Unsupported method provided to -method flag: %s", c.flagMethod))
|
||||
return 2
|
||||
|
@ -186,7 +228,7 @@ func (c *KVPatchCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if Format(c.UI) == "table" {
|
||||
outputPath(c.UI, path, "Secret Path")
|
||||
outputPath(c.UI, fullPath, "Secret Path")
|
||||
metadata := secret.Data
|
||||
c.UI.Info(getHeaderForMap("Metadata", metadata))
|
||||
return OutputData(c.UI, metadata)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -19,6 +20,7 @@ type KVPutCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagCAS int
|
||||
flagMount string
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
|
@ -33,22 +35,28 @@ Usage: vault kv put [options] KEY [DATA]
|
|||
Writes the data to the given path in the key-value store. The data can be of
|
||||
any type.
|
||||
|
||||
$ vault kv put -mount=secret foo bar=baz
|
||||
|
||||
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/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv put secret/foo bar=baz
|
||||
|
||||
The data can also be consumed from a file on disk by prefixing with the "@"
|
||||
symbol. For example:
|
||||
|
||||
$ vault kv put secret/foo @data.json
|
||||
$ vault kv put -mount=secret foo @data.json
|
||||
|
||||
Or it can be read from stdin using the "-" symbol:
|
||||
|
||||
$ echo "abcd1234" | vault kv put secret/foo bar=-
|
||||
$ echo "abcd1234" | vault kv put -mount=secret foo bar=-
|
||||
|
||||
To perform a Check-And-Set operation, specify the -cas flag with the
|
||||
appropriate version number corresponding to the key you want to perform
|
||||
the CAS operation on:
|
||||
|
||||
$ vault kv put -cas=1 secret/foo bar=baz
|
||||
$ vault kv put -mount=secret -cas=1 foo bar=baz
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
|
@ -73,6 +81,17 @@ func (c *KVPutCommand) Flags() *FlagSets {
|
|||
parameter.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -109,7 +128,6 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
|
@ -123,14 +141,41 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
// Add /data to v2 paths only
|
||||
var fullPath string
|
||||
if v2 {
|
||||
path = addPrefixToKVPath(path, mountPath, "data")
|
||||
fullPath = addPrefixToKVPath(partialPath, mountPath, "data")
|
||||
data = map[string]interface{}{
|
||||
"data": data,
|
||||
"options": map[string]interface{}{},
|
||||
|
@ -139,11 +184,18 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
if c.flagCAS > -1 {
|
||||
data["options"].(map[string]interface{})["cas"] = c.flagCAS
|
||||
}
|
||||
} else {
|
||||
// v1
|
||||
if mountFlagSyntax {
|
||||
fullPath = path.Join(mountPath, partialPath)
|
||||
} else {
|
||||
fullPath = partialPath
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(path, data)
|
||||
secret, err := client.Logical().Write(fullPath, data)
|
||||
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", fullPath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
@ -152,7 +204,7 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", fullPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@ -162,7 +214,7 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if Format(c.UI) == "table" {
|
||||
outputPath(c.UI, path, "Secret Path")
|
||||
outputPath(c.UI, fullPath, "Secret Path")
|
||||
metadata := secret.Data
|
||||
c.UI.Info(getHeaderForMap("Metadata", metadata))
|
||||
return OutputData(c.UI, metadata)
|
||||
|
|
|
@ -18,6 +18,7 @@ type KVRollbackCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagVersion int
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVRollbackCommand) Synopsis() string {
|
||||
|
@ -35,6 +36,12 @@ Usage: vault kv rollback [options] KEY
|
|||
is 5 and the rollback version is 2, the data from version 2 will become
|
||||
version 6.
|
||||
|
||||
$ vault kv rollback -mount=secret -version=2 foo
|
||||
|
||||
The deprecated path-like syntax can also be used, but this should be avoided,
|
||||
as the fact that it is not actually the full API path to
|
||||
the secret (secret/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv rollback -version=2 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
@ -55,6 +62,17 @@ func (c *KVRollbackCommand) Flags() *FlagSets {
|
|||
Usage: `Specifies the version number that should be made current again.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -96,7 +114,6 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
|
@ -104,10 +121,35 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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 {
|
||||
|
@ -115,7 +157,7 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "data")
|
||||
fullPath := addPrefixToKVPath(partialPath, mountPath, "data")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
|
@ -124,31 +166,31 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
// First, do a read to get the current version for check-and-set
|
||||
var meta map[string]interface{}
|
||||
{
|
||||
secret, err := kvReadRequest(client, path, nil)
|
||||
secret, err := kvReadRequest(client, fullPath, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error doing pre-read at %s: %s", path, err))
|
||||
c.UI.Error(fmt.Sprintf("Error doing pre-read at %s: %s", fullPath, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Make sure a value already exists
|
||||
if secret == nil || secret.Data == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", fullPath))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Verify metadata found
|
||||
rawMeta, ok := secret.Data["metadata"]
|
||||
if !ok || rawMeta == nil {
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
meta, ok = rawMeta.(map[string]interface{})
|
||||
if !ok {
|
||||
c.UI.Error(fmt.Sprintf("Metadata found at %s is not the expected type (JSON object)", path))
|
||||
c.UI.Error(fmt.Sprintf("Metadata found at %s is not the expected type (JSON object)", fullPath))
|
||||
return 2
|
||||
}
|
||||
if meta == nil {
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
@ -163,31 +205,31 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
// Now run it again and read the version we want to roll back to
|
||||
var data map[string]interface{}
|
||||
{
|
||||
secret, err := kvReadRequest(client, path, versionParam)
|
||||
secret, err := kvReadRequest(client, fullPath, versionParam)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error doing pre-read at %s: %s", path, err))
|
||||
c.UI.Error(fmt.Sprintf("Error doing pre-read at %s: %s", fullPath, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Make sure a value already exists
|
||||
if secret == nil || secret.Data == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", fullPath))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Verify metadata found
|
||||
rawMeta, ok := secret.Data["metadata"]
|
||||
if !ok || rawMeta == nil {
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
meta, ok := rawMeta.(map[string]interface{})
|
||||
if !ok {
|
||||
c.UI.Error(fmt.Sprintf("Metadata found at %s is not the expected type (JSON object)", path))
|
||||
c.UI.Error(fmt.Sprintf("Metadata found at %s is not the expected type (JSON object)", fullPath))
|
||||
return 2
|
||||
}
|
||||
if meta == nil {
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No metadata found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
|
||||
|
@ -205,34 +247,34 @@ func (c *KVRollbackCommand) Run(args []string) int {
|
|||
// Verify old data found
|
||||
rawData, ok := secret.Data["data"]
|
||||
if !ok || rawData == nil {
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
data, ok = rawData.(map[string]interface{})
|
||||
if !ok {
|
||||
c.UI.Error(fmt.Sprintf("Data found at %s is not the expected type (JSON object)", path))
|
||||
c.UI.Error(fmt.Sprintf("Data found at %s is not the expected type (JSON object)", fullPath))
|
||||
return 2
|
||||
}
|
||||
if data == nil {
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s; rollback only works on existing data", path))
|
||||
c.UI.Error(fmt.Sprintf("No data found at %s; rollback only works on existing data", fullPath))
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(path, map[string]interface{}{
|
||||
secret, err := client.Logical().Write(fullPath, map[string]interface{}{
|
||||
"data": data,
|
||||
"options": map[string]interface{}{
|
||||
"cas": casVersion,
|
||||
},
|
||||
})
|
||||
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", fullPath, err))
|
||||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", fullPath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -123,6 +123,12 @@ func TestKVPutCommand(t *testing.T) {
|
|||
[]string{"Success!"},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v1_mount_flag_syntax",
|
||||
[]string{"-mount", "secret", "write/foo", "foo=bar"},
|
||||
[]string{"Success!"},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v2_single_value",
|
||||
[]string{"kv/write/foo", "foo=bar"},
|
||||
|
@ -141,6 +147,12 @@ func TestKVPutCommand(t *testing.T) {
|
|||
[]string{"== Secret Path ==", "kv/data/write/foo"},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v2_mount_flag_syntax",
|
||||
[]string{"-mount", "kv", "write/foo", "foo=bar"},
|
||||
v2ExpectedFields,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v2_single_value_backslash",
|
||||
[]string{"kv/write/foo", "foo=\\"},
|
||||
|
@ -428,20 +440,30 @@ func TestKVGetCommand(t *testing.T) {
|
|||
[]string{"bar"},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v1_mount_flag_syntax",
|
||||
[]string{"-mount", "secret", "read/foo"},
|
||||
[]string{"foo"},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v2_field",
|
||||
[]string{"-field", "foo", "kv/read/foo"},
|
||||
[]string{"bar"},
|
||||
0,
|
||||
},
|
||||
|
||||
{
|
||||
"v2_mount_flag_syntax",
|
||||
[]string{"-mount", "kv", "read/foo"},
|
||||
append(baseV2ExpectedFields, "foo"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
"v2_not_found",
|
||||
[]string{"kv/nope/not/once/never"},
|
||||
[]string{"No value found at kv/data/nope/not/once/never"},
|
||||
2,
|
||||
},
|
||||
|
||||
{
|
||||
"v2_read",
|
||||
[]string{"kv/read/foo"},
|
||||
|
@ -573,6 +595,12 @@ func TestKVMetadataGetCommand(t *testing.T) {
|
|||
append(expectedTopLevelFields, expectedVersionFields[:]...),
|
||||
0,
|
||||
},
|
||||
{
|
||||
"mount_flag_syntax",
|
||||
[]string{"-mount", "kv", "foo"},
|
||||
expectedTopLevelFields,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("validations", func(t *testing.T) {
|
||||
|
@ -667,6 +695,12 @@ func TestKVPatchCommand_ArgValidation(t *testing.T) {
|
|||
"Failed to parse K=V data",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"mount_flag_syntax",
|
||||
[]string{"-mount", "kv"},
|
||||
"Not enough arguments",
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -708,7 +742,7 @@ func expectedPatchFields() []string {
|
|||
}
|
||||
}
|
||||
|
||||
func TestKvPatchCommand_StdinFull(t *testing.T) {
|
||||
func TestKVPatchCommand_StdinFull(t *testing.T) {
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
|
@ -726,52 +760,56 @@ func TestKvPatchCommand_StdinFull(t *testing.T) {
|
|||
t.Fatalf("write failed, err: %#v\n", err)
|
||||
}
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte(`{"foo":"bar"}`))
|
||||
stdinW.Close()
|
||||
}()
|
||||
cases := [][]string{
|
||||
{"kv/patch/foo", "-"},
|
||||
{"-mount", "kv", "patch/foo", "-"},
|
||||
}
|
||||
for i, args := range cases {
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte(fmt.Sprintf(`{"foo%d":"bar%d"}`, i, i)))
|
||||
stdinW.Close()
|
||||
}()
|
||||
code, combined := kvPatchWithRetry(t, client, args, stdinR)
|
||||
|
||||
args := []string{"kv/patch/foo", "-"}
|
||||
code, combined := kvPatchWithRetry(t, client, args, stdinR)
|
||||
|
||||
for _, str := range expectedPatchFields() {
|
||||
if !strings.Contains(combined, str) {
|
||||
t.Errorf("expected %q to contain %q", combined, str)
|
||||
for _, str := range expectedPatchFields() {
|
||||
if !strings.Contains(combined, str) {
|
||||
t.Errorf("expected %q to contain %q", combined, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if code != 0 {
|
||||
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v\n", code, args)
|
||||
}
|
||||
if code != 0 {
|
||||
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v\n", code, args)
|
||||
}
|
||||
|
||||
secret, err := client.Logical().ReadWithContext(context.Background(), "kv/data/patch/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("read failed, err: %#v\n", err)
|
||||
}
|
||||
secret, err := client.Logical().ReadWithContext(context.Background(), "kv/data/patch/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("read failed, err: %#v\n", err)
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("expected secret to have data")
|
||||
}
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("expected secret to have data")
|
||||
}
|
||||
|
||||
secretDataRaw, ok := secret.Data["data"]
|
||||
secretDataRaw, ok := secret.Data["data"]
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("expected secret to have nested data key, data: %#v", secret.Data)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected secret to have nested data key, data: %#v", secret.Data)
|
||||
}
|
||||
|
||||
secretData := secretDataRaw.(map[string]interface{})
|
||||
foo, ok := secretData["foo"].(string)
|
||||
if !ok {
|
||||
t.Fatal("expected foo to be a string but it wasn't")
|
||||
}
|
||||
secretData := secretDataRaw.(map[string]interface{})
|
||||
foo, ok := secretData[fmt.Sprintf("foo%d", i)].(string)
|
||||
if !ok {
|
||||
t.Fatal("expected foo to be a string but it wasn't")
|
||||
}
|
||||
|
||||
if exp, act := "bar", foo; exp != act {
|
||||
t.Fatalf("expected %q to be %q, data: %#v\n", act, exp, secret.Data)
|
||||
if exp, act := fmt.Sprintf("bar%d", i), foo; exp != act {
|
||||
t.Fatalf("expected %q to be %q, data: %#v\n", act, exp, secret.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKvPatchCommand_StdinValue(t *testing.T) {
|
||||
func TestKVPatchCommand_StdinValue(t *testing.T) {
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
|
@ -789,43 +827,49 @@ func TestKvPatchCommand_StdinValue(t *testing.T) {
|
|||
t.Fatalf("write failed, err: %#v\n", err)
|
||||
}
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte("bar"))
|
||||
stdinW.Close()
|
||||
}()
|
||||
|
||||
args := []string{"kv/patch/foo", "foo=-"}
|
||||
code, combined := kvPatchWithRetry(t, client, args, stdinR)
|
||||
if code != 0 {
|
||||
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v\n", code, args)
|
||||
cases := [][]string{
|
||||
{"kv/patch/foo", "foo=-"},
|
||||
{"-mount", "kv", "patch/foo", "foo=-"},
|
||||
}
|
||||
|
||||
for _, str := range expectedPatchFields() {
|
||||
if !strings.Contains(combined, str) {
|
||||
t.Errorf("expected %q to contain %q", combined, str)
|
||||
for i, args := range cases {
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte(fmt.Sprintf("bar%d", i)))
|
||||
stdinW.Close()
|
||||
}()
|
||||
|
||||
code, combined := kvPatchWithRetry(t, client, args, stdinR)
|
||||
if code != 0 {
|
||||
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v\n", code, args)
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := client.Logical().ReadWithContext(context.Background(), "kv/data/patch/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("read failed, err: %#v\n", err)
|
||||
}
|
||||
for _, str := range expectedPatchFields() {
|
||||
if !strings.Contains(combined, str) {
|
||||
t.Errorf("expected %q to contain %q", combined, str)
|
||||
}
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("expected secret to have data")
|
||||
}
|
||||
secret, err := client.Logical().ReadWithContext(context.Background(), "kv/data/patch/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("read failed, err: %#v\n", err)
|
||||
}
|
||||
|
||||
secretDataRaw, ok := secret.Data["data"]
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("expected secret to have data")
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("expected secret to have nested data key, data: %#v\n", secret.Data)
|
||||
}
|
||||
secretDataRaw, ok := secret.Data["data"]
|
||||
|
||||
secretData := secretDataRaw.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected secret to have nested data key, data: %#v\n", secret.Data)
|
||||
}
|
||||
|
||||
if exp, act := "bar", secretData["foo"].(string); exp != act {
|
||||
t.Fatalf("expected %q to be %q, data: %#v\n", act, exp, secret.Data)
|
||||
secretData := secretDataRaw.(map[string]interface{})
|
||||
|
||||
if exp, act := fmt.Sprintf("bar%d", i), secretData["foo"].(string); exp != act {
|
||||
t.Fatalf("expected %q to be %q, data: %#v\n", act, exp, secret.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -839,16 +883,22 @@ func TestKVPatchCommand_RWMethodNotExists(t *testing.T) {
|
|||
t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err)
|
||||
}
|
||||
|
||||
args := []string{"-method", "rw", "kv/patch/foo", "foo=a"}
|
||||
code, combined := kvPatchWithRetry(t, client, args, nil)
|
||||
|
||||
if code != 2 {
|
||||
t.Fatalf("expected code to be 2 but was %d for patch cmd with args %#v\n", code, args)
|
||||
cases := [][]string{
|
||||
{"-method", "rw", "kv/patch/foo", "foo=a"},
|
||||
{"-method", "rw", "-mount", "kv", "patch/foo", "foo=a"},
|
||||
}
|
||||
|
||||
expectedOutputSubstr := "No value found"
|
||||
if !strings.Contains(combined, expectedOutputSubstr) {
|
||||
t.Fatalf("expected output %q to contain %q for patch cmd with args %#v\n", combined, expectedOutputSubstr, args)
|
||||
for _, args := range cases {
|
||||
code, combined := kvPatchWithRetry(t, client, args, nil)
|
||||
|
||||
if code != 2 {
|
||||
t.Fatalf("expected code to be 2 but was %d for patch cmd with args %#v\n", code, args)
|
||||
}
|
||||
|
||||
expectedOutputSubstr := "No value found"
|
||||
if !strings.Contains(combined, expectedOutputSubstr) {
|
||||
t.Fatalf("expected output %q to contain %q for patch cmd with args %#v\n", combined, expectedOutputSubstr, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,6 +979,13 @@ func TestKVPatchCommand_CAS(t *testing.T) {
|
|||
[]string{"check-and-set parameter did not match the current version"},
|
||||
2,
|
||||
},
|
||||
{
|
||||
"mount_flag_syntax",
|
||||
[]string{"-mount", "kv", "-cas", "1", "foo", "bar=quux"},
|
||||
"quux",
|
||||
expectedPatchFields(),
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
@ -17,6 +17,7 @@ type KVUndeleteCommand struct {
|
|||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
flagMount string
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) Synopsis() string {
|
||||
|
@ -32,6 +33,12 @@ Usage: vault kv undelete [options] KEY
|
|||
|
||||
To undelete version 3 of key "foo":
|
||||
|
||||
$ vault kv undelete -mount=secret -versions=3 foo
|
||||
|
||||
The deprecated path-like syntax can also be used, but this should be avoided,
|
||||
as the fact that it is not actually the full API path to
|
||||
the secret (secret/data/foo) can cause confusion:
|
||||
|
||||
$ vault kv undelete -versions=3 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
@ -53,6 +60,17 @@ func (c *KVUndeleteCommand) Flags() *FlagSets {
|
|||
Usage: `Specifies the version numbers to undelete.`,
|
||||
})
|
||||
|
||||
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 /data/ automatically appended between KV
|
||||
v2 secrets.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -93,25 +111,50 @@ func (c *KVUndeleteCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
mountPath, v2, err := isKVv2(path, 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 = sanitizePath(c.flagMount)
|
||||
_, v2, err = isKVv2(mountPath, client)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
} 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("Undelete not supported on KV Version 1")
|
||||
return 1
|
||||
}
|
||||
|
||||
path = addPrefixToKVPath(path, mountPath, "undelete")
|
||||
undeletePath := addPrefixToKVPath(partialPath, mountPath, "undelete")
|
||||
data := map[string]interface{}{
|
||||
"versions": kvParseVersionsFlags(c.flagVersions),
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(path, data)
|
||||
secret, err := client.Logical().Write(undeletePath, data)
|
||||
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", undeletePath, err))
|
||||
if secret != nil {
|
||||
OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
@ -120,7 +163,7 @@ func (c *KVUndeleteCommand) Run(args []string) int {
|
|||
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))
|
||||
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", undeletePath))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -165,12 +165,17 @@ After the secrets engine is configured and a user/machine has a Vault token with
|
|||
the proper permission, it can generate credentials. The `kv` secrets engine
|
||||
allows for writing keys with arbitrary values.
|
||||
|
||||
The path-like KV-v1 syntax for referencing a secret (`secret/foo`) can still
|
||||
be used in KV-v2, but we recommend using the `-mount=secret` flag syntax to
|
||||
avoid mistaking it for the actual path to the secret (`secret/data/foo` is the
|
||||
real path).
|
||||
|
||||
### Writing/Reading arbitrary data
|
||||
|
||||
1. Write arbitrary data:
|
||||
|
||||
```shell-session
|
||||
$ vault kv put secret/my-secret foo=a bar=b
|
||||
$ vault kv put -mount=secret my-secret foo=a bar=b
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:20:22.985303Z
|
||||
|
@ -183,7 +188,7 @@ allows for writing keys with arbitrary values.
|
|||
1. Read arbitrary data:
|
||||
|
||||
```shell-session
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -208,7 +213,7 @@ allows for writing keys with arbitrary values.
|
|||
cas parameter.
|
||||
|
||||
```shell-session
|
||||
$ vault kv put -cas=1 secret/my-secret foo=aa bar=bb
|
||||
$ vault kv put -mount=secret -cas=1 my-secret foo=aa bar=bb
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:22:23.369372Z
|
||||
|
@ -221,7 +226,7 @@ allows for writing keys with arbitrary values.
|
|||
1. Reading now will return the newest version of the data:
|
||||
|
||||
```shell-session
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -251,7 +256,7 @@ allows for writing keys with arbitrary values.
|
|||
the read to perform a check-and-set operation in the subsequent write.
|
||||
|
||||
```shell-session
|
||||
$ vault kv patch -cas=2 secret/my-secret bar=bbb
|
||||
$ vault kv patch -mount=secret -cas=2 my-secret bar=bbb
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:23:49.199802Z
|
||||
|
@ -268,7 +273,7 @@ allows for writing keys with arbitrary values.
|
|||
Perform a patch using the `patch` method:
|
||||
|
||||
```shell-session
|
||||
$ vault kv patch -method=patch -cas=2 secret/my-secret bar=bbb
|
||||
$ vault kv patch -mount=secret -method=patch -cas=2 my-secret bar=bbb
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:23:49.199802Z
|
||||
|
@ -281,7 +286,7 @@ allows for writing keys with arbitrary values.
|
|||
Perform a patch using the read-then-write method:
|
||||
|
||||
```shell-session
|
||||
$ vault kv patch -method=rw secret/my-secret bar=bbb
|
||||
$ vault kv patch -mount=secret -method=rw my-secret bar=bbb
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:23:49.199802Z
|
||||
|
@ -291,11 +296,11 @@ allows for writing keys with arbitrary values.
|
|||
version 3
|
||||
```
|
||||
|
||||
1. Reading after a patch will return the newest version of the data in which
|
||||
2. Reading after a patch will return the newest version of the data in which
|
||||
only the specified fields were updated:
|
||||
|
||||
```shell-session
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -312,10 +317,10 @@ allows for writing keys with arbitrary values.
|
|||
bar bbb
|
||||
```
|
||||
|
||||
1. Previous versions can be accessed with the `-version` flag:
|
||||
3. Previous versions can be accessed with the `-version` flag:
|
||||
|
||||
```shell-session
|
||||
$ vault kv get -version=1 secret/my-secret
|
||||
$ vault kv get -mount=secret -version=1 my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -352,17 +357,17 @@ See the commands below for more information:
|
|||
takes a `-versions` flag to delete prior versions:
|
||||
|
||||
```shell-session
|
||||
$ vault kv delete secret/my-secret
|
||||
Success! Data deleted (if it existed) at: secret/my-secret
|
||||
$ vault kv delete -mount=secret my-secret
|
||||
Success! Data deleted (if it existed) at: secret/data/my-secret
|
||||
```
|
||||
|
||||
1. Versions can be undeleted:
|
||||
|
||||
```shell-session
|
||||
$ vault kv undelete -versions=2 secret/my-secret
|
||||
$ vault kv undelete -mount=secret -versions=2 my-secret
|
||||
Success! Data written to: secret/undelete/my-secret
|
||||
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -381,7 +386,7 @@ See the commands below for more information:
|
|||
1. Destroying a version permanently deletes the underlying data:
|
||||
|
||||
```shell-session
|
||||
$ vault kv destroy -versions=2 secret/my-secret
|
||||
$ vault kv destroy -mount=secret -versions=2 my-secret
|
||||
Success! Data written to: secret/destroy/my-secret
|
||||
```
|
||||
|
||||
|
@ -396,7 +401,7 @@ See the commands below for more information:
|
|||
1. All metadata and versions for a key can be viewed:
|
||||
|
||||
```shell-session
|
||||
$ vault kv metadata get secret/my-secret
|
||||
$ vault kv metadata get -mount=secret my-secret
|
||||
========== Metadata ==========
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -427,7 +432,7 @@ See the commands below for more information:
|
|||
1. The metadata settings for a key can be configured:
|
||||
|
||||
```shell-session
|
||||
$ vault kv metadata put -max-versions 2 -delete-version-after="3h25m19s" secret/my-secret
|
||||
$ vault kv metadata put -mount=secret -max-versions 2 -delete-version-after="3h25m19s" my-secret
|
||||
Success! Data written to: secret/metadata/my-secret
|
||||
```
|
||||
|
||||
|
@ -435,7 +440,7 @@ See the commands below for more information:
|
|||
changes will be applied on next write:
|
||||
|
||||
```shell-session
|
||||
$ vault kv put secret/my-secret my-value=newer-s3cr3t
|
||||
$ vault kv put -mount=secret my-secret my-value=newer-s3cr3t
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2019-06-19T17:31:16.662563Z
|
||||
|
@ -449,7 +454,7 @@ See the commands below for more information:
|
|||
are cleaned up:
|
||||
|
||||
```shell-session
|
||||
$ vault kv metadata get secret/my-secret
|
||||
$ vault kv metadata get -mount=secret my-secret
|
||||
========== Metadata ==========
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -484,10 +489,10 @@ See the commands below for more information:
|
|||
The `vault kv metadata put` command can be used to fully overwrite the value of `custom_metadata`:
|
||||
|
||||
```shell-session
|
||||
$ vault kv metadata put -custom-metadata=foo=abc -custom-metadata=bar=123 secret/ my-secret
|
||||
$ vault kv metadata put -mount=secret -custom-metadata=foo=abc -custom-metadata=bar=123 my-secret
|
||||
Success! Data written to: secret/metadata/my-secret
|
||||
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -508,10 +513,10 @@ See the commands below for more information:
|
|||
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
|
||||
$ vault kv metadata patch -mount=secret -custom-metadata=foo=def my-secret
|
||||
Success! Data written to: secret/metadata/my-secret
|
||||
|
||||
$ vault kv get secret/my-secret
|
||||
$ vault kv get -mount=secret my-secret
|
||||
====== Metadata ======
|
||||
Key Value
|
||||
--- -----
|
||||
|
@ -531,7 +536,7 @@ See the commands below for more information:
|
|||
1. Permanently delete all metadata and versions for a key:
|
||||
|
||||
```shell-session
|
||||
$ vault kv metadata delete secret/my-secret
|
||||
$ vault kv metadata delete -mount=secret my-secret
|
||||
Success! Data deleted (if it existed) at: secret/metadata/my-secret
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in New Issue