From d820d1fc79d7b7175fe25441a846f5cc62e818f3 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Mon, 24 Sep 2018 17:30:53 -0400 Subject: [PATCH 01/10] Support key trimming --- builtin/logical/transit/path_config.go | 62 ++++- builtin/logical/transit/path_config_test.go | 251 +++++++++++++++++++- builtin/logical/transit/path_keys.go | 1 + helper/keysutil/policy.go | 21 +- 4 files changed, 328 insertions(+), 7 deletions(-) diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index e97a0a698a88..9b0b3a81e664 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -33,6 +33,11 @@ to be used for signing. If set to zero, only the latest version of the key is allowed.`, }, + "trimmed_min_version": &framework.FieldSchema{ + Type: framework.TypeInt, + Description: `If set, permanently deletes all the key versions before the given version. This should always be greater than both 'min_decryption_version' and 'min_encryption_version'. This is not allowed to be set when either 'min_encryption_version' or 'min_decryption_version' is set to zero.`, + }, + "deletion_allowed": &framework.FieldSchema{ Type: framework.TypeBool, Description: "Whether to allow deletion of the key", @@ -58,7 +63,7 @@ the latest version of the key is allowed.`, } } -func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { +func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (resp *logical.Response, retErr error) { name := d.Get("name").(string) // Check if the policy already exists before we lock everything @@ -79,7 +84,25 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * } defer p.Unlock() - resp := &logical.Response{} + originalMinDecryptionVersion := p.MinDecryptionVersion + originalMinEncryptionVersion := p.MinEncryptionVersion + originalTrimmedMinVersion := p.TrimmedMinVersion + originalDeletionAllowed := p.DeletionAllowed + originalExportable := p.Exportable + originalAllowPlaintextBackup := p.AllowPlaintextBackup + + defer func() { + if retErr != nil || (resp != nil && resp.IsError()) { + p.MinDecryptionVersion = originalMinDecryptionVersion + p.MinEncryptionVersion = originalMinEncryptionVersion + p.TrimmedMinVersion = originalTrimmedMinVersion + p.DeletionAllowed = originalDeletionAllowed + p.Exportable = originalExportable + p.AllowPlaintextBackup = originalAllowPlaintextBackup + } + }() + + resp = &logical.Response{} persistNeeded := false @@ -124,6 +147,31 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * } } + trimmedMinVersionRaw, ok := d.GetOk("trimmed_min_version") + if ok { + trimmedMinVersion := trimmedMinVersionRaw.(int) + + switch { + case p.MinEncryptionVersion == 0: + return logical.ErrorResponse("trimmed min version cannot be set when min encryption version is not set"), nil + case p.MinDecryptionVersion == 0: + return logical.ErrorResponse("trimmed min version cannot be set when min decryption version is not set"), nil + case trimmedMinVersion > p.MinEncryptionVersion: + return logical.ErrorResponse("trimmed min version cannot be greater than min encryption version"), nil + case trimmedMinVersion > p.MinDecryptionVersion: + return logical.ErrorResponse("trimmed min version cannot be greater than min decryption version"), nil + case trimmedMinVersion < 0: + return logical.ErrorResponse("trimmed min version cannot be negative"), nil + case trimmedMinVersion == 0: + return logical.ErrorResponse("trimmed min version should be positive"), nil + case trimmedMinVersion < p.TrimmedMinVersion: + return logical.ErrorResponse(fmt.Sprintf("trimmed min version cannot be less than the already set value of %d", p.TrimmedMinVersion)), nil + } + + p.TrimmedMinVersion = trimmedMinVersion + persistNeeded = true + } + // Check here to get the final picture after the logic on each // individually. MinDecryptionVersion will always be 1 or above. if p.MinEncryptionVersion > 0 && @@ -173,6 +221,16 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * return nil, nil } + // Do these checks after 'min_encryption_version', 'min_decryption_version' + // and 'trim_before_version' fields are processed. These are applicable + // even if one of them is being modified. + switch { + case p.TrimmedMinVersion > p.MinEncryptionVersion: + return logical.ErrorResponse("min encryption version should not be less than trimmed min version"), nil + case p.TrimmedMinVersion > p.MinDecryptionVersion: + return logical.ErrorResponse("min decryption version should not be less then trimmed min version"), nil + } + if len(resp.Warnings) == 0 { return nil, p.Persist(ctx, req.Storage) } diff --git a/builtin/logical/transit/path_config_test.go b/builtin/logical/transit/path_config_test.go index 7da7dfcd482f..1db2182e5c92 100644 --- a/builtin/logical/transit/path_config_test.go +++ b/builtin/logical/transit/path_config_test.go @@ -6,15 +6,264 @@ import ( "strings" "testing" + "github.com/hashicorp/vault/helper/keysutil" + "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/logical" ) +func TestTransit_ConfigTrimmedMinVersion(t *testing.T) { + b, storage := createBackendWithSysView(t) + + doReq := func(t *testing.T, req *logical.Request) *logical.Response { + t.Helper() + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("got err:\n%#v\nresp:\n%#v\n", err, resp) + } + return resp + } + doErrReq := func(t *testing.T, req *logical.Request) { + t.Helper() + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + if err == nil && (resp == nil || !resp.IsError()) { + t.Fatalf("expected error; resp:\n%#v\n", resp) + } + } + + // Create a key + req := &logical.Request{ + Path: "keys/aes", + Storage: storage, + Operation: logical.UpdateOperation, + } + doReq(t, req) + + // Get the policy and check that the archive has correct number of keys + p, _, err := b.lm.GetPolicy(namespace.RootContext(nil), keysutil.PolicyRequest{ + Storage: storage, + Name: "aes", + }) + if err != nil { + t.Fatal(err) + } + + // Archive: 0, 1 + archive, err := p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + // Index "0" in the archive is unused. Hence the length of the archived + // keys will always be 1 more than the actual number of keys. + if len(archive.Keys) != 2 { + t.Fatalf("bad: len of archived keys; expected: 2, actual: %d", len(archive.Keys)) + } + + // Ensure that there are 5 key versions, by rotating the key 4 times + for i := 0; i < 4; i++ { + req.Path = "keys/aes/rotate" + req.Data = nil + doReq(t, req) + } + + // Archive: 0, 1, 2, 3, 4, 5 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 6 { + t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys)) + } + + // Trimmed min version should not be set when min_encryption_version is not + // set + req.Path = "keys/aes/config" + req.Data = map[string]interface{}{ + "trimmed_min_version": 1, + } + doErrReq(t, req) + + // Set min_encryption_version to 4 + req.Data = map[string]interface{}{ + "min_encryption_version": 4, + } + doReq(t, req) + + // Set min_decryption_version to 3 + req.Data = map[string]interface{}{ + "min_decryption_version": 3, + } + doReq(t, req) + + // Trimmed min version cannot be greater than min encryption version + req.Data = map[string]interface{}{ + "trimmed_min_version": 5, + } + doErrReq(t, req) + + // Trimmed min version cannot be greater than min decryption version + req.Data["trimmed_min_version"] = 4 + doErrReq(t, req) + + // Trimmed min version cannot be negative + req.Data["trimmed_min_version"] = -1 + req.Data = map[string]interface{}{ + "trimmed_min_version": -1, + } + doErrReq(t, req) + + // Trimmed min version should be positive + req.Data["trimmed_min_version"] = 0 + doErrReq(t, req) + + // Trim all keys before version 3. Index 0 and index 1 will be deleted from + // archived keys. + req.Data["trimmed_min_version"] = 3 + doReq(t, req) + + // Archive: 3, 4, 5 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 3 { + t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys)) + } + + // Min decryption version should not be less than trimmed min version + req.Data = map[string]interface{}{ + "min_decryption_version": 1, + } + doErrReq(t, req) + + // Min encryption version should not be less than trimmed min version + req.Data = map[string]interface{}{ + "min_encryption_version": 2, + } + doErrReq(t, req) + + // Rotate 5 more times + for i := 0; i < 5; i++ { + doReq(t, &logical.Request{ + Path: "keys/aes/rotate", + Storage: storage, + Operation: logical.UpdateOperation, + }) + } + + // Archive: 3, 4, 5, 6, 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 8 { + t.Fatalf("bad: len of archived keys; expected: 8, actual: %d", len(archive.Keys)) + } + + // Set min encryption version to 7 + req.Path = "keys/aes/config" + req.Data = map[string]interface{}{ + "min_encryption_version": 7, + } + doReq(t, req) + + // Set min decryption version to 7 + req.Data = map[string]interface{}{ + "min_decryption_version": 7, + } + doReq(t, req) + + // Trimmed all versions before 7 + req.Data = map[string]interface{}{ + "trimmed_min_version": 7, + } + doReq(t, req) + + // Archive: 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 4 { + t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) + } + + // Read the key + req.Path = "keys/aes" + req.Operation = logical.ReadOperation + resp := doReq(t, req) + keys := resp.Data["keys"].(map[string]int64) + if len(keys) != 4 { + t.Fatalf("bad: number of keys; expected: 4, actual: %d", len(keys)) + } + + // Test if moving the min_encryption_version and min_decryption_versions + // are working fine + + // Set min encryption version to 10 + req.Path = "keys/aes/config" + req.Operation = logical.UpdateOperation + req.Data = map[string]interface{}{ + "min_encryption_version": 10, + } + doReq(t, req) + if p.MinEncryptionVersion != 10 { + t.Fatalf("failed to set min encryption version") + } + + // Set min decryption version to 9 + req.Data = map[string]interface{}{ + "min_decryption_version": 9, + } + doReq(t, req) + if p.MinDecryptionVersion != 9 { + t.Fatalf("failed to set min encryption version") + } + + // Reduce the min decryption version to 8 + req.Data = map[string]interface{}{ + "min_decryption_version": 8, + } + doReq(t, req) + if p.MinDecryptionVersion != 8 { + t.Fatalf("failed to set min encryption version") + } + + // Reduce the min encryption version to 8 + req.Data = map[string]interface{}{ + "min_encryption_version": 8, + } + doReq(t, req) + if p.MinDecryptionVersion != 8 { + t.Fatalf("failed to set min decryption version") + } + + // Read the key to ensure that the keys are properly copied from the + // archive into the policy + req.Path = "keys/aes" + req.Operation = logical.ReadOperation + resp = doReq(t, req) + keys = resp.Data["keys"].(map[string]int64) + if len(keys) != 3 { + t.Fatalf("bad: number of keys; expected: 3, actual: %d", len(keys)) + } + + // Ensure that archive has remained unchanged + // Archive: 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 4 { + t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) + } +} + func TestTransit_ConfigSettings(t *testing.T) { b, storage := createBackendWithSysView(t) doReq := func(req *logical.Request) *logical.Response { resp, err := b.HandleRequest(context.Background(), req) - if err != nil { + if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("got err:\n%#v\nreq:\n%#v\n", err, *req) } return resp diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 332d05bb82a3..866fed55ce35 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -208,6 +208,7 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f "deletion_allowed": p.DeletionAllowed, "min_decryption_version": p.MinDecryptionVersion, "min_encryption_version": p.MinEncryptionVersion, + "trimmed_min_version": p.TrimmedMinVersion, "latest_version": p.LatestVersion, "exportable": p.Exportable, "allow_plaintext_backup": p.AllowPlaintextBackup, diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index edba36a73821..a42108350aa0 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -326,6 +326,13 @@ type Policy struct { // a max. ArchiveVersion int `json:"archive_version"` + // ArchiveMinVersion is the minimum version of the key in the archive. + ArchiveMinVersion int `json:"archive_min_version"` + + // TrimmedMinVersion is the minimum version of the key present. All key + // versions before this would have been deleted. + TrimmedMinVersion int `json:"trimmed_min_version"` + // Whether the key is allowed to be deleted DeletionAllowed bool `json:"deletion_allowed"` @@ -462,7 +469,7 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e if !keysContainsMinimum { // Need to move keys *from* archive for i := p.MinDecryptionVersion; i <= p.LatestVersion; i++ { - p.Keys[strconv.Itoa(i)] = archive.Keys[i] + p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.TrimmedMinVersion] } return nil @@ -473,9 +480,9 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We need a size that is equivalent to the latest version (number of keys) // but adding one since slice numbering starts at 0 and we're indexing by // key version - if len(archive.Keys) < p.LatestVersion+1 { + if len(archive.Keys)+p.TrimmedMinVersion < p.LatestVersion+1 { // Increase the size of the archive slice - newKeys := make([]KeyEntry, p.LatestVersion+1) + newKeys := make([]KeyEntry, p.LatestVersion-p.TrimmedMinVersion+1) copy(newKeys, archive.Keys) archive.Keys = newKeys } @@ -483,10 +490,16 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We are storing all keys in the archive, so we ensure that it is up to // date up to p.LatestVersion for i := p.ArchiveVersion + 1; i <= p.LatestVersion; i++ { - archive.Keys[i] = p.Keys[strconv.Itoa(i)] + archive.Keys[i-p.TrimmedMinVersion] = p.Keys[strconv.Itoa(i)] p.ArchiveVersion = i } + // Trim the keys if required + if p.ArchiveMinVersion < p.TrimmedMinVersion { + archive.Keys = archive.Keys[p.TrimmedMinVersion-p.ArchiveMinVersion:] + p.ArchiveMinVersion = p.TrimmedMinVersion + } + err = p.storeArchive(ctx, storage, archive) if err != nil { return err From 07689b1548be367cf50964884b13d2fa31482f87 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 28 Sep 2018 10:55:13 -0400 Subject: [PATCH 02/10] Add doc --- website/source/api/secret/transit/index.html.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/source/api/secret/transit/index.html.md b/website/source/api/secret/transit/index.html.md index d0f37c5c290a..b3da9628240c 100644 --- a/website/source/api/secret/transit/index.html.md +++ b/website/source/api/secret/transit/index.html.md @@ -218,6 +218,10 @@ are returned during a read operation on the named key.) - `allow_plaintext_backup` `(bool: false)` - If set, enables taking backup of named key in the plaintext format. Once set, this cannot be disabled. +- `trimmed_min_version` `(int: 0)` - If set, all versions before this version + will be permanently removed from the key ring. The value can at most be equal + to the lesser of `min_encryption_version` and `min_decryption_version`. + ### Sample Payload ```json From 0bfd5fc4c2b607318607bf2ce6df95bbe5e3c28e Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 11 Oct 2018 18:19:14 -0400 Subject: [PATCH 03/10] Move trimming to its own endpoint --- builtin/logical/transit/backend.go | 1 + builtin/logical/transit/path_config.go | 34 +-- builtin/logical/transit/path_config_test.go | 249 ------------------- builtin/logical/transit/path_keys.go | 2 +- builtin/logical/transit/path_trim.go | 101 ++++++++ builtin/logical/transit/path_trim_test.go | 256 ++++++++++++++++++++ helper/keysutil/policy.go | 20 +- 7 files changed, 371 insertions(+), 292 deletions(-) create mode 100644 builtin/logical/transit/path_trim.go create mode 100644 builtin/logical/transit/path_trim_test.go diff --git a/builtin/logical/transit/backend.go b/builtin/logical/transit/backend.go index 1bb7f4c92714..76e2bea1c46b 100644 --- a/builtin/logical/transit/backend.go +++ b/builtin/logical/transit/backend.go @@ -46,6 +46,7 @@ func Backend(conf *logical.BackendConfig) *backend { b.pathVerify(), b.pathBackup(), b.pathRestore(), + b.pathTrim(), }, Secrets: []*framework.Secret{}, diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index 9b0b3a81e664..4537b1639a8c 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -86,7 +86,6 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * originalMinDecryptionVersion := p.MinDecryptionVersion originalMinEncryptionVersion := p.MinEncryptionVersion - originalTrimmedMinVersion := p.TrimmedMinVersion originalDeletionAllowed := p.DeletionAllowed originalExportable := p.Exportable originalAllowPlaintextBackup := p.AllowPlaintextBackup @@ -95,7 +94,6 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * if retErr != nil || (resp != nil && resp.IsError()) { p.MinDecryptionVersion = originalMinDecryptionVersion p.MinEncryptionVersion = originalMinEncryptionVersion - p.TrimmedMinVersion = originalTrimmedMinVersion p.DeletionAllowed = originalDeletionAllowed p.Exportable = originalExportable p.AllowPlaintextBackup = originalAllowPlaintextBackup @@ -147,31 +145,6 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * } } - trimmedMinVersionRaw, ok := d.GetOk("trimmed_min_version") - if ok { - trimmedMinVersion := trimmedMinVersionRaw.(int) - - switch { - case p.MinEncryptionVersion == 0: - return logical.ErrorResponse("trimmed min version cannot be set when min encryption version is not set"), nil - case p.MinDecryptionVersion == 0: - return logical.ErrorResponse("trimmed min version cannot be set when min decryption version is not set"), nil - case trimmedMinVersion > p.MinEncryptionVersion: - return logical.ErrorResponse("trimmed min version cannot be greater than min encryption version"), nil - case trimmedMinVersion > p.MinDecryptionVersion: - return logical.ErrorResponse("trimmed min version cannot be greater than min decryption version"), nil - case trimmedMinVersion < 0: - return logical.ErrorResponse("trimmed min version cannot be negative"), nil - case trimmedMinVersion == 0: - return logical.ErrorResponse("trimmed min version should be positive"), nil - case trimmedMinVersion < p.TrimmedMinVersion: - return logical.ErrorResponse(fmt.Sprintf("trimmed min version cannot be less than the already set value of %d", p.TrimmedMinVersion)), nil - } - - p.TrimmedMinVersion = trimmedMinVersion - persistNeeded = true - } - // Check here to get the final picture after the logic on each // individually. MinDecryptionVersion will always be 1 or above. if p.MinEncryptionVersion > 0 && @@ -221,13 +194,10 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * return nil, nil } - // Do these checks after 'min_encryption_version', 'min_decryption_version' - // and 'trim_before_version' fields are processed. These are applicable - // even if one of them is being modified. switch { - case p.TrimmedMinVersion > p.MinEncryptionVersion: + case p.MinVersion > p.MinEncryptionVersion: return logical.ErrorResponse("min encryption version should not be less than trimmed min version"), nil - case p.TrimmedMinVersion > p.MinDecryptionVersion: + case p.MinVersion > p.MinDecryptionVersion: return logical.ErrorResponse("min decryption version should not be less then trimmed min version"), nil } diff --git a/builtin/logical/transit/path_config_test.go b/builtin/logical/transit/path_config_test.go index 1db2182e5c92..8dce5d0c45d3 100644 --- a/builtin/logical/transit/path_config_test.go +++ b/builtin/logical/transit/path_config_test.go @@ -6,258 +6,9 @@ import ( "strings" "testing" - "github.com/hashicorp/vault/helper/keysutil" - "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/logical" ) -func TestTransit_ConfigTrimmedMinVersion(t *testing.T) { - b, storage := createBackendWithSysView(t) - - doReq := func(t *testing.T, req *logical.Request) *logical.Response { - t.Helper() - resp, err := b.HandleRequest(namespace.RootContext(nil), req) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("got err:\n%#v\nresp:\n%#v\n", err, resp) - } - return resp - } - doErrReq := func(t *testing.T, req *logical.Request) { - t.Helper() - resp, err := b.HandleRequest(namespace.RootContext(nil), req) - if err == nil && (resp == nil || !resp.IsError()) { - t.Fatalf("expected error; resp:\n%#v\n", resp) - } - } - - // Create a key - req := &logical.Request{ - Path: "keys/aes", - Storage: storage, - Operation: logical.UpdateOperation, - } - doReq(t, req) - - // Get the policy and check that the archive has correct number of keys - p, _, err := b.lm.GetPolicy(namespace.RootContext(nil), keysutil.PolicyRequest{ - Storage: storage, - Name: "aes", - }) - if err != nil { - t.Fatal(err) - } - - // Archive: 0, 1 - archive, err := p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - // Index "0" in the archive is unused. Hence the length of the archived - // keys will always be 1 more than the actual number of keys. - if len(archive.Keys) != 2 { - t.Fatalf("bad: len of archived keys; expected: 2, actual: %d", len(archive.Keys)) - } - - // Ensure that there are 5 key versions, by rotating the key 4 times - for i := 0; i < 4; i++ { - req.Path = "keys/aes/rotate" - req.Data = nil - doReq(t, req) - } - - // Archive: 0, 1, 2, 3, 4, 5 - archive, err = p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - if len(archive.Keys) != 6 { - t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys)) - } - - // Trimmed min version should not be set when min_encryption_version is not - // set - req.Path = "keys/aes/config" - req.Data = map[string]interface{}{ - "trimmed_min_version": 1, - } - doErrReq(t, req) - - // Set min_encryption_version to 4 - req.Data = map[string]interface{}{ - "min_encryption_version": 4, - } - doReq(t, req) - - // Set min_decryption_version to 3 - req.Data = map[string]interface{}{ - "min_decryption_version": 3, - } - doReq(t, req) - - // Trimmed min version cannot be greater than min encryption version - req.Data = map[string]interface{}{ - "trimmed_min_version": 5, - } - doErrReq(t, req) - - // Trimmed min version cannot be greater than min decryption version - req.Data["trimmed_min_version"] = 4 - doErrReq(t, req) - - // Trimmed min version cannot be negative - req.Data["trimmed_min_version"] = -1 - req.Data = map[string]interface{}{ - "trimmed_min_version": -1, - } - doErrReq(t, req) - - // Trimmed min version should be positive - req.Data["trimmed_min_version"] = 0 - doErrReq(t, req) - - // Trim all keys before version 3. Index 0 and index 1 will be deleted from - // archived keys. - req.Data["trimmed_min_version"] = 3 - doReq(t, req) - - // Archive: 3, 4, 5 - archive, err = p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - if len(archive.Keys) != 3 { - t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys)) - } - - // Min decryption version should not be less than trimmed min version - req.Data = map[string]interface{}{ - "min_decryption_version": 1, - } - doErrReq(t, req) - - // Min encryption version should not be less than trimmed min version - req.Data = map[string]interface{}{ - "min_encryption_version": 2, - } - doErrReq(t, req) - - // Rotate 5 more times - for i := 0; i < 5; i++ { - doReq(t, &logical.Request{ - Path: "keys/aes/rotate", - Storage: storage, - Operation: logical.UpdateOperation, - }) - } - - // Archive: 3, 4, 5, 6, 7, 8, 9, 10 - archive, err = p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - if len(archive.Keys) != 8 { - t.Fatalf("bad: len of archived keys; expected: 8, actual: %d", len(archive.Keys)) - } - - // Set min encryption version to 7 - req.Path = "keys/aes/config" - req.Data = map[string]interface{}{ - "min_encryption_version": 7, - } - doReq(t, req) - - // Set min decryption version to 7 - req.Data = map[string]interface{}{ - "min_decryption_version": 7, - } - doReq(t, req) - - // Trimmed all versions before 7 - req.Data = map[string]interface{}{ - "trimmed_min_version": 7, - } - doReq(t, req) - - // Archive: 7, 8, 9, 10 - archive, err = p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - if len(archive.Keys) != 4 { - t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) - } - - // Read the key - req.Path = "keys/aes" - req.Operation = logical.ReadOperation - resp := doReq(t, req) - keys := resp.Data["keys"].(map[string]int64) - if len(keys) != 4 { - t.Fatalf("bad: number of keys; expected: 4, actual: %d", len(keys)) - } - - // Test if moving the min_encryption_version and min_decryption_versions - // are working fine - - // Set min encryption version to 10 - req.Path = "keys/aes/config" - req.Operation = logical.UpdateOperation - req.Data = map[string]interface{}{ - "min_encryption_version": 10, - } - doReq(t, req) - if p.MinEncryptionVersion != 10 { - t.Fatalf("failed to set min encryption version") - } - - // Set min decryption version to 9 - req.Data = map[string]interface{}{ - "min_decryption_version": 9, - } - doReq(t, req) - if p.MinDecryptionVersion != 9 { - t.Fatalf("failed to set min encryption version") - } - - // Reduce the min decryption version to 8 - req.Data = map[string]interface{}{ - "min_decryption_version": 8, - } - doReq(t, req) - if p.MinDecryptionVersion != 8 { - t.Fatalf("failed to set min encryption version") - } - - // Reduce the min encryption version to 8 - req.Data = map[string]interface{}{ - "min_encryption_version": 8, - } - doReq(t, req) - if p.MinDecryptionVersion != 8 { - t.Fatalf("failed to set min decryption version") - } - - // Read the key to ensure that the keys are properly copied from the - // archive into the policy - req.Path = "keys/aes" - req.Operation = logical.ReadOperation - resp = doReq(t, req) - keys = resp.Data["keys"].(map[string]int64) - if len(keys) != 3 { - t.Fatalf("bad: number of keys; expected: 3, actual: %d", len(keys)) - } - - // Ensure that archive has remained unchanged - // Archive: 7, 8, 9, 10 - archive, err = p.LoadArchive(namespace.RootContext(nil), storage) - if err != nil { - t.Fatal(err) - } - if len(archive.Keys) != 4 { - t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) - } -} - func TestTransit_ConfigSettings(t *testing.T) { b, storage := createBackendWithSysView(t) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 866fed55ce35..4cefb411606c 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -208,7 +208,7 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f "deletion_allowed": p.DeletionAllowed, "min_decryption_version": p.MinDecryptionVersion, "min_encryption_version": p.MinEncryptionVersion, - "trimmed_min_version": p.TrimmedMinVersion, + "min_version": p.MinVersion, "latest_version": p.LatestVersion, "exportable": p.Exportable, "allow_plaintext_backup": p.AllowPlaintextBackup, diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go new file mode 100644 index 000000000000..bc127097e48d --- /dev/null +++ b/builtin/logical/transit/path_trim.go @@ -0,0 +1,101 @@ +package transit + +import ( + "context" + "fmt" + + "github.com/hashicorp/vault/helper/keysutil" + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/logical/framework" +) + +func (b *backend) pathTrim() *framework.Path { + return &framework.Path{ + Pattern: "keys/" + framework.GenericNameRegex("name") + "/trim", + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the key", + }, + "min_version": &framework.FieldSchema{ + Type: framework.TypeInt, + Description: `The minimum version for the key ring. All versions before this version will be +permanently removed. This should always be greater than both +'min_decryption_version' and 'min_encryption_version'. This is not allowed to +be set when either 'min_encryption_version' or 'min_decryption_version' is set +to zero.`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathTrimUpdate(), + }, + + HelpSynopsis: pathTrimHelpSyn, + HelpDescription: pathTrimHelpDesc, + } +} + +func (b *backend) pathTrimUpdate() framework.OperationFunc { + return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (resp *logical.Response, retErr error) { + name := d.Get("name").(string) + + p, _, err := b.lm.GetPolicy(ctx, keysutil.PolicyRequest{ + Storage: req.Storage, + Name: name, + }) + if err != nil { + return nil, err + } + if p == nil { + return logical.ErrorResponse("invalid key name"), logical.ErrInvalidRequest + } + if !b.System().CachingDisabled() { + p.Lock(true) + } + defer p.Unlock() + + minVersionRaw, ok := d.GetOk("min_version") + if !ok { + return logical.ErrorResponse("missing min_version"), nil + } + + // Ensure that cache doesn't get corrupted in error cases + originalMinVersion := p.MinVersion + defer func() { + if retErr != nil || (resp != nil && resp.IsError()) { + p.MinVersion = originalMinVersion + } + }() + + p.MinVersion = minVersionRaw.(int) + + switch { + case p.MinVersion < originalMinVersion: + return logical.ErrorResponse("minimum version cannot be decremented"), nil + case p.MinEncryptionVersion == 0: + return logical.ErrorResponse("minimum version cannot be set when minimum encryption version is not set"), nil + case p.MinDecryptionVersion == 0: + return logical.ErrorResponse("minimum version cannot be set when minimum decryption version is not set"), nil + case p.MinVersion > p.MinEncryptionVersion: + return logical.ErrorResponse("minimum version cannot be greater than minmum encryption version"), nil + case p.MinVersion > p.MinDecryptionVersion: + return logical.ErrorResponse("minimum version cannot be greater than minimum decryption version"), nil + case p.MinVersion < 0: + return logical.ErrorResponse("minimum version cannot be negative"), nil + case p.MinVersion == 0: + return logical.ErrorResponse("minimum version should be positive"), nil + case p.MinVersion < p.MinVersion: + return logical.ErrorResponse(fmt.Sprintf("minimum version cannot be less than the already set value of %d", p.MinVersion)), nil + } + + return nil, p.Persist(ctx, req.Storage) + } +} + +const pathTrimHelpSyn = `Trim key versions in the named key` + +const pathTrimHelpDesc = ` +This path is used to trim key versions of a named key. Trimming only happens +from the lower end of version numbers. +` diff --git a/builtin/logical/transit/path_trim_test.go b/builtin/logical/transit/path_trim_test.go new file mode 100644 index 000000000000..94ce7df630a7 --- /dev/null +++ b/builtin/logical/transit/path_trim_test.go @@ -0,0 +1,256 @@ +package transit + +import ( + "testing" + + "github.com/hashicorp/vault/helper/keysutil" + "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/logical" +) + +func TestTransit_Trim(t *testing.T) { + b, storage := createBackendWithSysView(t) + + doReq := func(t *testing.T, req *logical.Request) *logical.Response { + t.Helper() + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("got err:\n%#v\nresp:\n%#v\n", err, resp) + } + return resp + } + doErrReq := func(t *testing.T, req *logical.Request) { + t.Helper() + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + if err == nil && (resp == nil || !resp.IsError()) { + t.Fatalf("expected error; resp:\n%#v\n", resp) + } + } + + // Create a key + req := &logical.Request{ + Path: "keys/aes", + Storage: storage, + Operation: logical.UpdateOperation, + } + doReq(t, req) + + // Get the policy and check that the archive has correct number of keys + p, _, err := b.lm.GetPolicy(namespace.RootContext(nil), keysutil.PolicyRequest{ + Storage: storage, + Name: "aes", + }) + if err != nil { + t.Fatal(err) + } + + // Archive: 0, 1 + archive, err := p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + // Index "0" in the archive is unused. Hence the length of the archived + // keys will always be 1 more than the actual number of keys. + if len(archive.Keys) != 2 { + t.Fatalf("bad: len of archived keys; expected: 2, actual: %d", len(archive.Keys)) + } + + // Ensure that there are 5 key versions, by rotating the key 4 times + for i := 0; i < 4; i++ { + req.Path = "keys/aes/rotate" + req.Data = nil + doReq(t, req) + } + + // Archive: 0, 1, 2, 3, 4, 5 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 6 { + t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys)) + } + + // Trimmed min version should not be set when min_encryption_version is not + // set + req.Path = "keys/aes/trim" + req.Data = map[string]interface{}{ + "min_version": 1, + } + doErrReq(t, req) + + // Set min_encryption_version to 4 + req.Path = "keys/aes/config" + req.Data = map[string]interface{}{ + "min_encryption_version": 4, + } + doReq(t, req) + + // Set min_decryption_version to 3 + req.Data = map[string]interface{}{ + "min_decryption_version": 3, + } + doReq(t, req) + + // Min version cannot be greater than min encryption version + req.Path = "keys/aes/trim" + req.Data = map[string]interface{}{ + "min_version": 5, + } + doErrReq(t, req) + + // Min version cannot be greater than min decryption version + req.Data["min_version"] = 4 + doErrReq(t, req) + + // Min version cannot be negative + req.Data["min_version"] = -1 + doErrReq(t, req) + + // Min version should be positive + req.Data["min_version"] = 0 + doErrReq(t, req) + + // Trim all keys before version 3. Index 0 and index 1 will be deleted from + // archived keys. + req.Data["min_version"] = 3 + doReq(t, req) + + // Archive: 3, 4, 5 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 3 { + t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys)) + } + + // Min decryption version should not be less than trimmed min version + req.Path = "keys/aes/config" + req.Data = map[string]interface{}{ + "min_decryption_version": 1, + } + doErrReq(t, req) + + // Min encryption version should not be less than trimmed min version + req.Data = map[string]interface{}{ + "min_encryption_version": 2, + } + doErrReq(t, req) + + // Rotate 5 more times + for i := 0; i < 5; i++ { + doReq(t, &logical.Request{ + Path: "keys/aes/rotate", + Storage: storage, + Operation: logical.UpdateOperation, + }) + } + + // Archive: 3, 4, 5, 6, 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 8 { + t.Fatalf("bad: len of archived keys; expected: 8, actual: %d", len(archive.Keys)) + } + + // Set min encryption version to 7 + req.Data = map[string]interface{}{ + "min_encryption_version": 7, + } + doReq(t, req) + + // Set min decryption version to 7 + req.Data = map[string]interface{}{ + "min_decryption_version": 7, + } + doReq(t, req) + + // Trimmed all versions before 7 + req.Path = "keys/aes/trim" + req.Data = map[string]interface{}{ + "min_version": 7, + } + doReq(t, req) + + // Archive: 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 4 { + t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) + } + + // Read the key + req.Path = "keys/aes" + req.Operation = logical.ReadOperation + resp := doReq(t, req) + keys := resp.Data["keys"].(map[string]int64) + if len(keys) != 4 { + t.Fatalf("bad: number of keys; expected: 4, actual: %d", len(keys)) + } + + // Test if moving the min_encryption_version and min_decryption_versions + // are working fine + + // Set min encryption version to 10 + req.Path = "keys/aes/config" + req.Operation = logical.UpdateOperation + req.Data = map[string]interface{}{ + "min_encryption_version": 10, + } + doReq(t, req) + if p.MinEncryptionVersion != 10 { + t.Fatalf("failed to set min encryption version") + } + + // Set min decryption version to 9 + req.Data = map[string]interface{}{ + "min_decryption_version": 9, + } + doReq(t, req) + if p.MinDecryptionVersion != 9 { + t.Fatalf("failed to set min encryption version") + } + + // Reduce the min decryption version to 8 + req.Data = map[string]interface{}{ + "min_decryption_version": 8, + } + doReq(t, req) + if p.MinDecryptionVersion != 8 { + t.Fatalf("failed to set min encryption version") + } + + // Reduce the min encryption version to 8 + req.Data = map[string]interface{}{ + "min_encryption_version": 8, + } + doReq(t, req) + if p.MinDecryptionVersion != 8 { + t.Fatalf("failed to set min decryption version") + } + + // Read the key to ensure that the keys are properly copied from the + // archive into the policy + req.Path = "keys/aes" + req.Operation = logical.ReadOperation + resp = doReq(t, req) + keys = resp.Data["keys"].(map[string]int64) + if len(keys) != 3 { + t.Fatalf("bad: number of keys; expected: 3, actual: %d", len(keys)) + } + + // Ensure that archive has remained unchanged + // Archive: 7, 8, 9, 10 + archive, err = p.LoadArchive(namespace.RootContext(nil), storage) + if err != nil { + t.Fatal(err) + } + if len(archive.Keys) != 4 { + t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys)) + } +} diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index a42108350aa0..f5193c5083b1 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -329,9 +329,9 @@ type Policy struct { // ArchiveMinVersion is the minimum version of the key in the archive. ArchiveMinVersion int `json:"archive_min_version"` - // TrimmedMinVersion is the minimum version of the key present. All key - // versions before this would have been deleted. - TrimmedMinVersion int `json:"trimmed_min_version"` + // MinVersion is the minimum version of the key present. All key versions + // before this would have been deleted. + MinVersion int `json:"min_version"` // Whether the key is allowed to be deleted DeletionAllowed bool `json:"deletion_allowed"` @@ -469,7 +469,7 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e if !keysContainsMinimum { // Need to move keys *from* archive for i := p.MinDecryptionVersion; i <= p.LatestVersion; i++ { - p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.TrimmedMinVersion] + p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.MinVersion] } return nil @@ -480,9 +480,9 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We need a size that is equivalent to the latest version (number of keys) // but adding one since slice numbering starts at 0 and we're indexing by // key version - if len(archive.Keys)+p.TrimmedMinVersion < p.LatestVersion+1 { + if len(archive.Keys)+p.MinVersion < p.LatestVersion+1 { // Increase the size of the archive slice - newKeys := make([]KeyEntry, p.LatestVersion-p.TrimmedMinVersion+1) + newKeys := make([]KeyEntry, p.LatestVersion-p.MinVersion+1) copy(newKeys, archive.Keys) archive.Keys = newKeys } @@ -490,14 +490,14 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We are storing all keys in the archive, so we ensure that it is up to // date up to p.LatestVersion for i := p.ArchiveVersion + 1; i <= p.LatestVersion; i++ { - archive.Keys[i-p.TrimmedMinVersion] = p.Keys[strconv.Itoa(i)] + archive.Keys[i-p.MinVersion] = p.Keys[strconv.Itoa(i)] p.ArchiveVersion = i } // Trim the keys if required - if p.ArchiveMinVersion < p.TrimmedMinVersion { - archive.Keys = archive.Keys[p.TrimmedMinVersion-p.ArchiveMinVersion:] - p.ArchiveMinVersion = p.TrimmedMinVersion + if p.ArchiveMinVersion < p.MinVersion { + archive.Keys = archive.Keys[p.MinVersion-p.ArchiveMinVersion:] + p.ArchiveMinVersion = p.MinVersion } err = p.storeArchive(ctx, storage, archive) From fb43329721558a0f0bdf03f9158b62f2595b32ae Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 11 Oct 2018 18:21:49 -0400 Subject: [PATCH 04/10] Remove trimmed_min_version field from config endpoint --- builtin/logical/transit/path_config.go | 5 ----- builtin/logical/transit/path_trim_test.go | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index 4537b1639a8c..66c62b080d67 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -33,11 +33,6 @@ to be used for signing. If set to zero, only the latest version of the key is allowed.`, }, - "trimmed_min_version": &framework.FieldSchema{ - Type: framework.TypeInt, - Description: `If set, permanently deletes all the key versions before the given version. This should always be greater than both 'min_decryption_version' and 'min_encryption_version'. This is not allowed to be set when either 'min_encryption_version' or 'min_decryption_version' is set to zero.`, - }, - "deletion_allowed": &framework.FieldSchema{ Type: framework.TypeBool, Description: "Whether to allow deletion of the key", diff --git a/builtin/logical/transit/path_trim_test.go b/builtin/logical/transit/path_trim_test.go index 94ce7df630a7..c341708a71e9 100644 --- a/builtin/logical/transit/path_trim_test.go +++ b/builtin/logical/transit/path_trim_test.go @@ -71,7 +71,7 @@ func TestTransit_Trim(t *testing.T) { t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys)) } - // Trimmed min version should not be set when min_encryption_version is not + // Min version should not be set when min_encryption_version is not // set req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ @@ -125,14 +125,14 @@ func TestTransit_Trim(t *testing.T) { t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys)) } - // Min decryption version should not be less than trimmed min version + // Min decryption version should not be less than min version req.Path = "keys/aes/config" req.Data = map[string]interface{}{ "min_decryption_version": 1, } doErrReq(t, req) - // Min encryption version should not be less than trimmed min version + // Min encryption version should not be less than min version req.Data = map[string]interface{}{ "min_encryption_version": 2, } @@ -168,7 +168,7 @@ func TestTransit_Trim(t *testing.T) { } doReq(t, req) - // Trimmed all versions before 7 + // Trim all versions before 7 req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ "min_version": 7, From e472dff47968f9bc98d36da4a827be4a71440bfe Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 11 Oct 2018 18:36:04 -0400 Subject: [PATCH 05/10] Fix description --- builtin/logical/transit/path_config.go | 4 ++-- builtin/logical/transit/path_keys.go | 2 +- builtin/logical/transit/path_trim.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index 66c62b080d67..c28b40973a4c 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -191,9 +191,9 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * switch { case p.MinVersion > p.MinEncryptionVersion: - return logical.ErrorResponse("min encryption version should not be less than trimmed min version"), nil + return logical.ErrorResponse("min encryption version should not be less than min version"), nil case p.MinVersion > p.MinDecryptionVersion: - return logical.ErrorResponse("min decryption version should not be less then trimmed min version"), nil + return logical.ErrorResponse("min decryption version should not be less then min version"), nil } if len(resp.Warnings) == 0 { diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 4cefb411606c..5393fa0a5fd8 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -206,9 +206,9 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f "type": p.Type.String(), "derived": p.Derived, "deletion_allowed": p.DeletionAllowed, + "min_version": p.MinVersion, "min_decryption_version": p.MinDecryptionVersion, "min_encryption_version": p.MinEncryptionVersion, - "min_version": p.MinVersion, "latest_version": p.LatestVersion, "exportable": p.Exportable, "allow_plaintext_backup": p.AllowPlaintextBackup, diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go index bc127097e48d..4f7e72363a2b 100644 --- a/builtin/logical/transit/path_trim.go +++ b/builtin/logical/transit/path_trim.go @@ -20,7 +20,7 @@ func (b *backend) pathTrim() *framework.Path { "min_version": &framework.FieldSchema{ Type: framework.TypeInt, Description: `The minimum version for the key ring. All versions before this version will be -permanently removed. This should always be greater than both +permanently removed. This value can at most be equal to the lesser of 'min_decryption_version' and 'min_encryption_version'. This is not allowed to be set when either 'min_encryption_version' or 'min_decryption_version' is set to zero.`, From f3195a25e840147a2b81e76e79e93288a4ab6815 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 11 Oct 2018 18:51:57 -0400 Subject: [PATCH 06/10] Doc updates --- builtin/logical/transit/path_trim.go | 4 +- .../source/api/secret/transit/index.html.md | 41 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go index 4f7e72363a2b..a84057b9c49d 100644 --- a/builtin/logical/transit/path_trim.go +++ b/builtin/logical/transit/path_trim.go @@ -20,7 +20,7 @@ func (b *backend) pathTrim() *framework.Path { "min_version": &framework.FieldSchema{ Type: framework.TypeInt, Description: `The minimum version for the key ring. All versions before this version will be -permanently removed. This value can at most be equal to the lesser of +permanently deleted. This value can at most be equal to the lesser of 'min_decryption_version' and 'min_encryption_version'. This is not allowed to be set when either 'min_encryption_version' or 'min_decryption_version' is set to zero.`, @@ -93,7 +93,7 @@ func (b *backend) pathTrimUpdate() framework.OperationFunc { } } -const pathTrimHelpSyn = `Trim key versions in the named key` +const pathTrimHelpSyn = `Trim key versions of a named key` const pathTrimHelpDesc = ` This path is used to trim key versions of a named key. Trimming only happens diff --git a/website/source/api/secret/transit/index.html.md b/website/source/api/secret/transit/index.html.md index b3da9628240c..469989ee1503 100644 --- a/website/source/api/secret/transit/index.html.md +++ b/website/source/api/secret/transit/index.html.md @@ -218,10 +218,6 @@ are returned during a read operation on the named key.) - `allow_plaintext_backup` `(bool: false)` - If set, enables taking backup of named key in the plaintext format. Once set, this cannot be disabled. -- `trimmed_min_version` `(int: 0)` - If set, all versions before this version - will be permanently removed from the key ring. The value can at most be equal - to the lesser of `min_encryption_version` and `min_decryption_version`. - ### Sample Payload ```json @@ -1001,3 +997,40 @@ $ curl \ --data @payload.json \ http://127.0.0.1:8200/v1/transit/restore ``` + +## Trim Key + +This endpoint trims older key versions setting a minimum version for the +keyring. Once trimmed, previous versions of the key cannot be recovered. + +| Method | Path | Produces | +| :------- | :------------------------- | :--------------------- | +| `POST` | `/transit/keys/:name/trim` | `200 application/json` | + +### Parameters + +- `min_version` `(int: )` - The minimum version for the key ring. All + versions before this version will be permanently deleted. This value can at + most be equal to the lesser of `min_decryption_version` and + `min_encryption_version`. This is not allowed to be set when either + `min_encryption_version` or `min_decryption_version` is set to zero. + +### Sample Payload + +```json +{ + { + "min_version": 2 + } +} +``` + +### Sample Request + +``` +$ curl \ + --header "X-Vault-Token: ..." \ + --request POST \ + --data @payload.json \ + http://127.0.0.1:8200/v1/transit/keys/my-key/trim +``` From 341ad439666cfa01c9e853e894e4718232e04983 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 11 Oct 2018 18:56:27 -0400 Subject: [PATCH 07/10] Fix response json in docs --- website/source/api/secret/transit/index.html.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/source/api/secret/transit/index.html.md b/website/source/api/secret/transit/index.html.md index 469989ee1503..537ee546edd5 100644 --- a/website/source/api/secret/transit/index.html.md +++ b/website/source/api/secret/transit/index.html.md @@ -1019,9 +1019,7 @@ keyring. Once trimmed, previous versions of the key cannot be recovered. ```json { - { "min_version": 2 - } } ``` From 24589c067f0be9c98e67723702dcb79444420a83 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 12 Oct 2018 14:04:37 -0400 Subject: [PATCH 08/10] Address review feedback --- builtin/logical/transit/path_trim.go | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go index a84057b9c49d..e97b1005eacb 100644 --- a/builtin/logical/transit/path_trim.go +++ b/builtin/logical/transit/path_trim.go @@ -2,7 +2,6 @@ package transit import ( "context" - "fmt" "github.com/hashicorp/vault/helper/keysutil" "github.com/hashicorp/vault/logical" @@ -59,37 +58,35 @@ func (b *backend) pathTrimUpdate() framework.OperationFunc { if !ok { return logical.ErrorResponse("missing min_version"), nil } + minVersion := minVersionRaw.(int) - // Ensure that cache doesn't get corrupted in error cases originalMinVersion := p.MinVersion - defer func() { - if retErr != nil || (resp != nil && resp.IsError()) { - p.MinVersion = originalMinVersion - } - }() - - p.MinVersion = minVersionRaw.(int) switch { - case p.MinVersion < originalMinVersion: + case minVersion < originalMinVersion: return logical.ErrorResponse("minimum version cannot be decremented"), nil case p.MinEncryptionVersion == 0: return logical.ErrorResponse("minimum version cannot be set when minimum encryption version is not set"), nil case p.MinDecryptionVersion == 0: return logical.ErrorResponse("minimum version cannot be set when minimum decryption version is not set"), nil - case p.MinVersion > p.MinEncryptionVersion: + case minVersion > p.MinEncryptionVersion: return logical.ErrorResponse("minimum version cannot be greater than minmum encryption version"), nil - case p.MinVersion > p.MinDecryptionVersion: + case minVersion > p.MinDecryptionVersion: return logical.ErrorResponse("minimum version cannot be greater than minimum decryption version"), nil - case p.MinVersion < 0: + case minVersion < 0: return logical.ErrorResponse("minimum version cannot be negative"), nil - case p.MinVersion == 0: + case minVersion == 0: return logical.ErrorResponse("minimum version should be positive"), nil - case p.MinVersion < p.MinVersion: - return logical.ErrorResponse(fmt.Sprintf("minimum version cannot be less than the already set value of %d", p.MinVersion)), nil } - return nil, p.Persist(ctx, req.Storage) + // Ensure that cache doesn't get corrupted in error cases + p.MinVersion = minVersion + if err := p.Persist(ctx, req.Storage); err != nil { + p.MinVersion = originalMinVersion + return nil, err + } + + return nil, nil } } From 6687dcbef969aa58eeb6cbcc4067e62ede6f0b9f Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 12 Oct 2018 14:16:55 -0400 Subject: [PATCH 09/10] s/min_version/min_available_version --- builtin/logical/transit/path_config.go | 4 +-- builtin/logical/transit/path_keys.go | 2 +- builtin/logical/transit/path_trim.go | 35 ++++++++++++----------- builtin/logical/transit/path_trim_test.go | 14 ++++----- helper/keysutil/policy.go | 20 ++++++------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index c28b40973a4c..a442765720b0 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -190,9 +190,9 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * } switch { - case p.MinVersion > p.MinEncryptionVersion: + case p.MinAvailableVersion > p.MinEncryptionVersion: return logical.ErrorResponse("min encryption version should not be less than min version"), nil - case p.MinVersion > p.MinDecryptionVersion: + case p.MinAvailableVersion > p.MinDecryptionVersion: return logical.ErrorResponse("min decryption version should not be less then min version"), nil } diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 5393fa0a5fd8..94068e631688 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -206,7 +206,7 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f "type": p.Type.String(), "derived": p.Derived, "deletion_allowed": p.DeletionAllowed, - "min_version": p.MinVersion, + "min_available_version": p.MinAvailableVersion, "min_decryption_version": p.MinDecryptionVersion, "min_encryption_version": p.MinEncryptionVersion, "latest_version": p.LatestVersion, diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go index e97b1005eacb..60123310d943 100644 --- a/builtin/logical/transit/path_trim.go +++ b/builtin/logical/transit/path_trim.go @@ -16,13 +16,14 @@ func (b *backend) pathTrim() *framework.Path { Type: framework.TypeString, Description: "Name of the key", }, - "min_version": &framework.FieldSchema{ + "min_available_version": &framework.FieldSchema{ Type: framework.TypeInt, - Description: `The minimum version for the key ring. All versions before this version will be -permanently deleted. This value can at most be equal to the lesser of -'min_decryption_version' and 'min_encryption_version'. This is not allowed to -be set when either 'min_encryption_version' or 'min_decryption_version' is set -to zero.`, + Description: ` +The minimum available version for the key ring. All versions before this +version will be permanently deleted. This value can at most be equal to the +lesser of 'min_decryption_version' and 'min_encryption_version'. This is not +allowed to be set when either 'min_encryption_version' or +'min_decryption_version' is set to zero.`, }, }, @@ -54,35 +55,35 @@ func (b *backend) pathTrimUpdate() framework.OperationFunc { } defer p.Unlock() - minVersionRaw, ok := d.GetOk("min_version") + minAvailableVersionRaw, ok := d.GetOk("min_available_version") if !ok { - return logical.ErrorResponse("missing min_version"), nil + return logical.ErrorResponse("missing min_available_version"), nil } - minVersion := minVersionRaw.(int) + minAvailableVersion := minAvailableVersionRaw.(int) - originalMinVersion := p.MinVersion + originalMinAvailableVersion := p.MinAvailableVersion switch { - case minVersion < originalMinVersion: + case minAvailableVersion < originalMinAvailableVersion: return logical.ErrorResponse("minimum version cannot be decremented"), nil case p.MinEncryptionVersion == 0: return logical.ErrorResponse("minimum version cannot be set when minimum encryption version is not set"), nil case p.MinDecryptionVersion == 0: return logical.ErrorResponse("minimum version cannot be set when minimum decryption version is not set"), nil - case minVersion > p.MinEncryptionVersion: + case minAvailableVersion > p.MinEncryptionVersion: return logical.ErrorResponse("minimum version cannot be greater than minmum encryption version"), nil - case minVersion > p.MinDecryptionVersion: + case minAvailableVersion > p.MinDecryptionVersion: return logical.ErrorResponse("minimum version cannot be greater than minimum decryption version"), nil - case minVersion < 0: + case minAvailableVersion < 0: return logical.ErrorResponse("minimum version cannot be negative"), nil - case minVersion == 0: + case minAvailableVersion == 0: return logical.ErrorResponse("minimum version should be positive"), nil } // Ensure that cache doesn't get corrupted in error cases - p.MinVersion = minVersion + p.MinAvailableVersion = minAvailableVersion if err := p.Persist(ctx, req.Storage); err != nil { - p.MinVersion = originalMinVersion + p.MinAvailableVersion = originalMinAvailableVersion return nil, err } diff --git a/builtin/logical/transit/path_trim_test.go b/builtin/logical/transit/path_trim_test.go index c341708a71e9..2102f6fc7927 100644 --- a/builtin/logical/transit/path_trim_test.go +++ b/builtin/logical/transit/path_trim_test.go @@ -75,7 +75,7 @@ func TestTransit_Trim(t *testing.T) { // set req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ - "min_version": 1, + "min_available_version": 1, } doErrReq(t, req) @@ -95,25 +95,25 @@ func TestTransit_Trim(t *testing.T) { // Min version cannot be greater than min encryption version req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ - "min_version": 5, + "min_available_version": 5, } doErrReq(t, req) // Min version cannot be greater than min decryption version - req.Data["min_version"] = 4 + req.Data["min_available_version"] = 4 doErrReq(t, req) // Min version cannot be negative - req.Data["min_version"] = -1 + req.Data["min_available_version"] = -1 doErrReq(t, req) // Min version should be positive - req.Data["min_version"] = 0 + req.Data["min_available_version"] = 0 doErrReq(t, req) // Trim all keys before version 3. Index 0 and index 1 will be deleted from // archived keys. - req.Data["min_version"] = 3 + req.Data["min_available_version"] = 3 doReq(t, req) // Archive: 3, 4, 5 @@ -171,7 +171,7 @@ func TestTransit_Trim(t *testing.T) { // Trim all versions before 7 req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ - "min_version": 7, + "min_available_version": 7, } doReq(t, req) diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index f5193c5083b1..4654c647b32a 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -329,9 +329,9 @@ type Policy struct { // ArchiveMinVersion is the minimum version of the key in the archive. ArchiveMinVersion int `json:"archive_min_version"` - // MinVersion is the minimum version of the key present. All key versions - // before this would have been deleted. - MinVersion int `json:"min_version"` + // MinAvailableVersion is the minimum version of the key present. All key + // versions before this would have been deleted. + MinAvailableVersion int `json:"min_available_version"` // Whether the key is allowed to be deleted DeletionAllowed bool `json:"deletion_allowed"` @@ -469,7 +469,7 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e if !keysContainsMinimum { // Need to move keys *from* archive for i := p.MinDecryptionVersion; i <= p.LatestVersion; i++ { - p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.MinVersion] + p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.MinAvailableVersion] } return nil @@ -480,9 +480,9 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We need a size that is equivalent to the latest version (number of keys) // but adding one since slice numbering starts at 0 and we're indexing by // key version - if len(archive.Keys)+p.MinVersion < p.LatestVersion+1 { + if len(archive.Keys)+p.MinAvailableVersion < p.LatestVersion+1 { // Increase the size of the archive slice - newKeys := make([]KeyEntry, p.LatestVersion-p.MinVersion+1) + newKeys := make([]KeyEntry, p.LatestVersion-p.MinAvailableVersion+1) copy(newKeys, archive.Keys) archive.Keys = newKeys } @@ -490,14 +490,14 @@ func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) e // We are storing all keys in the archive, so we ensure that it is up to // date up to p.LatestVersion for i := p.ArchiveVersion + 1; i <= p.LatestVersion; i++ { - archive.Keys[i-p.MinVersion] = p.Keys[strconv.Itoa(i)] + archive.Keys[i-p.MinAvailableVersion] = p.Keys[strconv.Itoa(i)] p.ArchiveVersion = i } // Trim the keys if required - if p.ArchiveMinVersion < p.MinVersion { - archive.Keys = archive.Keys[p.MinVersion-p.ArchiveMinVersion:] - p.ArchiveMinVersion = p.MinVersion + if p.ArchiveMinVersion < p.MinAvailableVersion { + archive.Keys = archive.Keys[p.MinAvailableVersion-p.ArchiveMinVersion:] + p.ArchiveMinVersion = p.MinAvailableVersion } err = p.storeArchive(ctx, storage, archive) From f09c5fc0e57d39e57abb533f6fa5fbf765a40f96 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Mon, 15 Oct 2018 11:45:47 -0400 Subject: [PATCH 10/10] Commenting and error statement updates --- builtin/logical/transit/path_config.go | 4 ++-- builtin/logical/transit/path_trim.go | 14 +++++++------- builtin/logical/transit/path_trim_test.go | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/builtin/logical/transit/path_config.go b/builtin/logical/transit/path_config.go index a442765720b0..ae5624b63f96 100644 --- a/builtin/logical/transit/path_config.go +++ b/builtin/logical/transit/path_config.go @@ -191,9 +191,9 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * switch { case p.MinAvailableVersion > p.MinEncryptionVersion: - return logical.ErrorResponse("min encryption version should not be less than min version"), nil + return logical.ErrorResponse("min encryption version should not be less than min available version"), nil case p.MinAvailableVersion > p.MinDecryptionVersion: - return logical.ErrorResponse("min decryption version should not be less then min version"), nil + return logical.ErrorResponse("min decryption version should not be less then min available version"), nil } if len(resp.Warnings) == 0 { diff --git a/builtin/logical/transit/path_trim.go b/builtin/logical/transit/path_trim.go index 60123310d943..363ad0e6a4f4 100644 --- a/builtin/logical/transit/path_trim.go +++ b/builtin/logical/transit/path_trim.go @@ -65,19 +65,19 @@ func (b *backend) pathTrimUpdate() framework.OperationFunc { switch { case minAvailableVersion < originalMinAvailableVersion: - return logical.ErrorResponse("minimum version cannot be decremented"), nil + return logical.ErrorResponse("minimum available version cannot be decremented"), nil case p.MinEncryptionVersion == 0: - return logical.ErrorResponse("minimum version cannot be set when minimum encryption version is not set"), nil + return logical.ErrorResponse("minimum available version cannot be set when minimum encryption version is not set"), nil case p.MinDecryptionVersion == 0: - return logical.ErrorResponse("minimum version cannot be set when minimum decryption version is not set"), nil + return logical.ErrorResponse("minimum available version cannot be set when minimum decryption version is not set"), nil case minAvailableVersion > p.MinEncryptionVersion: - return logical.ErrorResponse("minimum version cannot be greater than minmum encryption version"), nil + return logical.ErrorResponse("minimum available version cannot be greater than minmum encryption version"), nil case minAvailableVersion > p.MinDecryptionVersion: - return logical.ErrorResponse("minimum version cannot be greater than minimum decryption version"), nil + return logical.ErrorResponse("minimum available version cannot be greater than minimum decryption version"), nil case minAvailableVersion < 0: - return logical.ErrorResponse("minimum version cannot be negative"), nil + return logical.ErrorResponse("minimum available version cannot be negative"), nil case minAvailableVersion == 0: - return logical.ErrorResponse("minimum version should be positive"), nil + return logical.ErrorResponse("minimum available version should be positive"), nil } // Ensure that cache doesn't get corrupted in error cases diff --git a/builtin/logical/transit/path_trim_test.go b/builtin/logical/transit/path_trim_test.go index 2102f6fc7927..cab6fba56b17 100644 --- a/builtin/logical/transit/path_trim_test.go +++ b/builtin/logical/transit/path_trim_test.go @@ -71,7 +71,7 @@ func TestTransit_Trim(t *testing.T) { t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys)) } - // Min version should not be set when min_encryption_version is not + // Min available version should not be set when min_encryption_version is not // set req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ @@ -92,22 +92,22 @@ func TestTransit_Trim(t *testing.T) { } doReq(t, req) - // Min version cannot be greater than min encryption version + // Min available version cannot be greater than min encryption version req.Path = "keys/aes/trim" req.Data = map[string]interface{}{ "min_available_version": 5, } doErrReq(t, req) - // Min version cannot be greater than min decryption version + // Min available version cannot be greater than min decryption version req.Data["min_available_version"] = 4 doErrReq(t, req) - // Min version cannot be negative + // Min available version cannot be negative req.Data["min_available_version"] = -1 doErrReq(t, req) - // Min version should be positive + // Min available version should be positive req.Data["min_available_version"] = 0 doErrReq(t, req) @@ -125,14 +125,14 @@ func TestTransit_Trim(t *testing.T) { t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys)) } - // Min decryption version should not be less than min version + // Min decryption version should not be less than min available version req.Path = "keys/aes/config" req.Data = map[string]interface{}{ "min_decryption_version": 1, } doErrReq(t, req) - // Min encryption version should not be less than min version + // Min encryption version should not be less than min available version req.Data = map[string]interface{}{ "min_encryption_version": 2, }