| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- /*
- Copyright © 2025 ESO Maintainer Team
- 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 vaultdynamic
- import (
- "context"
- "errors"
- "testing"
- "github.com/google/go-cmp/cmp"
- vaultapi "github.com/hashicorp/vault/api"
- corev1 "k8s.io/api/core/v1"
- apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
- clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
- utilfake "github.com/external-secrets/external-secrets/pkg/provider/util/fake"
- provider "github.com/external-secrets/external-secrets/pkg/provider/vault"
- "github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
- "github.com/external-secrets/external-secrets/pkg/provider/vault/util"
- )
- type args struct {
- jsonSpec *apiextensions.JSON
- kube kclient.Client
- corev1 typedcorev1.CoreV1Interface
- vaultClientFn func(config *vaultapi.Config) (vaultutil.Client, error)
- }
- type want struct {
- val map[string][]byte
- partialVal map[string][]byte
- err error
- }
- type testCase struct {
- reason string
- args args
- want want
- }
- func TestVaultDynamicSecretGenerator(t *testing.T) {
- cases := map[string]testCase{
- "NilSpec": {
- reason: "Raise an error with empty spec.",
- args: args{
- jsonSpec: nil,
- },
- want: want{
- err: errors.New("no config spec provided"),
- },
- },
- "InvalidSpec": {
- reason: "Raise an error with invalid spec.",
- args: args{
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(``),
- },
- },
- want: want{
- err: errors.New("no Vault provider config in spec"),
- },
- },
- "MissingRoleName": {
- reason: "Raise error if incomplete k8s auth config is provided.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- serviceAccountRef:
- name: "testing"
- method: GET
- path: "github/token/example"`),
- },
- kube: clientfake.NewClientBuilder().Build(),
- },
- want: want{
- err: errors.New("unable to setup Vault client: no role name was provided"),
- },
- },
- "EmptyVaultResponse": {
- reason: "Fail on empty response from Vault.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- method: GET
- path: "github/token/example"`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- },
- want: want{
- err: errors.New("unable to get dynamic secret: empty response from Vault"),
- },
- },
- "EmptyVaultPOST": {
- reason: "Fail on empty response from Vault.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- method: POST
- parameters:
- foo: "bar"
- path: "github/token/example"`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- },
- want: want{
- err: errors.New("unable to get dynamic secret: empty response from Vault"),
- },
- },
- "AllowEmptyVaultPOST": {
- reason: "Allow empty response from Vault POST.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- method: POST
- parameters:
- foo: "bar"
- path: "github/token/example"
- allowEmptyResponse: true`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- },
- want: want{
- err: nil,
- val: nil,
- },
- },
- "AllowEmptyVaultGET": {
- reason: "Allow empty response from Vault GET.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- method: GET
- parameters:
- foo: "bar"
- path: "github/token/example"
- allowEmptyResponse: true`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- },
- want: want{
- err: nil,
- val: nil,
- },
- },
- "DataResultType": {
- reason: "Allow accessing the data section of the response from Vault API.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- path: "github/token/example"`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- vaultClientFn: fake.ModifiableClientWithLoginMock(
- func(cl *fake.VaultClient) {
- cl.MockLogical.ReadWithDataWithContextFn = func(ctx context.Context, path string, data map[string][]string) (*vaultapi.Secret, error) {
- return &vaultapi.Secret{
- Data: map[string]interface{}{
- "key": "value",
- },
- }, nil
- }
- },
- ),
- },
- want: want{
- err: nil,
- val: map[string][]byte{"key": []byte("value")},
- },
- },
- "AuthResultType": {
- reason: "Allow accessing auth section of the response from Vault API.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- path: "github/token/example"
- resultType: "Auth"`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- vaultClientFn: fake.ModifiableClientWithLoginMock(
- func(cl *fake.VaultClient) {
- cl.MockLogical.ReadWithDataWithContextFn = func(ctx context.Context, path string, data map[string][]string) (*vaultapi.Secret, error) {
- return &vaultapi.Secret{
- Auth: &vaultapi.SecretAuth{
- EntityID: "123",
- },
- }, nil
- }
- },
- ),
- },
- want: want{
- err: nil,
- partialVal: map[string][]byte{"entity_id": []byte("123")},
- },
- },
- "RawResultType": {
- reason: "Allow accessing auth section of the response from Vault API.",
- args: args{
- corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
- jsonSpec: &apiextensions.JSON{
- Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
- kind: VaultDynamicSecret
- spec:
- provider:
- auth:
- kubernetes:
- role: test
- serviceAccountRef:
- name: "testing"
- path: "github/token/example"
- resultType: "Raw"`),
- },
- kube: clientfake.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: "testing",
- Namespace: "testing",
- },
- Secrets: []corev1.ObjectReference{
- {
- Name: "test",
- },
- },
- }).Build(),
- vaultClientFn: fake.ModifiableClientWithLoginMock(
- func(cl *fake.VaultClient) {
- cl.MockLogical.ReadWithDataWithContextFn = func(ctx context.Context, path string, data map[string][]string) (*vaultapi.Secret, error) {
- return &vaultapi.Secret{
- LeaseID: "123",
- Data: map[string]interface{}{
- "key": "value",
- },
- }, nil
- }
- },
- ),
- },
- want: want{
- err: nil,
- partialVal: map[string][]byte{
- "lease_id": []byte("123"),
- "data": []byte(`{"key":"value"}`),
- },
- },
- },
- }
- for name, tc := range cases {
- t.Run(name, func(t *testing.T) {
- newClientFn := fake.ClientWithLoginMock
- if tc.args.vaultClientFn != nil {
- newClientFn = tc.args.vaultClientFn
- }
- c := &provider.Provider{NewVaultClient: newClientFn}
- gen := &Generator{}
- val, _, err := gen.generate(context.Background(), c, tc.args.jsonSpec, tc.args.kube, tc.args.corev1, "testing")
- if tc.want.err != nil {
- if err != nil {
- if diff := cmp.Diff(tc.want.err.Error(), err.Error()); diff != "" {
- t.Errorf("\n%s\nvault.GetSecret(...): -want error, +got error:\n%s", tc.reason, diff)
- }
- } else {
- t.Errorf("\n%s\nvault.GetSecret(...): -want error, +got val:\n%s", tc.reason, val)
- }
- } else if tc.want.partialVal != nil {
- for k, v := range tc.want.partialVal {
- if diff := cmp.Diff(v, val[k]); diff != "" {
- t.Errorf("\n%s\nvault.GetSecret(...) -> %s: -want partial, +got partial:\n%s", k, tc.reason, diff)
- }
- }
- } else {
- if diff := cmp.Diff(tc.want.val, val); diff != "" {
- t.Errorf("\n%s\nvault.GetSecret(...): -want val, +got val:\n%s", tc.reason, diff)
- }
- }
- })
- }
- }
|