Просмотр исходного кода

Adding DeleteSecret for AWS ParameterStore

Signed-off-by: Gustavo <gusfcarvalho@gmail.com>
Gustavo 3 лет назад
Родитель
Сommit
d0be6d2ca0

+ 12 - 0
pkg/provider/aws/parameterstore/fake/fake.go

@@ -27,6 +27,7 @@ import (
 type Client struct {
 	GetParameterWithContextFn        GetParameterWithContextFn
 	PutParameterWithContextFn        PutParameterWithContextFn
+	DeleteParameterWithContextFn     DeleteParameterWithContextFn
 	DescribeParametersWithContextFn  DescribeParametersWithContextFn
 	ListTagsForResourceWithContextFn ListTagsForResourceWithContextFn
 }
@@ -35,6 +36,7 @@ type GetParameterWithContextFn func(aws.Context, *ssm.GetParameterInput, ...requ
 type PutParameterWithContextFn func(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
 type DescribeParametersWithContextFn func(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
 type ListTagsForResourceWithContextFn func(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
+type DeleteParameterWithContextFn func(ctx aws.Context, input *ssm.DeleteParameterInput, opts ...request.Option) (*ssm.DeleteParameterOutput, error)
 
 func (sm *Client) ListTagsForResourceWithContext(ctx aws.Context, input *ssm.ListTagsForResourceInput, options ...request.Option) (*ssm.ListTagsForResourceOutput, error) {
 	return sm.ListTagsForResourceWithContextFn(ctx, input, options...)
@@ -46,6 +48,16 @@ func NewListTagsForResourceWithContextFn(output *ssm.ListTagsForResourceOutput,
 	}
 }
 
+func (sm *Client) DeleteParameterWithContext(ctx aws.Context, input *ssm.DeleteParameterInput, opts ...request.Option) (*ssm.DeleteParameterOutput, error) {
+	return sm.DeleteParameterWithContextFn(ctx, input, opts...)
+}
+
+func NewDeleteParameterWithContextFn(output *ssm.DeleteParameterOutput, err error) DeleteParameterWithContextFn {
+	return func(aws.Context, *ssm.DeleteParameterInput, ...request.Option) (*ssm.DeleteParameterOutput, error) {
+		return output, err
+	}
+}
+
 func (sm *Client) GetParameterWithContext(ctx aws.Context, input *ssm.GetParameterInput, options ...request.Option) (*ssm.GetParameterOutput, error) {
 	return sm.GetParameterWithContextFn(ctx, input, options...)
 }

+ 33 - 1
pkg/provider/aws/parameterstore/parameterstore.go

@@ -53,6 +53,7 @@ type PMInterface interface {
 	PutParameterWithContext(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
 	DescribeParametersWithContext(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
 	ListTagsForResourceWithContext(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
+	DeleteParameterWithContext(ctx aws.Context, input *ssm.DeleteParameterInput, opts ...request.Option) (*ssm.DeleteParameterOutput, error)
 }
 
 const (
@@ -85,7 +86,38 @@ func (pm *ParameterStore) getTagsByName(ctx aws.Context, ref *ssm.GetParameterOu
 }
 
 func (pm *ParameterStore) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
-	return fmt.Errorf("not implemented")
+	secretName := remoteRef.GetRemoteKey()
+	secretValue := ssm.GetParameterInput{
+		Name: &secretName,
+	}
+	existing, err := pm.client.GetParameterWithContext(ctx, &secretValue)
+	var awsError awserr.Error
+	ok := errors.As(err, &awsError)
+	if err != nil && (!ok || awsError.Code() != ssm.ErrCodeParameterNotFound) {
+		return fmt.Errorf("unexpected error getting parameter %v: %w", secretName, err)
+	}
+	if existing != nil && existing.Parameter != nil {
+		fmt.Println("The existing value contains data:", existing.String())
+		tags, err := pm.getTagsByName(ctx, existing)
+		if err != nil {
+			return fmt.Errorf("error getting the existing tags for the parameter %v: %w", secretName, err)
+		}
+
+		isManaged := isManagedByESO(tags)
+
+		if !isManaged {
+			// If the secret is not managed by external-secrets, it is "deleted" effectively by all means
+			return nil
+		}
+		deleteInput := &ssm.DeleteParameterInput{
+			Name: &secretName,
+		}
+		_, err = pm.client.DeleteParameterWithContext(ctx, deleteInput)
+		if err != nil {
+			return fmt.Errorf("could not delete parameter %v: %w", secretName, err)
+		}
+	}
+	return nil
 }
 
 func (pm *ParameterStore) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {

+ 169 - 0
pkg/provider/aws/parameterstore/parameterstore_test.go

@@ -21,6 +21,7 @@ import (
 	"testing"
 
 	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/google/go-cmp/cmp"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -91,6 +92,174 @@ func makeValidParameterStoreTestCaseCustom(tweaks ...func(pstc *parameterstoreTe
 	return pstc
 }
 
+func TestDeleteSecret(t *testing.T) {
+	fakeClient := fakeps.Client{}
+	parameterName := "parameter"
+	managedBy := "managed-by"
+	manager := "external-secrets"
+	ssmTag := ssm.Tag{
+		Key:   &managedBy,
+		Value: &manager,
+	}
+	type args struct {
+		client                fakeps.Client
+		getParameterOutput    *ssm.GetParameterOutput
+		listTagsOutput        *ssm.ListTagsForResourceOutput
+		deleteParameterOutput *ssm.DeleteParameterOutput
+		getParameterError     error
+		listTagsError         error
+		deleteParameterError  error
+	}
+
+	type want struct {
+		err error
+	}
+
+	type testCase struct {
+		args   args
+		want   want
+		reason string
+	}
+	tests := map[string]testCase{
+		"Deletes Successfully": {
+			args: args{
+				client: fakeClient,
+				getParameterOutput: &ssm.GetParameterOutput{
+					Parameter: &ssm.Parameter{
+						Name: &parameterName,
+					},
+				},
+				listTagsOutput: &ssm.ListTagsForResourceOutput{
+					TagList: []*ssm.Tag{&ssmTag},
+				},
+				deleteParameterOutput: nil,
+				getParameterError:     nil,
+				listTagsError:         nil,
+				deleteParameterError:  nil,
+			},
+			want: want{
+				err: nil,
+			},
+			reason: "",
+		},
+		"Secret Not Found": {
+			args: args{
+				client:                fakeClient,
+				getParameterOutput:    nil,
+				listTagsOutput:        nil,
+				deleteParameterOutput: nil,
+				getParameterError:     awserr.New(ssm.ErrCodeParameterNotFound, "not here, sorry dude", nil),
+				listTagsError:         nil,
+				deleteParameterError:  nil,
+			},
+			want: want{
+				err: nil,
+			},
+			reason: "",
+		},
+		"No permissions to get secret": {
+			args: args{
+				client:                fakeClient,
+				getParameterOutput:    nil,
+				listTagsOutput:        nil,
+				deleteParameterOutput: nil,
+				getParameterError:     errors.New("no permissions"),
+				listTagsError:         nil,
+				deleteParameterError:  nil,
+			},
+			want: want{
+				err: errors.New("no permissions"),
+			},
+			reason: "",
+		},
+		"No permissions to get tags": {
+			args: args{
+				client: fakeClient,
+				getParameterOutput: &ssm.GetParameterOutput{
+					Parameter: &ssm.Parameter{
+						Name: &parameterName,
+					},
+				},
+				listTagsOutput:        nil,
+				deleteParameterOutput: nil,
+				getParameterError:     nil,
+				listTagsError:         errors.New("no permissions"),
+				deleteParameterError:  nil,
+			},
+			want: want{
+				err: errors.New("no permissions"),
+			},
+			reason: "",
+		},
+		"Secret Not Managed by External Secrets": {
+			args: args{
+				client: fakeClient,
+				getParameterOutput: &ssm.GetParameterOutput{
+					Parameter: &ssm.Parameter{
+						Name: &parameterName,
+					},
+				},
+				listTagsOutput: &ssm.ListTagsForResourceOutput{
+					TagList: []*ssm.Tag{},
+				},
+				deleteParameterOutput: nil,
+				getParameterError:     nil,
+				listTagsError:         nil,
+				deleteParameterError:  nil,
+			},
+			want: want{
+				err: nil,
+			},
+			reason: "",
+		},
+		"No permissions delete secret": {
+			args: args{
+				client: fakeClient,
+				getParameterOutput: &ssm.GetParameterOutput{
+					Parameter: &ssm.Parameter{
+						Name: &parameterName,
+					},
+				},
+				listTagsOutput: &ssm.ListTagsForResourceOutput{
+					TagList: []*ssm.Tag{&ssmTag},
+				},
+				deleteParameterOutput: nil,
+				getParameterError:     nil,
+				listTagsError:         nil,
+				deleteParameterError:  errors.New("no permissions"),
+			},
+			want: want{
+				err: errors.New("no permissions"),
+			},
+			reason: "",
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			ref := fakeRef{key: "fake-key"}
+			ps := ParameterStore{
+				client: &tc.args.client,
+			}
+			tc.args.client.GetParameterWithContextFn = fakeps.NewGetParameterWithContextFn(tc.args.getParameterOutput, tc.args.getParameterError)
+			tc.args.client.ListTagsForResourceWithContextFn = fakeps.NewListTagsForResourceWithContextFn(tc.args.listTagsOutput, tc.args.listTagsError)
+			tc.args.client.DeleteParameterWithContextFn = fakeps.NewDeleteParameterWithContextFn(tc.args.deleteParameterOutput, tc.args.deleteParameterError)
+			err := ps.DeleteSecret(context.TODO(), ref)
+
+			// 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)
+				}
+			}
+		})
+	}
+}
 func TestPushSecret(t *testing.T) {
 	invalidParameters := errors.New(ssm.ErrCodeInvalidParameters)
 	alreadyExistsError := errors.New(ssm.ErrCodeAlreadyExistsException)