command/kv: Add a "kv" subcommand for using the key-value store (#4168)
* Add more cli subcommands * Add metadata commands * Add more subcommands * Update cli * Move archive commands to delete * Add helpers for making http calls to the kv backend * rename cli header * Format the various maps from kv * Add list command * Update help text * Add a command to enable versioning on a backend * Rename enable-versions command * Some review feedback * Fix listing of top level keys * Fix issue when metadata is nil * Add test for lising top level keys * Fix some typos * Add a note about deleting all versions
This commit is contained in:
parent
695eae6ede
commit
5c84c36915
|
@ -675,6 +675,90 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
|||
},
|
||||
}, nil
|
||||
},
|
||||
"kv": func() (cli.Command, error) {
|
||||
return &KVCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv put": func() (cli.Command, error) {
|
||||
return &KVPutCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv get": func() (cli.Command, error) {
|
||||
return &KVGetCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv delete": func() (cli.Command, error) {
|
||||
return &KVDeleteCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv list": func() (cli.Command, error) {
|
||||
return &KVListCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv destroy": func() (cli.Command, error) {
|
||||
return &KVDestroyCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv undelete": func() (cli.Command, error) {
|
||||
return &KVUndeleteCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv enable-versioning": func() (cli.Command, error) {
|
||||
return &KVEnableVersioningCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv metadata": func() (cli.Command, error) {
|
||||
return &KVMetadataCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv metadata put": func() (cli.Command, error) {
|
||||
return &KVMetadataPutCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv metadata get": func() (cli.Command, error) {
|
||||
return &KVMetadataGetCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
"kv metadata delete": func() (cli.Command, error) {
|
||||
return &KVMetadataDeleteCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Deprecated commands
|
||||
|
|
|
@ -134,6 +134,9 @@ func (t TableFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{})
|
|||
return t.OutputList(ui, secret, data)
|
||||
case []string:
|
||||
return t.OutputList(ui, nil, data)
|
||||
case map[string]interface{}:
|
||||
t.OutputMap(ui, data.(map[string]interface{}))
|
||||
return nil
|
||||
default:
|
||||
return errors.New("Cannot use the table formatter for this type")
|
||||
}
|
||||
|
@ -261,6 +264,34 @@ func (t TableFormatter) OutputSecret(ui cli.Ui, secret *api.Secret) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t TableFormatter) OutputMap(ui cli.Ui, data map[string]interface{}) {
|
||||
out := make([]string, 0, len(data)+1)
|
||||
if len(data) > 0 {
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
out = append(out, fmt.Sprintf("%s %s %v", k, hopeDelim, data[k]))
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far and still don't have any data, there's nothing to print,
|
||||
// sorry.
|
||||
if len(out) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Prepend the header
|
||||
out = append([]string{"Key" + hopeDelim + "Value"}, out...)
|
||||
|
||||
ui.Output(tableOutput(out, &columnize.Config{
|
||||
Delim: hopeDelim,
|
||||
}))
|
||||
}
|
||||
|
||||
// OutputSealStatus will print *api.SealStatusResponse in the CLI according to the format provided
|
||||
func OutputSealStatus(ui cli.Ui, client *api.Client, status *api.SealStatusResponse) int {
|
||||
switch Format(ui) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVCommand)(nil)
|
||||
|
||||
type KVCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVCommand) Synopsis() string {
|
||||
return "Interact with Vault's Key-Value storage"
|
||||
}
|
||||
|
||||
func (c *KVCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv <subcommand> [options] [args]
|
||||
|
||||
This command has subcommands for interacting with Vault's key-value
|
||||
store. Here are some simple examples, and more detailed examples are
|
||||
available in the subcommands or the documentation.
|
||||
|
||||
Create or update the key named "foo" in the "secret" mount with the value
|
||||
"bar=baz":
|
||||
|
||||
$ vault kv put secret/foo bar=baz
|
||||
|
||||
Read this value back:
|
||||
|
||||
$ vault kv get secret/foo
|
||||
|
||||
Get metadata for the key:
|
||||
|
||||
$ vault kv metadata get secret/foo
|
||||
|
||||
Get a specific version of the key:
|
||||
|
||||
$ vault kv get -version=1 secret/foo
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVDeleteCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVDeleteCommand)(nil)
|
||||
|
||||
type KVDeleteCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) Synopsis() string {
|
||||
return "Deletes versions in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv delete [options] PATH
|
||||
|
||||
Deletes the data for the provided version and path in the key-value store. The
|
||||
versioned data will not be fully removed, but marked as deleted and will no
|
||||
longer be returned in normal get requests.
|
||||
|
||||
To delete the latest version of the key "foo":
|
||||
|
||||
$ vault kv delete secret/foo
|
||||
|
||||
To delete version 3 of key foo:
|
||||
|
||||
$ vault kv delete -versions=3 secret/foo
|
||||
|
||||
To delete all versions and metadata, see the "vault kv metadata" subcommand.
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP)
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "versions",
|
||||
Target: &c.flagVersions,
|
||||
Default: nil,
|
||||
Usage: `Specifies the version numbers to delete.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) AutocompleteArgs() complete.Predictor {
|
||||
return c.PredictVaultFiles()
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
var err error
|
||||
if len(c.flagVersions) > 0 {
|
||||
err = c.deleteVersions(path, c.flagVersions)
|
||||
} else {
|
||||
err = c.deleteLatest(path)
|
||||
}
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", path, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", path))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) deleteLatest(path string) error {
|
||||
var err error
|
||||
path, err = addPrefixToVKVPath(path, "data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = kvDeleteRequest(client, path)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *KVDeleteCommand) deleteVersions(path string, versions []string) error {
|
||||
var err error
|
||||
path, err = addPrefixToVKVPath(path, "delete")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"versions": c.flagVersions,
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = kvWriteRequest(client, path, data)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVDestroyCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVDestroyCommand)(nil)
|
||||
|
||||
type KVDestroyCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) Synopsis() string {
|
||||
return "Permanently removes one or more versions in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv destroy [options] KEY
|
||||
|
||||
Permanently removes the specified versions' data from the key-value store. If
|
||||
no key exists at the path, no action is taken.
|
||||
|
||||
To destroy version 3 of key foo:
|
||||
|
||||
$ vault kv destroy -versions=3 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "versions",
|
||||
Target: &c.flagVersions,
|
||||
Default: nil,
|
||||
Usage: `Specifies the version numbers to destroy.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVDestroyCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(c.flagVersions) == 0 {
|
||||
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])
|
||||
path, err = addPrefixToVKVPath(path, "destroy")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"versions": c.flagVersions,
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvWriteRequest(client, path, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, 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))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVEnableVersioningCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVEnableVersioningCommand)(nil)
|
||||
|
||||
type KVEnableVersioningCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) Synopsis() string {
|
||||
return "Turns on versioning for a KV store"
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv enable-versions [options] KEY
|
||||
|
||||
This command turns on versioning for the backend at the provided path.
|
||||
|
||||
$ vault kv enable-versions secret
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVEnableVersioningCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
// Append a trailing slash to indicate it's a path in output
|
||||
mountPath := ensureTrailingSlash(sanitizePath(args[0]))
|
||||
|
||||
if err := client.Sys().TuneMount(mountPath, api.MountConfigInput{
|
||||
Options: map[string]string{
|
||||
"versioned": "true",
|
||||
},
|
||||
}); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error tuning secrets engine %s: %s", mountPath, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Output(fmt.Sprintf("Success! Tuned the secrets engine at: %s", mountPath))
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVGetCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVGetCommand)(nil)
|
||||
|
||||
type KVGetCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagVersion int
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) Synopsis() string {
|
||||
return "Retrieves data from the KV store"
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv get [options] KEY
|
||||
|
||||
Retrieves the value from Vault's key-value store at the given key name. If no
|
||||
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 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
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.IntVar(&IntVar{
|
||||
Name: "version",
|
||||
Target: &c.flagVersion,
|
||||
Default: 0,
|
||||
Usage: `If passed, the value at the version number will be returned.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVGetCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "data")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
var versionParam map[string]string
|
||||
if c.flagVersion > 0 {
|
||||
versionParam = map[string]string{
|
||||
"version": fmt.Sprintf("%d", c.flagVersion),
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := kvReadRequest(client, path, versionParam)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err))
|
||||
return 2
|
||||
}
|
||||
if secret == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
return 2
|
||||
}
|
||||
|
||||
if c.flagField != "" {
|
||||
return PrintRawField(c.UI, secret, c.flagField)
|
||||
}
|
||||
|
||||
// If we have wrap info print the secret normally.
|
||||
if secret.WrapInfo != nil || c.flagFormat != "table" {
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
||||
if metadata, ok := secret.Data["metadata"]; ok && metadata != nil {
|
||||
c.UI.Info(getHeaderForMap("Metadata", metadata.(map[string]interface{})))
|
||||
OutputData(c.UI, metadata)
|
||||
c.UI.Info("")
|
||||
}
|
||||
if data, ok := secret.Data["data"]; ok && data != nil {
|
||||
c.UI.Info(getHeaderForMap("Data", data.(map[string]interface{})))
|
||||
OutputData(c.UI, data)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
)
|
||||
|
||||
func kvReadRequest(client *api.Client, path string, params map[string]string) (*api.Secret, error) {
|
||||
r := client.NewRequest("GET", "/v1/"+path)
|
||||
if r.Headers == nil {
|
||||
r.Headers = http.Header{}
|
||||
}
|
||||
r.Headers.Add(consts.VaultKVCLIClientHeader, "v1")
|
||||
|
||||
for k, v := range params {
|
||||
r.Params.Set(k, v)
|
||||
}
|
||||
resp, err := client.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
func kvListRequest(client *api.Client, path string) (*api.Secret, error) {
|
||||
r := client.NewRequest("LIST", "/v1/"+path)
|
||||
if r.Headers == nil {
|
||||
r.Headers = http.Header{}
|
||||
}
|
||||
r.Headers.Add(consts.VaultKVCLIClientHeader, "v1")
|
||||
|
||||
// Set this for broader compatibility, but we use LIST above to be able to
|
||||
// handle the wrapping lookup function
|
||||
r.Method = "GET"
|
||||
r.Params.Set("list", "true")
|
||||
resp, err := client.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
func kvWriteRequest(client *api.Client, path string, data map[string]interface{}) (*api.Secret, error) {
|
||||
r := client.NewRequest("PUT", "/v1/"+path)
|
||||
if r.Headers == nil {
|
||||
r.Headers = http.Header{}
|
||||
}
|
||||
r.Headers.Add(consts.VaultKVCLIClientHeader, "v1")
|
||||
if err := r.SetJSONBody(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return api.ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func kvDeleteRequest(client *api.Client, path string) (*api.Secret, error) {
|
||||
r := client.NewRequest("DELETE", "/v1/"+path)
|
||||
if r.Headers == nil {
|
||||
r.Headers = http.Header{}
|
||||
}
|
||||
r.Headers.Add(consts.VaultKVCLIClientHeader, "v1")
|
||||
resp, err := client.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return api.ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func addPrefixToVKVPath(p, apiPrefix string) (string, error) {
|
||||
parts := strings.SplitN(p, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", errors.New("Invalid path")
|
||||
}
|
||||
|
||||
return path.Join(parts[0], apiPrefix, parts[1]), nil
|
||||
}
|
||||
|
||||
func getHeaderForMap(header string, data map[string]interface{}) string {
|
||||
maxKey := 0
|
||||
for k := range data {
|
||||
if len(k) > maxKey {
|
||||
maxKey = len(k)
|
||||
}
|
||||
}
|
||||
|
||||
// 4 for the column spaces and 5 for the len("value")
|
||||
totalLen := maxKey + 4 + 5
|
||||
|
||||
equalSigns := totalLen - (len(header) + 2)
|
||||
|
||||
// If we have zero or fewer equal signs bump it back up to two on either
|
||||
// side of the header.
|
||||
if equalSigns <= 0 {
|
||||
equalSigns = 4
|
||||
}
|
||||
|
||||
// If the number of equal signs is not divisible by two add a sign.
|
||||
if equalSigns%2 != 0 {
|
||||
equalSigns = equalSigns + 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s %s", strings.Repeat("=", equalSigns/2), header, strings.Repeat("=", equalSigns/2))
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVListCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVListCommand)(nil)
|
||||
|
||||
type KVListCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVListCommand) Synopsis() string {
|
||||
return "List data or secrets"
|
||||
}
|
||||
|
||||
func (c *KVListCommand) Help() string {
|
||||
helpText := `
|
||||
|
||||
Usage: vault kv list [options] PATH
|
||||
|
||||
Lists data from Vault's key-value store at the given path.
|
||||
|
||||
List values under the "my-app" folder of the key-value store:
|
||||
|
||||
$ vault kv list secret/my-app/
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVListCommand) Flags() *FlagSets {
|
||||
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
}
|
||||
|
||||
func (c *KVListCommand) AutocompleteArgs() complete.Predictor {
|
||||
return c.PredictVaultFolders()
|
||||
}
|
||||
|
||||
func (c *KVListCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVListCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
path := ensureTrailingSlash(sanitizePath(args[0]))
|
||||
path, err = addPrefixToVKVPath(path, "metadata")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvListRequest(client, path)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error listing %s: %s", path, err))
|
||||
return 2
|
||||
}
|
||||
if secret == nil || secret.Data == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
return 2
|
||||
}
|
||||
|
||||
// If the secret is wrapped, return the wrapped response.
|
||||
if secret.WrapInfo != nil && secret.WrapInfo.TTL != 0 {
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
||||
if _, ok := extractListData(secret); !ok {
|
||||
c.UI.Error(fmt.Sprintf("No entries found at %s", path))
|
||||
return 2
|
||||
}
|
||||
|
||||
return OutputList(c.UI, secret)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVMetadataCommand)(nil)
|
||||
|
||||
type KVMetadataCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVMetadataCommand) Synopsis() string {
|
||||
return "Interact with Vault's Key-Value storage"
|
||||
}
|
||||
|
||||
func (c *KVMetadataCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv metadata <subcommand> [options] [args]
|
||||
|
||||
This command has subcommands for interacting with the metadata endpoint in
|
||||
Vault's key-value store. Here are some simple examples, and more detailed
|
||||
examples are available in the subcommands or the documentation.
|
||||
|
||||
Create or update a metadata entry for a key:
|
||||
|
||||
$ vault kv metadata put -max-versions=5 secret/foo
|
||||
|
||||
Get the metadata for a key, this provides information about each existing
|
||||
version:
|
||||
|
||||
$ vault kv metadata get secret/foo
|
||||
|
||||
Delete a key and all existing versions:
|
||||
|
||||
$ vault kv metadata delete secret/foo
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVMetadataCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVMetadataDeleteCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVMetadataDeleteCommand)(nil)
|
||||
|
||||
type KVMetadataDeleteCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Synopsis() string {
|
||||
return "Deletes all versions and metadata for a key in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv metadata delete [options] PATH
|
||||
|
||||
Deletes all versions and metadata for the provided key.
|
||||
|
||||
$ vault kv metadata delete secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Flags() *FlagSets {
|
||||
return c.flagSet(FlagSetHTTP)
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) AutocompleteArgs() complete.Predictor {
|
||||
return c.PredictVaultFiles()
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVMetadataDeleteCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "metadata")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
if _, err := kvDeleteRequest(client, path); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", path, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", path))
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVMetadataGetCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVMetadataGetCommand)(nil)
|
||||
|
||||
type KVMetadataGetCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) Synopsis() string {
|
||||
return "Retrieves key metadata from the KV store"
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) Help() string {
|
||||
helpText := `
|
||||
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 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVMetadataGetCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "metadata")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvReadRequest(client, path, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err))
|
||||
return 2
|
||||
}
|
||||
if secret == nil {
|
||||
c.UI.Error(fmt.Sprintf("No value found at %s", path))
|
||||
return 2
|
||||
}
|
||||
|
||||
if c.flagField != "" {
|
||||
return PrintRawField(c.UI, secret, c.flagField)
|
||||
}
|
||||
|
||||
// If we have wrap info print the secret normally.
|
||||
if secret.WrapInfo != nil || c.flagFormat != "table" {
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
||||
|
||||
versions := secret.Data["versions"].(map[string]interface{})
|
||||
|
||||
delete(secret.Data, "versions")
|
||||
|
||||
c.UI.Info(getHeaderForMap("Metadata", secret.Data))
|
||||
OutputSecret(c.UI, secret)
|
||||
|
||||
versionKeys := []int{}
|
||||
for k := range versions {
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error parsing version %s", k))
|
||||
return 2
|
||||
}
|
||||
|
||||
versionKeys = append(versionKeys, i)
|
||||
}
|
||||
|
||||
sort.Ints(versionKeys)
|
||||
|
||||
for _, v := range versionKeys {
|
||||
c.UI.Info("\n" + getHeaderForMap(fmt.Sprintf("Version %d", v), versions[strconv.Itoa(v)].(map[string]interface{})))
|
||||
OutputData(c.UI, versions[strconv.Itoa(v)])
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVMetadataPutCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVMetadataPutCommand)(nil)
|
||||
|
||||
type KVMetadataPutCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagMaxVersions int
|
||||
flagCASRequired bool
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) Synopsis() string {
|
||||
return "Sets or updates key settings in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault metadata kv put [options] KEY [DATA]
|
||||
|
||||
This command can be used to create a blank key in the key-value store or to
|
||||
update key configuration for a specified key.
|
||||
|
||||
Create a key in the key-value store with no data:
|
||||
|
||||
$ vault kv metadata put secret/foo
|
||||
|
||||
Set a max versions setting on the key:
|
||||
|
||||
$ vault kv metadata put -max-versions=5 secret/foo
|
||||
|
||||
Require Check-and-Set for this key:
|
||||
|
||||
$ vault kv metadata put -require-cas secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.IntVar(&IntVar{
|
||||
Name: "max-versions",
|
||||
Target: &c.flagMaxVersions,
|
||||
Default: 0,
|
||||
Usage: `The number of versions to keep. If not set, the backend’s configured max version is used.`,
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "cas-required",
|
||||
Target: &c.flagCASRequired,
|
||||
Default: false,
|
||||
Usage: `If true the key will require the cas parameter to be set on all write requests. If false, the backend’s configuration will be used.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVMetadataPutCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "metadata")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"max_versions": c.flagMaxVersions,
|
||||
"cas_required": c.flagCASRequired,
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvWriteRequest(client, path, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, 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))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVPutCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVPutCommand)(nil)
|
||||
|
||||
type KVPutCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagCAS int
|
||||
testStdin io.Reader // for tests
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) Synopsis() string {
|
||||
return "Sets or updates data in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) Help() string {
|
||||
helpText := `
|
||||
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 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
|
||||
|
||||
Or it can be read from stdin using the "-" symbol:
|
||||
|
||||
$ echo "abcd1234" | vault kv put 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
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.IntVar(&IntVar{
|
||||
Name: "cas",
|
||||
Target: &c.flagCAS,
|
||||
Default: -1,
|
||||
Usage: `Specifies to use a Check-And-Set operation. If not set the write
|
||||
will be allowed. If set to 0 a write will only be allowed if the key
|
||||
doesn’t exist. If the index is non-zero the write will only be allowed
|
||||
if the key’s current version matches the version specified in the cas
|
||||
parameter.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVPutCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
// Pull our fake stdin if needed
|
||||
stdin := (io.Reader)(os.Stdin)
|
||||
if c.testStdin != nil {
|
||||
stdin = c.testStdin
|
||||
}
|
||||
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "data")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
data, err := parseArgsData(stdin, args[1:])
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to parse K=V data: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
data = map[string]interface{}{
|
||||
"data": data,
|
||||
"options": map[string]interface{}{},
|
||||
}
|
||||
|
||||
if c.flagCAS > -1 {
|
||||
data["options"].(map[string]interface{})["cas"] = c.flagCAS
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvWriteRequest(client, path, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, 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))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*KVUndeleteCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*KVUndeleteCommand)(nil)
|
||||
|
||||
type KVUndeleteCommand struct {
|
||||
*BaseCommand
|
||||
|
||||
flagVersions []string
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) Synopsis() string {
|
||||
return "Undeletes versions in the KV store"
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault kv undelete [options] KEY
|
||||
|
||||
Undeletes the data for the provided version and path in the key-value store.
|
||||
This restores the data, allowing it to be returned on get requests.
|
||||
|
||||
To undelete version 3 of key "foo":
|
||||
|
||||
$ vault kv undelete -versions=3 secret/foo
|
||||
|
||||
Additional flags and more advanced use cases are detailed below.
|
||||
|
||||
` + c.Flags().Help()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||
|
||||
// Common Options
|
||||
f := set.NewFlagSet("Common Options")
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "versions",
|
||||
Target: &c.flagVersions,
|
||||
Default: nil,
|
||||
Usage: `Specifies the version numbers to undelete.`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *KVUndeleteCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
switch {
|
||||
case len(args) < 1:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case len(args) > 1:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(c.flagVersions) == 0 {
|
||||
c.UI.Error("No versions provided, use the \"-versions\" flag to specify the version to undelete.")
|
||||
return 1
|
||||
}
|
||||
var err error
|
||||
path := sanitizePath(args[0])
|
||||
path, err = addPrefixToVKVPath(path, "undelete")
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"versions": c.flagVersions,
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
secret, err := kvWriteRequest(client, path, data)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, 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))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, secret)
|
||||
}
|
|
@ -134,6 +134,13 @@ type encryptedKeyStorage struct {
|
|||
prefix string
|
||||
}
|
||||
|
||||
func ensureTailingSlash(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
return path + "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// List implements the logical.Storage List method, and decrypts all the items
|
||||
// in a path prefix. This can only operate on full folder structures so the
|
||||
// prefix should end in a "/".
|
||||
|
@ -143,7 +150,7 @@ func (s *encryptedKeyStorage) List(ctx context.Context, prefix string) ([]string
|
|||
return nil, err
|
||||
}
|
||||
|
||||
keys, err := s.s.List(ctx, encPrefix+"/")
|
||||
keys, err := s.s.List(ctx, ensureTailingSlash(encPrefix))
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
|
@ -244,6 +251,10 @@ func (s *encryptedKeyStorage) Delete(ctx context.Context, path string) error {
|
|||
// by "/") with the object's key policy. The context for each encryption is the
|
||||
// plaintext path prefix for the key.
|
||||
func (s *encryptedKeyStorage) encryptPath(path string) (string, error) {
|
||||
if path == "" || path == "/" {
|
||||
return s.prefix, nil
|
||||
}
|
||||
|
||||
path = paths.Clean(path)
|
||||
|
||||
// Trim the prefix if it starts with a "/"
|
||||
|
@ -252,7 +263,7 @@ func (s *encryptedKeyStorage) encryptPath(path string) (string, error) {
|
|||
parts := strings.Split(path, "/")
|
||||
|
||||
encPath := s.prefix
|
||||
context := s.prefix
|
||||
context := strings.TrimSuffix(s.prefix, "/")
|
||||
for _, p := range parts {
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(p))
|
||||
ciphertext, err := s.policy.Encrypt(0, []byte(context), nil, encoded)
|
||||
|
|
|
@ -160,7 +160,91 @@ func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEncrytedKeysStorage_CRUD(t *testing.T) {
|
||||
func TestEncryptedKeysStorage_List(t *testing.T) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := policy.Rotate(ctx, s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
es, err := NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||
Policy: policy,
|
||||
Prefix: "prefix",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = es.Wrap(s).Put(ctx, &logical.StorageEntry{
|
||||
Key: "test",
|
||||
Value: []byte("test"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = es.Wrap(s).Put(ctx, &logical.StorageEntry{
|
||||
Key: "test/foo",
|
||||
Value: []byte("test"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = es.Wrap(s).Put(ctx, &logical.StorageEntry{
|
||||
Key: "test/foo1/test",
|
||||
Value: []byte("test"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keys, err := es.Wrap(s).List(ctx, "test/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test prefixed with "/"
|
||||
keys, err = es.Wrap(s).List(ctx, "/test/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(keys) != 2 || keys[0] != "foo1/" || keys[1] != "foo" {
|
||||
t.Fatalf("bad keys: %#v", keys)
|
||||
}
|
||||
|
||||
keys, err = es.Wrap(s).List(ctx, "/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 2 || keys[0] != "test" || keys[1] != "test/" {
|
||||
t.Fatalf("bad keys: %#v", keys)
|
||||
}
|
||||
|
||||
keys, err = es.Wrap(s).List(ctx, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 2 || keys[0] != "test" || keys[1] != "test/" {
|
||||
t.Fatalf("bad keys: %#v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
Name: "metadata",
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
@ -26,6 +27,7 @@ var StdAllowedHeaders = []string{
|
|||
"X-Vault-Wrap-Format",
|
||||
"X-Vault-Wrap-TTL",
|
||||
"X-Vault-Policy-Override",
|
||||
consts.VaultKVCLIClientHeader,
|
||||
}
|
||||
|
||||
// CORSConfig stores the state of the CORS configuration.
|
||||
|
|
Loading…
Reference in New Issue