gitlab_test.go 35 KB

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