| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package vault
- import (
- "context"
- "errors"
- "fmt"
- "strings"
- "testing"
- corev1 "k8s.io/api/core/v1"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- "github.com/external-secrets/external-secrets/providers/v1/vault/fake"
- vaultutil "github.com/external-secrets/external-secrets/providers/v1/vault/util"
- testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
- )
- const (
- fakeKey = "fake-key"
- fakeValue = "fake-value"
- managedBy = "managed-by"
- managedByESO = "external-secrets"
- )
- func TestDeleteSecret(t *testing.T) {
- type args struct {
- store *esv1.VaultProvider
- vLogical vaultutil.Logical
- }
- type want struct {
- err error
- }
- tests := map[string]struct {
- reason string
- args args
- ref *testingfake.PushSecretData
- want want
- value []byte
- }{
- "DeleteSecretNoOpKV1": {
- reason: "delete secret is a no-op if v1 secret does not exist",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretNoOpKV2": {
- reason: "delete secret is a no-op if v2 secret does not exist",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretFailIfErrorKV1": {
- reason: "delete v1 secret fails if error occurs",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errors.New("failed to read")),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: errors.New("failed to read"),
- },
- },
- "DeleteSecretFailIfErrorKV2": {
- reason: "delete v2 secret fails if error occurs",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errors.New("failed to read")),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: errors.New("failed to read"),
- },
- },
- "DeleteSecretNotManagedKV1": {
- reason: "delete v1 secret when not managed by ESO",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: "another-secret-tool",
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretNotManagedKV2": {
- reason: "delete v2 secret when not managed by eso",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: "another-secret-tool",
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretSuccessKV1": {
- reason: "delete secret succeeds if secret is managed by ESO and exists in vault v1",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretSuccessKV2": {
- reason: "delete secret succeeds if secret is managed by ESO and exists in vault v2",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretErrorKV1": {
- reason: "delete secret fails if error occurs v1",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, errors.New("failed to delete")),
- },
- },
- want: want{
- err: errors.New("failed to delete"),
- },
- },
- "DeleteSecretErrorKV2": {
- reason: "delete secret fails if error occurs v2",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, errors.New("failed to delete")),
- },
- },
- want: want{
- err: errors.New("failed to delete"),
- },
- },
- "DeleteSecretUpdatePropertyKV1": {
- reason: "Secret should only be updated if Property is set v1",
- ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: fakeKey},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "foo": "bar",
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "foo": "bar",
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- }}),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretUpdatePropertyKV2": {
- reason: "Secret should only be updated if Property is set v2",
- ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: fakeKey},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- "foo": "bar",
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"foo": "bar"}}),
- DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretIfNoOtherPropertiesKV1": {
- reason: "Secret should only be deleted if no other properties are set v1",
- ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "foo": "bar",
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "DeleteSecretIfNoOtherPropertiesKV2": {
- reason: "Secret should only be deleted if no other properties are set v2",
- ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- "foo": "bar",
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- }
- for name, tc := range tests {
- t.Run(name, func(t *testing.T) {
- ref := testingfake.PushSecretData{RemoteKey: "secret", Property: ""}
- if tc.ref != nil {
- ref = *tc.ref
- }
- client := &client{
- logical: tc.args.vLogical,
- store: tc.args.store,
- }
- err := client.DeleteSecret(context.Background(), ref)
- // Error nil XOR tc.want.err nil
- if ((err == nil) || (tc.want.err == nil)) && !((err == nil) && (tc.want.err == nil)) {
- t.Errorf("\nTesting DeleteSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, err)
- }
- // if errors are the same type but their contents do not match
- if err != nil && tc.want.err != nil {
- if !strings.Contains(err.Error(), tc.want.err.Error()) {
- t.Errorf("\nTesting DeleteSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error got nil", name, tc.reason, tc.want.err)
- }
- }
- })
- }
- }
- func TestPushSecret(t *testing.T) {
- secretKey := "secret-key"
- noPermission := errors.New("no permission")
- type args struct {
- store *esv1.VaultProvider
- vLogical vaultutil.Logical
- }
- type want struct {
- err error
- }
- tests := map[string]struct {
- reason string
- args args
- want want
- data *testingfake.PushSecretData
- value []byte
- secret *corev1.Secret
- }{
- "SetSecretKV1": {
- reason: "secret is successfully set, with no existing vault secret",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.NewWriteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "SetSecretKV2": {
- reason: "secret is successfully set, with no existing vault secret",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.NewWriteWithContextFn(nil, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "SetSecretWithWriteErrorKV1": {
- reason: "secret cannot be pushed if write fails",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.NewWriteWithContextFn(nil, noPermission),
- },
- },
- want: want{
- err: noPermission,
- },
- },
- "SetSecretWithWriteErrorKV2": {
- reason: "secret cannot be pushed if write fails",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.NewWriteWithContextFn(nil, noPermission),
- },
- },
- want: want{
- err: noPermission,
- },
- },
- "SetSecretEqualsPushSecretV1": {
- reason: "vault secret kv equals secret to push kv",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "SetSecretEqualsPushSecretV2": {
- reason: "vault secret kv equals secret to push kv",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretPropertyKV1": {
- reason: "push secret with property adds the property",
- value: []byte(fakeValue),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]string{
- managedBy: managedByESO,
- },
- "foo": fakeValue,
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretPropertyKV2": {
- reason: "push secret with property adds the property",
- value: []byte(fakeValue),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{fakeKey: fakeValue, "foo": fakeValue}}),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretUpdatePropertyKV1": {
- reason: "push secret with property only updates the property",
- value: []byte("new-value"),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "foo": fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "foo": "new-value",
- "custom_metadata": map[string]string{
- managedBy: managedByESO,
- },
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretUpdatePropertyKV2": {
- reason: "push secret with property only updates the property",
- value: []byte("new-value"),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- "foo": fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"foo": "new-value"}}),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretPropertyNoUpdateKV1": {
- reason: "push secret with property only updates the property",
- value: []byte(fakeValue),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "foo": fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "PushSecretPropertyNoUpdateKV2": {
- reason: "push secret with property only updates the property",
- value: []byte(fakeValue),
- data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- "foo": fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
- },
- },
- want: want{
- err: nil,
- },
- },
- "SetSecretErrorReadingSecretKV1": {
- reason: "error occurs if secret cannot be read",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, noPermission),
- },
- },
- want: want{
- err: fmt.Errorf(errReadSecret, noPermission),
- },
- },
- "SetSecretErrorReadingSecretKV2": {
- reason: "error occurs if secret cannot be read",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, noPermission),
- },
- },
- want: want{
- err: fmt.Errorf(errReadSecret, noPermission),
- },
- },
- "SetSecretNotManagedByESOV1": {
- reason: "a secret not managed by ESO cannot be updated",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: "fake-value2",
- "custom_metadata": map[string]any{
- managedBy: "not-external-secrets",
- },
- }, nil),
- },
- },
- want: want{
- err: errors.New("secret not managed by external-secrets"),
- },
- },
- "SetSecretNotManagedByESOV2": {
- reason: "a secret not managed by ESO cannot be updated",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: "fake-value2",
- "custom_metadata": map[string]any{
- managedBy: "not-external-secrets",
- },
- },
- }, nil),
- },
- },
- want: want{
- err: errors.New("secret not managed by external-secrets"),
- },
- },
- "WholeSecretKV2": {
- reason: "secret is successfully set, with no existing vault secret",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"key1": "value1", "key2": "value2"}}),
- },
- },
- data: &testingfake.PushSecretData{SecretKey: "", RemoteKey: "secret", Property: ""},
- secret: &corev1.Secret{Data: map[string][]byte{"key1": []byte(`value1`), "key2": []byte(`value2`)}},
- want: want{
- err: nil,
- },
- },
- "CASRequiredNewSecretKV2": {
- reason: "CAS required: new secret should be created with cas=0",
- args: args{
- store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "options": map[string]any{
- "cas": 0,
- },
- "data": map[string]any{fakeKey: fakeValue},
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "CASRequiredExistingSecretKV2": {
- reason: "CAS required: existing secret should be updated with current version",
- args: args{
- store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithDataAndMetadataFn(
- map[string]any{
- "data": map[string]any{
- "existing": "value",
- },
- },
- map[string]any{
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- "current_version": 3,
- },
- nil, nil,
- ),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "options": map[string]any{
- "cas": 3,
- },
- "data": map[string]any{fakeKey: fakeValue},
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "CASRequiredPropertyUpdateKV2": {
- reason: "CAS required: property update should use current version",
- value: []byte("property-value"),
- data: &testingfake.PushSecretData{SecretKey: "secret-key", RemoteKey: "secret", Property: "new-prop"},
- args: args{
- store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithDataAndMetadataFn(
- map[string]any{
- "data": map[string]any{
- "existing": "value",
- },
- },
- map[string]any{
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- "current_version": 2,
- },
- nil, nil,
- ),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "options": map[string]any{
- "cas": 2,
- },
- "data": map[string]any{
- "existing": "value",
- "new-prop": "property-value",
- },
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "CASNotRequiredKV2": {
- reason: "CAS not required: should work without CAS options",
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- "data": map[string]any{fakeKey: fakeValue},
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- "CASIgnoredKV1": {
- reason: "CAS ignored for KV v1: should work without CAS options even when required",
- args: args{
- store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]string{
- managedBy: managedByESO,
- },
- }),
- },
- },
- want: want{
- err: nil,
- },
- },
- // Security regression tests: ensure json.Unmarshal errors don't leak secret data
- "InvalidJSONDoesNotLeakSecretDataKV1": {
- reason: "json.Unmarshal error should not leak secret data in error message",
- value: []byte(`not-valid-json-contains-secret-8019210420527506405`),
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- },
- },
- want: want{
- err: errors.New("error unmarshalling vault secret: invalid JSON format"),
- },
- },
- "InvalidJSONDoesNotLeakSecretDataKV2": {
- reason: "json.Unmarshal error should not leak secret data in error message",
- value: []byte(`not-valid-json-contains-secret-8019210420527506405`),
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
- },
- },
- want: want{
- err: errors.New("error unmarshalling vault secret: invalid JSON format"),
- },
- },
- "InvalidJSONCompareDoesNotLeakSecretDataKV1": {
- reason: "json.Unmarshal error during comparison should not leak secret data",
- value: []byte(`invalid-json-with-api-key-12345`),
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- fakeKey: fakeValue,
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- },
- },
- want: want{
- err: errors.New("error unmarshalling incoming secret value: invalid JSON format"),
- },
- },
- "InvalidJSONCompareDoesNotLeakSecretDataKV2": {
- reason: "json.Unmarshal error during comparison should not leak secret data",
- value: []byte(`invalid-json-with-api-key-12345`),
- args: args{
- store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
- vLogical: &fake.Logical{
- ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
- "data": map[string]any{
- fakeKey: fakeValue,
- },
- "custom_metadata": map[string]any{
- managedBy: managedByESO,
- },
- }, nil),
- },
- },
- want: want{
- err: errors.New("error unmarshalling incoming secret value: invalid JSON format"),
- },
- },
- }
- for name, tc := range tests {
- t.Run(name, func(t *testing.T) {
- data := testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: ""}
- if tc.data != nil {
- data = *tc.data
- }
- client := &client{
- logical: tc.args.vLogical,
- store: tc.args.store,
- }
- s := tc.secret
- if s == nil {
- val := tc.value
- if val == nil {
- val = []byte(`{"fake-key":"fake-value"}`)
- }
- s = &corev1.Secret{Data: map[string][]byte{secretKey: val}}
- }
- err := client.PushSecret(context.Background(), s, data)
- // Error nil XOR tc.want.err nil
- if ((err == nil) || (tc.want.err == nil)) && !((err == nil) && (tc.want.err == nil)) {
- t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, err)
- }
- // if errors are the same type but their contents do not match
- if err != nil && tc.want.err != nil {
- if !strings.Contains(err.Error(), tc.want.err.Error()) {
- t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error got nil", name, tc.reason, tc.want.err)
- }
- }
- // Security regression: ensure error messages don't leak secret data
- if err != nil && tc.value != nil {
- secretData := string(tc.value)
- if strings.Contains(err.Error(), secretData) {
- t.Errorf("\nSECURITY REGRESSION: Error message contains secret data!\nName: %v\nSecret data: %v\nError: %v", name, secretData, err)
- }
- }
- })
- }
- }
- func makeValidSecretStoreWithCASRequired(version esv1.VaultKVStoreVersion) *esv1.SecretStore {
- store := makeValidSecretStoreWithVersion(version)
- store.Spec.Provider.Vault.CheckAndSet = &esv1.VaultCheckAndSet{
- Required: true,
- }
- return store
- }
|