gitlab_test.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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. // TestResolveGroupIDs tests the resolving of group IDs for a GitLab store.
  408. func TestResolveGroupIDs(t *testing.T) {
  409. v := makeValidSecretManagerTestCaseCustom()
  410. sm := gitlabBase{}
  411. sm.store = &esv1.GitlabProvider{}
  412. sm.projectsClient = v.mockProjectsClient
  413. sm.store.ProjectID = v.projectID
  414. sm.store.InheritFromGroups = true
  415. err := sm.ResolveGroupIDs()
  416. if err != nil {
  417. t.Errorf(defaultErrorMessage, 0, err.Error(), "")
  418. }
  419. if !reflect.DeepEqual(sm.store.GroupIDs, []string{"1", "10", "100"}) {
  420. t.Errorf("unexpected groupIDs: %s, expected %s", sm.store.GroupIDs, []string{"1", "10", "100"})
  421. }
  422. }
  423. func TestGetAllSecrets(t *testing.T) {
  424. // good case: default version is set
  425. // key is passed in, output is sent back
  426. setMissingFindRegex := func(smtc *secretManagerTestCase) {
  427. smtc.refFind.Name = nil
  428. smtc.expectError = "'find.name' is mandatory"
  429. }
  430. setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
  431. path := "path"
  432. smtc.refFind.Path = &path
  433. smtc.expectError = "'find.path' is not implemented in the GitLab provider"
  434. }
  435. setUnsupportedFindTag := func(smtc *secretManagerTestCase) {
  436. smtc.expectError = "'find.tags' only supports 'environment_scope"
  437. smtc.refFind.Tags = map[string]string{"foo": ""}
  438. }
  439. setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
  440. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  441. Key: testKey,
  442. Value: projectvalue,
  443. EnvironmentScope: environment,
  444. }
  445. smtc.expectedSecret = projectvalue
  446. smtc.refFind.Name = makeFindName(findTestPrefix)
  447. }
  448. setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
  449. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  450. Key: testKey,
  451. Value: projectvalue,
  452. EnvironmentScope: environmentTest,
  453. }
  454. smtc.expectedSecret = ""
  455. smtc.refFind.Name = makeFindName("foo.*")
  456. }
  457. setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) {
  458. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  459. Key: testKey,
  460. Value: projectvalue,
  461. EnvironmentScope: environmentTest,
  462. }
  463. smtc.expectedSecret = ""
  464. smtc.refFind.Name = makeFindName(findTestPrefix)
  465. }
  466. setMatchingSecretFindTags := func(smtc *secretManagerTestCase) {
  467. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  468. Key: testKey,
  469. Value: projectvalue,
  470. EnvironmentScope: environment,
  471. }
  472. smtc.apiInputEnv = "*"
  473. smtc.expectedSecret = projectvalue
  474. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  475. }
  476. setEnvironmentConstrainedByStore := func(smtc *secretManagerTestCase) {
  477. smtc.expectedSecret = projectvalue
  478. smtc.expectError = "'find.tags' is constrained by 'environment_scope' of the store"
  479. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  480. }
  481. setWildcardDoesntOverwriteEnvironmentValue := func(smtc *secretManagerTestCase) {
  482. var1 := gitlab.ProjectVariable{
  483. Key: testKey,
  484. Value: "wildcardValue",
  485. EnvironmentScope: "*",
  486. }
  487. var2 := gitlab.ProjectVariable{
  488. Key: testKey,
  489. Value: "expectedValue",
  490. EnvironmentScope: environmentTest,
  491. }
  492. var3 := gitlab.ProjectVariable{
  493. Key: testKey,
  494. Value: "wildcardValue",
  495. EnvironmentScope: "*",
  496. }
  497. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3}
  498. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  499. smtc.projectAPIOutput = nil
  500. smtc.apiInputEnv = environmentTest
  501. smtc.expectedSecret = "expectedValue"
  502. smtc.refFind.Name = makeFindName(findTestPrefix)
  503. }
  504. setFilterByEnvironmentWithWildcard := func(smtc *secretManagerTestCase) {
  505. var1 := gitlab.ProjectVariable{
  506. Key: testKey,
  507. Value: projectvalue,
  508. EnvironmentScope: "*",
  509. }
  510. var2 := gitlab.ProjectVariable{
  511. Key: "testKey2",
  512. Value: "value2",
  513. EnvironmentScope: environment,
  514. }
  515. var3 := gitlab.ProjectVariable{
  516. Key: "testKey3",
  517. Value: "value3",
  518. EnvironmentScope: environmentTest,
  519. }
  520. var4 := gitlab.ProjectVariable{
  521. Key: "anotherKey4",
  522. Value: "value4",
  523. EnvironmentScope: environment,
  524. }
  525. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3, &var4}
  526. smtc.projectAPIOutput = nil
  527. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  528. smtc.apiInputEnv = environment
  529. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "testKey2": []byte("value2")}
  530. smtc.refFind.Name = makeFindName(findTestPrefix)
  531. }
  532. setPaginationInGroupAndProjectVars := func(smtc *secretManagerTestCase) {
  533. smtc.groupIDs = []string{groupid}
  534. gvar1 := gitlab.GroupVariable{
  535. Key: testKey + "Group",
  536. Value: "groupValue1",
  537. EnvironmentScope: environmentTest,
  538. }
  539. gvar2 := gitlab.GroupVariable{
  540. Key: testKey,
  541. Value: "groupValue2",
  542. EnvironmentScope: environmentTest,
  543. }
  544. pvar1 := gitlab.ProjectVariable{
  545. Key: testKey,
  546. Value: "testValue1",
  547. EnvironmentScope: environmentTest,
  548. }
  549. pvar2a := gitlab.ProjectVariable{
  550. Key: testKey + "2a",
  551. Value: "testValue2a",
  552. EnvironmentScope: environmentTest,
  553. }
  554. pvar2b := gitlab.ProjectVariable{
  555. Key: testKey + "2b",
  556. Value: "testValue2b",
  557. EnvironmentScope: environmentTest,
  558. }
  559. gPage1 := []*gitlab.GroupVariable{&gvar1}
  560. gResponsePage1 := makeValidGroupAPIResponse()
  561. gResponsePage1.TotalPages = 2
  562. gResponsePage1.CurrentPage = 1
  563. gPage2 := []*gitlab.GroupVariable{&gvar2}
  564. gResponsePage2 := makeValidGroupAPIResponse()
  565. gResponsePage2.TotalPages = 2
  566. gResponsePage2.CurrentPage = 1
  567. pPage1 := []*gitlab.ProjectVariable{&pvar1}
  568. pResponsePage1 := makeValidProjectAPIResponse()
  569. pResponsePage1.TotalPages = 2
  570. pResponsePage1.CurrentPage = 1
  571. pPage2 := []*gitlab.ProjectVariable{&pvar2a, &pvar2b}
  572. pResponsePage2 := makeValidProjectAPIResponse()
  573. pResponsePage2.TotalPages = 2
  574. pResponsePage2.CurrentPage = 2
  575. smtc.groupAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]{{Output: gPage1, Response: gResponsePage1, Error: nil}, {Output: gPage2, Response: gResponsePage2, Error: nil}}
  576. smtc.groupAPIOutput = nil
  577. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: pPage1, Response: pResponsePage1, Error: nil}, {Output: pPage2, Response: pResponsePage2, Error: nil}}
  578. smtc.projectAPIOutput = nil
  579. smtc.apiInputEnv = environmentTest
  580. smtc.expectedData = map[string][]byte{testKey: []byte("testValue1"), "testKey2a": []byte("testValue2a"), "testKey2b": []byte("testValue2b"), "testKeyGroup": []byte("groupValue1")}
  581. smtc.refFind.Name = makeFindName(findTestPrefix)
  582. }
  583. setGroupWildcardVariableInGetAllSecrets := func(smtc *secretManagerTestCase) {
  584. smtc.groupIDs = []string{groupid}
  585. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  586. Key: testKey,
  587. Value: projectvalue,
  588. EnvironmentScope: environment,
  589. }
  590. smtc.groupAPIOutput = &gitlab.GroupVariable{
  591. Key: testKey,
  592. Value: groupvalue,
  593. EnvironmentScope: "*",
  594. }
  595. // Project variable should override group wildcard variable
  596. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  597. smtc.refFind.Name = makeFindName(findTestPrefix)
  598. }
  599. setGroupWildcardVariableOnlyInGetAllSecrets := func(smtc *secretManagerTestCase) {
  600. smtc.groupIDs = []string{groupid}
  601. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  602. Key: testKey,
  603. Value: projectvalue,
  604. EnvironmentScope: environmentTest, // Different environment
  605. }
  606. smtc.groupAPIOutput = &gitlab.GroupVariable{
  607. Key: testKey,
  608. Value: groupvalue,
  609. EnvironmentScope: "*",
  610. }
  611. // Group wildcard variable should be used when project variable doesn't match environment
  612. smtc.apiInputEnv = environment
  613. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  614. smtc.refFind.Name = makeFindName(findTestPrefix)
  615. }
  616. setGroupWildcardVariableWithCollision := func(smtc *secretManagerTestCase) {
  617. smtc.groupIDs = []string{groupid}
  618. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  619. Key: testKey,
  620. Value: projectvalue,
  621. EnvironmentScope: "*",
  622. }
  623. smtc.groupAPIOutput = &gitlab.GroupVariable{
  624. Key: testKey,
  625. Value: groupvalue,
  626. EnvironmentScope: "*",
  627. }
  628. // Project wildcard variable should override group wildcard variable
  629. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  630. smtc.refFind.Name = makeFindName(findTestPrefix)
  631. }
  632. cases := []*secretManagerTestCase{
  633. makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
  634. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
  635. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTag),
  636. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
  637. makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
  638. makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
  639. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindTags),
  640. makeValidSecretManagerGetAllTestCaseCustom(setWildcardDoesntOverwriteEnvironmentValue),
  641. makeValidSecretManagerGetAllTestCaseCustom(setEnvironmentConstrainedByStore),
  642. makeValidSecretManagerGetAllTestCaseCustom(setFilterByEnvironmentWithWildcard),
  643. makeValidSecretManagerGetAllTestCaseCustom(setPaginationInGroupAndProjectVars),
  644. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableInGetAllSecrets),
  645. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableOnlyInGetAllSecrets),
  646. makeValidSecretManagerGetAllTestCaseCustom(setGroupWildcardVariableWithCollision),
  647. makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
  648. makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
  649. }
  650. sm := gitlabBase{}
  651. sm.store = &esv1.GitlabProvider{}
  652. for k, v := range cases {
  653. sm.projectVariablesClient = v.mockProjectVarClient
  654. sm.groupVariablesClient = v.mockGroupVarClient
  655. sm.store.Environment = v.apiInputEnv
  656. sm.store.GroupIDs = v.groupIDs
  657. if v.expectedSecret != "" {
  658. v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)}
  659. }
  660. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  661. if !ErrorContains(err, v.expectError) {
  662. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  663. }
  664. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  665. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  666. }
  667. }
  668. }
  669. func TestGetAllSecretsWithGroups(t *testing.T) {
  670. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  671. smtc.projectAPIOutput.Value = projectvalue
  672. smtc.refFind.Name = makeFindName(findTestPrefix)
  673. smtc.groupAPIResponse = nil
  674. smtc.groupAPIOutput = nil
  675. smtc.expectedSecret = smtc.projectAPIOutput.Value
  676. }
  677. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  678. smtc.groupIDs = []string{groupid}
  679. smtc.projectAPIOutput.Value = projectvalue
  680. smtc.groupAPIOutput.Value = groupvalue
  681. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "groupKey": []byte(groupvalue)}
  682. smtc.refFind.Name = makeFindName(".*Key")
  683. }
  684. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  685. smtc.groupIDs = []string{groupid}
  686. smtc.projectAPIOutput.Value = projectvalue
  687. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  688. smtc.groupAPIOutput.Value = groupvalue
  689. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  690. smtc.refFind.Name = makeFindName(".*Key")
  691. }
  692. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  693. smtc.groupIDs = []string{groupid}
  694. smtc.projectAPIOutput.Value = projectvalue
  695. smtc.projectAPIOutput.EnvironmentScope = environmentTest
  696. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  697. smtc.groupAPIOutput.Value = groupvalue
  698. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  699. smtc.refFind.Name = makeFindName(".*Key")
  700. }
  701. cases := []*secretManagerTestCase{
  702. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  703. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  704. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  705. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  706. }
  707. sm := gitlabBase{}
  708. sm.store = &esv1.GitlabProvider{}
  709. sm.store.Environment = environment
  710. for k, v := range cases {
  711. sm.projectVariablesClient = v.mockProjectVarClient
  712. sm.groupVariablesClient = v.mockGroupVarClient
  713. sm.store.ProjectID = v.projectID
  714. sm.store.GroupIDs = v.groupIDs
  715. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  716. if !ErrorContains(err, v.expectError) {
  717. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  718. }
  719. if v.expectError == "" {
  720. if len(v.expectedData) > 0 {
  721. if !reflect.DeepEqual(v.expectedData, out) {
  722. t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData)
  723. }
  724. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  725. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  726. }
  727. }
  728. }
  729. }
  730. func TestValidate(t *testing.T) {
  731. successCases := []*secretManagerTestCase{
  732. makeValidSecretManagerTestCaseCustom(),
  733. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  734. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  735. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  736. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  737. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  738. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  739. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  740. }
  741. sm := gitlabBase{}
  742. sm.store = &esv1.GitlabProvider{}
  743. for k, v := range successCases {
  744. sm.projectsClient = v.mockProjectsClient
  745. sm.projectVariablesClient = v.mockProjectVarClient
  746. sm.groupVariablesClient = v.mockGroupVarClient
  747. sm.store.ProjectID = v.projectID
  748. sm.store.GroupIDs = v.groupIDs
  749. sm.store.InheritFromGroups = v.inheritFromGroups
  750. t.Logf("%+v", v)
  751. validationResult, err := sm.Validate()
  752. if !ErrorContains(err, v.expectError) {
  753. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  754. }
  755. if validationResult != v.expectedValidationResult {
  756. t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
  757. }
  758. if sm.store.InheritFromGroups && sm.store.GroupIDs[0] != "1" {
  759. t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.store.GroupIDs[0])
  760. }
  761. }
  762. }
  763. func TestGetSecretMap(t *testing.T) {
  764. // good case: default version & deserialization
  765. setDeserialization := func(smtc *secretManagerTestCase) {
  766. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  767. smtc.expectedData["foo"] = []byte("bar")
  768. }
  769. // bad case: invalid json
  770. setInvalidJSON := func(smtc *secretManagerTestCase) {
  771. smtc.projectAPIOutput.Value = `-----------------`
  772. smtc.expectError = "unable to unmarshal secret"
  773. }
  774. successCases := []*secretManagerTestCase{
  775. makeValidSecretManagerTestCaseCustom(setDeserialization),
  776. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  777. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  778. makeValidSecretManagerTestCaseCustom(setAPIErr),
  779. }
  780. sm := gitlabBase{}
  781. sm.store = &esv1.GitlabProvider{}
  782. for k, v := range successCases {
  783. sm.projectVariablesClient = v.mockProjectVarClient
  784. sm.groupVariablesClient = v.mockGroupVarClient
  785. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  786. if !ErrorContains(err, v.expectError) {
  787. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  788. }
  789. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  790. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  791. }
  792. }
  793. }
  794. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1.SecretStore {
  795. store := &esv1.SecretStore{
  796. Spec: esv1.SecretStoreSpec{
  797. Provider: &esv1.SecretStoreProvider{
  798. Gitlab: &esv1.GitlabProvider{
  799. Auth: esv1.GitlabAuth{},
  800. ProjectID: projectID,
  801. Environment: environment,
  802. },
  803. },
  804. },
  805. }
  806. for _, f := range fn {
  807. store = f(store)
  808. }
  809. return store
  810. }
  811. func withAccessToken(name, key string, namespace *string) storeModifier {
  812. return func(store *esv1.SecretStore) *esv1.SecretStore {
  813. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  814. Name: name,
  815. Key: key,
  816. Namespace: namespace,
  817. }
  818. return store
  819. }
  820. }
  821. func withGroups(ids []string, inherit bool) storeModifier {
  822. return func(store *esv1.SecretStore) *esv1.SecretStore {
  823. store.Spec.Provider.Gitlab.GroupIDs = ids
  824. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  825. return store
  826. }
  827. }
  828. type ValidateStoreTestCase struct {
  829. store *esv1.SecretStore
  830. err error
  831. }
  832. func TestValidateStore(t *testing.T) {
  833. namespace := "my-namespace"
  834. testCases := []ValidateStoreTestCase{
  835. {
  836. store: makeSecretStore("", environment),
  837. err: errors.New("projectID and groupIDs must not both be empty"),
  838. },
  839. {
  840. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  841. err: errors.New("defining groupIDs and inheritFromGroups = true is not allowed"),
  842. },
  843. {
  844. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  845. err: errors.New("accessToken.name cannot be empty"),
  846. },
  847. {
  848. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  849. err: errors.New("accessToken.key cannot be empty"),
  850. },
  851. {
  852. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  853. err: errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  854. },
  855. {
  856. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  857. err: nil,
  858. },
  859. {
  860. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  861. err: nil,
  862. },
  863. }
  864. p := Provider{}
  865. for _, tc := range testCases {
  866. _, err := p.ValidateStore(tc.store)
  867. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  868. t.Errorf("test failed! want %v, got %v", tc.err, err)
  869. } else if tc.err == nil && err != nil {
  870. t.Errorf("want nil got err %v", err)
  871. } else if tc.err != nil && err == nil {
  872. t.Errorf("want err %v got nil", tc.err)
  873. }
  874. }
  875. }
  876. func ErrorContains(out error, want string) bool {
  877. if out == nil {
  878. return want == ""
  879. }
  880. if want == "" {
  881. return false
  882. }
  883. return strings.Contains(out.Error(), want)
  884. }
  885. type storeModifier func(*esv1.SecretStore) *esv1.SecretStore
  886. func setGroupWildcardVariable(smtc *secretManagerTestCase) {
  887. smtc.groupIDs = []string{groupid}
  888. smtc.projectAPIResponse.Response.StatusCode = 404
  889. smtc.groupAPIOutput.Key = testKey
  890. smtc.groupAPIOutput.Value = groupvalue
  891. smtc.groupAPIOutput.EnvironmentScope = "*"
  892. smtc.expectedSecret = smtc.groupAPIOutput.Value
  893. }
  894. func setGroupWildcardVariableWithEnvironment(smtc *secretManagerTestCase) {
  895. smtc.groupIDs = []string{groupid}
  896. smtc.projectAPIResponse.Response.StatusCode = 404
  897. smtc.groupAPIOutput.Key = testKey
  898. smtc.groupAPIOutput.Value = groupvalue
  899. smtc.groupAPIOutput.EnvironmentScope = "*"
  900. smtc.apiInputEnv = environment
  901. smtc.expectedSecret = smtc.groupAPIOutput.Value
  902. }
  903. func setGroupWildcardVariableNotFoundThenFound(smtc *secretManagerTestCase) {
  904. smtc.groupIDs = []string{groupid}
  905. smtc.projectAPIResponse.Response.StatusCode = 404
  906. // For this test, we'll use a simpler approach - just return the wildcard variable
  907. smtc.groupAPIOutput = &gitlab.GroupVariable{
  908. Key: testKey,
  909. Value: groupvalue,
  910. EnvironmentScope: "*",
  911. }
  912. smtc.groupAPIResponse = &gitlab.Response{
  913. Response: &http.Response{
  914. StatusCode: http.StatusOK,
  915. },
  916. }
  917. smtc.apiInputEnv = environment
  918. smtc.expectedSecret = groupvalue
  919. }