From 770d902f601a6ba318bec4fa72e5bc7f33305b9e Mon Sep 17 00:00:00 2001 From: Victor Rodriguez Date: Wed, 9 Oct 2024 10:04:50 -0400 Subject: [PATCH 1/7] Use stored seal generation info for response to sys/seal-backend-status (#28631) Use stored seal generation info for response to sys/seal-backend-status. --- changelog/28631.txt | 3 +++ vault/logical_system.go | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 changelog/28631.txt diff --git a/changelog/28631.txt b/changelog/28631.txt new file mode 100644 index 000000000000..a4857ea11233 --- /dev/null +++ b/changelog/28631.txt @@ -0,0 +1,3 @@ +```release-note:bug +core/seal: Fix an issue that could cause reading from sys/seal-backend-status to return stale information. +``` diff --git a/vault/logical_system.go b/vault/logical_system.go index 2fcfcc788fea..f57c239d8bb8 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -5642,7 +5642,16 @@ func (c *Core) GetSealBackendStatus(ctx context.Context) (*SealBackendStatusResp if err != nil { return nil, fmt.Errorf("could not list partially seal wrapped values: %w", err) } - genInfo := c.seal.GetAccess().GetSealGenerationInfo() + // When multi-seal is enabled, use the stored seal generation information. Note that the in-memory + // value may not be up-to-date on non-active nodes. + genInfo, err := PhysicalSealGenInfo(ctx, c.physical) + if err != nil { + return nil, fmt.Errorf("could not read seal generation information: %w", err) + } + if genInfo == nil { + // Multi-seal is not enabled, use the in-memory value. + genInfo = c.seal.GetAccess().GetSealGenerationInfo() + } r.FullyWrapped = genInfo.IsRewrapped() && len(pps) == 0 return &r, nil } From 3c0656e4c4a3ebfdad14b88dc9d0461ada3eb986 Mon Sep 17 00:00:00 2001 From: Scott Miller Date: Wed, 9 Oct 2024 09:30:14 -0500 Subject: [PATCH 2/7] Update marcellanz/transit_pkcs1v15 RSA encryption support (#25486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [transit-pkcs1v15] transit support for the pkcs1v15 padding scheme – without UI tests (yet). * [transit-pkcs1v15] renamed padding_scheme parameter in transit documentation. * [transit-pkcs1v15] add changelog file. * [transit-pkcs1v15] remove the algorithm path as padding_scheme is chosen by parameter. * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Add warnings to PKCS1v1.5 usage * Update transit * Update transit, including separating encrypt/decrypt paddings for rewrap * Clean up factory use in the presence of padding * address review feedback * remove defaults * lint * more lint * Some fixes for UI issues - Fix padding scheme dropdown console error by adding values to the transit-key-actions.hbs - Populate both padding scheme drop down menus within rewrap, not just the one padding_scheme - Do not submit a padding_scheme value through POST for non-rsa keys * Fix Transit rewrap API to use decrypt_padding_scheme, encrypt_padding_scheme - Map the appropriate API fields for the RSA padding scheme to the batch items within the rewrap API - Add the ability to create RSA keys within the encrypt API endpoint - Add test case for rewrap api that leverages the padding_scheme fields * Fix code linting issues * simply padding scheme enum * Apply suggestions from code review Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Fix padding_scheme processing on data key api - The data key api was using the incorrect parameter name for the padding scheme - Enforce that padding_scheme is only used on RSA keys, we are punting on supporting it for managed keys at the moment. * Add tests for parsePaddingSchemeArg * Add missing copywrite headers * Some small UI fixes * Add missing param to datakey in api-docs * Do not send padding_scheme for non-RSA key types within UI * add UI tests for transit key actions form --------- Co-authored-by: Marcel Lanz Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> Co-authored-by: Steve Clark Co-authored-by: claire bontempo --- builtin/logical/transit/api_utils.go | 29 ++++ builtin/logical/transit/api_utils_test.go | 52 +++++++ builtin/logical/transit/backend_test.go | 145 ++++++++++-------- builtin/logical/transit/path_datakey.go | 22 ++- builtin/logical/transit/path_datakey_test.go | 125 +++++++++++++++ builtin/logical/transit/path_decrypt.go | 28 +++- builtin/logical/transit/path_encrypt.go | 48 +++++- builtin/logical/transit/path_rewrap.go | 80 +++++++++- builtin/logical/transit/path_rewrap_test.go | 113 ++++++++++++++ changelog/25486.txt | 3 + sdk/helper/keysutil/policy.go | 83 +++++++++- sdk/helper/keysutil/policy_test.go | 80 +++++++++- ui/app/components/transit-key-actions.hbs | 5 + ui/app/components/transit-key-actions.js | 22 ++- .../components/transit-key-action/datakey.hbs | 26 +++- .../components/transit-key-action/decrypt.hbs | 26 +++- .../components/transit-key-action/encrypt.hbs | 29 +++- .../components/transit-key-action/rewrap.hbs | 51 +++++- .../components/transit-key-actions-test.js | 44 ++++++ website/content/api-docs/secret/transit.mdx | 42 +++++ 20 files changed, 949 insertions(+), 104 deletions(-) create mode 100644 builtin/logical/transit/api_utils.go create mode 100644 builtin/logical/transit/api_utils_test.go create mode 100644 builtin/logical/transit/path_datakey_test.go create mode 100644 changelog/25486.txt diff --git a/builtin/logical/transit/api_utils.go b/builtin/logical/transit/api_utils.go new file mode 100644 index 000000000000..75ba53a2b220 --- /dev/null +++ b/builtin/logical/transit/api_utils.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package transit + +import ( + "fmt" + + "github.com/hashicorp/vault/sdk/helper/keysutil" +) + +// parsePaddingSchemeArg validate that the provided padding scheme argument received on the api can be used. +func parsePaddingSchemeArg(keyType keysutil.KeyType, rawPs any) (keysutil.PaddingScheme, error) { + ps, ok := rawPs.(string) + if !ok { + return "", fmt.Errorf("argument was not a string: %T", rawPs) + } + + paddingScheme, err := keysutil.ParsePaddingScheme(ps) + if err != nil { + return "", err + } + + if !keyType.PaddingSchemesSupported() { + return "", fmt.Errorf("unsupported key type %s for padding scheme", keyType.String()) + } + + return paddingScheme, nil +} diff --git a/builtin/logical/transit/api_utils_test.go b/builtin/logical/transit/api_utils_test.go new file mode 100644 index 000000000000..96223a6c69cd --- /dev/null +++ b/builtin/logical/transit/api_utils_test.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package transit + +import ( + "testing" + + "github.com/hashicorp/vault/sdk/helper/keysutil" +) + +// Test_parsePaddingSchemeArg validate the various use cases we have around parsing +// the various padding_scheme arg possible values. +func Test_parsePaddingSchemeArg(t *testing.T) { + type args struct { + keyType keysutil.KeyType + rawPs any + } + tests := []struct { + name string + args args + want keysutil.PaddingScheme + wantErr bool + }{ + // Error cases + {name: "nil-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: nil}, wantErr: true}, + {name: "nonstring-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: 5}, wantErr: true}, + {name: "invalid-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: "unknown"}, wantErr: true}, + {name: "bad-keytype-oaep", args: args{keyType: keysutil.KeyType_AES128_CMAC, rawPs: "oaep"}, wantErr: true}, + {name: "bad-keytype-pkcs1", args: args{keyType: keysutil.KeyType_ECDSA_P256, rawPs: "pkcs1v15"}, wantErr: true}, + {name: "oaep-capped", args: args{keyType: keysutil.KeyType_RSA4096, rawPs: "OAEP"}, wantErr: true}, + {name: "pkcs1-whitespace", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: " pkcs1v15 "}, wantErr: true}, + + // Valid cases + {name: "oaep-2048", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP}, + {name: "oaep-3072", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP}, + {name: "oaep-4096", args: args{keyType: keysutil.KeyType_RSA4096, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP}, + {name: "pkcs1", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: "pkcs1v15"}, want: keysutil.PaddingScheme_PKCS1v15}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parsePaddingSchemeArg(tt.args.keyType, tt.args.rawPs) + if (err != nil) != tt.wantErr { + t.Errorf("parsePaddingSchemeArg() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("parsePaddingSchemeArg() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index 528ccf68218c..ff3afb1189c6 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -148,83 +148,96 @@ func testTransit_RSA(t *testing.T, keyType string) { plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox" - encryptReq := &logical.Request{ - Path: "encrypt/rsa", - Operation: logical.UpdateOperation, - Storage: storage, - Data: map[string]interface{}{ - "plaintext": plaintext, - }, - } + for _, padding := range []keysutil.PaddingScheme{keysutil.PaddingScheme_OAEP, keysutil.PaddingScheme_PKCS1v15, ""} { + encryptReq := &logical.Request{ + Path: "encrypt/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "plaintext": plaintext, + }, + } - resp, err = b.HandleRequest(context.Background(), encryptReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } + if padding != "" { + encryptReq.Data["padding_scheme"] = padding + } - ciphertext1 := resp.Data["ciphertext"].(string) + resp, err = b.HandleRequest(context.Background(), encryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } - decryptReq := &logical.Request{ - Path: "decrypt/rsa", - Operation: logical.UpdateOperation, - Storage: storage, - Data: map[string]interface{}{ - "ciphertext": ciphertext1, - }, - } + ciphertext1 := resp.Data["ciphertext"].(string) - resp, err = b.HandleRequest(context.Background(), decryptReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } + decryptReq := &logical.Request{ + Path: "decrypt/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "ciphertext": ciphertext1, + }, + } + if padding != "" { + decryptReq.Data["padding_scheme"] = padding + } - decryptedPlaintext := resp.Data["plaintext"] + resp, err = b.HandleRequest(context.Background(), decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } - if plaintext != decryptedPlaintext { - t.Fatalf("bad: plaintext; expected: %q\nactual: %q", plaintext, decryptedPlaintext) - } + decryptedPlaintext := resp.Data["plaintext"] - // Rotate the key - rotateReq := &logical.Request{ - Path: "keys/rsa/rotate", - Operation: logical.UpdateOperation, - Storage: storage, - } - resp, err = b.HandleRequest(context.Background(), rotateReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } + if plaintext != decryptedPlaintext { + t.Fatalf("bad: plaintext; expected: %q\nactual: %q", plaintext, decryptedPlaintext) + } - // Encrypt again - resp, err = b.HandleRequest(context.Background(), encryptReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } - ciphertext2 := resp.Data["ciphertext"].(string) + // Rotate the key + rotateReq := &logical.Request{ + Path: "keys/rsa/rotate", + Operation: logical.UpdateOperation, + Storage: storage, + } + resp, err = b.HandleRequest(context.Background(), rotateReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } - if ciphertext1 == ciphertext2 { - t.Fatalf("expected different ciphertexts") - } + // Encrypt again + resp, err = b.HandleRequest(context.Background(), encryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + ciphertext2 := resp.Data["ciphertext"].(string) - // See if the older ciphertext can still be decrypted - resp, err = b.HandleRequest(context.Background(), decryptReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } - if resp.Data["plaintext"].(string) != plaintext { - t.Fatal("failed to decrypt old ciphertext after rotating the key") - } + if ciphertext1 == ciphertext2 { + t.Fatalf("expected different ciphertexts") + } - // Decrypt the new ciphertext - decryptReq.Data = map[string]interface{}{ - "ciphertext": ciphertext2, - } - resp, err = b.HandleRequest(context.Background(), decryptReq) - if err != nil || (resp != nil && resp.IsError()) { - t.Fatalf("bad: err: %v\nresp: %#v", err, resp) - } - if resp.Data["plaintext"].(string) != plaintext { - t.Fatal("failed to decrypt ciphertext after rotating the key") + // See if the older ciphertext can still be decrypted + resp, err = b.HandleRequest(context.Background(), decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if resp.Data["plaintext"].(string) != plaintext { + t.Fatal("failed to decrypt old ciphertext after rotating the key") + } + + // Decrypt the new ciphertext + decryptReq.Data = map[string]interface{}{ + "ciphertext": ciphertext2, + } + if padding != "" { + decryptReq.Data["padding_scheme"] = padding + } + + resp, err = b.HandleRequest(context.Background(), decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if resp.Data["plaintext"].(string) != plaintext { + t.Fatal("failed to decrypt ciphertext after rotating the key") + } } signReq := &logical.Request{ diff --git a/builtin/logical/transit/path_datakey.go b/builtin/logical/transit/path_datakey.go index 53aff54690bb..47969673d72b 100644 --- a/builtin/logical/transit/path_datakey.go +++ b/builtin/logical/transit/path_datakey.go @@ -39,6 +39,12 @@ func (b *backend) pathDatakey() *framework.Path { ciphertext; "wrapped" will return the ciphertext only.`, }, + "padding_scheme": { + Type: framework.TypeString, + Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types. +Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`, + }, + "context": { Type: framework.TypeString, Description: "Context for key derivation. Required for derived keys.", @@ -142,23 +148,31 @@ func (b *backend) pathDatakeyWrite(ctx context.Context, req *logical.Request, d return nil, err } - var managedKeyFactory ManagedKeyFactory + factories := make([]any, 0) + if ps, ok := d.GetOk("padding_scheme"); ok { + paddingScheme, err := parsePaddingSchemeArg(p.Type, ps) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("padding_scheme argument invalid: %s", err.Error())), logical.ErrInvalidRequest + } + factories = append(factories, paddingScheme) + + } if p.Type == keysutil.KeyType_MANAGED_KEY { managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView) if !ok { return nil, errors.New("unsupported system view") } - managedKeyFactory = ManagedKeyFactory{ + factories = append(factories, ManagedKeyFactory{ managedKeyParams: keysutil.ManagedKeyParameters{ ManagedKeySystemView: managedKeySystemView, BackendUUID: b.backendUUID, Context: ctx, }, - } + }) } - ciphertext, err := p.EncryptWithFactory(ver, context, nonce, base64.StdEncoding.EncodeToString(newKey), nil, managedKeyFactory) + ciphertext, err := p.EncryptWithFactory(ver, context, nonce, base64.StdEncoding.EncodeToString(newKey), factories...) if err != nil { switch err.(type) { case errutil.UserError: diff --git a/builtin/logical/transit/path_datakey_test.go b/builtin/logical/transit/path_datakey_test.go new file mode 100644 index 000000000000..2207419f84e4 --- /dev/null +++ b/builtin/logical/transit/path_datakey_test.go @@ -0,0 +1,125 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package transit + +import ( + "context" + "testing" + + "github.com/hashicorp/vault/sdk/logical" + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/require" +) + +// TestDataKeyWithPaddingScheme validates that we properly leverage padding scheme +// args for the returned keys +func TestDataKeyWithPaddingScheme(t *testing.T) { + b, s := createBackendWithStorage(t) + keyName := "test" + createKeyReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "keys/" + keyName, + Storage: s, + Data: map[string]interface{}{ + "type": "rsa-2048", + }, + } + + resp, err := b.HandleRequest(context.Background(), createKeyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("failed key creation: err: %v resp: %#v", err, resp) + } + + tests := []struct { + Name string + PaddingScheme string + DecryptPaddingScheme string + ShouldFailToDecrypt bool + }{ + {"no-padding-scheme", "", "", false}, + {"oaep", "oaep", "oaep", false}, + {"pkcs1v15", "pkcs1v15", "pkcs1v15", false}, + {"mixed-should-fail", "pkcs1v15", "oaep", true}, + {"mixed-based-on-default-should-fail", "", "pkcs1v15", true}, + } + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + dataKeyReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "datakey/wrapped/" + keyName, + Storage: s, + Data: map[string]interface{}{}, + } + if len(tc.PaddingScheme) > 0 { + dataKeyReq.Data["padding_scheme"] = tc.PaddingScheme + } + + resp, err = b.HandleRequest(context.Background(), dataKeyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("failed data key api: err: %v resp: %#v", err, resp) + } + require.NotNil(t, resp, "Got nil nil response") + var d struct { + Ciphertext string `mapstructure:"ciphertext"` + } + err = mapstructure.Decode(resp.Data, &d) + require.NoError(t, err, "failed decoding datakey api response") + require.NotEmpty(t, d.Ciphertext, "ciphertext should not be empty") + + // Attempt to decrypt with data key with the same padding scheme + decryptReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "decrypt/" + keyName, + Storage: s, + Data: map[string]interface{}{ + "ciphertext": d.Ciphertext, + }, + } + if len(tc.DecryptPaddingScheme) > 0 { + decryptReq.Data["padding_scheme"] = tc.DecryptPaddingScheme + } + + resp, err = b.HandleRequest(context.Background(), decryptReq) + if tc.ShouldFailToDecrypt { + require.Error(t, err, "Should have failed decryption as padding schemes are mixed") + } else { + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("failed to decrypt data key: err: %v resp: %#v", err, resp) + } + } + }) + } +} + +// TestDataKeyWithPaddingSchemeInvalidKeyType validates we fail when we specify a +// padding_scheme value on an invalid key type (non-RSA) +func TestDataKeyWithPaddingSchemeInvalidKeyType(t *testing.T) { + b, s := createBackendWithStorage(t) + keyName := "test" + createKeyReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "keys/" + keyName, + Storage: s, + Data: map[string]interface{}{}, + } + + resp, err := b.HandleRequest(context.Background(), createKeyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("failed key creation: err: %v resp: %#v", err, resp) + } + + dataKeyReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "datakey/wrapped/" + keyName, + Storage: s, + Data: map[string]interface{}{ + "padding_scheme": "oaep", + }, + } + + resp, err = b.HandleRequest(context.Background(), dataKeyReq) + require.ErrorContains(t, err, "invalid request") + require.NotNil(t, resp, "response should not be nil") + require.Contains(t, resp.Error().Error(), "padding_scheme argument invalid: unsupported key") +} diff --git a/builtin/logical/transit/path_decrypt.go b/builtin/logical/transit/path_decrypt.go index 1daf74daf5d1..dc1bcbf608ce 100644 --- a/builtin/logical/transit/path_decrypt.go +++ b/builtin/logical/transit/path_decrypt.go @@ -50,6 +50,12 @@ func (b *backend) pathDecrypt() *framework.Path { The ciphertext to decrypt, provided as returned by encrypt.`, }, + "padding_scheme": { + Type: framework.TypeString, + Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types. +Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`, + }, + "context": { Type: framework.TypeString, Description: ` @@ -130,6 +136,9 @@ func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, d Nonce: d.Get("nonce").(string), AssociatedData: d.Get("associated_data").(string), } + if ps, ok := d.GetOk("padding_scheme"); ok { + batchInputItems[0].PaddingScheme = ps.(string) + } } batchResponseItems := make([]DecryptBatchResponseItem, len(batchInputItems)) @@ -192,33 +201,40 @@ func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, d continue } - var factory interface{} + var factories []any + if item.PaddingScheme != "" { + paddingScheme, err := parsePaddingSchemeArg(p.Type, item.PaddingScheme) + if err != nil { + batchResponseItems[i].Error = fmt.Sprintf("'[%d].padding_scheme' invalid: %s", i, err.Error()) + continue + } + factories = append(factories, paddingScheme) + } if item.AssociatedData != "" { if !p.Type.AssociatedDataSupported() { batchResponseItems[i].Error = fmt.Sprintf("'[%d].associated_data' provided for non-AEAD cipher suite %v", i, p.Type.String()) continue } - factory = AssocDataFactory{item.AssociatedData} + factories = append(factories, AssocDataFactory{item.AssociatedData}) } - var managedKeyFactory ManagedKeyFactory if p.Type == keysutil.KeyType_MANAGED_KEY { managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView) if !ok { batchResponseItems[i].Error = errors.New("unsupported system view").Error() } - managedKeyFactory = ManagedKeyFactory{ + factories = append(factories, ManagedKeyFactory{ managedKeyParams: keysutil.ManagedKeyParameters{ ManagedKeySystemView: managedKeySystemView, BackendUUID: b.backendUUID, Context: ctx, }, - } + }) } - plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factory, managedKeyFactory) + plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factories...) if err != nil { switch err.(type) { case errutil.InternalError: diff --git a/builtin/logical/transit/path_encrypt.go b/builtin/logical/transit/path_encrypt.go index 38c618f9b363..c0502db4292a 100644 --- a/builtin/logical/transit/path_encrypt.go +++ b/builtin/logical/transit/path_encrypt.go @@ -34,6 +34,9 @@ type BatchRequestItem struct { // Ciphertext for decryption Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"` + // PaddingScheme for encryption/decryption + PaddingScheme string `json:"padding_scheme" structs:"padding_scheme" mapstructure:"padding_scheme"` + // Nonce to be used when v1 convergent encryption is used Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"` @@ -105,6 +108,12 @@ func (b *backend) pathEncrypt() *framework.Path { Description: "Base64 encoded plaintext value to be encrypted", }, + "padding_scheme": { + Type: framework.TypeString, + Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types. +Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`, + }, + "context": { Type: framework.TypeString, Description: "Base64 encoded context for key derivation. Required if key derivation is enabled", @@ -259,6 +268,13 @@ func decodeBatchRequestItems(src interface{}, requirePlaintext bool, requireCiph } else if requirePlaintext { errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].plaintext' missing plaintext to encrypt", i)) } + if v, has := item["padding_scheme"]; has { + if casted, ok := v.(string); ok { + (*dst)[i].PaddingScheme = casted + } else { + errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].padding_scheme' expected type 'string', got unconvertible type '%T'", i, item["padding_scheme"])) + } + } if v, has := item["nonce"]; has { if !reflect.ValueOf(v).IsValid() { @@ -358,6 +374,13 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d KeyVersion: d.Get("key_version").(int), AssociatedData: d.Get("associated_data").(string), } + if psRaw, ok := d.GetOk("padding_scheme"); ok { + if ps, ok := psRaw.(string); ok { + batchInputItems[0].PaddingScheme = ps + } else { + return logical.ErrorResponse("padding_scheme was not a string"), logical.ErrInvalidRequest + } + } } batchResponseItems := make([]EncryptBatchResponseItem, len(batchInputItems)) @@ -435,6 +458,12 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d polReq.KeyType = keysutil.KeyType_AES256_GCM96 case "chacha20-poly1305": polReq.KeyType = keysutil.KeyType_ChaCha20_Poly1305 + case "rsa-2048": + polReq.KeyType = keysutil.KeyType_RSA2048 + case "rsa-3072": + polReq.KeyType = keysutil.KeyType_RSA3072 + case "rsa-4096": + polReq.KeyType = keysutil.KeyType_RSA4096 case "ecdsa-p256", "ecdsa-p384", "ecdsa-p521": return logical.ErrorResponse(fmt.Sprintf("key type %v not supported for this operation", keyType)), logical.ErrInvalidRequest case "managed_key": @@ -482,33 +511,40 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d warnAboutNonceUsage = true } - var factory interface{} + var factories []any + if item.PaddingScheme != "" { + paddingScheme, err := parsePaddingSchemeArg(p.Type, item.PaddingScheme) + if err != nil { + batchResponseItems[i].Error = fmt.Sprintf("'[%d].padding_scheme' invalid: %s", i, err.Error()) + continue + } + factories = append(factories, paddingScheme) + } if item.AssociatedData != "" { if !p.Type.AssociatedDataSupported() { batchResponseItems[i].Error = fmt.Sprintf("'[%d].associated_data' provided for non-AEAD cipher suite %v", i, p.Type.String()) continue } - factory = AssocDataFactory{item.AssociatedData} + factories = append(factories, AssocDataFactory{item.AssociatedData}) } - var managedKeyFactory ManagedKeyFactory if p.Type == keysutil.KeyType_MANAGED_KEY { managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView) if !ok { batchResponseItems[i].Error = errors.New("unsupported system view").Error() } - managedKeyFactory = ManagedKeyFactory{ + factories = append(factories, ManagedKeyFactory{ managedKeyParams: keysutil.ManagedKeyParameters{ ManagedKeySystemView: managedKeySystemView, BackendUUID: b.backendUUID, Context: ctx, }, - } + }) } - ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, item.Plaintext, factory, managedKeyFactory) + ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, item.Plaintext, factories...) if err != nil { switch err.(type) { case errutil.InternalError: diff --git a/builtin/logical/transit/path_rewrap.go b/builtin/logical/transit/path_rewrap.go index 49b69c7255e1..ea5f8cccd4a7 100644 --- a/builtin/logical/transit/path_rewrap.go +++ b/builtin/logical/transit/path_rewrap.go @@ -19,6 +19,39 @@ import ( var ErrNonceNotAllowed = errors.New("provided nonce not allowed for this key") +type RewrapBatchRequestItem struct { + // Context for key derivation. This is required for derived keys. + Context string `json:"context" structs:"context" mapstructure:"context"` + + // DecodedContext is the base64 decoded version of Context + DecodedContext []byte + + // Ciphertext for decryption + Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"` + + // Nonce to be used when v1 convergent encryption is used + Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"` + + // The key version to be used for encryption + KeyVersion int `json:"key_version" structs:"key_version" mapstructure:"key_version"` + + // DecodedNonce is the base64 decoded version of Nonce + DecodedNonce []byte + + // Associated Data for AEAD ciphers + AssociatedData string `json:"associated_data" struct:"associated_data" mapstructure:"associated_data"` + + // Reference is an arbitrary caller supplied string value that will be placed on the + // batch response to ease correlation between inputs and outputs + Reference string `json:"reference" structs:"reference" mapstructure:"reference"` + + // EncryptPaddingScheme specifies the RSA padding scheme for encryption + EncryptPaddingScheme string `json:"encrypt_padding_scheme" structs:"encrypt_padding_scheme" mapstructure:"encrypt_padding_scheme"` + + // DecryptPaddingScheme specifies the RSA padding scheme for decryption + DecryptPaddingScheme string `json:"decrypt_padding_scheme" structs:"decrypt_padding_scheme" mapstructure:"decrypt_padding_scheme"` +} + func (b *backend) pathRewrap() *framework.Path { return &framework.Path{ Pattern: "rewrap/" + framework.GenericNameRegex("name"), @@ -39,6 +72,18 @@ func (b *backend) pathRewrap() *framework.Path { Description: "Ciphertext value to rewrap", }, + "encrypt_padding_scheme": { + Type: framework.TypeString, + Description: `The padding scheme to use for rewrap's encrypt step. Currently only applies to RSA key types. +Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`, + }, + + "decrypt_padding_scheme": { + Type: framework.TypeString, + Description: `The padding scheme to use for rewrap's decrypt step. Currently only applies to RSA key types. +Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`, + }, + "context": { Type: framework.TypeString, Description: "Base64 encoded context for key derivation. Required for derived keys.", @@ -76,7 +121,7 @@ Any batch output will preserve the order of the batch input.`, func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { batchInputRaw := d.Raw["batch_input"] - var batchInputItems []BatchRequestItem + var batchInputItems []RewrapBatchRequestItem var err error if batchInputRaw != nil { err = mapstructure.Decode(batchInputRaw, &batchInputItems) @@ -93,13 +138,19 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d * return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest } - batchInputItems = make([]BatchRequestItem, 1) - batchInputItems[0] = BatchRequestItem{ + batchInputItems = make([]RewrapBatchRequestItem, 1) + batchInputItems[0] = RewrapBatchRequestItem{ Ciphertext: ciphertext, Context: d.Get("context").(string), Nonce: d.Get("nonce").(string), KeyVersion: d.Get("key_version").(int), } + if ps, ok := d.GetOk("decrypt_padding_scheme"); ok { + batchInputItems[0].DecryptPaddingScheme = ps.(string) + } + if ps, ok := d.GetOk("encrypt_padding_scheme"); ok { + batchInputItems[0].EncryptPaddingScheme = ps.(string) + } } batchResponseItems := make([]EncryptBatchResponseItem, len(batchInputItems)) @@ -156,12 +207,21 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d * continue } + var factories []any + if item.DecryptPaddingScheme != "" { + paddingScheme, err := parsePaddingSchemeArg(p.Type, item.DecryptPaddingScheme) + if err != nil { + batchResponseItems[i].Error = fmt.Sprintf("'[%d].decrypt_padding_scheme' invalid: %s", i, err.Error()) + continue + } + factories = append(factories, paddingScheme) + } if item.Nonce != "" && !nonceAllowed(p) { batchResponseItems[i].Error = ErrNonceNotAllowed.Error() continue } - plaintext, err := p.Decrypt(item.DecodedContext, item.DecodedNonce, item.Ciphertext) + plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factories...) if err != nil { switch err.(type) { case errutil.UserError: @@ -172,11 +232,21 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d * } } + factories = make([]any, 0) + if item.EncryptPaddingScheme != "" { + paddingScheme, err := parsePaddingSchemeArg(p.Type, item.EncryptPaddingScheme) + if err != nil { + batchResponseItems[i].Error = fmt.Sprintf("'[%d].encrypt_padding_scheme' invalid: %s", i, err.Error()) + continue + } + factories = append(factories, paddingScheme) + factories = append(factories, keysutil.PaddingScheme(item.EncryptPaddingScheme)) + } if !warnAboutNonceUsage && shouldWarnAboutNonceUsage(p, item.DecodedNonce) { warnAboutNonceUsage = true } - ciphertext, err := p.Encrypt(item.KeyVersion, item.DecodedContext, item.DecodedNonce, plaintext) + ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, plaintext, factories...) if err != nil { switch err.(type) { case errutil.UserError: diff --git a/builtin/logical/transit/path_rewrap_test.go b/builtin/logical/transit/path_rewrap_test.go index 55f28874656e..4018d63ae8fa 100644 --- a/builtin/logical/transit/path_rewrap_test.go +++ b/builtin/logical/transit/path_rewrap_test.go @@ -326,3 +326,116 @@ func TestTransit_BatchRewrapCase3(t *testing.T) { } } + +// TestTransit_BatchRewrapCase4 batch rewrap leveraging RSA padding schemes +func TestTransit_BatchRewrapCase4(t *testing.T) { + var resp *logical.Response + var err error + + b, s := createBackendWithStorage(t) + + batchEncryptionInput := []interface{}{ + map[string]interface{}{"plaintext": "dmlzaGFsCg==", "reference": "ek", "padding_scheme": "pkcs1v15"}, + map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "reference": "do", "padding_scheme": "pkcs1v15"}, + } + batchEncryptionData := map[string]interface{}{ + "type": "rsa-2048", + "batch_input": batchEncryptionInput, + } + batchReq := &logical.Request{ + Operation: logical.CreateOperation, + Path: "encrypt/upserted_key", + Storage: s, + Data: batchEncryptionData, + } + resp, err = b.HandleRequest(context.Background(), batchReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + + batchEncryptionResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem) + + batchRewrapInput := make([]interface{}, len(batchEncryptionResponseItems)) + for i, item := range batchEncryptionResponseItems { + batchRewrapInput[i] = map[string]interface{}{ + "ciphertext": item.Ciphertext, + "reference": item.Reference, + "decrypt_padding_scheme": "pkcs1v15", + "encrypt_padding_scheme": "oaep", + } + } + + batchRewrapData := map[string]interface{}{ + "batch_input": batchRewrapInput, + } + + rotateReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "keys/upserted_key/rotate", + Storage: s, + } + resp, err = b.HandleRequest(context.Background(), rotateReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + + rewrapReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "rewrap/upserted_key", + Storage: s, + Data: batchRewrapData, + } + + resp, err = b.HandleRequest(context.Background(), rewrapReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + + batchRewrapResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem) + + if len(batchRewrapResponseItems) != len(batchEncryptionResponseItems) { + t.Fatalf("bad: length of input and output or rewrap are not matching; expected: %d, actual: %d", len(batchEncryptionResponseItems), len(batchRewrapResponseItems)) + } + + decReq := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "decrypt/upserted_key", + Storage: s, + } + + for i, eItem := range batchEncryptionResponseItems { + rItem := batchRewrapResponseItems[i] + + inputRef := batchEncryptionInput[i].(map[string]interface{})["reference"] + if eItem.Reference != inputRef { + t.Fatalf("bad: reference mismatch. Expected %s, Actual: %s", inputRef, eItem.Reference) + } + + if eItem.Ciphertext == rItem.Ciphertext { + t.Fatalf("bad: rewrap input and output are the same") + } + + if !strings.HasPrefix(rItem.Ciphertext, "vault:v2") { + t.Fatalf("bad: invalid version of ciphertext in rewrap response; expected: 'vault:v2', actual: %s", rItem.Ciphertext) + } + + if rItem.KeyVersion != 2 { + t.Fatalf("unexpected key version; got: %d, expected: %d", rItem.KeyVersion, 2) + } + + decReq.Data = map[string]interface{}{ + "ciphertext": rItem.Ciphertext, + } + + resp, err = b.HandleRequest(context.Background(), decReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + + plaintext1 := "dGhlIHF1aWNrIGJyb3duIGZveA==" + plaintext2 := "dmlzaGFsCg==" + if resp.Data["plaintext"] != plaintext1 && resp.Data["plaintext"] != plaintext2 { + t.Fatalf("bad: plaintext. Expected: %q or %q, Actual: %q", plaintext1, plaintext2, resp.Data["plaintext"]) + } + } +} diff --git a/changelog/25486.txt b/changelog/25486.txt new file mode 100644 index 000000000000..9293c69dba04 --- /dev/null +++ b/changelog/25486.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/transit: Add support for RSA padding scheme pkcs1v15 for encryption +``` diff --git a/sdk/helper/keysutil/policy.go b/sdk/helper/keysutil/policy.go index 873f99149eda..234b4413cfe2 100644 --- a/sdk/helper/keysutil/policy.go +++ b/sdk/helper/keysutil/policy.go @@ -82,6 +82,30 @@ const ( DefaultVersionTemplate = "vault:v{{version}}:" ) +type PaddingScheme string + +const ( + PaddingScheme_OAEP = PaddingScheme("oaep") + PaddingScheme_PKCS1v15 = PaddingScheme("pkcs1v15") +) + +func (p PaddingScheme) String() string { + return string(p) +} + +// ParsePaddingScheme expects a lower case string that can be directly compared to +// a defined padding scheme or returns an error. +func ParsePaddingScheme(s string) (PaddingScheme, error) { + switch s { + case PaddingScheme_OAEP.String(): + return PaddingScheme_OAEP, nil + case PaddingScheme_PKCS1v15.String(): + return PaddingScheme_PKCS1v15, nil + default: + return "", fmt.Errorf("unknown padding scheme: %s", s) + } +} + type AEADFactory interface { GetAEAD(iv []byte) (cipher.AEAD, error) } @@ -199,6 +223,15 @@ func (kt KeyType) ImportPublicKeySupported() bool { return false } +func (kt KeyType) PaddingSchemesSupported() bool { + switch kt { + case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096: + return true + default: + return false + } +} + func (kt KeyType) String() string { switch kt { case KeyType_AES128_GCM96: @@ -939,7 +972,7 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) { return p.DecryptWithFactory(context, nonce, value, nil) } -func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factories ...interface{}) (string, error) { +func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factories ...any) (string, error) { if !p.Type.DecryptionSupported() { return "", errutil.UserError{Err: fmt.Sprintf("message decryption not supported for key type %v", p.Type)} } @@ -1034,15 +1067,24 @@ func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factori return "", err } case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096: + paddingScheme, err := getPaddingScheme(factories) + if err != nil { + return "", err + } keyEntry, err := p.safeGetKeyEntry(ver) if err != nil { return "", err } key := keyEntry.RSAKey - if key == nil { - return "", errutil.InternalError{Err: fmt.Sprintf("cannot decrypt ciphertext, key version does not have a private counterpart")} + + switch paddingScheme { + case PaddingScheme_PKCS1v15: + plain, err = rsa.DecryptPKCS1v15(rand.Reader, key, decoded) + case PaddingScheme_OAEP: + plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil) + default: + return "", errutil.InternalError{Err: fmt.Sprintf("unsupported RSA padding scheme %s", paddingScheme)} } - plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil) if err != nil { return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA decrypt the ciphertext: %v", err)} } @@ -2033,7 +2075,7 @@ func (p *Policy) SymmetricDecryptRaw(encKey, ciphertext []byte, opts SymmetricOp return plain, nil } -func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value string, factories ...interface{}) (string, error) { +func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value string, factories ...any) (string, error) { if !p.Type.EncryptionSupported() { return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)} } @@ -2128,6 +2170,10 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value return "", err } case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096: + paddingScheme, err := getPaddingScheme(factories) + if err != nil { + return "", err + } keyEntry, err := p.safeGetKeyEntry(ver) if err != nil { return "", err @@ -2138,7 +2184,15 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value } else { publicKey = keyEntry.RSAPublicKey } - ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil) + switch paddingScheme { + case PaddingScheme_PKCS1v15: + ciphertext, err = rsa.EncryptPKCS1v15(rand.Reader, publicKey, plaintext) + case PaddingScheme_OAEP: + ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil) + default: + return "", errutil.InternalError{Err: fmt.Sprintf("unsupported RSA padding scheme %s", paddingScheme)} + } + if err != nil { return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA encrypt the plaintext: %v", err)} } @@ -2184,6 +2238,19 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value return encoded, nil } +func getPaddingScheme(factories []any) (PaddingScheme, error) { + for _, rawFactory := range factories { + if rawFactory == nil { + continue + } + + if p, ok := rawFactory.(PaddingScheme); ok && p != "" { + return p, nil + } + } + return PaddingScheme_OAEP, nil +} + func (p *Policy) KeyVersionCanBeUpdated(keyVersion int, isPrivateKey bool) error { keyEntry, err := p.safeGetKeyEntry(keyVersion) if err != nil { @@ -2379,7 +2446,7 @@ func (ke *KeyEntry) parseFromKey(PolKeyType KeyType, parsedKey any) error { return nil } -func (p *Policy) WrapKey(ver int, targetKey interface{}, targetKeyType KeyType, hash hash.Hash) (string, error) { +func (p *Policy) WrapKey(ver int, targetKey any, targetKeyType KeyType, hash hash.Hash) (string, error) { if !p.Type.SigningSupported() { return "", fmt.Errorf("message signing not supported for key type %v", p.Type) } @@ -2403,7 +2470,7 @@ func (p *Policy) WrapKey(ver int, targetKey interface{}, targetKeyType KeyType, return keyEntry.WrapKey(targetKey, targetKeyType, hash) } -func (ke *KeyEntry) WrapKey(targetKey interface{}, targetKeyType KeyType, hash hash.Hash) (string, error) { +func (ke *KeyEntry) WrapKey(targetKey any, targetKeyType KeyType, hash hash.Hash) (string, error) { // Presently this method implements a CKM_RSA_AES_KEY_WRAP-compatible // wrapping interface and only works on RSA keyEntries as a result. if ke.RSAPublicKey == nil { diff --git a/sdk/helper/keysutil/policy_test.go b/sdk/helper/keysutil/policy_test.go index fd753f22ba7e..cd921a52065b 100644 --- a/sdk/helper/keysutil/policy_test.go +++ b/sdk/helper/keysutil/policy_test.go @@ -11,6 +11,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/base64" "errors" "fmt" mathrand "math/rand" @@ -933,6 +934,25 @@ func autoVerify(depth int, t *testing.T, p *Policy, input []byte, sig *SigningRe } } +func autoVerifyDecrypt(depth int, t *testing.T, p *Policy, input []byte, ct string, factories ...any) { + tabs := strings.Repeat("\t", depth) + t.Log(tabs, "Automatically decrypting with options:", factories) + + tabs = strings.Repeat("\t", depth+1) + ptb64, err := p.DecryptWithFactory(nil, nil, ct, factories...) + if err != nil { + t.Fatal(tabs, "❌ Failed to automatically verify signature:", err) + } + + pt, err := base64.StdEncoding.DecodeString(ptb64) + if err != nil { + t.Fatal(tabs, "❌ Failed decoding plaintext:", err) + } + if !bytes.Equal(input, pt) { + t.Fatal(tabs, "❌ Failed to automatically decrypt") + } +} + func Test_RSA_PSS(t *testing.T) { t.Log("Testing RSA PSS") mathrand.Seed(time.Now().UnixNano()) @@ -1083,8 +1103,64 @@ func Test_RSA_PSS(t *testing.T) { } } -func Test_RSA_PKCS1(t *testing.T) { - t.Log("Testing RSA PKCS#1v1.5") +func Test_RSA_PKCS1Encryption(t *testing.T) { + t.Log("Testing RSA PKCS#1v1.5 padded encryption") + + ctx := context.Background() + storage := &logical.InmemStorage{} + // https://crypto.stackexchange.com/a/1222 + pt := []byte("Sphinx of black quartz, judge my vow") + input := base64.StdEncoding.EncodeToString(pt) + + tabs := make(map[int]string) + for i := 1; i <= 6; i++ { + tabs[i] = strings.Repeat("\t", i) + } + + test_RSA_PKCS1 := func(t *testing.T, p *Policy, rsaKey *rsa.PrivateKey, padding PaddingScheme) { + // 1. Make a signature with the given key size and hash algorithm. + t.Log(tabs[3], "Make an automatic signature") + ct, err := p.EncryptWithFactory(0, nil, nil, string(input), padding) + if err != nil { + t.Fatal(tabs[4], "❌ Failed to automatically encrypt:", err) + } + + // 1.1 Verify this signature using the *inferred* salt length. + autoVerifyDecrypt(4, t, p, pt, ct, padding) + } + + rsaKeyTypes := []KeyType{KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096} + testKeys, err := generateTestKeys() + if err != nil { + t.Fatalf("error generating test keys: %s", err) + } + + // 1. For each standard RSA key size 2048, 3072, and 4096... + for _, rsaKeyType := range rsaKeyTypes { + t.Log("Key size: ", rsaKeyType) + p := &Policy{ + Name: fmt.Sprint(rsaKeyType), // NOTE: crucial to create a new key per key size + Type: rsaKeyType, + } + + rsaKeyBytes := testKeys[rsaKeyType] + err := p.Import(ctx, storage, rsaKeyBytes, rand.Reader) + if err != nil { + t.Fatal(tabs[1], "❌ Failed to import key:", err) + } + rsaKeyAny, err := x509.ParsePKCS8PrivateKey(rsaKeyBytes) + if err != nil { + t.Fatalf("error parsing test keys: %s", err) + } + rsaKey := rsaKeyAny.(*rsa.PrivateKey) + for _, padding := range []PaddingScheme{PaddingScheme_OAEP, PaddingScheme_PKCS1v15, ""} { + t.Run(fmt.Sprintf("%s/%s", rsaKeyType.String(), padding), func(t *testing.T) { test_RSA_PKCS1(t, p, rsaKey, padding) }) + } + } +} + +func Test_RSA_PKCS1Signing(t *testing.T) { + t.Log("Testing RSA PKCS#1v1.5 signatures") ctx := context.Background() storage := &logical.InmemStorage{} diff --git a/ui/app/components/transit-key-actions.hbs b/ui/app/components/transit-key-actions.hbs index d9070392a9e6..5579a0a1e9cc 100644 --- a/ui/app/components/transit-key-actions.hbs +++ b/ui/app/components/transit-key-actions.hbs @@ -13,6 +13,7 @@ @nonce={{this.props.nonce}} @bits={{this.props.bits}} @key_version={{this.props.key_version}} + @padding_scheme={{this.props.padding_scheme}} @encodedBase64={{this.props.encodedBase64}} @toggleEncodeBase64={{this.toggleEncodeBase64}} @plaintext={{this.props.plaintext}} @@ -27,6 +28,7 @@ @ciphertext={{this.props.ciphertext}} @context={{this.props.context}} @nonce={{this.props.nonce}} + @padding_scheme={{this.props.padding_scheme}} @isModalActive={{this.isModalActive}} @plaintext={{this.props.plaintext}} @doSubmit={{perform this.doSubmit}} @@ -40,6 +42,7 @@ @nonce={{this.props.nonce}} @bits={{this.props.bits}} @plaintext={{this.props.plaintext}} + @padding_scheme={{this.props.padding_scheme}} @ciphertext={{this.props.ciphertext}} @doSubmit={{perform this.doSubmit}} @isModalActive={{this.isModalActive}} @@ -54,6 +57,8 @@ @key_version={{this.props.key_version}} @ciphertext={{this.props.ciphertext}} @isModalActive={{this.isModalActive}} + @decrypt_padding_scheme={{this.props.decrypt_padding_scheme}} + @encrypt_padding_scheme={{this.props.encrypt_padding_scheme}} @doSubmit={{perform this.doSubmit}} data-test-transit-action={{@selectedAction}} /> diff --git a/ui/app/components/transit-key-actions.js b/ui/app/components/transit-key-actions.js index 854908bfeeed..078733584aca 100644 --- a/ui/app/components/transit-key-actions.js +++ b/ui/app/components/transit-key-actions.js @@ -31,6 +31,9 @@ const STARTING_TRANSIT_PROPS = { hash_algorithm: 'sha2-256', algorithm: 'sha2-256', signature_algorithm: 'pss', + padding_scheme: 'oaep', + decrypt_padding_scheme: 'oaep', + encrypt_padding_scheme: 'oaep', bits: 256, bytes: 32, ciphertext: null, @@ -59,12 +62,19 @@ const STARTING_TRANSIT_PROPS = { }; const PROPS_TO_KEEP = { - encrypt: ['plaintext', 'context', 'nonce', 'key_version'], - decrypt: ['ciphertext', 'context', 'nonce'], + encrypt: ['plaintext', 'context', 'padding_scheme', 'nonce', 'key_version'], + decrypt: ['ciphertext', 'context', 'padding_scheme', 'nonce'], sign: ['input', 'hash_algorithm', 'key_version', 'prehashed', 'signature_algorithm'], verify: ['input', 'hmac', 'signature', 'hash_algorithm', 'prehashed'], hmac: ['input', 'algorithm', 'key_version'], - rewrap: ['ciphertext', 'context', 'nonce', 'key_version'], + rewrap: [ + 'ciphertext', + 'context', + 'decrypt_padding_scheme', + 'encrypt_padding_scheme', + 'nonce', + 'key_version', + ], datakey: [], }; @@ -182,6 +192,12 @@ export default class TransitKeyActions extends Component { formData.input = encodeString(formData.input); } } + if (!this.keyIsRSA) { + // Remove various rsa specific padding_scheme arguments if we aren't an RSA key + delete formData.encrypt_padding_scheme; + delete formData.decrypt_padding_scheme; + delete formData.padding_scheme; + } const payload = formData ? this.compactData(formData) : null; try { diff --git a/ui/app/templates/components/transit-key-action/datakey.hbs b/ui/app/templates/components/transit-key-action/datakey.hbs index 04c090071593..ab4562c4d886 100644 --- a/ui/app/templates/components/transit-key-action/datakey.hbs +++ b/ui/app/templates/components/transit-key-action/datakey.hbs @@ -3,7 +3,10 @@ SPDX-License-Identifier: BUSL-1.1 ~}} -
+
@@ -65,6 +68,27 @@
+ {{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}} +
+ +
+
+ +
+
+
+ {{/if}}
diff --git a/ui/app/templates/components/transit-key-action/decrypt.hbs b/ui/app/templates/components/transit-key-action/decrypt.hbs index 2ece29f34fed..cba4bd26384f 100644 --- a/ui/app/templates/components/transit-key-action/decrypt.hbs +++ b/ui/app/templates/components/transit-key-action/decrypt.hbs @@ -3,7 +3,10 @@ SPDX-License-Identifier: BUSL-1.1 ~}} - +

You can decrypt ciphertext using {{@key.name}} as the cryptographic key.

@@ -33,6 +36,27 @@
{{/if}} + {{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}} +
+ +
+
+ +
+
+
+ {{/if}} {{#if (eq @key.convergentEncryptionVersion 1)}}
diff --git a/ui/app/templates/components/transit-key-action/encrypt.hbs b/ui/app/templates/components/transit-key-action/encrypt.hbs index fb1a3d4e5103..9c2181b0e187 100644 --- a/ui/app/templates/components/transit-key-action/encrypt.hbs +++ b/ui/app/templates/components/transit-key-action/encrypt.hbs @@ -4,7 +4,13 @@ ~}}
@@ -49,6 +55,27 @@
{{/if}} + {{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}} +
+ +
+
+ +
+
+
+ {{/if}} {{#if (eq @key.convergentEncryptionVersion 1)}}
diff --git a/ui/app/templates/components/transit-key-action/rewrap.hbs b/ui/app/templates/components/transit-key-action/rewrap.hbs index 966a62927f07..0524e54c80f0 100644 --- a/ui/app/templates/components/transit-key-action/rewrap.hbs +++ b/ui/app/templates/components/transit-key-action/rewrap.hbs @@ -4,7 +4,20 @@ ~}}
@@ -37,6 +50,42 @@
{{/if}} + {{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}} +
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ {{/if}} {{#if (eq @key.convergentEncryptionVersion 1)}}
diff --git a/ui/tests/integration/components/transit-key-actions-test.js b/ui/tests/integration/components/transit-key-actions-test.js index f6208965c52a..bd1d2c3b3d3d 100644 --- a/ui/tests/integration/components/transit-key-actions-test.js +++ b/ui/tests/integration/components/transit-key-actions-test.js @@ -95,6 +95,50 @@ module('Integration | Component | transit key actions', function (hooks) { .exists({ count: 1 }, 'renders signature_algorithm field on verify with rsa key'); }); + test('it renders: padding_scheme field for rsa key types', async function (assert) { + const supportedActions = ['datakey', 'decrypt', 'encrypt']; + const supportedKeyTypes = ['rsa-2048', 'rsa-3072', 'rsa-4096']; + + for (const key of supportedKeyTypes) { + this.set('key', { + type: key, + backend: 'transit', + supportedActions, + }); + for (const action of this.key.supportedActions) { + this.selectedAction = action; + await render(hbs` + `); + assert + .dom('[data-test-padding-scheme]') + .hasValue( + 'oaep', + `key type: ${key} renders padding_scheme field with default value for action: ${action}` + ); + } + } + }); + test('it renders: decrypt_padding_scheme and encrypt_padding_scheme fields for rsa key types', async function (assert) { + this.selectedAction = 'rewrap'; + const supportedKeyTypes = ['rsa-2048', 'rsa-3072', 'rsa-4096']; + const SELECTOR = (type) => `[data-test-padding-scheme="${type}"]`; + for (const key of supportedKeyTypes) { + this.set('key', { + type: key, + backend: 'transit', + supportedActions: [this.selectedAction], + }); + await render(hbs` + `); + assert + .dom(SELECTOR('encrypt')) + .hasValue('oaep', `key type: ${key} renders ${SELECTOR('encrypt')} field with default value`); + assert + .dom(SELECTOR('decrypt')) + .hasValue('oaep', `key type: ${key} renders ${SELECTOR('decrypt')} field with default value`); + } + }); + async function doEncrypt(assert, actions = [], keyattrs = {}) { const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat(actions) }; diff --git a/website/content/api-docs/secret/transit.mdx b/website/content/api-docs/secret/transit.mdx index dbcafe5bd92c..248283cd6184 100644 --- a/website/content/api-docs/secret/transit.mdx +++ b/website/content/api-docs/secret/transit.mdx @@ -797,6 +797,16 @@ will be returned. data (also known as additional data or AAD) to also be authenticated with AEAD ciphers (`aes128-gcm96`, `aes256-gcm`, and `chacha20-poly1305`). +- `padding_scheme` `(string: "oaep")` – Specifies the RSA encryption padding + scheme for RSA keys. Must be one of the following supported signature types: + + - `oaep` + - `pkcs1v15` + + ~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses. + It is recommended that the default of OAEP be used unless specific backwards + compatibility is required. + - `context` `(string: "")` – Specifies the **base64 encoded** context for key derivation. This is required if key derivation is enabled for this key. @@ -922,6 +932,12 @@ This endpoint decrypts the provided ciphertext using the named key. data (also known as additional data or AAD) to also be authenticated with AEAD ciphers (`aes128-gcm96`, `aes256-gcm`, and `chacha20-poly1305`). +- `padding_scheme` `(string: "oaep")` – Specifies the RSA decryption padding + scheme for RSA keys. Must be one of the following supported signature types: + + - `oaep` + - `pkcs1v15` + - `context` `(string: "")` – Specifies the **base64 encoded** context for key derivation. This is required if key derivation is enabled. @@ -1008,6 +1024,22 @@ functionality to untrusted users or scripts. - `ciphertext` `(string: )` – Specifies the ciphertext to re-encrypt. +- `decrypt_padding_scheme` `(string: "oaep")` – Specifies the RSA padding + scheme for RSA keys for the decrypt step. Must be one of the following supported signature types: + + - `oaep` + - `pkcs1v15` + +- `encrypt_padding_scheme` `(string: "oaep")` – Specifies the RSA padding + scheme for RSA keys for the encrypt step. Must be one of the following supported signature types: + + - `oaep` + - `pkcs1v15` + + ~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses. + It is recommended that the default of OAEP be used unless specific backwards + compatibility is required. + - `context` `(string: "")` – Specifies the **base64 encoded** context for key derivation. This is required if key derivation is enabled. @@ -1109,6 +1141,16 @@ then made available to trusted users. - `bits` `(int: 256)` – Specifies the number of bits in the desired key. Can be 128, 256, or 512. +- `padding_scheme` `(string: "oaep")` – Specifies the RSA encryption padding + scheme for RSA keys. Must be one of the following supported signature types: + + - `oaep` + - `pkcs1v15` + + ~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses. + It is recommended that the default of OAEP be used unless specific backwards + compatibility is required. + ### Sample payload ```json From 60d705170033ad49aa3287f822b974d163d36ed4 Mon Sep 17 00:00:00 2001 From: Robert <17119716+robmonte@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:27:33 -0500 Subject: [PATCH 3/7] Add docs and api-docs info for gcp replication locations sync, github environments, github organizations beta, and add sys/activation-flags page (#28463) * Add field to API docs, add small section to overview * Update examples, wording * Update github API docs * Apply suggestions from code review Co-authored-by: John-Michael Faircloth * Update wording * Be a little more specific on repository owner * Put BETA tag on each org field, put visibility explanation in paragraph * Add org secrets limitation * Add sys/activation-flags page * Update Vercel granularity note * Apply suggestions from code review Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/docs/sync/vercelproject.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Small rewording, remove optional tags with defaults --------- Co-authored-by: John-Michael Faircloth Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> --- .../api-docs/system/activation-flags.mdx | 92 +++++++++++++++++++ .../content/api-docs/system/secrets-sync.mdx | 84 ++++++++++++++--- website/content/docs/sync/gcpsm.mdx | 13 ++- website/content/docs/sync/github.mdx | 89 +++++++++++++++--- website/content/docs/sync/vercelproject.mdx | 16 +++- website/data/api-docs-nav-data.json | 9 ++ 6 files changed, 274 insertions(+), 29 deletions(-) create mode 100644 website/content/api-docs/system/activation-flags.mdx diff --git a/website/content/api-docs/system/activation-flags.mdx b/website/content/api-docs/system/activation-flags.mdx new file mode 100644 index 000000000000..109840d49414 --- /dev/null +++ b/website/content/api-docs/system/activation-flags.mdx @@ -0,0 +1,92 @@ +--- +layout: api +page_title: /sys/activation-flags - HTTP API +description: The `/sys/activation-flags` endpoints are used to enable features that are gated by a one-time flag. +--- + +# `/sys/activation-flags` + +@include 'alerts/restricted-root.mdx' + +Use the `/sys/activation-flags` endpoints to read and manage Vault +features that are gated by one-time flags. Gated features are +blocked and return errors until activated. Once removed, you cannot +un-activate gated features. + +## ReadActivationFlags + +ReadActivationFlags is an unauthenticated endpoint that returns information +about gated features and their activation status as two lists: `activated` and +`unactivated`. The activated list contains features ready to be used. The +unactivated list contains **available** but gated features. It filters out those +which are already active within your Vault instance. + +| Method | Path | +| :----- | :---------------------- | +| `GET` | `/sys/activation-flags` | + +### Sample request + +```shell-session +$ curl \ + --request GET \ + http://127.0.0.1:8200/v1/sys/activation-flags +``` + +### Sample response + +```json +{ + "request_id": "9f70548c-a039-24a6-147d-7fa43698e044", + "lease_id": "", + "lease_duration": 0, + "renewable": false, + "data": { + "activated": [], + "unactivated": [ + "secrets-sync" + ] + }, + "warnings": null +} +``` + +## WriteActivationFlags + +WriteActivationFlags unblocks and enables gated Vault features. + +| Method | Path | +| :----- | :---------------------------------------- | +| `PUT` | `/sys/activation-flags/:feature/activate` | + +### URL parameters + +- `feature` `(string: )` Feature key from ReadActivationFlags indicating the feature to activate. + +### Sample request + +```shell-session +$ curl \ + --request PUT \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + http://127.0.0.1:8200/v1/sys/activation-flags/secrets-sync/activate +``` + +### Sample response + +```json +{ + "request_id": "7636e655-e11d-e2aa-8286-bd38c1d9c600", + "lease_id": "", + "lease_duration": 0, + "renewable": false, + "data": { + "activated": [ + "secrets-sync" + ], + "unactivated": [] + }, + "warnings": null, + "mount_type": "system" +} +``` diff --git a/website/content/api-docs/system/secrets-sync.mdx b/website/content/api-docs/system/secrets-sync.mdx index 25c822a2d8d7..60c975b362aa 100644 --- a/website/content/api-docs/system/secrets-sync.mdx +++ b/website/content/api-docs/system/secrets-sync.mdx @@ -381,11 +381,14 @@ This endpoint creates a destination to synchronize secrets with the GCP Secret M - `credentials` `(string: )` - JSON credentials (either file contents or '@path/to/file') See docs for [alternative ways](/vault/docs/secrets/gcp#authentication) to pass in to this parameter -- `project_id` `(string: )` - The target project to manage secrets in. If set, +- `project_id` `(string: "")` - The target project to manage secrets in. If set, overrides the project ID derived from the service account JSON credentials or application default credentials. The service account must be [authorized](https://cloud.google.com/iam/docs/service-account-overview#locations) to perform Secret Manager actions in the target project. +- `replication_locations` `(list: nil)` - A list of GCP location names the destination can use to +store replicated secrets. Note that secrets remain globally readable regardless of the selected locations. + - `secret_name_template` `(string: "")` - Template to use when generating the secret names on the external system. The default template yields names like `vault/kv_1234/my-secret`. See [this documentation](/vault/docs/sync#name-template) for more details. @@ -398,7 +401,11 @@ destination. See [this documentation](/vault/docs/sync#granularity) for more det ### Sample payload ```json { - "credentials": "" + "credentials": "", + "replication_locations": [ + "us-east1", + "us-west1" + ] } ``` @@ -424,26 +431,50 @@ This endpoint creates a destination to synchronize action secrets with a GitHub - `name` `(string: )` - Specifies the name for this destination. This is specified as part of the URL. -- `access_token` `(string: )` - Fine-grained or personal access token. +- `secrets_location` `(string: "repository")` - The GitHub location type of secrets to sync. Must be either `organization` or `repository`. + +- `access_token` `(string: ""` - Fine-grained or personal access token. Use `access_token` as an alternative to authenticating with a GitHub app. -- `app_name` `(string: )` - The name of a GitHub App configured in Vault to use for +- `app_name` `(string: "")` - The name of a GitHub App configured in Vault to use for authentication. You can use `app_name` with `installation_id` as authentication instead of an access token. Refer to the [Configure a custom GitHub app section](/vault/api-docs/system/secrets-sync#configure-a-custom-github-app) of the Secrets sync API docs for more information. - - `installation_id` `(string: )` - The installation ID of the GitHub - app to use for authentication. Required when using `app_name` for - authentication. + - `installation_id` `(string: "")` - The installation ID of the GitHub + app to use for authentication. Required when using `app_name` for authentication. + +- `repository_owner` `(string: "")` - GitHub owner of the secrets sync + target location when `secrets_location` is `repository`. For example, if the + target repository URL is `https://github.com/hashicorp/vault.git`, the owner + is `hashicorp`. + + - `repository_name` `(string: "")` - GitHub repository name of the + secrets sync target location when `secrets_location` is `repository`. For + example, if the target repository URL is + `https://github.com/hashicorp/vault.git`, the repository name is `vault`. + + - `environment_name` `(string: "")` - GitHub environment name of the secrets + sync target location when `secrets_location` is `repository`. By default, + secrets are global to the targeted repository. -- `repository_owner` `(string: )` - GitHub organization or username that owns the repository. For example, if a repository is located at https://github.com/hashicorp/vault.git the owner is hashicorp. +- `organization_name` `(string: "")` - **(BETA)** GitHub organization + name of the secrets sync target location when `secrets_location` is + `organization`. For example, if the organization is + `https://github.com/hashicorp`, the organization name is `hashicorp`. -- `repository_name` `(string: )` - Name of the repository. For example, if a repository is located at https://github.com/hashicorp/vault.git the name is vault. + - `organization_visibility` `(string: "")` - **(BETA)** Controls which + repositories within the secrets sync target location can see synced secrets + when `secrets_location` is `organization`. Must be one of: + - `all` - all repositories can access synced secrets + - `private` - private and internal repositories can access synced secrets + - `selected` - repositories explicitly named in `selected_repository_names` + can access synced secrets. - - `environment_name` `(string: '')` - The name of a GitHub environment - within the repo specified by `repository_name`. By default, secrets are - global to the targeted repository. + - `selected_repository_names` `(list: nil)` - **(BETA)** Explicit list of + repository names in the secrets sync target location that can access + synced secrets when `secrets_location` is `organization`. - `secret_name_template` `(string: "")` - Template to use when generating the secret names on the external system. The default template yields names like `VAULT_KV_1234_MY_SECRET`. See [this documentation](/vault/docs/sync#name-template) for more details. @@ -451,12 +482,37 @@ The default template yields names like `VAULT_KV_1234_MY_SECRET`. See [this docu - `granularity` `(string: "secret-key")` - Determines what level of information is synced as a distinct resource at the destination. See [this documentation](/vault/docs/sync#granularity) for more details. -### Sample payload +## Example requests + +### Sync secrets to a GitHub repository +```json +{ + "access_token": "github_pat_12345", + "secrets_location": "repository", + "repository_owner": "my-organization-or-username", + "repository_name": "my-repository", +} +``` + +### Sync secrets to a GitHub environment ```json { "access_token": "github_pat_12345", + "secrets_location": "repository", "repository_owner": "my-organization-or-username", - "repository_name": "my-repository" + "repository_name": "my-repository", + "environment_name": "my-environment" +} +``` + +### Sync secrets to a GitHub organization +```json +{ + "access_token": "github_pat_12345", + "secrets_location": "organization", + "organization_name": "my-organization", + "organization_visibility": "selected", + "selected_repository_names": "my-repository-1,my-repository-2,my-repository-3" } ``` diff --git a/website/content/docs/sync/gcpsm.mdx b/website/content/docs/sync/gcpsm.mdx index c383ae553a35..e5df04026c8b 100644 --- a/website/content/docs/sync/gcpsm.mdx +++ b/website/content/docs/sync/gcpsm.mdx @@ -28,6 +28,7 @@ Prerequisites: ```shell-session $ vault write sys/sync/destinations/gcp-sm/my-dest \ credentials='@path/to/credentials.json' + replication_locations='us-east1,us-west1' ``` **Output:** @@ -37,7 +38,7 @@ Prerequisites: ```plaintext Key Value --- ----- - connection_details map[credentials:*****] + connection_details map[credentials:***** replication_locations:us-east1,us-west1] name my-dest type gcp-sm ``` @@ -117,6 +118,16 @@ Moving forward, any modification on the Vault secret will be propagated in near counterpart. Creating a new secret version in Vault will create a new version in GCP Secret Manager. Deleting the secret or the association in Vault will delete the secret in your GCP project as well. +### Replication policy + +GCP can target specific geographic regions to provide strict control on where +your applications store data and sync secrets. You can target specific GCP +regions for each sync destinations during creation which will limit where Vault writes +secrets. + +Regardless of the region limits on writes, synced secrets are always readable +globally when the client has the required permissions. + ## Permissions The credentials given to Vault must have the following permissions to synchronize secrets: diff --git a/website/content/docs/sync/github.mdx b/website/content/docs/sync/github.mdx index 5a00a2ab2228..174dbf6e5d89 100644 --- a/website/content/docs/sync/github.mdx +++ b/website/content/docs/sync/github.mdx @@ -6,32 +6,35 @@ description: The GitHub destination syncs secrets from Vault to GitHub. # GitHub actions secrets -The GitHub actions sync destination allows Vault to safely synchronize secrets as GitHub repository or environment secrets. +The GitHub actions sync destination allows Vault to safely synchronize secrets as GitHub organization, repository, or environment secrets. This is a low footprint option that enables your applications to benefit from Vault-managed secrets without requiring them to connect directly with Vault. This guide walks you through the configuration process. Prerequisites: * Ability to read or create KVv2 secrets -* Ability to create GitHub fine-grained or personal tokens (or a GitHub application) with access to modify repository secrets +* Ability to create GitHub fine-grained or personal tokens (or a GitHub application) with access to modify organization and/or repository secrets * Ability to create sync destinations and associations on your Vault server ## Setup 1. To get started with syncing Vault secrets to your GitHub, you will need a configured [GitHub application](#github-application) or an [access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) - which has access to the repository you want to manage secrets for and write permissions on the repository's actions secrets. - In the list of GitHub permissions, this is simply called "Secrets". Choosing this will automatically include read-only Metadata. + that has write permission on the target sync location in GitHub for "Secrets". The "Secrets" permissions in GitHub automatically includes read-only "Metadata" access. + Access tokens are tied to a user account and can be revoked at any time, causing disruptions to the sync process. GitHub applications are long-lived and do not expire. Using a GitHub application for authentication is preferred over using a personal access token. +### Repositories + Use `vault write` to configure a repository sync destination with an access token: ```shell-session $ vault write sys/sync/destinations/gh/DESTINATION_NAME \ access_token="GITHUB_ACCESS_TOKEN" \ + secrets_location="GITHUB_SECRETS_LOCATION" \ repository_owner="GITHUB_OWNER_NAME" \ repository_name="GITHUB_REPO_NAME" ``` @@ -43,23 +46,27 @@ Use `vault write` to configure a repository sync destination with an access toke ``` $ vault write sys/sync/destinations/gh/hcrepo-sandbox \ access_token="github_pat_11ABC000000000000000000000DEF" \ + secrets_location="repository" \ repository_owner="hashicorp" \ repository_name="hcrepo" Key Value --- ----- - connection_details map[access_token:***** repository_owner:hashicorp repository_name:hcrepo] + connection_details map[access_token:***** secrets_location:repository repository_owner:hashicorp repository_name:hcrepo] name hcrepo-sandbox type gh ``` - Use `vault write` to configure an environment sync destination: +### Environments + +Use `vault write` to configure an environment sync destination: ```shell-session $ vault write sys/sync/destinations/gh/DESTINATION_NAME \ access_token="GITHUB_ACCESS_TOKEN" \ + secrets_location="GITHUB_SECRETS_LOCATION" \ repository_owner="GITHUB_OWNER_NAME" \ repository_name="GITHUB_REPO_NAME" \ environment_name="GITHUB_ENVIRONMENT_NAME" @@ -72,13 +79,71 @@ Use `vault write` to configure a repository sync destination with an access toke ``` $ vault write sys/sync/destinations/gh/hcrepo-sandbox \ access_token="github_pat_11ABC000000000000000000000DEF" \ + secrets_location="repository" \ repository_owner="hashicorp" \ repository_name="hcrepo" \ environment_name="sandbox" Key Value --- ----- - connection_details map[access_token:***** environment_name:sandbox repository_owner:hashicorp repository_name:hcrepo] + connection_details map[access_token:***** secrets_location:repository environment_name:sandbox repository_owner:hashicorp repository_name:hcrepo] + name hcrepo-sandbox + type gh + ``` + + + +### Organizations + +@include 'alerts/beta.mdx' + + +Beta limitations: + +- You cannot update visibility (`organization_visibility`) after creating a + secrets sync destination. +- You cannot update the list of repositories with access to synced secrets + (`selected_repository_names`) after creating a secrets sync destination. + +Sync secrets to GitHub organization to share those secrets across repositories +in the organizations. You choose to make secrets global to the organization, +limited to private/internal repos, or limited to specifically named repositories. + +Refer to the [Secrets sync API docs](/vault/docs/sync/github#api) for detailed +configuration information. + + + + Organization secrets are + [not visible to private repositories for GitHub Free accounts](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-an-organization). + + + +Use `vault write` to configure an organization sync destination: + + ```shell-session + $ vault write sys/sync/destinations/gh/DESTINATION_NAME \ + access_token="GITHUB_ACCESS_TOKEN" \ + secrets_location="GITHUB_SECRETS_LOCATION" \ + organization_name="ORGANIZATION_NAME" \ + organization_visibility="ORGANIZATION_VISIBILITY" + ``` + + For example: + + + + ``` + $ vault write sys/sync/destinations/gh/hcrepo-sandbox \ + access_token="github_pat_11ABC000000000000000000000DEF" \ + secrets_location="organization" \ + organization_name="hashicorp" \ + organization_visibility="selected" \ + selected_repository_names="hcrepo-1,hcrepo-2" + + Key Value + --- ----- + connection_details map[access_token:***** secrets_location:organization organization_name:hashicorp organization_visibility:all selected_repository_names:[hcrepo-1 hcrepo-2]] name hcrepo-sandbox type gh ``` @@ -162,15 +227,17 @@ or the association in Vault will delete the secret in GitHub as well. -GitHub only supports single-value secrets, and Vault syncs secrets differently -depending on whether you have configured +Vault syncs secrets differently depending on whether you have configured `secret-key` or `secret-path` [granularity](/vault/docs/sync#granularity): - `secret-key` granularity splits KVv2 secrets from Vault into key-value pairs - and stores the pairs as distinct entries in GitHub secrets. For example, + and stores the pairs as distinct entries in GitHub. For example, `secrets.key1="val1"` and `secrets.key2="val2"`. + - `secret-path` granularity stores secrets as a single JSON string that contains - all of the associated key-value pairs. For example, `{"key1":"val1", "key2":"val2"}`. + all the associated key-value pairs. For example, `{"key1":"val1", "key2":"val2"}`. + +Since GitHub limits secrets to single-value secrets, the sync granularity defaults to `secret-key`. diff --git a/website/content/docs/sync/vercelproject.mdx b/website/content/docs/sync/vercelproject.mdx index c7fb61340f62..0e22eb78d2db 100644 --- a/website/content/docs/sync/vercelproject.mdx +++ b/website/content/docs/sync/vercelproject.mdx @@ -68,7 +68,7 @@ Prerequisites: 1. Create secrets you wish to sync with a target Vercel project. ```shell-session - $ vault kv put -mount='my-kv' my-secret foo='bar' + $ vault kv put -mount='my-kv' my-secret key1='val1' ``` **Output:** @@ -122,8 +122,18 @@ or the association in Vault will delete the secret on Vercel as well. -Vercel Project environment variables only support single value secrets, so KVv2 secrets from Vault will be stored as a JSON string. -In the example above, the value for secret "my-secret" will be synced to Vercel as the JSON string `{"foo":"bar"}`. +Vault syncs secrets differently depending on whether you have configured +`secret-key` or `secret-path` [granularity](/vault/docs/sync#granularity): + +- `secret-key` granularity splits KVv2 secrets from Vault into key-value pairs + and stores the pairs as distinct entries in Vercel. For example, + `secrets.key1="val1"` and `secrets.key2="val2"`. + +- `secret-path` granularity stores secrets as a single JSON string that contains + all the associated key-value pairs. For example, `{"key1":"val1", "key2":"val2"}`. + +Since Vercel projects limit environment variables to single-value secrets, the +sync granularity defaults to `secret-key`. diff --git a/website/data/api-docs-nav-data.json b/website/data/api-docs-nav-data.json index c25556dc840f..1de809d9beab 100644 --- a/website/data/api-docs-nav-data.json +++ b/website/data/api-docs-nav-data.json @@ -394,6 +394,15 @@ "title": "Overview", "path": "system" }, + { + "title": "/sys/activation-flags", + "path": "system/activation-flags", + "badge": { + "text": "ENT", + "type": "outlined", + "color": "neutral" + } + }, { "title": "/sys/audit", "path": "system/audit" From 1229f5723a24fbd73d3796c7801c360a8101597b Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Wed, 9 Oct 2024 12:19:10 -0600 Subject: [PATCH 4/7] Ember data remove deprecation Ember Promise Many Array behaviors (#28652) * fix * remove deprecation from config --- .../controllers/vault/cluster/access/mfa/methods/create.js | 5 +++-- ui/config/deprecation-workflow.js | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/app/controllers/vault/cluster/access/mfa/methods/create.js b/ui/app/controllers/vault/cluster/access/mfa/methods/create.js index 4f5eea0d093a..806f92365f11 100644 --- a/ui/app/controllers/vault/cluster/access/mfa/methods/create.js +++ b/ui/app/controllers/vault/cluster/access/mfa/methods/create.js @@ -96,8 +96,9 @@ export default class MfaMethodCreateController extends Controller { // first save method yield this.method.save(); if (this.enforcement) { - // mfa_methods is type PromiseManyArray so slice in necessary to convert it to an Array - this.enforcement.mfa_methods = addToArray(this.enforcement.mfa_methods.slice(), this.method); + // mfa_methods is type PromiseManyArray. Array methods like slice are no longer allowed on PromiseManyArray. We must yield the promise first, then call the method. + const mfaMethods = yield this.enforcement.mfa_methods; + this.enforcement.mfa_methods = addToArray(mfaMethods.slice(), this.method); try { // now save enforcement and catch error separately yield this.enforcement.save(); diff --git a/ui/config/deprecation-workflow.js b/ui/config/deprecation-workflow.js index 113a40887371..90516f7ad170 100644 --- a/ui/config/deprecation-workflow.js +++ b/ui/config/deprecation-workflow.js @@ -11,8 +11,5 @@ self.deprecationWorkflow.config = { self.deprecationWorkflow.config = { // current output from deprecationWorkflow.flushDeprecations(); - workflow: [ - // ember-data - { handler: 'silence', matchId: 'ember-data:deprecate-promise-many-array-behaviors' }, // MFA - ], + workflow: [], }; From 210da8f70552774265b480f0eb477939b8512391 Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Wed, 9 Oct 2024 15:43:49 -0400 Subject: [PATCH 5/7] Fix data race around static secret capability manager (#28653) * Fix data race around static secret capability manager * Actually, clone the map --- .../cache/static_secret_capability_manager.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/command/agentproxyshared/cache/static_secret_capability_manager.go b/command/agentproxyshared/cache/static_secret_capability_manager.go index b4dc3b2bd078..6d5e00098d49 100644 --- a/command/agentproxyshared/cache/static_secret_capability_manager.go +++ b/command/agentproxyshared/cache/static_secret_capability_manager.go @@ -143,8 +143,10 @@ func (sscm *StaticSecretCapabilityManager) StartRenewingCapabilities(indexToRene capabilitiesIndex.IndexLock.RLock() token := capabilitiesIndex.Token - indexReadablePathsMap := capabilitiesIndex.ReadablePaths + indexReadablePathsMap := map[string]struct{}{} + maps.Copy(indexReadablePathsMap, capabilitiesIndex.ReadablePaths) capabilitiesIndex.IndexLock.RUnlock() + indexReadablePaths := maps.Keys(indexReadablePathsMap) client, err := sscm.client.Clone() From 38df9cf48837cc3b17c34642e39265621396a938 Mon Sep 17 00:00:00 2001 From: Scott Miller Date: Wed, 9 Oct 2024 15:16:26 -0500 Subject: [PATCH 6/7] Tweak the ocsp_ca_certificate param docs to be more clear about what kind of cert it wants (#28659) --- website/content/api-docs/auth/cert.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/api-docs/auth/cert.mdx b/website/content/api-docs/auth/cert.mdx index a838e27e63e5..25deca159e15 100644 --- a/website/content/api-docs/auth/cert.mdx +++ b/website/content/api-docs/auth/cert.mdx @@ -69,7 +69,7 @@ Sets a CA cert and associated parameters in a role name. by a dash (-) instead of a dot (.) to allow usage in ACL templates. - `ocsp_enabled` `(bool: false)` - If enabled, validate certificates' revocation status using OCSP. -- `ocsp_ca_certificates` `(string: "")` Any additional CA certificates needed to +- `ocsp_ca_certificates` `(string: "")` Any additional OCSP responder certificates needed to verify OCSP responses. Provided as base64 encoded PEM data. - `ocsp_servers_override` `(array: [])`: A comma-separated list of OCSP server addresses. If unset, the OCSP server is determined from the AuthorityInformationAccess From 3b0614abd00805d527b70ef99b7a01d2d824d51b Mon Sep 17 00:00:00 2001 From: Leland Ursu Date: Wed, 9 Oct 2024 17:02:51 -0400 Subject: [PATCH 7/7] added changelog update records for 1.18.0, 1.17.7, 1.16.11, and 1.15.16 (#28655) * added changelog update records for 1.18.0, 1.17.7, 1.16.11, and 1.15.16 * removed the word enterprise to make consistant * revert WIF chnage log item wording * updated the backport versions to denote that they are enterprise releases --------- Co-authored-by: lursu --- CHANGELOG.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82c8f74c0c42..956520290acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ - [v1.0.0 - v1.9.10](CHANGELOG-pre-v1.10.md) - [v0.11.6 and earlier](CHANGELOG-v0.md) -## 1.18.0-rc1 -## September 18, 2024 +## 1.18.0 +## October 9, 2024 CHANGES: @@ -51,6 +51,7 @@ The endTime will be set to the end of the current month. This applies to /sys/in * secrets/kv: Update plugin to v0.20.0 [[GH-28334](https://github.com/hashicorp/vault/pull/28334)] * secrets/mongodbatlas: Update plugin to v0.13.0 [[GH-28348](https://github.com/hashicorp/vault/pull/28348)] * secrets/openldap: Update plugin to v0.14.0 [[GH-28325](https://github.com/hashicorp/vault/pull/28325)] +* secrets/ssh: Add a flag, `allow_empty_principals` to allow keys or certs to apply to any user/principal. [[GH-28466](https://github.com/hashicorp/vault/pull/28466)] * secrets/terraform: Update plugin to v0.10.0 [[GH-28312](https://github.com/hashicorp/vault/pull/28312)] * secrets/terraform: Update plugin to v0.9.0 [[GH-28016](https://github.com/hashicorp/vault/pull/28016)] * ui: Uses the internal/counters/activity/export endpoint for client count export data. [[GH-27455](https://github.com/hashicorp/vault/pull/27455)] @@ -84,7 +85,7 @@ visibly sensible totals. [[GH-27547](https://github.com/hashicorp/vault/pull/275 * audit: Ensure that any underyling errors from audit devices are logged even if we consider auditing to be a success. [[GH-27809](https://github.com/hashicorp/vault/pull/27809)] * audit: Internal implementation changes to the audit subsystem which improve performance. [[GH-27952](https://github.com/hashicorp/vault/pull/27952)] * audit: Internal implementation changes to the audit subsystem which improve relability. [[GH-28286](https://github.com/hashicorp/vault/pull/28286)] -* audit: sinks (file, socket, syslog) will attempt to log errors to the server operational +* audit: sinks (file, socket, syslog) will attempt to log errors to the server operational log before returning (if there are errors to log, and the context is done). [[GH-27859](https://github.com/hashicorp/vault/pull/27859)] * auth/cert: Cache full list of role trust information separately to avoid eviction, and avoid duplicate loading during multiple simultaneous logins on @@ -96,11 +97,13 @@ the same role. [[GH-27902](https://github.com/hashicorp/vault/pull/27902)] * core/cli: Example 'help' pages for vault read / write docs improved. [[GH-19064](https://github.com/hashicorp/vault/pull/19064)] * core/identity: allow identity backend to be tuned using standard secrets backend tuning parameters. [[GH-14723](https://github.com/hashicorp/vault/pull/14723)] * core/metrics: ensure core HA metrics are always output to Prometheus. [[GH-27966](https://github.com/hashicorp/vault/pull/27966)] +* core: log at level ERROR rather than INFO when all seals are unhealthy. [[GH-28564](https://github.com/hashicorp/vault/pull/28564)] * core: make authLock and mountsLock in Core configurable via the detect_deadlocks configuration parameter. [[GH-27633](https://github.com/hashicorp/vault/pull/27633)] * database/postgres: Add new fields to the plugin's config endpoint for client certificate authentication. [[GH-28024](https://github.com/hashicorp/vault/pull/28024)] * db/cassandra: Add `disable_host_initial_lookup` option to backend, allowing the disabling of initial host lookup. [[GH-9733](https://github.com/hashicorp/vault/pull/9733)] * identity: alias metadata is now returned when listing entity aliases [[GH-26073](https://github.com/hashicorp/vault/pull/26073)] * license utilization reporting (enterprise): Auto-roll billing start date. [[GH-27656](https://github.com/hashicorp/vault/pull/27656)] +* physical/raft: Log when the MAP_POPULATE mmap flag gets disabled before opening the database. [[GH-28526](https://github.com/hashicorp/vault/pull/28526)] * proxy/sink: Allow configuration of the user and group ID of the file sink. [[GH-27123](https://github.com/hashicorp/vault/pull/27123)] * proxy: Add the ability to dump pprof to the filesystem using SIGUSR2 [[GH-27510](https://github.com/hashicorp/vault/pull/27510)] * raft-snapshot (enterprise): add support for managed identity credentials for azure snapshots @@ -112,6 +115,7 @@ the same role. [[GH-27902](https://github.com/hashicorp/vault/pull/27902)] * secrets/database/hana: Update HANA db client to v1.10.1 [[GH-27950](https://github.com/hashicorp/vault/pull/27950)] * secrets/database: Add support for GCP CloudSQL private IP's. [[GH-26828](https://github.com/hashicorp/vault/pull/26828)] * secrets/pki: Key Usage can now be set on intermediate and root CAs, and CSRs generated by the PKI secret's engine. [[GH-28237](https://github.com/hashicorp/vault/pull/28237)] +* secrets/pki: Track the last time auto-tidy ran to address auto-tidy not running if the auto-tidy interval is longer than scheduled Vault restarts. [[GH-28488](https://github.com/hashicorp/vault/pull/28488)] * serviceregistration: Added support for Consul ServiceMeta tags from config file from the new `service_meta` config field. [[GH-11084](https://github.com/hashicorp/vault/pull/11084)] * storage/azure: Updated metadata endpoint to `GetMSIEndpoint`, which supports more than just the metadata service. [[GH-10624](https://github.com/hashicorp/vault/pull/10624)] * storage/dynamodb: Speed up list and delete of large directories by only requesting keys from DynamoDB [[GH-21159](https://github.com/hashicorp/vault/pull/21159)] @@ -137,29 +141,37 @@ BUG FIXES: * agent: Fixed an issue causing excessive CPU usage during normal operation [[GH-27518](https://github.com/hashicorp/vault/pull/27518)] * auth/appid, auth/cert, auth/github, auth/ldap, auth/okta, auth/radius, auth/userpass: fixed an issue with policy name normalization that would prevent a token associated with a policy containing an uppercase character to be renewed. [[GH-16484](https://github.com/hashicorp/vault/pull/16484)] * auth/aws: fixes an issue where not supplying an external id was interpreted as an empty external id [[GH-27858](https://github.com/hashicorp/vault/pull/27858)] +* auth/cert: During certificate validation, OCSP requests are debug logged even if Vault's log level is above DEBUG. [[GH-28450](https://github.com/hashicorp/vault/pull/28450)] * auth/cert: Merge error messages returned in login failures and include error when present [[GH-27202](https://github.com/hashicorp/vault/pull/27202)] * auth/cert: Use subject's serial number, not issuer's within error message text in OCSP request errors [[GH-27696](https://github.com/hashicorp/vault/pull/27696)] +* auth/cert: When using ocsp_ca_certificates, an error was produced though extra certs validation succeeded. [[GH-28597](https://github.com/hashicorp/vault/pull/28597)] * auth/cert: ocsp_ca_certificates field was not honored when validating OCSP responses signed by a CA that did not issue the certificate. [[GH-28309](https://github.com/hashicorp/vault/pull/28309)] +* auth/token: Fix token TTL calculation so that it uses `max_lease_ttl` tune value for tokens created via `auth/token/create`. [[GH-28498](https://github.com/hashicorp/vault/pull/28498)] * auth/token: fixes an edge case bug that "identity_policies" is nil and causes cli vault login error [[GH-17007](https://github.com/hashicorp/vault/pull/17007)] +* auth: Updated error handling for missing login credentials in AppRole and UserPass auth methods to return a 400 error instead of a 500 error. [[GH-28441](https://github.com/hashicorp/vault/pull/28441)] * cli: Fixed an erroneous warning appearing about `-address` not being set when it is. [[GH-27265](https://github.com/hashicorp/vault/pull/27265)] * cli: Fixed issue with `vault hcp connect` where HCP resources with uppercase letters were inaccessible when entering the correct project name. [[GH-27694](https://github.com/hashicorp/vault/pull/27694)] * command: The `vault secrets move` and `vault auth move` command will no longer attempt to write to storage on performance standby nodes. [[GH-28059](https://github.com/hashicorp/vault/pull/28059)] -* config: Vault TCP listener config now correctly supports the documented proxy_protocol_behavior +* config: Vault TCP listener config now correctly supports the documented proxy_protocol_behavior setting of 'deny_unauthorized' [[GH-27459](https://github.com/hashicorp/vault/pull/27459)] * core (enterprise): Fix 500 errors that occurred querying `sys/internal/ui/mounts` for a mount prefixed by a namespace path when path filters are configured. [[GH-27939](https://github.com/hashicorp/vault/pull/27939)] * core (enterprise): Fix HTTP redirects in namespaces to use the correct path and (in the case of event subscriptions) the correct URI scheme. [[GH-27660](https://github.com/hashicorp/vault/pull/27660)] * core (enterprise): Fix deletion of MFA login-enforcement configurations on standby nodes -* core/audit: Audit logging a Vault request/response checks if the existing context +* core/audit: Audit logging a Vault request/response checks if the existing context is cancelled and will now use a new context with a 5 second timeout. If the existing context is cancelled a new context, will be used. [[GH-27531](https://github.com/hashicorp/vault/pull/27531)] -* core/config: fix issue when using `proxy_protocol_behavior` with `deny_unauthorized`, +* core/config: fix issue when using `proxy_protocol_behavior` with `deny_unauthorized`, which causes the Vault TCP listener to close after receiving an untrusted upstream proxy connection. [[GH-27589](https://github.com/hashicorp/vault/pull/27589)] * core/identity: Fixed an issue where deleted/reassigned entity-aliases were not removed from in-memory database. [[GH-27750](https://github.com/hashicorp/vault/pull/27750)] +* core/seal (enterprise): Fix bug that caused seal generation information to be replicated, which prevented disaster recovery and performance replication clusters from using their own seal high-availability configuration. * core: Fixed an issue where maximum request duration timeout was not being added to all requests containing strings sys/monitor and sys/events. With this change, timeout is now added to all requests except monitor and events endpoint. [[GH-28230](https://github.com/hashicorp/vault/pull/28230)] * core: Fixed an issue with performance standbys not being able to handle rotate root requests. [[GH-27631](https://github.com/hashicorp/vault/pull/27631)] +* database/postgresql: Fix potential error revoking privileges in postgresql database secrets engine when a schema contains special characters [[GH-28519](https://github.com/hashicorp/vault/pull/28519)] +* databases: fix issue where local timezone was getting lost when using a rotation schedule cron [[GH-28509](https://github.com/hashicorp/vault/pull/28509)] * helper/pkcs7: Fix parsing certain messages containing only certificates [[GH-27435](https://github.com/hashicorp/vault/pull/27435)] * identity/oidc: prevent JWKS from being generated by multiple concurrent requests [[GH-27929](https://github.com/hashicorp/vault/pull/27929)] * licensing (enterprise): fixed issue where billing start date might not be correctly updated on performance standbys +* proxy/cache (enterprise): Fixed a data race that could occur while tracking capabilities in Proxy's static secret cache. [[GH-28494](https://github.com/hashicorp/vault/pull/28494)] * proxy/cache (enterprise): Fixed an issue where Proxy with static secret caching enabled would not correctly handle requests to older secret versions for KVv2 secrets. Proxy's static secret cache now properly handles all requests relating to older versions for KVv2 secrets. [[GH-28207](https://github.com/hashicorp/vault/pull/28207)] * proxy/cache (enterprise): Fixed an issue where Proxy would not correctly update KV secrets when talking to a perf standby. Proxy will now attempt to forward requests to update secrets triggered by events to the active node. Note that this requires `allow_forwarding_via_header` to be configured on the cluster. [[GH-27891](https://github.com/hashicorp/vault/pull/27891)] * proxy/cache (enterprise): Fixed an issue where cached static secrets could fail to update if the secrets belonged to a non-root namespace. [[GH-27730](https://github.com/hashicorp/vault/pull/27730)] @@ -167,6 +179,7 @@ which causes the Vault TCP listener to close after receiving an untrusted upstre * raft/autopilot: Fixed panic that may occur during shutdown [[GH-27726](https://github.com/hashicorp/vault/pull/27726)] * replication (enterprise): fix cache invalidation issue leading to namespace custom metadata not being shown correctly on performance secondaries * secrets-sync (enterprise): Destination set/remove operations will no longer be blocked as "purge in progress" after a purge job ended in failure. +* secrets-sync (enterprise): Fix KV secret access sometimes being denied, due to a double forward-slash (`//`) in the mount path, when the token should otherwise have access. * secrets-sync (enterprise): Normalize custom_tag keys and values for recoverable invalid characters. * secrets-sync (enterprise): Normalize secret key names before storing the external_name in a secret association. * secrets-sync (enterprise): Patching github sync destination credentials will properly update and save the new credentials. @@ -190,11 +203,28 @@ use versioned plugins. [[GH-27881](https://github.com/hashicorp/vault/pull/27881 * ui: Ensure token expired banner displays when batch token expires [[GH-27479](https://github.com/hashicorp/vault/pull/27479)] * ui: Fix UI improperly checking capabilities for enabling performance and dr replication [[GH-28371](https://github.com/hashicorp/vault/pull/28371)] * ui: Fix cursor jump on KVv2 json editor that would occur after pressing ENTER. [[GH-27569](https://github.com/hashicorp/vault/pull/27569)] +* ui: fix `default_role` input missing from oidc auth method configuration form [[GH-28539](https://github.com/hashicorp/vault/pull/28539)] * ui: fix issue where enabling then disabling "Tidy ACME" in PKI results in failed API call. [[GH-27742](https://github.com/hashicorp/vault/pull/27742)] * ui: fix namespace picker not working when in small screen where the sidebar is collapsed by default. [[GH-27728](https://github.com/hashicorp/vault/pull/27728)] * ui: fixes renew-self being called right after login for non-renewable tokens [[GH-28204](https://github.com/hashicorp/vault/pull/28204)] * ui: fixes toast (flash) alert message saying "created" when deleting a kv v2 secret [[GH-28093](https://github.com/hashicorp/vault/pull/28093)] +## 1.17.7 Enterprise +### October 09, 2024 + +IMPROVEMENTS: + +* core: log at level ERROR rather than INFO when all seals are unhealthy. [[GH-28564](https://github.com/hashicorp/vault/pull/28564)] +* physical/raft: Log when the MAP_POPULATE mmap flag gets disabled before opening the database. [[GH-28526](https://github.com/hashicorp/vault/pull/28526)] +* secrets/pki: Track the last time auto-tidy ran to address auto-tidy not running if the auto-tidy interval is longer than scheduled Vault restarts. [[GH-28488](https://github.com/hashicorp/vault/pull/28488)] + +BUG FIXES: + +* auth/cert: When using ocsp_ca_certificates, an error was produced though extra certs validation succeeded. [[GH-28597](https://github.com/hashicorp/vault/pull/28597)] +* auth/token: Fix token TTL calculation so that it uses `max_lease_ttl` tune value for tokens created via `auth/token/create`. [[GH-28498](https://github.com/hashicorp/vault/pull/28498)] +* databases: fix issue where local timezone was getting lost when using a rotation schedule cron [[GH-28509](https://github.com/hashicorp/vault/pull/28509)] +* secrets-sync (enterprise): Fix KV secret access sometimes being denied, due to a double forward-slash (`//`) in the mount path, when the token should otherwise have access. + ## 1.17.6 ### September 25, 2024 @@ -551,6 +581,23 @@ autopilot to fail to discover new server versions and so not trigger an upgrade. * ui: fixed a bug where the replication pages did not update display when navigating between DR and performance [[GH-26325](https://github.com/hashicorp/vault/pull/26325)] * ui: fixes undefined start time in filename for downloaded client count attribution csv [[GH-26485](https://github.com/hashicorp/vault/pull/26485)] +## 1.16.11 Enterprise +### October 09, 2024 + +**Enterprise LTS:** Vault Enterprise 1.16 is a [Long-Term Support (LTS)](https://developer.hashicorp.com/vault/docs/enterprise/lts) release. + +IMPROVEMENTS: + +* core: log at level ERROR rather than INFO when all seals are unhealthy. [[GH-28564](https://github.com/hashicorp/vault/pull/28564)] +* physical/raft: Log when the MAP_POPULATE mmap flag gets disabled before opening the database. [[GH-28526](https://github.com/hashicorp/vault/pull/28526)] + +BUG FIXES: + +* auth/cert: When using ocsp_ca_certificates, an error was produced though extra certs validation succeeded. [[GH-28597](https://github.com/hashicorp/vault/pull/28597)] +* auth/token: Fix token TTL calculation so that it uses `max_lease_ttl` tune value for tokens created via `auth/token/create`. [[GH-28498](https://github.com/hashicorp/vault/pull/28498)] +* databases: fix issue where local timezone was getting lost when using a rotation schedule cron [[GH-28509](https://github.com/hashicorp/vault/pull/28509)] +* secrets-sync (enterprise): Fix KV secret access sometimes being denied, due to a double forward-slash (`//`) in the mount path, when the token should otherwise have access. + ## 1.16.10 Enterprise ### September 25, 2024 @@ -1178,6 +1225,19 @@ leading to failure to complete merkle sync without a full re-index. [[GH-23013]( * ui: remove user_lockout_config settings for unsupported methods [[GH-25867](https://github.com/hashicorp/vault/pull/25867)] * ui: show error from API when seal fails [[GH-23921](https://github.com/hashicorp/vault/pull/23921)] + +## 1.15.16 Enterprise +### October 09, 2024 + +IMPROVEMENTS: + +* core: log at level ERROR rather than INFO when all seals are unhealthy. [[GH-28564](https://github.com/hashicorp/vault/pull/28564)] + +BUG FIXES: + +* auth/cert: When using ocsp_ca_certificates, an error was produced though extra certs validation succeeded. [[GH-28597](https://github.com/hashicorp/vault/pull/28597)] +* auth/token: Fix token TTL calculation so that it uses `max_lease_ttl` tune value for tokens created via `auth/token/create`. [[GH-28498](https://github.com/hashicorp/vault/pull/28498)] + ## 1.15.15 Enterprise ### September 25, 2024