/* 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 http://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 gitlab import ( "context" "encoding/json" "fmt" "net/http" "reflect" "strings" "testing" "github.com/google/uuid" tassert "github.com/stretchr/testify/assert" "github.com/xanzy/go-gitlab" "github.com/yandex-cloud/go-sdk/iamkey" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1" fakegitlab "github.com/external-secrets/external-secrets/pkg/provider/gitlab/fake" ) const ( project = "my-Project" username = "user-name" userkey = "user-key" environment = "prod" environmentTest = "test" projectvalue = "projectvalue" groupvalue = "groupvalue" groupid = "groupId" defaultErrorMessage = "[%d] unexpected error: [%s], expected: [%s]" errMissingCredentials = "credentials are empty" testKey = "testKey" findTestPrefix = "test.*" ) type secretManagerTestCase struct { mockProjectsClient *fakegitlab.GitlabMockProjectsClient mockProjectVarClient *fakegitlab.GitlabMockProjectVariablesClient mockGroupVarClient *fakegitlab.GitlabMockGroupVariablesClient apiInputProjectID string apiInputKey string apiInputEnv string projectAPIOutput *gitlab.ProjectVariable projectAPIResponse *gitlab.Response projectAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable] projectGroupsAPIOutput []*gitlab.ProjectGroup projectGroupsAPIResponse *gitlab.Response groupAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.GroupVariable] groupAPIOutput *gitlab.GroupVariable groupAPIResponse *gitlab.Response ref *esv1beta1.ExternalSecretDataRemoteRef refFind *esv1beta1.ExternalSecretFind projectID string groupIDs []string inheritFromGroups bool apiErr error expectError string expectedSecret string expectedValidationResult esv1beta1.ValidationResult // for testing secretmap expectedData map[string][]byte } func makeValidSecretManagerTestCase() *secretManagerTestCase { smtc := secretManagerTestCase{ mockProjectsClient: &fakegitlab.GitlabMockProjectsClient{}, mockProjectVarClient: &fakegitlab.GitlabMockProjectVariablesClient{}, mockGroupVarClient: &fakegitlab.GitlabMockGroupVariablesClient{}, apiInputProjectID: makeValidAPIInputProjectID(), apiInputKey: makeValidAPIInputKey(), apiInputEnv: makeValidEnvironment(), ref: makeValidRef(), refFind: makeValidFindRef(), projectID: makeValidProjectID(), groupIDs: makeEmptyGroupIds(), projectAPIOutput: makeValidProjectAPIOutput(), projectAPIResponse: makeValidProjectAPIResponse(), projectGroupsAPIOutput: makeValidProjectGroupsAPIOutput(), projectGroupsAPIResponse: makeValidProjectGroupsAPIResponse(), groupAPIOutput: makeValidGroupAPIOutput(), groupAPIResponse: makeValidGroupAPIResponse(), apiErr: nil, expectError: "", expectedSecret: "", expectedValidationResult: esv1beta1.ValidationResultReady, expectedData: map[string][]byte{}, } prepareMockProjectVarClient(&smtc) prepareMockGroupVarClient(&smtc) return &smtc } func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef { return &esv1beta1.ExternalSecretDataRemoteRef{ Key: testKey, Version: "default", } } func makeValidFindRef() *esv1beta1.ExternalSecretFind { return &esv1beta1.ExternalSecretFind{} } func makeValidProjectID() string { return "projectId" } func makeEmptyGroupIds() []string { return []string{} } func makeFindName(regexp string) *esv1beta1.FindName { return &esv1beta1.FindName{ RegExp: regexp, } } func makeValidAPIInputProjectID() string { return "testID" } func makeValidAPIInputKey() string { return testKey } func makeValidEnvironment() string { return environment } func makeValidProjectAPIResponse() *gitlab.Response { return &gitlab.Response{ Response: &http.Response{ StatusCode: http.StatusOK, }, CurrentPage: 1, TotalPages: 1, } } func makeValidProjectGroupsAPIResponse() *gitlab.Response { return &gitlab.Response{ Response: &http.Response{ StatusCode: http.StatusOK, }, CurrentPage: 1, TotalPages: 1, } } func makeValidGroupAPIResponse() *gitlab.Response { return &gitlab.Response{ Response: &http.Response{ StatusCode: http.StatusOK, }, CurrentPage: 1, TotalPages: 1, } } func makeValidProjectAPIOutput() *gitlab.ProjectVariable { return &gitlab.ProjectVariable{ Key: testKey, Value: "", EnvironmentScope: environment, } } func makeValidProjectGroupsAPIOutput() []*gitlab.ProjectGroup { return []*gitlab.ProjectGroup{{ ID: 1, Name: "Group (1)", FullPath: "foo", }, { ID: 100, Name: "Group (100)", FullPath: "foo/bar/baz", }, { ID: 10, Name: "Group (10)", FullPath: "foo/bar", }} } func makeValidGroupAPIOutput() *gitlab.GroupVariable { return &gitlab.GroupVariable{ Key: "groupKey", Value: "", EnvironmentScope: environment, } } func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase { smtc := makeValidSecretManagerTestCase() for _, fn := range tweaks { fn(smtc) } smtc.mockProjectsClient.WithValue(smtc.projectGroupsAPIOutput, smtc.projectGroupsAPIResponse, smtc.apiErr) prepareMockProjectVarClient(smtc) prepareMockGroupVarClient(smtc) return smtc } func makeValidSecretManagerGetAllTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase { smtc := makeValidSecretManagerTestCase() smtc.ref = nil smtc.refFind.Name = makeFindName(".*") for _, fn := range tweaks { fn(smtc) } prepareMockProjectVarClient(smtc) prepareMockGroupVarClient(smtc) return smtc } func prepareMockProjectVarClient(smtc *secretManagerTestCase) { responses := make([]fakegitlab.APIResponse[[]*gitlab.ProjectVariable], 0) if smtc.projectAPIOutput != nil { responses = append(responses, fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{Output: []*gitlab.ProjectVariable{smtc.projectAPIOutput}, Response: smtc.projectAPIResponse, Error: smtc.apiErr}) } for _, response := range smtc.projectAPIOutputs { responses = append(responses, *response) } smtc.mockProjectVarClient.WithValues(responses) } func prepareMockGroupVarClient(smtc *secretManagerTestCase) { responses := make([]fakegitlab.APIResponse[[]*gitlab.GroupVariable], 0) if smtc.projectAPIOutput != nil { responses = append(responses, fakegitlab.APIResponse[[]*gitlab.GroupVariable]{Output: []*gitlab.GroupVariable{smtc.groupAPIOutput}, Response: smtc.groupAPIResponse, Error: smtc.apiErr}) } for _, response := range smtc.groupAPIOutputs { responses = append(responses, *response) } smtc.mockGroupVarClient.WithValues(responses) } // This case can be shared by both GetSecret and GetSecretMap tests. // bad case: set apiErr. var setAPIErr = func(smtc *secretManagerTestCase) { smtc.apiErr = fmt.Errorf("oh no") smtc.expectError = "oh no" smtc.projectAPIResponse.Response.StatusCode = http.StatusInternalServerError smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setListAPIErr = func(smtc *secretManagerTestCase) { err := fmt.Errorf("oh no") smtc.apiErr = err smtc.expectError = fmt.Errorf(errList, err).Error() smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setProjectListAPIRespNil = func(smtc *secretManagerTestCase) { smtc.projectAPIResponse = nil smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error() smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setGroupListAPIRespNil = func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.groupAPIResponse = nil smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error() smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setProjectAndGroup = func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} } var setProjectAndInheritFromGroups = func(smtc *secretManagerTestCase) { smtc.groupIDs = nil smtc.inheritFromGroups = true } var setProjectListAPIRespBadCode = func(smtc *secretManagerTestCase) { smtc.projectAPIResponse.StatusCode = http.StatusUnauthorized smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error() smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setGroupListAPIRespBadCode = func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.groupAPIResponse.StatusCode = http.StatusUnauthorized smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error() smtc.expectedValidationResult = esv1beta1.ValidationResultError } var setNilMockClient = func(smtc *secretManagerTestCase) { smtc.mockProjectVarClient = nil smtc.mockGroupVarClient = nil smtc.expectError = errUninitializedGitlabProvider } func TestNewClient(t *testing.T) { ctx := context.Background() const namespace = "namespace" store := &esv1beta1.SecretStore{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, }, Spec: esv1beta1.SecretStoreSpec{ Provider: &esv1beta1.SecretStoreProvider{ Gitlab: &esv1beta1.GitlabProvider{}, }, }, } provider, err := esv1beta1.GetProvider(store) tassert.Nil(t, err) k8sClient := clientfake.NewClientBuilder().Build() secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.EqualError(t, err, errMissingCredentials) tassert.Nil(t, secretClient) store.Spec.Provider.Gitlab.Auth = esv1beta1.GitlabAuth{} secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.EqualError(t, err, errMissingCredentials) tassert.Nil(t, secretClient) store.Spec.Provider.Gitlab.Auth.SecretRef = esv1beta1.GitlabSecretRef{} secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.EqualError(t, err, errMissingCredentials) tassert.Nil(t, secretClient) store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{} secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.EqualError(t, err, errMissingCredentials) tassert.Nil(t, secretClient) const authorizedKeySecretName = "authorizedKeySecretName" const authorizedKeySecretKey = "authorizedKeySecretKey" store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Name = authorizedKeySecretName store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Key = authorizedKeySecretKey secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.EqualError(t, err, "couldn't find secret on cluster: secrets \"authorizedKeySecretName\" not found") tassert.Nil(t, secretClient) err = createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, newFakeAuthorizedKey())) tassert.Nil(t, err) secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) tassert.Nil(t, err) tassert.NotNil(t, secretClient) } func toJSON(t *testing.T, v interface{}) []byte { jsonBytes, err := json.Marshal(v) tassert.Nil(t, err) return jsonBytes } func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) error { err := k8sClient.Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: secretName, }, Data: map[string][]byte{secretKey: secretValue}, }) tassert.Nil(t, err) return nil } func newFakeAuthorizedKey() *iamkey.Key { uniqueLabel := uuid.NewString() return &iamkey.Key{ Id: uniqueLabel, Subject: &iamkey.Key_ServiceAccountId{ ServiceAccountId: uniqueLabel, }, PrivateKey: uniqueLabel, } } // test the sm<->gcp interface // make sure correct values are passed and errors are handled accordingly. func TestGetSecret(t *testing.T) { // good case: default version is set // key is passed in, output is sent back onlyProjectSecret := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = projectvalue smtc.groupAPIResponse = nil smtc.groupAPIOutput = nil smtc.expectedSecret = smtc.projectAPIOutput.Value } onlyWildcardSecret := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = "" smtc.projectAPIResponse.Response.StatusCode = 404 smtc.groupAPIResponse = nil smtc.groupAPIOutput = nil smtc.expectedSecret = smtc.projectAPIOutput.Value } groupSecretProjectOverride := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = projectvalue smtc.groupAPIOutput.Key = testKey smtc.groupAPIOutput.Value = groupvalue smtc.expectedSecret = smtc.projectAPIOutput.Value } groupWithoutProjectOverride := func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.projectAPIResponse.Response.StatusCode = 404 smtc.groupAPIOutput.Key = testKey smtc.groupAPIOutput.Value = groupvalue smtc.expectedSecret = smtc.groupAPIOutput.Value } successCases := []*secretManagerTestCase{ makeValidSecretManagerTestCaseCustom(onlyProjectSecret), makeValidSecretManagerTestCaseCustom(onlyWildcardSecret), makeValidSecretManagerTestCaseCustom(groupSecretProjectOverride), makeValidSecretManagerTestCaseCustom(groupWithoutProjectOverride), makeValidSecretManagerTestCaseCustom(setAPIErr), makeValidSecretManagerTestCaseCustom(setNilMockClient), } sm := Gitlab{} for k, v := range successCases { sm.projectVariablesClient = v.mockProjectVarClient sm.groupVariablesClient = v.mockGroupVarClient sm.projectID = v.projectID sm.groupIDs = v.groupIDs sm.environment = v.apiInputEnv out, err := sm.GetSecret(context.Background(), *v.ref) if !ErrorContains(err, v.expectError) { t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError) } if string(out) != v.expectedSecret { t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out), v.expectedSecret) } } } func TestResolveGroupIds(t *testing.T) { v := makeValidSecretManagerTestCaseCustom() sm := Gitlab{} sm.projectsClient = v.mockProjectsClient sm.projectID = v.projectID sm.inheritFromGroups = true err := sm.ResolveGroupIds() if err != nil { t.Errorf(defaultErrorMessage, 0, err.Error(), "") } if !reflect.DeepEqual(sm.groupIDs, []string{"1", "10", "100"}) { t.Errorf("unexpected groupIds: %s, expected %s", sm.groupIDs, []string{"1", "10", "100"}) } } func TestGetAllSecrets(t *testing.T) { // good case: default version is set // key is passed in, output is sent back setMissingFindRegex := func(smtc *secretManagerTestCase) { smtc.refFind.Name = nil smtc.expectError = "'find.name' is mandatory" } setUnsupportedFindPath := func(smtc *secretManagerTestCase) { path := "path" smtc.refFind.Path = &path smtc.expectError = "'find.path' is not implemented in the Gitlab provider" } setUnsupportedFindTag := func(smtc *secretManagerTestCase) { smtc.expectError = "'find.tags' only supports 'environment_scope" smtc.refFind.Tags = map[string]string{"foo": ""} } setMatchingSecretFindString := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput = &gitlab.ProjectVariable{ Key: testKey, Value: projectvalue, EnvironmentScope: environment, } smtc.expectedSecret = projectvalue smtc.refFind.Name = makeFindName(findTestPrefix) } setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput = &gitlab.ProjectVariable{ Key: testKey, Value: projectvalue, EnvironmentScope: environmentTest, } smtc.expectedSecret = "" smtc.refFind.Name = makeFindName("foo.*") } setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput = &gitlab.ProjectVariable{ Key: testKey, Value: projectvalue, EnvironmentScope: environmentTest, } smtc.expectedSecret = "" smtc.refFind.Name = makeFindName(findTestPrefix) } setMatchingSecretFindTags := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput = &gitlab.ProjectVariable{ Key: testKey, Value: projectvalue, EnvironmentScope: environment, } smtc.apiInputEnv = "*" smtc.expectedSecret = projectvalue smtc.refFind.Tags = map[string]string{"environment_scope": environment} } setEnvironmentConstrainedByStore := func(smtc *secretManagerTestCase) { smtc.expectedSecret = projectvalue smtc.expectError = "'find.tags' is constrained by 'environment_scope' of the store" smtc.refFind.Tags = map[string]string{"environment_scope": environment} } setWildcardDoesntOverwriteEnvironmentValue := func(smtc *secretManagerTestCase) { var1 := gitlab.ProjectVariable{ Key: testKey, Value: "wildcardValue", EnvironmentScope: "*", } var2 := gitlab.ProjectVariable{ Key: testKey, Value: "expectedValue", EnvironmentScope: environmentTest, } var3 := gitlab.ProjectVariable{ Key: testKey, Value: "wildcardValue", EnvironmentScope: "*", } vars := []*gitlab.ProjectVariable{&var1, &var2, &var3} smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}} smtc.projectAPIOutput = nil smtc.apiInputEnv = environmentTest smtc.expectedSecret = "expectedValue" smtc.refFind.Name = makeFindName(findTestPrefix) } setFilterByEnvironmentWithWildcard := func(smtc *secretManagerTestCase) { var1 := gitlab.ProjectVariable{ Key: testKey, Value: projectvalue, EnvironmentScope: "*", } var2 := gitlab.ProjectVariable{ Key: "testKey2", Value: "value2", EnvironmentScope: environment, } var3 := gitlab.ProjectVariable{ Key: "testKey3", Value: "value3", EnvironmentScope: environmentTest, } var4 := gitlab.ProjectVariable{ Key: "anotherKey4", Value: "value4", EnvironmentScope: environment, } vars := []*gitlab.ProjectVariable{&var1, &var2, &var3, &var4} smtc.projectAPIOutput = nil smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}} smtc.apiInputEnv = environment smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "testKey2": []byte("value2")} smtc.refFind.Name = makeFindName(findTestPrefix) } setPaginationInGroupAndProjectVars := func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} gvar1 := gitlab.GroupVariable{ Key: testKey + "Group", Value: "groupValue1", EnvironmentScope: environmentTest, } gvar2 := gitlab.GroupVariable{ Key: testKey, Value: "groupValue2", EnvironmentScope: environmentTest, } pvar1 := gitlab.ProjectVariable{ Key: testKey, Value: "testValue1", EnvironmentScope: environmentTest, } pvar2a := gitlab.ProjectVariable{ Key: testKey + "2a", Value: "testValue2a", EnvironmentScope: environmentTest, } pvar2b := gitlab.ProjectVariable{ Key: testKey + "2b", Value: "testValue2b", EnvironmentScope: environmentTest, } gPage1 := []*gitlab.GroupVariable{&gvar1} gResponsePage1 := makeValidGroupAPIResponse() gResponsePage1.TotalPages = 2 gResponsePage1.CurrentPage = 1 gPage2 := []*gitlab.GroupVariable{&gvar2} gResponsePage2 := makeValidGroupAPIResponse() gResponsePage2.TotalPages = 2 gResponsePage2.CurrentPage = 1 pPage1 := []*gitlab.ProjectVariable{&pvar1} pResponsePage1 := makeValidProjectAPIResponse() pResponsePage1.TotalPages = 2 pResponsePage1.CurrentPage = 1 pPage2 := []*gitlab.ProjectVariable{&pvar2a, &pvar2b} pResponsePage2 := makeValidProjectAPIResponse() pResponsePage2.TotalPages = 2 pResponsePage2.CurrentPage = 2 smtc.groupAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]{{Output: gPage1, Response: gResponsePage1, Error: nil}, {Output: gPage2, Response: gResponsePage2, Error: nil}} smtc.groupAPIOutput = nil smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: pPage1, Response: pResponsePage1, Error: nil}, {Output: pPage2, Response: pResponsePage2, Error: nil}} smtc.projectAPIOutput = nil smtc.apiInputEnv = environmentTest smtc.expectedData = map[string][]byte{testKey: []byte("testValue1"), "testKey2a": []byte("testValue2a"), "testKey2b": []byte("testValue2b"), "testKeyGroup": []byte("groupValue1")} smtc.refFind.Name = makeFindName(findTestPrefix) } cases := []*secretManagerTestCase{ makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex), makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath), makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTag), makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString), makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString), makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString), makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindTags), makeValidSecretManagerGetAllTestCaseCustom(setWildcardDoesntOverwriteEnvironmentValue), makeValidSecretManagerGetAllTestCaseCustom(setEnvironmentConstrainedByStore), makeValidSecretManagerGetAllTestCaseCustom(setFilterByEnvironmentWithWildcard), makeValidSecretManagerGetAllTestCaseCustom(setPaginationInGroupAndProjectVars), makeValidSecretManagerGetAllTestCaseCustom(setAPIErr), makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient), } sm := Gitlab{} for k, v := range cases { sm.environment = v.apiInputEnv sm.projectVariablesClient = v.mockProjectVarClient sm.groupVariablesClient = v.mockGroupVarClient sm.groupIDs = v.groupIDs if v.expectedSecret != "" { v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)} } out, err := sm.GetAllSecrets(context.Background(), *v.refFind) if !ErrorContains(err, v.expectError) { t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError) } if err == nil && !reflect.DeepEqual(out, v.expectedData) { t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData) } } } func TestGetAllSecretsWithGroups(t *testing.T) { onlyProjectSecret := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = projectvalue smtc.refFind.Name = makeFindName(findTestPrefix) smtc.groupAPIResponse = nil smtc.groupAPIOutput = nil smtc.expectedSecret = smtc.projectAPIOutput.Value } groupAndProjectSecrets := func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.projectAPIOutput.Value = projectvalue smtc.groupAPIOutput.Value = groupvalue smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "groupKey": []byte(groupvalue)} smtc.refFind.Name = makeFindName(".*Key") } groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.projectAPIOutput.Value = projectvalue smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key smtc.groupAPIOutput.Value = groupvalue smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)} smtc.refFind.Name = makeFindName(".*Key") } groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) { smtc.groupIDs = []string{groupid} smtc.projectAPIOutput.Value = projectvalue smtc.projectAPIOutput.EnvironmentScope = environmentTest smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key smtc.groupAPIOutput.Value = groupvalue smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)} smtc.refFind.Name = makeFindName(".*Key") } cases := []*secretManagerTestCase{ makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret), makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets), makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets), makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets), } sm := Gitlab{} sm.environment = environment for k, v := range cases { sm.projectVariablesClient = v.mockProjectVarClient sm.groupVariablesClient = v.mockGroupVarClient sm.projectID = v.projectID sm.groupIDs = v.groupIDs out, err := sm.GetAllSecrets(context.Background(), *v.refFind) if !ErrorContains(err, v.expectError) { t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError) } if v.expectError == "" { if len(v.expectedData) > 0 { if !reflect.DeepEqual(v.expectedData, out) { t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData) } } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret { t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret) } } } } func TestValidate(t *testing.T) { successCases := []*secretManagerTestCase{ makeValidSecretManagerTestCaseCustom(), makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups), makeValidSecretManagerTestCaseCustom(setProjectAndGroup), makeValidSecretManagerTestCaseCustom(setListAPIErr), makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil), makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode), makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil), makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode), } sm := Gitlab{} for k, v := range successCases { sm.projectsClient = v.mockProjectsClient sm.projectVariablesClient = v.mockProjectVarClient sm.groupVariablesClient = v.mockGroupVarClient sm.projectID = v.projectID sm.groupIDs = v.groupIDs sm.inheritFromGroups = v.inheritFromGroups t.Logf("%+v", v) validationResult, err := sm.Validate() if !ErrorContains(err, v.expectError) { t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError) } if validationResult != v.expectedValidationResult { t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult) } if sm.inheritFromGroups && sm.groupIDs[0] != "1" { t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.groupIDs[0]) } } } func TestGetSecretMap(t *testing.T) { // good case: default version & deserialization setDeserialization := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = `{"foo":"bar"}` smtc.expectedData["foo"] = []byte("bar") } // bad case: invalid json setInvalidJSON := func(smtc *secretManagerTestCase) { smtc.projectAPIOutput.Value = `-----------------` smtc.expectError = "unable to unmarshal secret" } successCases := []*secretManagerTestCase{ makeValidSecretManagerTestCaseCustom(setDeserialization), makeValidSecretManagerTestCaseCustom(setInvalidJSON), makeValidSecretManagerTestCaseCustom(setNilMockClient), makeValidSecretManagerTestCaseCustom(setAPIErr), } sm := Gitlab{} for k, v := range successCases { sm.projectVariablesClient = v.mockProjectVarClient sm.groupVariablesClient = v.mockGroupVarClient out, err := sm.GetSecretMap(context.Background(), *v.ref) if !ErrorContains(err, v.expectError) { t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError) } if err == nil && !reflect.DeepEqual(out, v.expectedData) { t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData) } } } func ErrorContains(out error, want string) bool { if out == nil { return want == "" } if want == "" { return false } return strings.Contains(out.Error(), want) } type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore { store := &esv1beta1.SecretStore{ Spec: esv1beta1.SecretStoreSpec{ Provider: &esv1beta1.SecretStoreProvider{ Gitlab: &esv1beta1.GitlabProvider{ Auth: esv1beta1.GitlabAuth{}, ProjectID: projectID, Environment: environment, }, }, }, } for _, f := range fn { store = f(store) } return store } func withAccessToken(name, key string, namespace *string) storeModifier { return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore { store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{ Name: name, Key: key, Namespace: namespace, } return store } } func withGroups(ids []string, inherit bool) storeModifier { return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore { store.Spec.Provider.Gitlab.GroupIDs = ids store.Spec.Provider.Gitlab.InheritFromGroups = inherit return store } } type ValidateStoreTestCase struct { store *esv1beta1.SecretStore err error } func TestValidateStore(t *testing.T) { namespace := "my-namespace" testCases := []ValidateStoreTestCase{ { store: makeSecretStore("", environment), err: fmt.Errorf("projectID and groupIDs must not both be empty"), }, { store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)), err: fmt.Errorf("defining groupIDs and inheritFromGroups = true is not allowed"), }, { store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)), err: fmt.Errorf("accessToken.name cannot be empty"), }, { store: makeSecretStore(project, environment, withAccessToken(username, "", nil)), err: fmt.Errorf("accessToken.key cannot be empty"), }, { store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)), err: fmt.Errorf("namespace not allowed with namespaced SecretStore"), }, { store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)), err: nil, }, { store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)), err: nil, }, } p := Gitlab{} for _, tc := range testCases { err := p.ValidateStore(tc.store) if tc.err != nil && err != nil && err.Error() != tc.err.Error() { t.Errorf("test failed! want %v, got %v", tc.err, err) } else if tc.err == nil && err != nil { t.Errorf("want nil got err %v", err) } else if tc.err != nil && err == nil { t.Errorf("want err %v got nil", tc.err) } } }