Browse Source

chore: add tests for AWS/SM (#3057)

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 2 years ago
parent
commit
58cb47cc06

+ 5 - 2
pkg/provider/aws/secretsmanager/fake/fake.go

@@ -34,6 +34,7 @@ type Client struct {
 	PutSecretValueWithContextFn PutSecretValueWithContextFn
 	DescribeSecretWithContextFn DescribeSecretWithContextFn
 	DeleteSecretWithContextFn   DeleteSecretWithContextFn
+	ListSecretsFn               ListSecretsFn
 }
 
 type CreateSecretWithContextFn func(aws.Context, *awssm.CreateSecretInput, ...request.Option) (*awssm.CreateSecretOutput, error)
@@ -41,6 +42,7 @@ type GetSecretValueWithContextFn func(aws.Context, *awssm.GetSecretValueInput, .
 type PutSecretValueWithContextFn func(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error)
 type DescribeSecretWithContextFn func(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error)
 type DeleteSecretWithContextFn func(ctx aws.Context, input *awssm.DeleteSecretInput, opts ...request.Option) (*awssm.DeleteSecretOutput, error)
+type ListSecretsFn func(ctx aws.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error)
 
 func (sm Client) CreateSecretWithContext(ctx aws.Context, input *awssm.CreateSecretInput, options ...request.Option) (*awssm.CreateSecretOutput, error) {
 	return sm.CreateSecretWithContextFn(ctx, input, options...)
@@ -60,6 +62,7 @@ func NewCreateSecretWithContextFn(output *awssm.CreateSecretOutput, err error, e
 		return output, err
 	}
 }
+
 func (sm Client) DeleteSecretWithContext(ctx aws.Context, input *awssm.DeleteSecretInput, opts ...request.Option) (*awssm.DeleteSecretOutput, error) {
 	return sm.DeleteSecretWithContextFn(ctx, input, opts...)
 }
@@ -156,8 +159,8 @@ func (sm *Client) GetSecretValue(in *awssm.GetSecretValueInput) (*awssm.GetSecre
 	return nil, fmt.Errorf("test case not found")
 }
 
-func (sm *Client) ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error) {
-	return nil, nil
+func (sm *Client) ListSecrets(input *awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error) {
+	return sm.ListSecretsFn(nil, input)
 }
 
 func (sm *Client) cacheKeyForInput(in *awssm.GetSecretValueInput) string {

+ 286 - 0
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -18,16 +18,22 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"reflect"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/aws/session"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager/fake"
@@ -1025,3 +1031,283 @@ func getTagSlice() []*awssm.Tag {
 		},
 	}
 }
+func TestSecretsManagerGetAllSecrets(t *testing.T) {
+	ctx := context.Background()
+
+	errBoom := errors.New("boom")
+	secretName := "my-secret"
+	secretVersion := "AWSCURRENT"
+	secretPath := "/path/to/secret"
+	secretValue := "secret value"
+	secretTags := map[string]string{
+		"foo": "bar",
+	}
+	// Test cases
+	testCases := []struct {
+		name string
+		ref  esv1beta1.ExternalSecretFind
+
+		secretName    string
+		secretVersion string
+		secretValue   string
+		fetchError    error
+		listSecretsFn func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error)
+
+		expectedData  map[string][]byte
+		expectedError string
+	}{
+		{
+			name: "Matching secrets found",
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: secretName,
+				},
+				Path: ptr.To(secretPath),
+			},
+			secretName:    secretName,
+			secretVersion: secretVersion,
+			secretValue:   secretValue,
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				assert.Len(t, input.Filters, 1)
+				assert.Equal(t, "name", *input.Filters[0].Key)
+				assert.Equal(t, secretPath, *input.Filters[0].Values[0])
+				return &awssm.ListSecretsOutput{
+					SecretList: []*awssm.SecretListEntry{
+						{
+							Name: ptr.To(secretName),
+						},
+					},
+				}, nil
+			},
+			expectedData: map[string][]byte{
+				secretName: []byte(secretValue),
+			},
+			expectedError: "",
+		},
+		{
+			name: "Error occurred while fetching secret value",
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: secretName,
+				},
+				Path: ptr.To(secretPath),
+			},
+			secretName:    secretName,
+			secretVersion: secretVersion,
+			secretValue:   secretValue,
+			fetchError:    errBoom,
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				return &awssm.ListSecretsOutput{
+					SecretList: []*awssm.SecretListEntry{
+						{
+							Name: ptr.To(secretName),
+						},
+					},
+				}, nil
+			},
+			expectedData:  nil,
+			expectedError: errBoom.Error(),
+		},
+		{
+			name: "regexp: error occurred while listing secrets",
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: secretName,
+				},
+			},
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				return nil, errBoom
+			},
+			expectedData:  nil,
+			expectedError: errBoom.Error(),
+		},
+		{
+			name: "regep: no matching secrets found",
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: secretName,
+				},
+			},
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				return &awssm.ListSecretsOutput{
+					SecretList: []*awssm.SecretListEntry{
+						{
+							Name: ptr.To("other-secret"),
+						},
+					},
+				}, nil
+			},
+			expectedData:  make(map[string][]byte),
+			expectedError: "",
+		},
+		{
+			name: "invalid regexp",
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: "[",
+				},
+			},
+			expectedData:  nil,
+			expectedError: "could not compile find.name.regexp [[]: error parsing regexp: missing closing ]: `[`",
+		},
+
+		{
+			name: "tags: Matching secrets found",
+			ref: esv1beta1.ExternalSecretFind{
+				Tags: secretTags,
+			},
+			secretName:    secretName,
+			secretVersion: secretVersion,
+			secretValue:   secretValue,
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				assert.Len(t, input.Filters, 2)
+				assert.Equal(t, "tag-key", *input.Filters[0].Key)
+				assert.Equal(t, "foo", *input.Filters[0].Values[0])
+				assert.Equal(t, "tag-value", *input.Filters[1].Key)
+				assert.Equal(t, "bar", *input.Filters[1].Values[0])
+				return &awssm.ListSecretsOutput{
+					SecretList: []*awssm.SecretListEntry{
+						{
+							Name: ptr.To(secretName),
+						},
+					},
+				}, nil
+			},
+			expectedData: map[string][]byte{
+				secretName: []byte(secretValue),
+			},
+			expectedError: "",
+		},
+		{
+			name: "tags: error occurred while fetching secret value",
+			ref: esv1beta1.ExternalSecretFind{
+				Tags: secretTags,
+			},
+			secretName:    secretName,
+			secretVersion: secretVersion,
+			secretValue:   secretValue,
+			fetchError:    errBoom,
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				return &awssm.ListSecretsOutput{
+					SecretList: []*awssm.SecretListEntry{
+						{
+							Name: ptr.To(secretName),
+						},
+					},
+				}, nil
+			},
+			expectedData:  nil,
+			expectedError: errBoom.Error(),
+		},
+		{
+			name: "tags: error occurred while listing secrets",
+			ref: esv1beta1.ExternalSecretFind{
+				Tags: secretTags,
+			},
+			listSecretsFn: func(ctx context.Context, input *awssm.ListSecretsInput, opts ...request.Option) (*awssm.ListSecretsOutput, error) {
+				return nil, errBoom
+			},
+			expectedData:  nil,
+			expectedError: errBoom.Error(),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			fc := fakesm.NewClient()
+			fc.ListSecretsFn = tc.listSecretsFn
+			fc.WithValue(&awssm.GetSecretValueInput{
+				SecretId:     ptr.To(tc.secretName),
+				VersionStage: ptr.To(tc.secretVersion),
+			}, &awssm.GetSecretValueOutput{
+				Name:          ptr.To(tc.secretName),
+				VersionStages: []*string{ptr.To(tc.secretVersion)},
+				SecretBinary:  []byte(tc.secretValue),
+			}, tc.fetchError)
+			sm := SecretsManager{
+				client: fc,
+				cache:  make(map[string]*awssm.GetSecretValueOutput),
+			}
+			data, err := sm.GetAllSecrets(ctx, tc.ref)
+			if err != nil && err.Error() != tc.expectedError {
+				t.Errorf("unexpected error: got %v, want %v", err, tc.expectedError)
+			}
+			if !reflect.DeepEqual(data, tc.expectedData) {
+				t.Errorf("unexpected data: got %v, want %v", data, tc.expectedData)
+			}
+		})
+	}
+}
+
+func TestSecretsManagerValidate(t *testing.T) {
+	type fields struct {
+		sess         *session.Session
+		referentAuth bool
+	}
+	validSession, _ := session.NewSession(aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("fake", "fake", "fake")))
+	invalidSession, _ := session.NewSession(aws.NewConfig().WithCredentials(credentials.NewCredentials(&FakeCredProvider{
+		retrieveFunc: func() (credentials.Value, error) {
+			return credentials.Value{}, errors.New("invalid credentials")
+		},
+	})))
+	tests := []struct {
+		name    string
+		fields  fields
+		want    esv1beta1.ValidationResult
+		wantErr bool
+	}{
+		{
+			name: "ReferentAuth should always return unknown",
+			fields: fields{
+				referentAuth: true,
+			},
+			want: esv1beta1.ValidationResultUnknown,
+		},
+		{
+			name: "Valid credentials should return ready",
+			fields: fields{
+				sess: validSession,
+			},
+			want: esv1beta1.ValidationResultReady,
+		},
+		{
+			name: "Invalid credentials should return error",
+			fields: fields{
+				sess: invalidSession,
+			},
+			want:    esv1beta1.ValidationResultError,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sm := &SecretsManager{
+				sess:         tt.fields.sess,
+				referentAuth: tt.fields.referentAuth,
+			}
+			got, err := sm.Validate()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("SecretsManager.Validate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("SecretsManager.Validate() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+// FakeCredProvider implements the AWS credentials.Provider interface
+// It is used to inject an error into the AWS session to cause a
+// validation error.
+type FakeCredProvider struct {
+	retrieveFunc func() (credentials.Value, error)
+}
+
+func (f *FakeCredProvider) Retrieve() (credentials.Value, error) {
+	return f.retrieveFunc()
+}
+
+func (f *FakeCredProvider) IsExpired() bool {
+	return true
+}