gitlab_test.go 35 KB

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