gitlab_test.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  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. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  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 \"\": 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 *esv1beta1.ExternalSecretDataRemoteRef
  64. refFind *esv1beta1.ExternalSecretFind
  65. projectID string
  66. groupIDs []string
  67. inheritFromGroups bool
  68. apiErr error
  69. expectError string
  70. expectedSecret string
  71. expectedValidationResult esv1beta1.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: esv1beta1.ValidationResultReady,
  97. expectedData: map[string][]byte{},
  98. }
  99. prepareMockProjectVarClient(&smtc)
  100. prepareMockGroupVarClient(&smtc)
  101. return &smtc
  102. }
  103. func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
  104. return &esv1beta1.ExternalSecretDataRemoteRef{
  105. Key: testKey,
  106. Version: "default",
  107. }
  108. }
  109. func makeValidFindRef() *esv1beta1.ExternalSecretFind {
  110. return &esv1beta1.ExternalSecretFind{}
  111. }
  112. func makeValidProjectID() string {
  113. return "projectId"
  114. }
  115. func makeEmptyGroupIds() []string {
  116. return []string{}
  117. }
  118. func makeFindName(regexp string) *esv1beta1.FindName {
  119. return &esv1beta1.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.projectAPIOutput != 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 = esv1beta1.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 = esv1beta1.ValidationResultError
  242. }
  243. var setProjectListAPIRespNil = func(smtc *secretManagerTestCase) {
  244. smtc.projectAPIResponse = nil
  245. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  246. smtc.expectedValidationResult = esv1beta1.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 = esv1beta1.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 = esv1beta1.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 = esv1beta1.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 := &esv1beta1.SecretStore{
  281. ObjectMeta: metav1.ObjectMeta{
  282. Namespace: namespace,
  283. },
  284. Spec: esv1beta1.SecretStoreSpec{
  285. Provider: &esv1beta1.SecretStoreProvider{
  286. Gitlab: &esv1beta1.GitlabProvider{},
  287. },
  288. },
  289. }
  290. provider, err := esv1beta1.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 = esv1beta1.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 = esv1beta1.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\": 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(setAPIErr),
  384. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  385. }
  386. sm := gitlabBase{}
  387. sm.store = &esv1beta1.GitlabProvider{}
  388. for k, v := range successCases {
  389. sm.projectVariablesClient = v.mockProjectVarClient
  390. sm.groupVariablesClient = v.mockGroupVarClient
  391. sm.store.ProjectID = v.projectID
  392. sm.store.GroupIDs = v.groupIDs
  393. sm.store.Environment = v.apiInputEnv
  394. out, err := sm.GetSecret(context.Background(), *v.ref)
  395. if !ErrorContains(err, v.expectError) {
  396. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  397. }
  398. if string(out) != v.expectedSecret {
  399. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out), v.expectedSecret)
  400. }
  401. }
  402. }
  403. func TestResolveGroupIds(t *testing.T) {
  404. v := makeValidSecretManagerTestCaseCustom()
  405. sm := gitlabBase{}
  406. sm.store = &esv1beta1.GitlabProvider{}
  407. sm.projectsClient = v.mockProjectsClient
  408. sm.store.ProjectID = v.projectID
  409. sm.store.InheritFromGroups = true
  410. err := sm.ResolveGroupIds()
  411. if err != nil {
  412. t.Errorf(defaultErrorMessage, 0, err.Error(), "")
  413. }
  414. if !reflect.DeepEqual(sm.store.GroupIDs, []string{"1", "10", "100"}) {
  415. t.Errorf("unexpected groupIds: %s, expected %s", sm.store.GroupIDs, []string{"1", "10", "100"})
  416. }
  417. }
  418. func TestGetAllSecrets(t *testing.T) {
  419. // good case: default version is set
  420. // key is passed in, output is sent back
  421. setMissingFindRegex := func(smtc *secretManagerTestCase) {
  422. smtc.refFind.Name = nil
  423. smtc.expectError = "'find.name' is mandatory"
  424. }
  425. setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
  426. path := "path"
  427. smtc.refFind.Path = &path
  428. smtc.expectError = "'find.path' is not implemented in the GitLab provider"
  429. }
  430. setUnsupportedFindTag := func(smtc *secretManagerTestCase) {
  431. smtc.expectError = "'find.tags' only supports 'environment_scope"
  432. smtc.refFind.Tags = map[string]string{"foo": ""}
  433. }
  434. setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
  435. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  436. Key: testKey,
  437. Value: projectvalue,
  438. EnvironmentScope: environment,
  439. }
  440. smtc.expectedSecret = projectvalue
  441. smtc.refFind.Name = makeFindName(findTestPrefix)
  442. }
  443. setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
  444. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  445. Key: testKey,
  446. Value: projectvalue,
  447. EnvironmentScope: environmentTest,
  448. }
  449. smtc.expectedSecret = ""
  450. smtc.refFind.Name = makeFindName("foo.*")
  451. }
  452. setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) {
  453. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  454. Key: testKey,
  455. Value: projectvalue,
  456. EnvironmentScope: environmentTest,
  457. }
  458. smtc.expectedSecret = ""
  459. smtc.refFind.Name = makeFindName(findTestPrefix)
  460. }
  461. setMatchingSecretFindTags := func(smtc *secretManagerTestCase) {
  462. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  463. Key: testKey,
  464. Value: projectvalue,
  465. EnvironmentScope: environment,
  466. }
  467. smtc.apiInputEnv = "*"
  468. smtc.expectedSecret = projectvalue
  469. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  470. }
  471. setEnvironmentConstrainedByStore := func(smtc *secretManagerTestCase) {
  472. smtc.expectedSecret = projectvalue
  473. smtc.expectError = "'find.tags' is constrained by 'environment_scope' of the store"
  474. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  475. }
  476. setWildcardDoesntOverwriteEnvironmentValue := func(smtc *secretManagerTestCase) {
  477. var1 := gitlab.ProjectVariable{
  478. Key: testKey,
  479. Value: "wildcardValue",
  480. EnvironmentScope: "*",
  481. }
  482. var2 := gitlab.ProjectVariable{
  483. Key: testKey,
  484. Value: "expectedValue",
  485. EnvironmentScope: environmentTest,
  486. }
  487. var3 := gitlab.ProjectVariable{
  488. Key: testKey,
  489. Value: "wildcardValue",
  490. EnvironmentScope: "*",
  491. }
  492. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3}
  493. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  494. smtc.projectAPIOutput = nil
  495. smtc.apiInputEnv = environmentTest
  496. smtc.expectedSecret = "expectedValue"
  497. smtc.refFind.Name = makeFindName(findTestPrefix)
  498. }
  499. setFilterByEnvironmentWithWildcard := func(smtc *secretManagerTestCase) {
  500. var1 := gitlab.ProjectVariable{
  501. Key: testKey,
  502. Value: projectvalue,
  503. EnvironmentScope: "*",
  504. }
  505. var2 := gitlab.ProjectVariable{
  506. Key: "testKey2",
  507. Value: "value2",
  508. EnvironmentScope: environment,
  509. }
  510. var3 := gitlab.ProjectVariable{
  511. Key: "testKey3",
  512. Value: "value3",
  513. EnvironmentScope: environmentTest,
  514. }
  515. var4 := gitlab.ProjectVariable{
  516. Key: "anotherKey4",
  517. Value: "value4",
  518. EnvironmentScope: environment,
  519. }
  520. vars := []*gitlab.ProjectVariable{&var1, &var2, &var3, &var4}
  521. smtc.projectAPIOutput = nil
  522. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: vars, Response: smtc.projectAPIResponse, Error: nil}}
  523. smtc.apiInputEnv = environment
  524. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "testKey2": []byte("value2")}
  525. smtc.refFind.Name = makeFindName(findTestPrefix)
  526. }
  527. setPaginationInGroupAndProjectVars := func(smtc *secretManagerTestCase) {
  528. smtc.groupIDs = []string{groupid}
  529. gvar1 := gitlab.GroupVariable{
  530. Key: testKey + "Group",
  531. Value: "groupValue1",
  532. EnvironmentScope: environmentTest,
  533. }
  534. gvar2 := gitlab.GroupVariable{
  535. Key: testKey,
  536. Value: "groupValue2",
  537. EnvironmentScope: environmentTest,
  538. }
  539. pvar1 := gitlab.ProjectVariable{
  540. Key: testKey,
  541. Value: "testValue1",
  542. EnvironmentScope: environmentTest,
  543. }
  544. pvar2a := gitlab.ProjectVariable{
  545. Key: testKey + "2a",
  546. Value: "testValue2a",
  547. EnvironmentScope: environmentTest,
  548. }
  549. pvar2b := gitlab.ProjectVariable{
  550. Key: testKey + "2b",
  551. Value: "testValue2b",
  552. EnvironmentScope: environmentTest,
  553. }
  554. gPage1 := []*gitlab.GroupVariable{&gvar1}
  555. gResponsePage1 := makeValidGroupAPIResponse()
  556. gResponsePage1.TotalPages = 2
  557. gResponsePage1.CurrentPage = 1
  558. gPage2 := []*gitlab.GroupVariable{&gvar2}
  559. gResponsePage2 := makeValidGroupAPIResponse()
  560. gResponsePage2.TotalPages = 2
  561. gResponsePage2.CurrentPage = 1
  562. pPage1 := []*gitlab.ProjectVariable{&pvar1}
  563. pResponsePage1 := makeValidProjectAPIResponse()
  564. pResponsePage1.TotalPages = 2
  565. pResponsePage1.CurrentPage = 1
  566. pPage2 := []*gitlab.ProjectVariable{&pvar2a, &pvar2b}
  567. pResponsePage2 := makeValidProjectAPIResponse()
  568. pResponsePage2.TotalPages = 2
  569. pResponsePage2.CurrentPage = 2
  570. smtc.groupAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]{{Output: gPage1, Response: gResponsePage1, Error: nil}, {Output: gPage2, Response: gResponsePage2, Error: nil}}
  571. smtc.groupAPIOutput = nil
  572. smtc.projectAPIOutputs = []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{{Output: pPage1, Response: pResponsePage1, Error: nil}, {Output: pPage2, Response: pResponsePage2, Error: nil}}
  573. smtc.projectAPIOutput = nil
  574. smtc.apiInputEnv = environmentTest
  575. smtc.expectedData = map[string][]byte{testKey: []byte("testValue1"), "testKey2a": []byte("testValue2a"), "testKey2b": []byte("testValue2b"), "testKeyGroup": []byte("groupValue1")}
  576. smtc.refFind.Name = makeFindName(findTestPrefix)
  577. }
  578. cases := []*secretManagerTestCase{
  579. makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
  580. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
  581. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTag),
  582. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
  583. makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
  584. makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
  585. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindTags),
  586. makeValidSecretManagerGetAllTestCaseCustom(setWildcardDoesntOverwriteEnvironmentValue),
  587. makeValidSecretManagerGetAllTestCaseCustom(setEnvironmentConstrainedByStore),
  588. makeValidSecretManagerGetAllTestCaseCustom(setFilterByEnvironmentWithWildcard),
  589. makeValidSecretManagerGetAllTestCaseCustom(setPaginationInGroupAndProjectVars),
  590. makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
  591. makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
  592. }
  593. sm := gitlabBase{}
  594. sm.store = &esv1beta1.GitlabProvider{}
  595. for k, v := range cases {
  596. sm.projectVariablesClient = v.mockProjectVarClient
  597. sm.groupVariablesClient = v.mockGroupVarClient
  598. sm.store.Environment = v.apiInputEnv
  599. sm.store.GroupIDs = v.groupIDs
  600. if v.expectedSecret != "" {
  601. v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)}
  602. }
  603. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  604. if !ErrorContains(err, v.expectError) {
  605. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  606. }
  607. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  608. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  609. }
  610. }
  611. }
  612. func TestGetAllSecretsWithGroups(t *testing.T) {
  613. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  614. smtc.projectAPIOutput.Value = projectvalue
  615. smtc.refFind.Name = makeFindName(findTestPrefix)
  616. smtc.groupAPIResponse = nil
  617. smtc.groupAPIOutput = nil
  618. smtc.expectedSecret = smtc.projectAPIOutput.Value
  619. }
  620. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  621. smtc.groupIDs = []string{groupid}
  622. smtc.projectAPIOutput.Value = projectvalue
  623. smtc.groupAPIOutput.Value = groupvalue
  624. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "groupKey": []byte(groupvalue)}
  625. smtc.refFind.Name = makeFindName(".*Key")
  626. }
  627. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  628. smtc.groupIDs = []string{groupid}
  629. smtc.projectAPIOutput.Value = projectvalue
  630. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  631. smtc.groupAPIOutput.Value = groupvalue
  632. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  633. smtc.refFind.Name = makeFindName(".*Key")
  634. }
  635. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  636. smtc.groupIDs = []string{groupid}
  637. smtc.projectAPIOutput.Value = projectvalue
  638. smtc.projectAPIOutput.EnvironmentScope = environmentTest
  639. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  640. smtc.groupAPIOutput.Value = groupvalue
  641. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  642. smtc.refFind.Name = makeFindName(".*Key")
  643. }
  644. cases := []*secretManagerTestCase{
  645. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  646. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  647. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  648. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  649. }
  650. sm := gitlabBase{}
  651. sm.store = &esv1beta1.GitlabProvider{}
  652. sm.store.Environment = environment
  653. for k, v := range cases {
  654. sm.projectVariablesClient = v.mockProjectVarClient
  655. sm.groupVariablesClient = v.mockGroupVarClient
  656. sm.store.ProjectID = v.projectID
  657. sm.store.GroupIDs = v.groupIDs
  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 v.expectError == "" {
  663. if len(v.expectedData) > 0 {
  664. if !reflect.DeepEqual(v.expectedData, out) {
  665. t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData)
  666. }
  667. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  668. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  669. }
  670. }
  671. }
  672. }
  673. func TestValidate(t *testing.T) {
  674. successCases := []*secretManagerTestCase{
  675. makeValidSecretManagerTestCaseCustom(),
  676. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  677. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  678. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  679. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  680. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  681. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  682. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  683. }
  684. sm := gitlabBase{}
  685. sm.store = &esv1beta1.GitlabProvider{}
  686. for k, v := range successCases {
  687. sm.projectsClient = v.mockProjectsClient
  688. sm.projectVariablesClient = v.mockProjectVarClient
  689. sm.groupVariablesClient = v.mockGroupVarClient
  690. sm.store.ProjectID = v.projectID
  691. sm.store.GroupIDs = v.groupIDs
  692. sm.store.InheritFromGroups = v.inheritFromGroups
  693. t.Logf("%+v", v)
  694. validationResult, err := sm.Validate()
  695. if !ErrorContains(err, v.expectError) {
  696. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  697. }
  698. if validationResult != v.expectedValidationResult {
  699. t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
  700. }
  701. if sm.store.InheritFromGroups && sm.store.GroupIDs[0] != "1" {
  702. t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.store.GroupIDs[0])
  703. }
  704. }
  705. }
  706. func TestGetSecretMap(t *testing.T) {
  707. // good case: default version & deserialization
  708. setDeserialization := func(smtc *secretManagerTestCase) {
  709. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  710. smtc.expectedData["foo"] = []byte("bar")
  711. }
  712. // bad case: invalid json
  713. setInvalidJSON := func(smtc *secretManagerTestCase) {
  714. smtc.projectAPIOutput.Value = `-----------------`
  715. smtc.expectError = "unable to unmarshal secret"
  716. }
  717. successCases := []*secretManagerTestCase{
  718. makeValidSecretManagerTestCaseCustom(setDeserialization),
  719. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  720. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  721. makeValidSecretManagerTestCaseCustom(setAPIErr),
  722. }
  723. sm := gitlabBase{}
  724. sm.store = &esv1beta1.GitlabProvider{}
  725. for k, v := range successCases {
  726. sm.projectVariablesClient = v.mockProjectVarClient
  727. sm.groupVariablesClient = v.mockGroupVarClient
  728. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  729. if !ErrorContains(err, v.expectError) {
  730. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  731. }
  732. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  733. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  734. }
  735. }
  736. }
  737. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
  738. store := &esv1beta1.SecretStore{
  739. Spec: esv1beta1.SecretStoreSpec{
  740. Provider: &esv1beta1.SecretStoreProvider{
  741. Gitlab: &esv1beta1.GitlabProvider{
  742. Auth: esv1beta1.GitlabAuth{},
  743. ProjectID: projectID,
  744. Environment: environment,
  745. },
  746. },
  747. },
  748. }
  749. for _, f := range fn {
  750. store = f(store)
  751. }
  752. return store
  753. }
  754. func withAccessToken(name, key string, namespace *string) storeModifier {
  755. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  756. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  757. Name: name,
  758. Key: key,
  759. Namespace: namespace,
  760. }
  761. return store
  762. }
  763. }
  764. func withGroups(ids []string, inherit bool) storeModifier {
  765. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  766. store.Spec.Provider.Gitlab.GroupIDs = ids
  767. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  768. return store
  769. }
  770. }
  771. type ValidateStoreTestCase struct {
  772. store *esv1beta1.SecretStore
  773. err error
  774. }
  775. func TestValidateStore(t *testing.T) {
  776. namespace := "my-namespace"
  777. testCases := []ValidateStoreTestCase{
  778. {
  779. store: makeSecretStore("", environment),
  780. err: errors.New("projectID and groupIDs must not both be empty"),
  781. },
  782. {
  783. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  784. err: errors.New("defining groupIDs and inheritFromGroups = true is not allowed"),
  785. },
  786. {
  787. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  788. err: errors.New("accessToken.name cannot be empty"),
  789. },
  790. {
  791. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  792. err: errors.New("accessToken.key cannot be empty"),
  793. },
  794. {
  795. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  796. err: errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  797. },
  798. {
  799. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  800. err: nil,
  801. },
  802. {
  803. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  804. err: nil,
  805. },
  806. }
  807. p := Provider{}
  808. for _, tc := range testCases {
  809. _, err := p.ValidateStore(tc.store)
  810. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  811. t.Errorf("test failed! want %v, got %v", tc.err, err)
  812. } else if tc.err == nil && err != nil {
  813. t.Errorf("want nil got err %v", err)
  814. } else if tc.err != nil && err == nil {
  815. t.Errorf("want err %v got nil", tc.err)
  816. }
  817. }
  818. }
  819. func ErrorContains(out error, want string) bool {
  820. if out == nil {
  821. return want == ""
  822. }
  823. if want == "" {
  824. return false
  825. }
  826. return strings.Contains(out.Error(), want)
  827. }
  828. type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore