Add delete-version-after to kv CLI subcommands

Adds a new optional parameter "delete-version-after" to the following
CLI subcommands:

- kv metadata put
- kv put
- kv undelete
- kv rollback
This commit is contained in:
Michael Gaffney 2019-06-05 16:20:43 -04:00
parent 62e14c280d
commit 42324c22ff
8 changed files with 357 additions and 155 deletions

View File

@ -24,9 +24,9 @@ Usage: vault kv metadata <subcommand> [options] [args]
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:
Create or update a metadata entry for a key:
$ vault kv metadata put -max-versions=5 secret/foo
$ vault kv metadata put -max-versions=5 -delete-version-after=3h25m19s secret/foo
Get the metadata for a key, this provides information about each existing
version:

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io"
"strings"
"time"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@ -15,9 +16,10 @@ var _ cli.CommandAutocomplete = (*KVMetadataPutCommand)(nil)
type KVMetadataPutCommand struct {
*BaseCommand
flagMaxVersions int
flagCASRequired bool
testStdin io.Reader // for tests
flagMaxVersions int
flagCASRequired bool
flagDeleteVersionAfter time.Duration
testStdin io.Reader // for tests
}
func (c *KVMetadataPutCommand) Synopsis() string {
@ -30,16 +32,20 @@ Usage: vault metadata kv put [options] KEY
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:
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:
Set a max versions setting on the key:
$ vault kv metadata put -max-versions=5 secret/foo
Require Check-and-Set for this key:
Set delete-version-after on the key:
$ vault kv metadata put -delete-version-after=3h25m19s secret/foo
Require Check-and-Set for this key:
$ vault kv metadata put -cas-required secret/foo
@ -69,6 +75,19 @@ func (c *KVMetadataPutCommand) Flags() *FlagSets {
Usage: `If true the key will require the cas parameter to be set on all write requests. If false, the backends configuration will be used.`,
})
f.DurationVar(&DurationVar{
Name: "delete-version-after",
Target: &c.flagDeleteVersionAfter,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: `Specifies the length of time before a version is deleted.
If not set, the backend's configured delete-version-after is used. Cannot be
greater than the backend's delete-version-after. The delete-version-after is
specified as a numeric string with a suffix like "30s" or
"3h25m19s".`,
})
return set
}
@ -122,6 +141,10 @@ func (c *KVMetadataPutCommand) Run(args []string) int {
"cas_required": c.flagCASRequired,
}
if c.flagDeleteVersionAfter > 0 {
data["delete_version_after"] = c.flagDeleteVersionAfter.String()
}
secret, err := client.Logical().Write(path, data)
if err != nil {
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))

View File

@ -5,6 +5,7 @@ import (
"io"
"os"
"strings"
"time"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@ -16,8 +17,9 @@ var _ cli.CommandAutocomplete = (*KVPutCommand)(nil)
type KVPutCommand struct {
*BaseCommand
flagCAS int
testStdin io.Reader // for tests
flagCAS int
flagDeleteVersionAfter time.Duration
testStdin io.Reader // for tests
}
func (c *KVPutCommand) Synopsis() string {
@ -71,6 +73,19 @@ func (c *KVPutCommand) Flags() *FlagSets {
parameter.`,
})
f.DurationVar(&DurationVar{
Name: "delete-version-after",
Target: &c.flagDeleteVersionAfter,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: `Specifies the length of time before this version is
deleted. If not set, the metadata's delete-version-after is used.
Cannot be greater than the metadata's delete-version-after. The
delete-version-after is specified as a numeric string with a suffix
like "30s" or "3h25m19s".`,
})
return set
}
@ -137,6 +152,10 @@ func (c *KVPutCommand) Run(args []string) int {
if c.flagCAS > -1 {
data["options"].(map[string]interface{})["cas"] = c.flagCAS
}
if c.flagDeleteVersionAfter > 0 {
data["options"].(map[string]interface{})["delete_version_after"] = c.flagDeleteVersionAfter.String()
}
}
secret, err := client.Logical().Write(path, data)

View File

@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"strings"
"time"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@ -15,7 +16,8 @@ var _ cli.CommandAutocomplete = (*KVRollbackCommand)(nil)
type KVRollbackCommand struct {
*BaseCommand
flagVersion int
flagVersion int
flagDeleteVersionAfter time.Duration
}
func (c *KVRollbackCommand) Synopsis() string {
@ -53,6 +55,19 @@ func (c *KVRollbackCommand) Flags() *FlagSets {
Usage: `Specifies the version number that should be made current again.`,
})
f.DurationVar(&DurationVar{
Name: "delete-version-after",
Target: &c.flagDeleteVersionAfter,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: `Specifies the length of time before this version is
deleted. If not set, the metadata's delete-version-after is used.
Cannot be greater than the metadata's delete-version-after. The
delete-version-after is specified as a numeric string with a suffix
like "30s" or "3h25m19s".`,
})
return set
}
@ -217,12 +232,18 @@ func (c *KVRollbackCommand) Run(args []string) int {
}
}
secret, err := client.Logical().Write(path, map[string]interface{}{
data = map[string]interface{}{
"data": data,
"options": map[string]interface{}{
"cas": casVersion,
},
})
}
if c.flagDeleteVersionAfter > 0 {
data["options"].(map[string]interface{})["delete_version_after"] = c.flagDeleteVersionAfter.String()
}
secret, err := client.Logical().Write(path, data)
if err != nil {
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))
return 2

View File

@ -120,7 +120,7 @@ func TestKVPutCommand(t *testing.T) {
cmd.client = client
code := cmd.Run([]string{
"-cas", "0", "kv/write/cas", "bar=baz",
"-cas", "0", "-delete-version-after", "1h", "kv/write/cas", "bar=baz",
})
if code != 0 {
t.Fatalf("expected 0 to be %d", code)
@ -133,7 +133,7 @@ func TestKVPutCommand(t *testing.T) {
ui, cmd = testKVPutCommand(t)
cmd.client = client
code = cmd.Run([]string{
"-cas", "1", "kv/write/cas", "bar=baz",
"-cas", "1", "-delete-version-after", "1h", "kv/write/cas", "bar=baz",
})
if code != 0 {
t.Fatalf("expected 0 to be %d", code)

View File

@ -3,6 +3,7 @@ package command
import (
"fmt"
"strings"
"time"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@ -14,7 +15,8 @@ var _ cli.CommandAutocomplete = (*KVUndeleteCommand)(nil)
type KVUndeleteCommand struct {
*BaseCommand
flagVersions []string
flagVersions []string
flagDeleteVersionAfter time.Duration
}
func (c *KVUndeleteCommand) Synopsis() string {
@ -29,7 +31,7 @@ Usage: vault kv undelete [options] KEY
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.
@ -51,6 +53,20 @@ func (c *KVUndeleteCommand) Flags() *FlagSets {
Usage: `Specifies the version numbers to undelete.`,
})
f.DurationVar(&DurationVar{
Name: "delete-version-after",
Target: &c.flagDeleteVersionAfter,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: `Specifies the length of time before these versions will be
deleted. If not set, the metadata's delete-version-after is used.
Cannot be greater than the metadata's delete-version-after. The
delete-version-after is specified as a numeric string with a suffix
like "30s" or
"3h25m19s".`,
})
return set
}
@ -107,6 +123,10 @@ func (c *KVUndeleteCommand) Run(args []string) int {
"versions": kvParseVersionsFlags(c.flagVersions),
}
if c.flagDeleteVersionAfter > 0 {
data["delete_version_after"] = c.flagDeleteVersionAfter.String()
}
secret, err := client.Logical().Write(path, data)
if err != nil {
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))

View File

@ -20,7 +20,7 @@ possible to enable secrets engines at any location, please update your API calls
accordingly.
## Configure the KV Engine
## Configure the KV Engine
This path configures backend level settings that are applied to every key in the
key-value store.
@ -34,17 +34,24 @@ key-value store.
- `max_versions` `(int: 0)` The number of versions to keep per key. This value
applies to all keys, but a key's metadata setting can overwrite this value.
Once a key has more than the configured allowed versions the oldest version
will be permanently deleted. Defaults to 10.
will be permanently deleted. Defaults to 10.
- `cas_required` `(bool: false)` If true all keys will require the cas
parameter to be set on all write requests.
- `delete_version_after` `(string:"0s")` If set, specifies the length
of time before a version is deleted.
Accepts [Go duration format string][duration-godoc].
[duration-godoc]: https://golang.org/pkg/time/#ParseDuration
### Sample Payload
```json
{
"max_versions": 5,
"cas_required": false
"cas_required": false,
"delete_version_after": "3h25m19s"
}
```
@ -82,7 +89,8 @@ $ curl \
{
"data": {
"cas_required": false,
"max_versions": 0
"max_versions": 0,
"delete_version_after": "3h25m19s"
}
}
```
@ -143,11 +151,20 @@ have an ACL policy granting the `update` capability.
### Parameters
- `options` `(Map: <optional>)` An object that holds option settings.
- `cas` `(int: <optional>)` - Set the "cas" value 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 doesnt exist. If the index is non-zero the
write will only be allowed if the keys current version matches the
version specified in the cas parameter.
version specified in the cas parameter.
- `delete_version_after` (`string:"0s"`) Set the `delete_version_after`
value to a duration to specify the `deletion_time` for this
version. If not set, the metadata's `delete_version_after` will be used. If
the metadata's `delete_version_after` is not set, the backend's
`delete_version_after` will be used. If the value is greater than the
metadata's `delete_version_after`, the metadata's `delete_version_after` will be
used. Accepts [Go duration format string][duration-godoc].
- `data` `(Map: <required>)`  The contents of the data map will be stored and
returned on read.
@ -157,7 +174,8 @@ have an ACL policy granting the `update` capability.
```json
{
"options": {
"cas": 0
"cas": 0,
"delete_version_after": "3m"
},
"data": {
"foo": "bar",
@ -182,7 +200,7 @@ $ curl \
{
"data": {
"created_time": "2018-03-22T02:36:43.986212308Z",
"deletion_time": "",
"deletion_time": "2018-03-22T02:39:43.986212308Z",
"destroyed": false,
"version": 1
}
@ -264,14 +282,24 @@ This restores the data, allowing it to be returned on get requests.
- `path` `(string: <required>)` Specifies the path of the secret to undelete.
This is specified as part of the URL.
- `versions` `([]int: <required>)` - The versions to undelete. The versions will
be restored and their data will be returned on normal get requests.
- `delete_version_after` (`string:"0s"`) Set the `delete_version_after` value
to a duration to specify the `deletion_time` for the versions being
undeleted. If not set, the metadata's `delete_version_after` will be used. If
the metadata's `delete_version_after` is not set, the backend's `delete_version_after`
will be used. If the value is greater than the metadata's
`delete_version_after`, the metadata's `delete_version_after` will be used. Accepts
[Go duration format string][duration-godoc].
### Sample Payload
```json
{
"versions": [1, 2]
"versions": [1, 2],
"delete_version_after": "25m"
}
```
@ -298,8 +326,9 @@ numbers from the key-value store.
- `path` `(string: <required>)` Specifies the path of the secret to destroy.
This is specified as part of the URL.
- `versions` `([]int: <required>)` - The versions to destroy. Their data will be
permanently deleted.
permanently deleted.
### Sample Payload
@ -429,18 +458,26 @@ have an ACL policy granting the `update` capability.
- `max_versions` `(int: 0)` The number of versions to keep per key. If not
set, the backends configured max version is used. Once a key has more than
the configured allowed versions the oldest version will be permanently
deleted.
deleted.
- `cas_required` `(bool: false)` If true the key will require the cas
parameter to be set on all write requests. If false, the backends
configuration will be used. 
configuration will be used.
- `delete_version_after` `(string:"0s")` Set the `delete_version_after` value
to a duration to specify the `deletion_time` for all new versions
written to this key. If not set, the backend's `delete_version_after` will be
used. If the value is greater than the backend's `delete_version_after`, the
backend's `delete_version_after` will be used. Accepts [Go duration
format string][duration-godoc].
### Sample Payload
```json
{
"max_versions": 5,
"cas_required": false
"cas_required": false,
"delete_version_after": "3h25m19s"
}
```
@ -457,7 +494,7 @@ $ curl \
## Delete Metadata and All Versions
This endpoint permanently deletes the key metadata and all version data for the
specified key. All version history will be removed.
specified key. All version history will be removed.
| Method | Path |
| :--------------------------- | :--------------------- |

View File

@ -28,13 +28,13 @@ management tool.
A v2 `kv` secrets engine can be enabled by:
```
```text
$ vault secrets enable -version=2 kv
```
Or, you can pass `kv-v2` as the secrets engine type:
```
```text
$ vault secrets enable kv-v2
```
@ -44,20 +44,20 @@ different paths. Each instance of the KV secrets engine is isolated and unique.
## Upgrading from Version 1
An existing version 1 kv store can be upgraded to a version 2 kv store via the CLI or API, as shown below. This will start an upgrade process to upgrade the existing key/value data to a versioned format. The mount will be inaccessible during this process. This process could take a long time, so plan accordingly.
An existing version 1 kv store can be upgraded to a version 2 kv store via the CLI or API, as shown below. This will start an upgrade process to upgrade the existing key/value data to a versioned format. The mount will be inaccessible during this process. This process could take a long time, so plan accordingly.
Once upgraded to version 2, the former paths at which the data was accessible will no longer suffice. You will need to adjust user policies to add access to the version 2 paths as detailed in the [ACL Rules section below](/docs/secrets/kv/kv-v2.html#acl-rules). Similarly, users/applications will need to update the paths at which they interact with the kv data once it has been upgraded to version 2.
An existing version 1 kv can be upgraded to a version 2 KV store with the CLI command:
```
```text
$ vault kv enable-versioning secret/
Success! Tuned the secrets engine at: secret/
```
or via the API:
```
```text
$ cat payload.json
{
"options": {
@ -163,36 +163,34 @@ allows for writing keys with arbitrary values.
### Writing/Reading arbitrary data
1. Write arbitrary data:
```text
$ vault kv put secret/my-secret my-value=s3cr3t
Key Value
--- -----
created_time 2018-03-30T22:11:48.589157362Z
deletion_time n/a
destroyed false
version 1
Key Value
--- -----
created_time 2019-06-19T17:20:22.985303Z
deletion_time n/a
destroyed false
version 1
```
1. Read arbitrary data:
```text
$ vault kv get secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2018-03-30T22:11:48.589157362Z
deletion_time n/a
destroyed false
version 1
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:20:22.985303Z
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
my-value s3cr3t
====== Data ======
Key Value
--- -----
my-value s3cr3t
```
1. Write another version, the previous version will still be accessible. The
@ -204,48 +202,113 @@ allows for writing keys with arbitrary values.
```text
$ vault kv put -cas=1 secret/my-secret my-value=new-s3cr3t
Key Value
--- -----
created_time 2018-03-30T22:18:37.124228658Z
deletion_time n/a
destroyed false
version 2
Key Value
--- -----
created_time 2019-06-19T17:22:23.369372Z
deletion_time n/a
destroyed false
version 2
```
1. Reading now will return the newest version of the data:
```text
$ vault kv get secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2018-03-30T22:18:37.124228658Z
deletion_time n/a
destroyed false
version 2
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:22:23.369372Z
deletion_time n/a
destroyed false
version 2
====== Data ======
Key Value
--- -----
my-value new-s3cr3t
====== Data ======
Key Value
--- -----
my-value new-s3cr3t
```
1. Previous versions can be accessed with the `-version` flag:
```
```text
$ vault kv get -version=1 secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2018-03-30T22:16:39.808909557Z
deletion_time n/a
destroyed false
version 1
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:20:22.985303Z
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
my-value s3cr3t
====== Data ======
Key Value
--- -----
my-value s3cr3t
```
1. Write another version which will be deleted after a specified
duration. The `-delete-version-after` flag can optionally be passed to specify
a duration of time until the version will be deleted. The previous
versions will still be accessible.
```text
$ vault kv put -delete-version-after=2m secret/my-secret my-value=short-lived-s3cr3t
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time 2019-06-19T17:25:21.834403Z
destroyed false
version 3
```
1. Reading now will return the newest version of the data and show the
`deletion_time`:
```text
$ vault kv get secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time 2019-06-19T17:25:21.834403Z
destroyed false
version 3
====== Data ======
Key Value
--- -----
my-value short-lived-s3cr3t
```
1. Reading after the `deletion_time` will only return metadata:
```text
$ vault kv get secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time 2019-06-19T17:25:21.834403Z
destroyed false
version 3
```
1. Previous versions not deleted can still be accessed with the `-version` flag:
```text
$ vault kv get -version=2 secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:22:23.369372Z
deletion_time n/a
destroyed false
version 2
====== Data ======
Key Value
--- -----
my-value new-s3cr3t
```
### Deleting and Destroying Data
@ -253,7 +316,7 @@ allows for writing keys with arbitrary values.
When deleting data the standard `vault kv delete` command will perform a
soft delete. It will mark the version as deleted and populate a `deletion_time`
timestamp. Soft deletes do not remove the underlying version data from storage,
which allows the version to be undeleted. The `vault kv undelete` commmand
which allows the version to be undeleted. The `vault kv undelete` command
handles undeleting versions.
A version's data is permanently deleted only when the key has more versions than
@ -267,37 +330,37 @@ See the commands below for more information:
1. The latest version of a key can be deleted with the delete command, this also
takes a `-versions` flag to delete prior versions:
```
```text
$ vault kv delete secret/my-secret
Success! Data deleted (if it existed) at: secret/my-secret
Success! Data deleted (if it existed) at: secret/my-secret
```
1. Versions can be undeleted:
```
$ vault kv undelete -versions=2 secret/my-secret
Success! Data written to: secret/undelete/my-secret
```text
$ vault kv undelete -versions=3 secret/my-secret
Success! Data written to: secret/undelete/my-secret
$ vault kv get secret/my-secret
====== Metadata ======
Key Value
--- -----
created_time 2018-03-30T22:18:37.124228658Z
deletion_time n/a
destroyed false
version 2
====== Metadata ======
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time n/a
destroyed false
version 3
====== Data ======
Key Value
--- -----
my-value new-s3cr3t
====== Data ======
Key Value
--- -----
my-value short-lived-s3cr3t
```
1. Destroying a version permanently deletes the underlying data:
```
$ vault kv destroy -versions=2 secret/my-secret
Success! Data written to: secret/destroy/my-secret
```text
$ vault kv destroy -versions=3 secret/my-secret
Success! Data written to: secret/destroy/my-secret
```
### Key Metadata
@ -310,78 +373,97 @@ See the commands below for more information:
1. All metadata and versions for a key can be viewed:
```
```text
$ vault kv metadata get secret/my-secret
======= Metadata =======
Key Value
--- -----
created_time 2018-03-30T22:16:39.808909557Z
current_version 2
max_versions 0
oldest_version 0
updated_time 2018-03-30T22:18:37.124228658Z
========== Metadata ==========
Key Value
--- -----
cas_required false
created_time 2019-06-19T17:20:22.985303Z
current_version 3
delete_version_after 0s
max_versions 0
oldest_version 0
updated_time 2019-06-19T17:23:21.834403Z
====== Version 1 ======
Key Value
--- -----
created_time 2018-03-30T22:16:39.808909557Z
deletion_time n/a
destroyed false
====== Version 1 ======
Key Value
--- -----
created_time 2019-06-19T17:20:22.985303Z
deletion_time n/a
destroyed false
====== Version 2 ======
Key Value
--- -----
created_time 2018-03-30T22:18:37.124228658Z
deletion_time n/a
destroyed true
====== Version 2 ======
Key Value
--- -----
created_time 2019-06-19T17:22:23.369372Z
deletion_time n/a
destroyed false
====== Version 3 ======
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time n/a
destroyed true
```
1. The metadata settings for a key can be configured:
```
$ vault kv metadata put -max-versions 1 secret/my-secret
Success! Data written to: secret/metadata/my-secret
```text
$ vault kv metadata put -max-versions 2 -delete-version-after="3h25m19s" secret/my-secret
Success! Data written to: secret/metadata/my-secret
```
Max versions changes will be applied on next write:
Delete-version-after settings will apply only to new versions. Max versions
changes will be applied on next write:
```
```text
$ vault kv put secret/my-secret my-value=newer-s3cr3t
Key Value
--- -----
created_time 2018-03-30T22:41:09.193643571Z
deletion_time n/a
destroyed false
version 3
Key Value
--- -----
created_time 2019-06-19T17:31:16.662563Z
deletion_time 2019-06-19T20:56:35.662563Z
destroyed false
version 4
```
Once a key has more versions than max versions the oldest versions are cleaned
up:
Once a key has more versions than max versions the oldest versions
are cleaned up:
```
```text
$ vault kv metadata get secret/my-secret
======= Metadata =======
Key Value
--- -----
created_time 2018-03-30T22:16:39.808909557Z
current_version 3
max_versions 1
oldest_version 3
updated_time 2018-03-30T22:41:09.193643571Z
========== Metadata ==========
Key Value
--- -----
cas_required false
created_time 2019-06-19T17:20:22.985303Z
current_version 4
delete_version_after 3h25m19s
max_versions 2
oldest_version 3
updated_time 2019-06-19T17:31:16.662563Z
====== Version 3 ======
Key Value
--- -----
created_time 2018-03-30T22:41:09.193643571Z
deletion_time n/a
destroyed false
====== Version 3 ======
Key Value
--- -----
created_time 2019-06-19T17:23:21.834403Z
deletion_time n/a
destroyed true
====== Version 4 ======
Key Value
--- -----
created_time 2019-06-19T17:31:16.662563Z
deletion_time 2019-06-19T20:56:35.662563Z
destroyed false
```
1. Permanently delete all metadata and versions for a key:
```
```text
$ vault kv metadata delete secret/my-secret
Success! Data deleted (if it existed) at: secret/metadata/my-secret
Success! Data deleted (if it existed) at: secret/metadata/my-secret
```
## API