sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/secretsmanager/secret_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package secretsmanager
    18  
    19  import (
    20  	"math/rand"
    21  	"sort"
    22  	"testing"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/service/secretsmanager"
    26  	"github.com/golang/mock/gomock"
    27  	. "github.com/onsi/gomega"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors"
    35  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    36  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/secretsmanager/mock_secretsmanageriface"
    37  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    38  )
    39  
    40  func TestService_Create(t *testing.T) {
    41  	mockCtrl := gomock.NewController(t)
    42  	defer mockCtrl.Finish()
    43  
    44  	generateBytes := func(g *WithT, count int64) []byte {
    45  		token := make([]byte, count)
    46  		_, err := rand.Read(token)
    47  		g.Expect(err).NotTo(HaveOccurred())
    48  		return token
    49  	}
    50  
    51  	sortTagsByKey := func(tags []*secretsmanager.Tag) {
    52  		sort.Slice(tags, func(i, j int) bool {
    53  			return *(tags[i].Key) < *(tags[j].Key)
    54  		})
    55  	}
    56  
    57  	expectedTags := []*secretsmanager.Tag{
    58  		{
    59  			Key:   aws.String("Name"),
    60  			Value: aws.String("infra-cluster"),
    61  		},
    62  		{
    63  			Key:   aws.String("kubernetes.io/cluster/test"),
    64  			Value: aws.String("owned"),
    65  		},
    66  		{
    67  			Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test"),
    68  			Value: aws.String("owned"),
    69  		},
    70  		{
    71  			Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
    72  			Value: aws.String("node"),
    73  		},
    74  	}
    75  
    76  	check := func(g *WithT, actualPrefix string, expectedPrefix string, err error, wantErr bool) {
    77  		g.Expect(actualPrefix).Should(HavePrefix(expectedPrefix))
    78  		if wantErr {
    79  			g.Expect(err).To(HaveOccurred())
    80  			return
    81  		}
    82  		g.Expect(err).NotTo(HaveOccurred())
    83  	}
    84  
    85  	tests := []struct {
    86  		name           string
    87  		bytesCount     int64
    88  		secretPrefix   string
    89  		expectedPrefix string
    90  		wantErr        bool
    91  		expect         func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder)
    92  	}{
    93  		{
    94  			name:           "Should not store data in secret manager if data is having zero bytes",
    95  			bytesCount:     0,
    96  			secretPrefix:   "/awsprefix",
    97  			expectedPrefix: "/awsprefix",
    98  			wantErr:        false,
    99  			expect: func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   100  				m.CreateSecret(gomock.Any()).Times(0)
   101  			},
   102  		},
   103  		{
   104  			name:           "Should store data in secret manager if data is having non-zero bytes",
   105  			bytesCount:     10000,
   106  			secretPrefix:   "prefix",
   107  			expectedPrefix: "prefix",
   108  			wantErr:        false,
   109  			expect: func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   110  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).MinTimes(1).Return(&secretsmanager.CreateSecretOutput{}, nil).Do(
   111  					func(createSecretInput *secretsmanager.CreateSecretInput) {
   112  						g.Expect(*(createSecretInput.Name)).To(HavePrefix("prefix-"))
   113  						sortTagsByKey(createSecretInput.Tags)
   114  						g.Expect(createSecretInput.Tags).To(Equal(expectedTags))
   115  					},
   116  				)
   117  			},
   118  		},
   119  		{
   120  			name:           "Should not retry if non-retryable error occurred while storing data in secret manager",
   121  			bytesCount:     10,
   122  			secretPrefix:   "/prefix",
   123  			expectedPrefix: "/prefix",
   124  			wantErr:        true,
   125  			expect: func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   126  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(nil, &secretsmanager.InternalServiceError{}).Do(
   127  					func(createSecretInput *secretsmanager.CreateSecretInput) {
   128  						g.Expect(*(createSecretInput.Name)).To(HavePrefix("/prefix-"))
   129  						sortTagsByKey(createSecretInput.Tags)
   130  						g.Expect(createSecretInput.Tags).To(Equal(expectedTags))
   131  					},
   132  				)
   133  			},
   134  		},
   135  		{
   136  			name:           "Should retry if retryable error occurred while storing data in secret manager",
   137  			bytesCount:     10,
   138  			secretPrefix:   "",
   139  			expectedPrefix: "aws.cluster.x-k8s.io",
   140  			wantErr:        false,
   141  			expect: func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   142  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(nil, &secretsmanager.InvalidRequestException{})
   143  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(nil, &secretsmanager.ResourceNotFoundException{})
   144  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(&secretsmanager.CreateSecretOutput{}, nil)
   145  			},
   146  		},
   147  		{
   148  			name:           "Should delete and retry creation if resource already exists while storing data in secret manager",
   149  			bytesCount:     10,
   150  			secretPrefix:   "",
   151  			expectedPrefix: "aws.cluster.x-k8s.io",
   152  			wantErr:        false,
   153  			expect: func(g *WithT, m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   154  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(nil, &secretsmanager.ResourceExistsException{})
   155  				m.DeleteSecret(gomock.AssignableToTypeOf(&secretsmanager.DeleteSecretInput{})).Return(&secretsmanager.DeleteSecretOutput{}, nil)
   156  				m.CreateSecret(gomock.AssignableToTypeOf(&secretsmanager.CreateSecretInput{})).Return(&secretsmanager.CreateSecretOutput{}, nil)
   157  			},
   158  		},
   159  	}
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			g := NewWithT(t)
   163  			scheme := runtime.NewScheme()
   164  			_ = infrav1.AddToScheme(scheme)
   165  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   166  			clusterScope, err := getClusterScope(client)
   167  			g.Expect(err).NotTo(HaveOccurred())
   168  
   169  			secretManagerClientMock := mock_secretsmanageriface.NewMockSecretsManagerAPI(mockCtrl)
   170  			tt.expect(g, secretManagerClientMock.EXPECT())
   171  			s := NewService(clusterScope)
   172  			s.SecretsManagerClient = secretManagerClientMock
   173  			ms, err := getMachineScope(client, clusterScope)
   174  			g.Expect(err).NotTo(HaveOccurred())
   175  			ms.SetSecretPrefix(tt.secretPrefix)
   176  			data := generateBytes(g, tt.bytesCount)
   177  
   178  			prefix, _, err := s.Create(ms, data)
   179  			check(g, prefix, tt.expectedPrefix, err, tt.wantErr)
   180  		})
   181  	}
   182  }
   183  
   184  func TestService_Delete(t *testing.T) {
   185  	mockCtrl := gomock.NewController(t)
   186  	defer mockCtrl.Finish()
   187  
   188  	tests := []struct {
   189  		name        string
   190  		secretCount int32
   191  		expect      func(m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder)
   192  		check       func(*WithT, error)
   193  	}{
   194  		{
   195  			name:        "Should not call AWS when secret count has zero value",
   196  			secretCount: 0,
   197  			check: func(g *WithT, err error) {
   198  				g.Expect(err).NotTo(HaveOccurred())
   199  			},
   200  		},
   201  		{
   202  			name:        "Should not return error when delete is successful",
   203  			secretCount: 1,
   204  			expect: func(m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   205  				m.DeleteSecret(gomock.Eq(&secretsmanager.DeleteSecretInput{
   206  					SecretId:                   aws.String("prefix-0"),
   207  					ForceDeleteWithoutRecovery: aws.Bool(true),
   208  				})).Return(&secretsmanager.DeleteSecretOutput{}, nil)
   209  			},
   210  			check: func(g *WithT, err error) {
   211  				g.Expect(err).NotTo(HaveOccurred())
   212  			},
   213  		},
   214  		{
   215  			name:        "Should return all errors except not found errors",
   216  			secretCount: 3,
   217  			expect: func(m *mock_secretsmanageriface.MockSecretsManagerAPIMockRecorder) {
   218  				m.DeleteSecret(gomock.Eq(&secretsmanager.DeleteSecretInput{
   219  					SecretId:                   aws.String("prefix-0"),
   220  					ForceDeleteWithoutRecovery: aws.Bool(true),
   221  				})).Return(nil, awserrors.NewFailedDependency("failed dependency"))
   222  				m.DeleteSecret(gomock.Eq(&secretsmanager.DeleteSecretInput{
   223  					SecretId:                   aws.String("prefix-1"),
   224  					ForceDeleteWithoutRecovery: aws.Bool(true),
   225  				})).Return(nil, awserrors.NewNotFound("not found"))
   226  				m.DeleteSecret(gomock.Eq(&secretsmanager.DeleteSecretInput{
   227  					SecretId:                   aws.String("prefix-2"),
   228  					ForceDeleteWithoutRecovery: aws.Bool(true),
   229  				})).Return(nil, awserrors.NewConflict("new conflict"))
   230  			},
   231  			check: func(g *WithT, err error) {
   232  				g.Expect(err).ToNot(BeNil())
   233  				g.Expect((err.Error())).To(Equal("[failed dependency, new conflict]"))
   234  			},
   235  		},
   236  	}
   237  	for _, tt := range tests {
   238  		t.Run(tt.name, func(t *testing.T) {
   239  			g := NewWithT(t)
   240  			client := setupClient()
   241  			clusterScope, err := getClusterScope(client)
   242  			g.Expect(err).NotTo(HaveOccurred())
   243  
   244  			secretManagerClientMock := mock_secretsmanageriface.NewMockSecretsManagerAPI(mockCtrl)
   245  			if tt.expect != nil {
   246  				tt.expect(secretManagerClientMock.EXPECT())
   247  			}
   248  			s := NewService(clusterScope)
   249  			s.SecretsManagerClient = secretManagerClientMock
   250  			ms, err := getMachineScope(client, clusterScope)
   251  			g.Expect(err).NotTo(HaveOccurred())
   252  
   253  			ms.SetSecretPrefix("prefix")
   254  			ms.SetSecretCount(tt.secretCount)
   255  			err = s.Delete(ms)
   256  			tt.check(g, err)
   257  		})
   258  	}
   259  }
   260  
   261  func setupClient() client.Client {
   262  	scheme := runtime.NewScheme()
   263  	_ = infrav1.AddToScheme(scheme)
   264  	return fake.NewClientBuilder().WithScheme(scheme).Build()
   265  }
   266  
   267  func getClusterScope(client client.Client) (*scope.ClusterScope, error) {
   268  	cluster := &clusterv1.Cluster{
   269  		ObjectMeta: metav1.ObjectMeta{
   270  			Name: "test",
   271  		},
   272  	}
   273  	return scope.NewClusterScope(scope.ClusterScopeParams{
   274  		Client:     client,
   275  		Cluster:    cluster,
   276  		AWSCluster: &infrav1.AWSCluster{},
   277  	})
   278  }
   279  
   280  func getMachineScope(client client.Client, clusterScope *scope.ClusterScope) (*scope.MachineScope, error) {
   281  	return scope.NewMachineScope(scope.MachineScopeParams{
   282  		Client:       client,
   283  		Cluster:      clusterScope.Cluster,
   284  		Machine:      &clusterv1.Machine{},
   285  		InfraCluster: clusterScope,
   286  		AWSMachine: &infrav1.AWSMachine{
   287  			ObjectMeta: metav1.ObjectMeta{
   288  				Name: "infra-cluster",
   289  			},
   290  		},
   291  	})
   292  }