gitlab_test.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package gitlab
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "net/http"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "github.com/google/uuid"
  24. tassert "github.com/stretchr/testify/assert"
  25. "github.com/yandex-cloud/go-sdk/iamkey"
  26. gitlab "gitlab.com/gitlab-org/api/client-go"
  27. corev1 "k8s.io/api/core/v1"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
  30. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  31. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  32. esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1"
  33. fakegitlab "github.com/external-secrets/external-secrets/pkg/provider/gitlab/fake"
  34. )
  35. const (
  36. project = "my-Project"
  37. username = "user-name"
  38. userkey = "user-key"
  39. environment = "prod"
  40. environmentTest = "test"
  41. projectvalue = "projectvalue"
  42. groupvalue = "groupvalue"
  43. groupid = "groupId"
  44. defaultErrorMessage = "[%d] unexpected error: [%s], expected: [%s]"
  45. errMissingCredentials = "cannot get Kubernetes secret \"\" from namespace \"namespace\": secrets \"\" not found"
  46. testKey = "testKey"
  47. findTestPrefix = "test.*"
  48. )
  49. type secretManagerTestCase struct {
  50. mockProjectsClient *fakegitlab.GitlabMockProjectsClient
  51. mockProjectVarClient *fakegitlab.GitlabMockProjectVariablesClient
  52. mockGroupVarClient *fakegitlab.GitlabMockGroupVariablesClient
  53. apiInputProjectID string
  54. apiInputKey string
  55. apiInputEnv string
  56. projectAPIOutput *gitlab.ProjectVariable
  57. projectAPIResponse *gitlab.Response
  58. projectAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]
  59. projectGroupsAPIOutput []*gitlab.ProjectGroup
  60. projectGroupsAPIResponse *gitlab.Response
  61. groupAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]
  62. groupAPIOutput *gitlab.GroupVariable
  63. groupAPIResponse *gitlab.Response
  64. ref *esv1.ExternalSecretDataRemoteRef
  65. refFind *esv1.ExternalSecretFind
  66. projectID string
  67. groupIDs []string
  68. inheritFromGroups bool
  69. apiErr error
  70. expectError string
  71. expectedSecret string
  72. expectedValidationResult esv1.ValidationResult
  73. // for testing secretmap
  74. expectedData map[string][]byte
  75. }
  76. func makeValidSecretManagerTestCase() *secretManagerTestCase {
  77. smtc := secretManagerTestCase{
  78. mockProjectsClient: &fakegitlab.GitlabMockProjectsClient{},
  79. mockProjectVarClient: &fakegitlab.GitlabMockProjectVariablesClient{},
  80. mockGroupVarClient: &fakegitlab.GitlabMockGroupVariablesClient{},
  81. apiInputProjectID: makeValidAPIInputProjectID(),
  82. apiInputKey: makeValidAPIInputKey(),
  83. apiInputEnv: makeValidEnvironment(),
  84. ref: makeValidRef(),
  85. refFind: makeValidFindRef(),
  86. projectID: makeValidProjectID(),
  87. groupIDs: makeEmptyGroupIds(),
  88. projectAPIOutput: makeValidProjectAPIOutput(),
  89. projectAPIResponse: makeValidProjectAPIResponse(),
  90. projectGroupsAPIOutput: makeValidProjectGroupsAPIOutput(),
  91. projectGroupsAPIResponse: makeValidProjectGroupsAPIResponse(),
  92. groupAPIOutput: makeValidGroupAPIOutput(),
  93. groupAPIResponse: makeValidGroupAPIResponse(),
  94. apiErr: nil,
  95. expectError: "",
  96. expectedSecret: "",
  97. expectedValidationResult: esv1.ValidationResultReady,
  98. expectedData: map[string][]byte{},
  99. }
  100. prepareMockProjectVarClient(&smtc)
  101. prepareMockGroupVarClient(&smtc)
  102. return &smtc
  103. }
  104. func makeValidRef() *esv1.ExternalSecretDataRemoteRef {
  105. return &esv1.ExternalSecretDataRemoteRef{
  106. Key: testKey,
  107. Version: "default",
  108. }
  109. }
  110. func makeValidFindRef() *esv1.ExternalSecretFind {
  111. return &esv1.ExternalSecretFind{}
  112. }
  113. func makeValidProjectID() string {
  114. return "projectId"
  115. }
  116. func makeEmptyGroupIds() []string {
  117. return []string{}
  118. }
  119. func makeFindName(regexp string) *esv1.FindName {
  120. return &esv1.FindName{
  121. RegExp: regexp,
  122. }
  123. }
  124. func makeValidAPIInputProjectID() string {
  125. return "testID"
  126. }
  127. func makeValidAPIInputKey() string {
  128. return testKey
  129. }
  130. func makeValidEnvironment() string {
  131. return environment
  132. }
  133. func makeValidProjectAPIResponse() *gitlab.Response {
  134. return &gitlab.Response{
  135. Response: &http.Response{
  136. StatusCode: http.StatusOK,
  137. },
  138. CurrentPage: 1,
  139. TotalPages: 1,
  140. }
  141. }
  142. func makeValidProjectGroupsAPIResponse() *gitlab.Response {
  143. return &gitlab.Response{
  144. Response: &http.Response{
  145. StatusCode: http.StatusOK,
  146. },
  147. CurrentPage: 1,
  148. TotalPages: 1,
  149. }
  150. }
  151. func makeValidGroupAPIResponse() *gitlab.Response {
  152. return &gitlab.Response{
  153. Response: &http.Response{
  154. StatusCode: http.StatusOK,
  155. },
  156. CurrentPage: 1,
  157. TotalPages: 1,
  158. }
  159. }
  160. func makeValidProjectAPIOutput() *gitlab.ProjectVariable {
  161. return &gitlab.ProjectVariable{
  162. Key: testKey,
  163. Value: "",
  164. EnvironmentScope: environment,
  165. }
  166. }
  167. func makeValidProjectGroupsAPIOutput() []*gitlab.ProjectGroup {
  168. return []*gitlab.ProjectGroup{{
  169. ID: 1,
  170. Name: "Group (1)",
  171. FullPath: "foo",
  172. }, {
  173. ID: 100,
  174. Name: "Group (100)",
  175. FullPath: "foo/bar/baz",
  176. }, {
  177. ID: 10,
  178. Name: "Group (10)",
  179. FullPath: "foo/bar",
  180. }}
  181. }
  182. func makeValidGroupAPIOutput() *gitlab.GroupVariable {
  183. return &gitlab.GroupVariable{
  184. Key: "groupKey",
  185. Value: "",
  186. EnvironmentScope: environment,
  187. }
  188. }
  189. func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  190. smtc := makeValidSecretManagerTestCase()
  191. for _, fn := range tweaks {
  192. fn(smtc)
  193. }
  194. smtc.mockProjectsClient.WithValue(smtc.projectGroupsAPIOutput, smtc.projectGroupsAPIResponse, smtc.apiErr)
  195. prepareMockProjectVarClient(smtc)
  196. prepareMockGroupVarClient(smtc)
  197. return smtc
  198. }
  199. func makeValidSecretManagerGetAllTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  200. smtc := makeValidSecretManagerTestCase()
  201. smtc.ref = nil
  202. smtc.refFind.Name = makeFindName(".*")
  203. for _, fn := range tweaks {
  204. fn(smtc)
  205. }
  206. prepareMockProjectVarClient(smtc)
  207. prepareMockGroupVarClient(smtc)
  208. return smtc
  209. }
  210. func prepareMockProjectVarClient(smtc *secretManagerTestCase) {
  211. responses := make([]fakegitlab.APIResponse[[]*gitlab.ProjectVariable], 0)
  212. if smtc.projectAPIOutput != nil {
  213. responses = append(responses, fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{Output: []*gitlab.ProjectVariable{smtc.projectAPIOutput}, Response: smtc.projectAPIResponse, Error: smtc.apiErr})
  214. }
  215. for _, response := range smtc.projectAPIOutputs {
  216. responses = append(responses, *response)
  217. }
  218. smtc.mockProjectVarClient.WithValues(responses)
  219. }
  220. func prepareMockGroupVarClient(smtc *secretManagerTestCase) {
  221. responses := make([]fakegitlab.APIResponse[[]*gitlab.GroupVariable], 0)
  222. if smtc.groupAPIOutput != nil {
  223. responses = append(responses, fakegitlab.APIResponse[[]*gitlab.GroupVariable]{Output: []*gitlab.GroupVariable{smtc.groupAPIOutput}, Response: smtc.groupAPIResponse, Error: smtc.apiErr})
  224. }
  225. for _, response := range smtc.groupAPIOutputs {
  226. responses = append(responses, *response)
  227. }
  228. smtc.mockGroupVarClient.WithValues(responses)
  229. }
  230. // This case can be shared by both GetSecret and GetSecretMap tests.
  231. // bad case: set apiErr.
  232. var setAPIErr = func(smtc *secretManagerTestCase) {
  233. smtc.apiErr = errors.New("oh no")
  234. smtc.expectError = "oh no"
  235. smtc.projectAPIResponse.Response.StatusCode = http.StatusInternalServerError
  236. smtc.expectedValidationResult = esv1.ValidationResultError
  237. }
  238. var setListAPIErr = func(smtc *secretManagerTestCase) {
  239. err := errors.New("oh no")
  240. smtc.apiErr = err
  241. smtc.expectError = fmt.Errorf(errList, err).Error()
  242. smtc.expectedValidationResult = esv1.ValidationResultError
  243. }
  244. var setProjectListAPIRespNil = func(smtc *secretManagerTestCase) {
  245. smtc.projectAPIResponse = nil
  246. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  247. smtc.expectedValidationResult = esv1.ValidationResultError
  248. }
  249. var setGroupListAPIRespNil = func(smtc *secretManagerTestCase) {
  250. smtc.groupIDs = []string{groupid}
  251. smtc.groupAPIResponse = nil
  252. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  253. smtc.expectedValidationResult = esv1.ValidationResultError
  254. }
  255. var setProjectAndGroup = func(smtc *secretManagerTestCase) {
  256. smtc.groupIDs = []string{groupid}
  257. }
  258. var setProjectAndInheritFromGroups = func(smtc *secretManagerTestCase) {
  259. smtc.groupIDs = nil
  260. smtc.inheritFromGroups = true
  261. }
  262. var setProjectListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  263. smtc.projectAPIResponse.StatusCode = http.StatusUnauthorized
  264. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  265. smtc.expectedValidationResult = esv1.ValidationResultError
  266. }
  267. var setGroupListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  268. smtc.groupIDs = []string{groupid}
  269. smtc.groupAPIResponse.StatusCode = http.StatusUnauthorized
  270. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  271. smtc.expectedValidationResult = esv1.ValidationResultError
  272. }
  273. var setNilMockClient = func(smtc *secretManagerTestCase) {
  274. smtc.mockProjectVarClient = nil
  275. smtc.mockGroupVarClient = nil
  276. smtc.expectError = errUninitializedGitlabProvider
  277. }
  278. func TestNewClient(t *testing.T) {
  279. ctx := context.Background()
  280. const namespace = "namespace"
  281. store := &esv1.SecretStore{
  282. ObjectMeta: metav1.ObjectMeta{
  283. Namespace: namespace,
  284. },
  285. Spec: esv1.SecretStoreSpec{
  286. Provider: &esv1.SecretStoreProvider{
  287. Gitlab: &esv1.GitlabProvider{},
  288. },
  289. },
  290. }
  291. provider, err := esv1.GetProvider(store)
  292. tassert.Nil(t, err)
  293. k8sClient := clientfake.NewClientBuilder().Build()
  294. secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
  295. tassert.EqualError(t, err, errMissingCredentials)
  296. tassert.Nil(t, secretClient)
  297. store.Spec.Provider.Gitlab.Auth = esv1.GitlabAuth{}
  298. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  299. tassert.EqualError(t, err, errMissingCredentials)
  300. tassert.Nil(t, secretClient)
  301. store.Spec.Provider.Gitlab.Auth.SecretRef = esv1.GitlabSecretRef{}
  302. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  303. tassert.EqualError(t, err, errMissingCredentials)
  304. tassert.Nil(t, secretClient)
  305. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{}
  306. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  307. tassert.EqualError(t, err, errMissingCredentials)
  308. tassert.Nil(t, secretClient)
  309. const authorizedKeySecretName = "authorizedKeySecretName"
  310. const authorizedKeySecretKey = "authorizedKeySecretKey"
  311. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Name = authorizedKeySecretName
  312. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Key = authorizedKeySecretKey
  313. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  314. tassert.EqualError(t, err, "cannot get Kubernetes secret \"authorizedKeySecretName\" from namespace \"namespace\": secrets \"authorizedKeySecretName\" not found")
  315. tassert.Nil(t, secretClient)
  316. err = createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, newFakeAuthorizedKey()))
  317. tassert.Nil(t, err)
  318. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  319. tassert.Nil(t, err)
  320. tassert.NotNil(t, secretClient)
  321. }
  322. func toJSON(t *testing.T, v any) []byte {
  323. jsonBytes, err := json.Marshal(v)
  324. tassert.Nil(t, err)
  325. return jsonBytes
  326. }
  327. func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) error {
  328. err := k8sClient.Create(ctx, &corev1.Secret{
  329. ObjectMeta: metav1.ObjectMeta{
  330. Namespace: namespace,
  331. Name: secretName,
  332. },
  333. Data: map[string][]byte{secretKey: secretValue},
  334. })
  335. tassert.Nil(t, err)
  336. return nil
  337. }
  338. func newFakeAuthorizedKey() *iamkey.Key {
  339. uniqueLabel := uuid.NewString()
  340. return &iamkey.Key{
  341. Id: uniqueLabel,
  342. Subject: &iamkey.Key_ServiceAccountId{
  343. ServiceAccountId: uniqueLabel,
  344. },
  345. PrivateKey: uniqueLabel,
  346. }
  347. }
  348. // test the sm<->gcp interface
  349. // make sure correct values are passed and errors are handled accordingly.
  350. func TestGetSecret(t *testing.T) {
  351. // good case: default version is set
  352. // key is passed in, output is sent back
  353. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  354. smtc.projectAPIOutput.Value = projectvalue
  355. smtc.groupAPIResponse = nil
  356. smtc.groupAPIOutput = nil
  357. smtc.expectedSecret = smtc.projectAPIOutput.Value
  358. }
  359. onlyWildcardSecret := func(smtc *secretManagerTestCase) {
  360. smtc.projectAPIOutput.Value = ""
  361. smtc.projectAPIResponse.Response.StatusCode = 404
  362. smtc.groupAPIResponse = nil
  363. smtc.groupAPIOutput = nil
  364. smtc.expectedSecret = smtc.projectAPIOutput.Value
  365. }
  366. groupSecretProjectOverride := func(smtc *secretManagerTestCase) {
  367. smtc.projectAPIOutput.Value = projectvalue
  368. smtc.groupAPIOutput.Key = testKey
  369. smtc.groupAPIOutput.Value = groupvalue
  370. smtc.expectedSecret = smtc.projectAPIOutput.Value
  371. }
  372. groupWithoutProjectOverride := func(smtc *secretManagerTestCase) {
  373. smtc.groupIDs = []string{groupid}
  374. smtc.projectAPIResponse.Response.StatusCode = 404
  375. smtc.groupAPIOutput.Key = testKey
  376. smtc.groupAPIOutput.Value = groupvalue
  377. smtc.expectedSecret = smtc.groupAPIOutput.Value
  378. }
  379. successCases := []*secretManagerTestCase{
  380. makeValidSecretManagerTestCaseCustom(onlyProjectSecret),
  381. makeValidSecretManagerTestCaseCustom(onlyWildcardSecret),
  382. makeValidSecretManagerTestCaseCustom(groupSecretProjectOverride),
  383. makeValidSecretManagerTestCaseCustom(groupWithoutProjectOverride),
  384. makeValidSecretManagerTestCaseCustom(setGroupWildcardVariable),
  385. makeValidSecretManagerTestCaseCustom(setGroupWildcardVariableWithEnvironment),
  386. makeValidSecretManagerTestCaseCustom(setGroupWildcardVariableNotFoundThenFound),
  387. makeValidSecretManagerTestCaseCustom(setAPIErr),
  388. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  389. }
  390. sm := gitlabBase{}
  391. sm.store = &esv1.GitlabProvider{}
  392. for k, v := range successCases {
  393. sm.projectVariablesClient = v.mockProjectVarClient
  394. sm.groupVariablesClient = v.mockGroupVarClient
  395. sm.store.ProjectID = v.projectID
  396. sm.store.GroupIDs = v.groupIDs
  397. sm.store.Environment = v.apiInputEnv
  398. out, err := sm.GetSecret(context.Background(), *v.ref)
  399. if !ErrorContains(err, v.expectError) {
  400. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  401. }
  402. if string(out) != v.expectedSecret {
  403. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out), v.expectedSecret)
  404. }
  405. }
  406. }
  407. func TestResolveGroupIds(t *testing.T) {
  408. v := makeValidSecretManagerTestCaseCustom()
  409. sm := gitlabBase{}
  410. sm.store = &esv1.GitlabProvider{}
  411. sm.projectsClient = v.mockProjectsClient
  412. sm.store.ProjectID = v.projectID
  413. sm.store.InheritFromGroups = true
  414. err := sm.ResolveGroupIds()
  415. if err != nil {
  416. t.Errorf(defaultErrorMessage, 0, err.Error(), "")
  417. }
  418. if !reflect.DeepEqual(sm.store.GroupIDs, []string{"1", "10", "100"}) {
  419. t.Errorf("unexpected groupIds: %s, expected %s", sm.store.GroupIDs, []string{"1", "10", "100"})
  420. }
  421. }
  422. func TestGetAllSecrets(t *testing.T) {
  423. // good case: default version is set
  424. // key is passed in, output is sent back
  425. setMissingFindRegex := func(smtc *secretManagerTestCase) {
  426. smtc.refFind.Name = nil
  427. smtc.expectError = "'find.name' is mandatory"
  428. }
  429. setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
  430. path := "path"
  431. smtc.refFind.Path = &path
  432. smtc.expectError = "'find.path' is not implemented in the GitLab provider"
  433. }
  434. setUnsupportedFindTag := func(smtc *secretManagerTestCase) {
  435. smtc.expectError = "'find.tags' only supports 'environment_scope"
  436. smtc.refFind.Tags = map[string]string{"foo": ""}
  437. }
  438. setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
  439. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  440. Key: testKey,
  441. Value: projectvalue,
  442. EnvironmentScope: environment,
  443. }
  444. smtc.expectedSecret = projectvalue
  445. smtc.refFind.Name = makeFindName(findTestPrefix)
  446. }
  447. setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
  448. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  449. Key: testKey,
  450. Value: projectvalue,
  451. EnvironmentScope: environmentTest,
  452. }
  453. smtc.expectedSecret = ""
  454. smtc.refFind.Name = makeFindName("foo.*")
  455. }
  456. setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) {
  457. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  458. Key: testKey,
  459. Value: projectvalue,
  460. EnvironmentScope: environmentTest,
  461. }
  462. smtc.expectedSecret = ""
  463. smtc.refFind.Name = makeFindName(findTestPrefix)
  464. }
  465. setMatchingSecretFindTags := func(smtc *secretManagerTestCase) {
  466. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  467. Key: testKey,
  468. Value: projectvalue,
  469. EnvironmentScope: environment,
  470. }
  471. smtc.apiInputEnv = "*"
  472. smtc.expectedSecret = projectvalue
  473. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  474. }
  475. setEnvironmentConstrainedByStore := func(smtc *secretManagerTestCase) {
  476. smtc.expectedSecret = projectvalue
  477. smtc.expectError = "'find.tags' is constrained by 'environment_scope' of the store"
  478. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  479. }
  480. setWildcardDoesntOverwriteEnvironmentValue := func(smtc *secretManagerTestCase) {
  481. var1 := gitlab.ProjectVariable{
  482. Key: testKey,
  483. Value: "wildcardValue",
  484. EnvironmentScope: "*",
  485. }
  486. var2 := gitlab.ProjectVariable{
  487. Key: testKey,
  488. Value: "expectedValue",
  489. EnvironmentScope: environmentTest,
  490. }
  491. var3 := gitlab.ProjectVariable{
  492. Key: testKey,
  493. Value: "wildcardValue",
  494. EnvironmentScope: "*",
  495. }
  496. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3}
  497. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  498. smtc.projectAPIOutput = nil
  499. smtc.apiInputEnv = environmentTest
  500. smtc.expectedSecret = "expectedValue"
  501. smtc.refFind.Name = makeFindName(findTestPrefix)
  502. }
  503. setFilterByEnvironmentWithWildcard := func(smtc *secretManagerTestCase) {
  504. var1 := gitlab.ProjectVariable{
  505. Key: testKey,
  506. Value: projectvalue,
  507. EnvironmentScope: "*",
  508. }
  509. var2 := gitlab.ProjectVariable{
  510. Key: "testKey2",
  511. Value: "value2",
  512. EnvironmentScope: environment,
  513. }
  514. var3 := gitlab.ProjectVariable{
  515. Key: "testKey3",
  516. Value: "value3",
  517. EnvironmentScope: environmentTest,
  518. }
  519. var4 := gitlab.ProjectVariable{
  520. Key: "anotherKey4",
  521. Value: "value4",
  522. EnvironmentScope: environment,
  523. }
  524. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3, &var4}
  525. smtc.projectAPIOutput = nil
  526. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  527. smtc.apiInputEnv = environment
  528. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "testKey2": []byte("value2")}
  529. smtc.refFind.Name = makeFindName(findTestPrefix)
  530. }
  531. setPaginationInGroupAndProjectVars := func(smtc *secretManagerTestCase) {
  532. smtc.groupIDs = []string{groupid}
  533. gvar1 := gitlab.GroupVariable{
  534. Key: testKey + "Group",
  535. Value: "groupValue1",
  536. EnvironmentScope: environmentTest,
  537. }
  538. gvar2 := gitlab.GroupVariable{
  539. Key: testKey,
  540. Value: "groupValue2",
  541. EnvironmentScope: environmentTest,
  542. }
  543. pvar1 := gitlab.ProjectVariable{
  544. Key: testKey,
  545. Value: "testValue1",
  546. EnvironmentScope: environmentTest,
  547. }
  548. pvar2a := gitlab.ProjectVariable{
  549. Key: testKey + "2a",
  550. Value: "testValue2a",
  551. EnvironmentScope: environmentTest,
  552. }
  553. pvar2b := gitlab.ProjectVariable{
  554. Key: testKey + "2b",
  555. Value: "testValue2b",
  556. EnvironmentScope: environmentTest,
  557. }
  558. gPage1 := []*gitlab.GroupVariable{&gvar1}
  559. gResponsePage1 := makeValidGroupAPIResponse()
  560. gResponsePage1.TotalPages = 2
  561. gResponsePage1.CurrentPage = 1
  562. gPage2 := []*gitlab.GroupVariable{&gvar2}
  563. gResponsePage2 := makeValidGroupAPIResponse()
  564. gResponsePage2.TotalPages = 2
  565. gResponsePage2.CurrentPage = 1
  566. pPage1 := []*gitlab.ProjectVariable{&pvar1}
  567. pResponsePage1 := makeValidProjectAPIResponse()
  568. pResponsePage1.TotalPages = 2
  569. pResponsePage1.CurrentPage = 1
  570. pPage2 := []*gitlab.ProjectVariable{&pvar2a, &pvar2b}
  571. pResponsePage2 := makeValidProjectAPIResponse()
  572. pResponsePage2.TotalPages = 2
  573. pResponsePage2.CurrentPage = 2
  574. smtc.groupAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]{{Output: gPage1, Response: gResponsePage1, Error: nil}, {Output: gPage2, Response: gResponsePage2, Error: nil}}
  575. smtc.groupAPIOutput = nil
  576. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: pPage1, Response: pResponsePage1, Error: nil}, {Output: pPage2, Response: pResponsePage2, Error: nil}}
  577. smtc.projectAPIOutput = nil
  578. smtc.apiInputEnv = environmentTest
  579. smtc.expectedData = map[string][]byte{testKey: []byte("testValue1"), "testKey2a": []byte("testValue2a"), "testKey2b": []byte("testValue2b"), "testKeyGroup": []byte("groupValue1")}
  580. smtc.refFind.Name = makeFindName(findTestPrefix)
  581. }
  582. setGroupWildcardVariableInGetAllSecrets := func(smtc *secretManagerTestCase) {
  583. smtc.groupIDs = []string{groupid}
  584. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  585. Key: testKey,
  586. Value: projectvalue,
  587. EnvironmentScope: environment,
  588. }
  589. smtc.groupAPIOutput = &gitlab.GroupVariable{
  590. Key: testKey,
  591. Value: groupvalue,
  592. EnvironmentScope: "*",
  593. }
  594. // Project variable should override group wildcard variable
  595. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  596. smtc.refFind.Name = makeFindName(findTestPrefix)
  597. }
  598. setGroupWildcardVariableOnlyInGetAllSecrets := func(smtc *secretManagerTestCase) {
  599. smtc.groupIDs = []string{groupid}
  600. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  601. Key: testKey,
  602. Value: projectvalue,
  603. EnvironmentScope: environmentTest, // Different environment
  604. }
  605. smtc.groupAPIOutput = &gitlab.GroupVariable{
  606. Key: testKey,
  607. Value: groupvalue,
  608. EnvironmentScope: "*",
  609. }
  610. // Group wildcard variable should be used when project variable doesn't match environment
  611. smtc.apiInputEnv = environment
  612. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  613. smtc.refFind.Name = makeFindName(findTestPrefix)
  614. }
  615. setGroupWildcardVariableWithCollision := func(smtc *secretManagerTestCase) {
  616. smtc.groupIDs = []string{groupid}
  617. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  618. Key: testKey,
  619. Value: projectvalue,
  620. EnvironmentScope: "*",
  621. }
  622. smtc.groupAPIOutput = &gitlab.GroupVariable{
  623. Key: testKey,
  624. Value: groupvalue,
  625. EnvironmentScope: "*",
  626. }
  627. // Project wildcard variable should override group wildcard variable
  628. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  629. smtc.refFind.Name = makeFindName(findTestPrefix)
  630. }
  631. cases := []*secretManagerTestCase{
  632. makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
  633. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
  634. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTag),
  635. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
  636. makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
  637. makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
  638. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindTags),
  639. makeValidSecretManagerGetAllTestCaseCustom(setWildcardDoesntOverwriteEnvironmentValue),
  640. makeValidSecretManagerGetAllTestCaseCustom(setEnvironmentConstrainedByStore),
  641. makeValidSecretManagerGetAllTestCaseCustom(setFilterByEnvironmentWithWildcard),
  642. makeValidSecretManagerGetAllTestCaseCustom(setPaginationInGroupAndProjectVars),
  643. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableInGetAllSecrets),
  644. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableOnlyInGetAllSecrets),
  645. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableWithCollision),
  646. makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
  647. makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
  648. }
  649. sm := gitlabBase{}
  650. sm.store = &esv1.GitlabProvider{}
  651. for k, v := range cases {
  652. sm.projectVariablesClient = v.mockProjectVarClient
  653. sm.groupVariablesClient = v.mockGroupVarClient
  654. sm.store.Environment = v.apiInputEnv
  655. sm.store.GroupIDs = v.groupIDs
  656. if v.expectedSecret != "" {
  657. v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)}
  658. }
  659. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  660. if !ErrorContains(err, v.expectError) {
  661. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  662. }
  663. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  664. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  665. }
  666. }
  667. }
  668. func TestGetAllSecretsWithGroups(t *testing.T) {
  669. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  670. smtc.projectAPIOutput.Value = projectvalue
  671. smtc.refFind.Name = makeFindName(findTestPrefix)
  672. smtc.groupAPIResponse = nil
  673. smtc.groupAPIOutput = nil
  674. smtc.expectedSecret = smtc.projectAPIOutput.Value
  675. }
  676. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  677. smtc.groupIDs = []string{groupid}
  678. smtc.projectAPIOutput.Value = projectvalue
  679. smtc.groupAPIOutput.Value = groupvalue
  680. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "groupKey": []byte(groupvalue)}
  681. smtc.refFind.Name = makeFindName(".*Key")
  682. }
  683. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  684. smtc.groupIDs = []string{groupid}
  685. smtc.projectAPIOutput.Value = projectvalue
  686. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  687. smtc.groupAPIOutput.Value = groupvalue
  688. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  689. smtc.refFind.Name = makeFindName(".*Key")
  690. }
  691. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  692. smtc.groupIDs = []string{groupid}
  693. smtc.projectAPIOutput.Value = projectvalue
  694. smtc.projectAPIOutput.EnvironmentScope = environmentTest
  695. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  696. smtc.groupAPIOutput.Value = groupvalue
  697. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  698. smtc.refFind.Name = makeFindName(".*Key")
  699. }
  700. cases := []*secretManagerTestCase{
  701. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  702. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  703. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  704. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  705. }
  706. sm := gitlabBase{}
  707. sm.store = &esv1.GitlabProvider{}
  708. sm.store.Environment = environment
  709. for k, v := range cases {
  710. sm.projectVariablesClient = v.mockProjectVarClient
  711. sm.groupVariablesClient = v.mockGroupVarClient
  712. sm.store.ProjectID = v.projectID
  713. sm.store.GroupIDs = v.groupIDs
  714. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  715. if !ErrorContains(err, v.expectError) {
  716. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  717. }
  718. if v.expectError == "" {
  719. if len(v.expectedData) > 0 {
  720. if !reflect.DeepEqual(v.expectedData, out) {
  721. t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData)
  722. }
  723. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  724. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  725. }
  726. }
  727. }
  728. }
  729. func TestValidate(t *testing.T) {
  730. successCases := []*secretManagerTestCase{
  731. makeValidSecretManagerTestCaseCustom(),
  732. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  733. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  734. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  735. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  736. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  737. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  738. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  739. }
  740. sm := gitlabBase{}
  741. sm.store = &esv1.GitlabProvider{}
  742. for k, v := range successCases {
  743. sm.projectsClient = v.mockProjectsClient
  744. sm.projectVariablesClient = v.mockProjectVarClient
  745. sm.groupVariablesClient = v.mockGroupVarClient
  746. sm.store.ProjectID = v.projectID
  747. sm.store.GroupIDs = v.groupIDs
  748. sm.store.InheritFromGroups = v.inheritFromGroups
  749. t.Logf("%+v", v)
  750. validationResult, err := sm.Validate()
  751. if !ErrorContains(err, v.expectError) {
  752. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  753. }
  754. if validationResult != v.expectedValidationResult {
  755. t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
  756. }
  757. if sm.store.InheritFromGroups && sm.store.GroupIDs[0] != "1" {
  758. t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.store.GroupIDs[0])
  759. }
  760. }
  761. }
  762. func TestGetSecretMap(t *testing.T) {
  763. // good case: default version & deserialization
  764. setDeserialization := func(smtc *secretManagerTestCase) {
  765. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  766. smtc.expectedData["foo"] = []byte("bar")
  767. }
  768. // bad case: invalid json
  769. setInvalidJSON := func(smtc *secretManagerTestCase) {
  770. smtc.projectAPIOutput.Value = `-----------------`
  771. smtc.expectError = "unable to unmarshal secret"
  772. }
  773. successCases := []*secretManagerTestCase{
  774. makeValidSecretManagerTestCaseCustom(setDeserialization),
  775. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  776. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  777. makeValidSecretManagerTestCaseCustom(setAPIErr),
  778. }
  779. sm := gitlabBase{}
  780. sm.store = &esv1.GitlabProvider{}
  781. for k, v := range successCases {
  782. sm.projectVariablesClient = v.mockProjectVarClient
  783. sm.groupVariablesClient = v.mockGroupVarClient
  784. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  785. if !ErrorContains(err, v.expectError) {
  786. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  787. }
  788. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  789. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  790. }
  791. }
  792. }
  793. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1.SecretStore {
  794. store := &esv1.SecretStore{
  795. Spec: esv1.SecretStoreSpec{
  796. Provider: &esv1.SecretStoreProvider{
  797. Gitlab: &esv1.GitlabProvider{
  798. Auth: esv1.GitlabAuth{},
  799. ProjectID: projectID,
  800. Environment: environment,
  801. },
  802. },
  803. },
  804. }
  805. for _, f := range fn {
  806. store = f(store)
  807. }
  808. return store
  809. }
  810. func withAccessToken(name, key string, namespace *string) storeModifier {
  811. return func(store *esv1.SecretStore) *esv1.SecretStore {
  812. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  813. Name: name,
  814. Key: key,
  815. Namespace: namespace,
  816. }
  817. return store
  818. }
  819. }
  820. func withGroups(ids []string, inherit bool) storeModifier {
  821. return func(store *esv1.SecretStore) *esv1.SecretStore {
  822. store.Spec.Provider.Gitlab.GroupIDs = ids
  823. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  824. return store
  825. }
  826. }
  827. type ValidateStoreTestCase struct {
  828. store *esv1.SecretStore
  829. err error
  830. }
  831. func TestValidateStore(t *testing.T) {
  832. namespace := "my-namespace"
  833. testCases := []ValidateStoreTestCase{
  834. {
  835. store: makeSecretStore("", environment),
  836. err: errors.New("projectID and groupIDs must not both be empty"),
  837. },
  838. {
  839. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  840. err: errors.New("defining groupIDs and inheritFromGroups = true is not allowed"),
  841. },
  842. {
  843. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  844. err: errors.New("accessToken.name cannot be empty"),
  845. },
  846. {
  847. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  848. err: errors.New("accessToken.key cannot be empty"),
  849. },
  850. {
  851. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  852. err: errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  853. },
  854. {
  855. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  856. err: nil,
  857. },
  858. {
  859. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  860. err: nil,
  861. },
  862. }
  863. p := Provider{}
  864. for _, tc := range testCases {
  865. _, err := p.ValidateStore(tc.store)
  866. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  867. t.Errorf("test failed! want %v, got %v", tc.err, err)
  868. } else if tc.err == nil && err != nil {
  869. t.Errorf("want nil got err %v", err)
  870. } else if tc.err != nil && err == nil {
  871. t.Errorf("want err %v got nil", tc.err)
  872. }
  873. }
  874. }
  875. func ErrorContains(out error, want string) bool {
  876. if out == nil {
  877. return want == ""
  878. }
  879. if want == "" {
  880. return false
  881. }
  882. return strings.Contains(out.Error(), want)
  883. }
  884. type storeModifier func(*esv1.SecretStore) *esv1.SecretStore
  885. func setGroupWildcardVariable(smtc *secretManagerTestCase) {
  886. smtc.groupIDs = []string{groupid}
  887. smtc.projectAPIResponse.Response.StatusCode = 404
  888. smtc.groupAPIOutput.Key = testKey
  889. smtc.groupAPIOutput.Value = groupvalue
  890. smtc.groupAPIOutput.EnvironmentScope = "*"
  891. smtc.expectedSecret = smtc.groupAPIOutput.Value
  892. }
  893. func setGroupWildcardVariableWithEnvironment(smtc *secretManagerTestCase) {
  894. smtc.groupIDs = []string{groupid}
  895. smtc.projectAPIResponse.Response.StatusCode = 404
  896. smtc.groupAPIOutput.Key = testKey
  897. smtc.groupAPIOutput.Value = groupvalue
  898. smtc.groupAPIOutput.EnvironmentScope = "*"
  899. smtc.apiInputEnv = environment
  900. smtc.expectedSecret = smtc.groupAPIOutput.Value
  901. }
  902. func setGroupWildcardVariableNotFoundThenFound(smtc *secretManagerTestCase) {
  903. smtc.groupIDs = []string{groupid}
  904. smtc.projectAPIResponse.Response.StatusCode = 404
  905. // For this test, we'll use a simpler approach - just return the wildcard variable
  906. smtc.groupAPIOutput = &gitlab.GroupVariable{
  907. Key: testKey,
  908. Value: groupvalue,
  909. EnvironmentScope: "*",
  910. }
  911. smtc.groupAPIResponse = &gitlab.Response{
  912. Response: &http.Response{
  913. StatusCode: http.StatusOK,
  914. },
  915. }
  916. smtc.apiInputEnv = environment
  917. smtc.expectedSecret = groupvalue
  918. }