gitlab_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  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. "fmt"
  17. "net/http"
  18. "reflect"
  19. "strings"
  20. "testing"
  21. "github.com/google/uuid"
  22. tassert "github.com/stretchr/testify/assert"
  23. "github.com/xanzy/go-gitlab"
  24. "github.com/yandex-cloud/go-sdk/iamkey"
  25. corev1 "k8s.io/api/core/v1"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
  28. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  29. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  30. esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1"
  31. fakegitlab "github.com/external-secrets/external-secrets/pkg/provider/gitlab/fake"
  32. )
  33. const (
  34. project = "my-Project"
  35. username = "user-name"
  36. userkey = "user-key"
  37. environment = "prod"
  38. projectvalue = "projectvalue"
  39. groupvalue = "groupvalue"
  40. groupid = "groupId"
  41. defaultErrorMessage = "[%d] unexpected error: [%s], expected: [%s]"
  42. errMissingCredentials = "credentials are empty"
  43. findTestPrefix = "test.*"
  44. )
  45. type secretManagerTestCase struct {
  46. mockProjectsClient *fakegitlab.GitlabMockProjectsClient
  47. mockProjectVarClient *fakegitlab.GitlabMockProjectVariablesClient
  48. mockGroupVarClient *fakegitlab.GitlabMockGroupVariablesClient
  49. apiInputProjectID string
  50. apiInputKey string
  51. apiInputEnv string
  52. projectAPIOutput *gitlab.ProjectVariable
  53. projectAPIResponse *gitlab.Response
  54. projectGroupsAPIOutput []*gitlab.ProjectGroup
  55. projectGroupsAPIResponse *gitlab.Response
  56. groupAPIOutput *gitlab.GroupVariable
  57. groupAPIResponse *gitlab.Response
  58. ref *esv1beta1.ExternalSecretDataRemoteRef
  59. refFind *esv1beta1.ExternalSecretFind
  60. projectID string
  61. groupIDs []string
  62. inheritFromGroups bool
  63. apiErr error
  64. expectError string
  65. expectedSecret string
  66. expectedValidationResult esv1beta1.ValidationResult
  67. // for testing secretmap
  68. expectedData map[string][]byte
  69. }
  70. func makeValidSecretManagerTestCase() *secretManagerTestCase {
  71. smtc := secretManagerTestCase{
  72. mockProjectsClient: &fakegitlab.GitlabMockProjectsClient{},
  73. mockProjectVarClient: &fakegitlab.GitlabMockProjectVariablesClient{},
  74. mockGroupVarClient: &fakegitlab.GitlabMockGroupVariablesClient{},
  75. apiInputProjectID: makeValidAPIInputProjectID(),
  76. apiInputKey: makeValidAPIInputKey(),
  77. apiInputEnv: makeValidEnvironment(),
  78. ref: makeValidRef(),
  79. refFind: makeValidFindRef(),
  80. projectID: makeValidProjectID(),
  81. groupIDs: makeEmptyGroupIds(),
  82. projectAPIOutput: makeValidProjectAPIOutput(),
  83. projectAPIResponse: makeValidProjectAPIResponse(),
  84. projectGroupsAPIOutput: makeValidProjectGroupsAPIOutput(),
  85. projectGroupsAPIResponse: makeValidProjectGroupsAPIResponse(),
  86. groupAPIOutput: makeValidGroupAPIOutput(),
  87. groupAPIResponse: makeValidGroupAPIResponse(),
  88. apiErr: nil,
  89. expectError: "",
  90. expectedSecret: "",
  91. expectedValidationResult: esv1beta1.ValidationResultReady,
  92. expectedData: map[string][]byte{},
  93. }
  94. smtc.mockProjectVarClient.WithValue(smtc.apiInputEnv, smtc.apiInputKey, smtc.projectAPIOutput, smtc.projectAPIResponse, smtc.apiErr)
  95. smtc.mockGroupVarClient.WithValue(smtc.groupAPIOutput, smtc.groupAPIResponse, smtc.apiErr)
  96. return &smtc
  97. }
  98. func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
  99. return &esv1beta1.ExternalSecretDataRemoteRef{
  100. Key: "test-secret",
  101. Version: "default",
  102. }
  103. }
  104. func makeValidFindRef() *esv1beta1.ExternalSecretFind {
  105. return &esv1beta1.ExternalSecretFind{}
  106. }
  107. func makeValidProjectID() string {
  108. return "projectId"
  109. }
  110. func makeEmptyGroupIds() []string {
  111. return []string{}
  112. }
  113. func makeFindName(regexp string) *esv1beta1.FindName {
  114. return &esv1beta1.FindName{
  115. RegExp: regexp,
  116. }
  117. }
  118. func makeValidAPIInputProjectID() string {
  119. return "testID"
  120. }
  121. func makeValidAPIInputKey() string {
  122. return "testKey"
  123. }
  124. func makeValidEnvironment() string {
  125. return environment
  126. }
  127. func makeValidProjectAPIResponse() *gitlab.Response {
  128. return &gitlab.Response{
  129. Response: &http.Response{
  130. StatusCode: http.StatusOK,
  131. },
  132. }
  133. }
  134. func makeValidProjectGroupsAPIResponse() *gitlab.Response {
  135. return &gitlab.Response{
  136. Response: &http.Response{
  137. StatusCode: http.StatusOK,
  138. },
  139. }
  140. }
  141. func makeValidGroupAPIResponse() *gitlab.Response {
  142. return &gitlab.Response{
  143. Response: &http.Response{
  144. StatusCode: http.StatusOK,
  145. },
  146. }
  147. }
  148. func makeValidProjectAPIOutput() *gitlab.ProjectVariable {
  149. return &gitlab.ProjectVariable{
  150. Key: "testKey",
  151. Value: "",
  152. EnvironmentScope: environment,
  153. }
  154. }
  155. func makeValidProjectGroupsAPIOutput() []*gitlab.ProjectGroup {
  156. return []*gitlab.ProjectGroup{{
  157. ID: 1,
  158. Name: "Group (1)",
  159. FullPath: "foo",
  160. }, {
  161. ID: 100,
  162. Name: "Group (100)",
  163. FullPath: "foo/bar/baz",
  164. }, {
  165. ID: 10,
  166. Name: "Group (10)",
  167. FullPath: "foo/bar",
  168. }}
  169. }
  170. func makeValidGroupAPIOutput() *gitlab.GroupVariable {
  171. return &gitlab.GroupVariable{
  172. Key: "groupKey",
  173. Value: "",
  174. EnvironmentScope: environment,
  175. }
  176. }
  177. func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  178. smtc := makeValidSecretManagerTestCase()
  179. for _, fn := range tweaks {
  180. fn(smtc)
  181. }
  182. smtc.mockProjectsClient.WithValue(smtc.projectGroupsAPIOutput, smtc.projectGroupsAPIResponse, smtc.apiErr)
  183. smtc.mockProjectVarClient.WithValue(smtc.apiInputEnv, smtc.apiInputKey, smtc.projectAPIOutput, smtc.projectAPIResponse, smtc.apiErr)
  184. smtc.mockGroupVarClient.WithValue(smtc.groupAPIOutput, smtc.groupAPIResponse, smtc.apiErr)
  185. return smtc
  186. }
  187. func makeValidSecretManagerGetAllTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  188. smtc := makeValidSecretManagerTestCase()
  189. smtc.ref = nil
  190. smtc.refFind.Name = makeFindName(".*")
  191. for _, fn := range tweaks {
  192. fn(smtc)
  193. }
  194. smtc.mockProjectVarClient.WithValue(smtc.apiInputEnv, smtc.apiInputKey, smtc.projectAPIOutput, smtc.projectAPIResponse, smtc.apiErr)
  195. smtc.mockGroupVarClient.WithValue(smtc.groupAPIOutput, smtc.groupAPIResponse, smtc.apiErr)
  196. return smtc
  197. }
  198. // This case can be shared by both GetSecret and GetSecretMap tests.
  199. // bad case: set apiErr.
  200. var setAPIErr = func(smtc *secretManagerTestCase) {
  201. smtc.apiErr = fmt.Errorf("oh no")
  202. smtc.expectError = "oh no"
  203. smtc.projectAPIResponse.Response.StatusCode = http.StatusInternalServerError
  204. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  205. }
  206. var setListAPIErr = func(smtc *secretManagerTestCase) {
  207. err := fmt.Errorf("oh no")
  208. smtc.apiErr = err
  209. smtc.expectError = fmt.Errorf(errList, err).Error()
  210. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  211. }
  212. var setProjectListAPIRespNil = func(smtc *secretManagerTestCase) {
  213. smtc.projectAPIResponse = nil
  214. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  215. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  216. }
  217. var setGroupListAPIRespNil = func(smtc *secretManagerTestCase) {
  218. smtc.groupIDs = []string{groupid}
  219. smtc.groupAPIResponse = nil
  220. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  221. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  222. }
  223. var setProjectAndGroup = func(smtc *secretManagerTestCase) {
  224. smtc.groupIDs = []string{groupid}
  225. }
  226. var setProjectAndInheritFromGroups = func(smtc *secretManagerTestCase) {
  227. smtc.groupIDs = nil
  228. smtc.inheritFromGroups = true
  229. }
  230. var setProjectListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  231. smtc.projectAPIResponse.StatusCode = http.StatusUnauthorized
  232. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  233. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  234. }
  235. var setGroupListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  236. smtc.groupIDs = []string{groupid}
  237. smtc.groupAPIResponse.StatusCode = http.StatusUnauthorized
  238. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  239. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  240. }
  241. var setNilMockClient = func(smtc *secretManagerTestCase) {
  242. smtc.mockProjectVarClient = nil
  243. smtc.mockGroupVarClient = nil
  244. smtc.expectError = errUninitializedGitlabProvider
  245. }
  246. func TestNewClient(t *testing.T) {
  247. ctx := context.Background()
  248. const namespace = "namespace"
  249. store := &esv1beta1.SecretStore{
  250. ObjectMeta: metav1.ObjectMeta{
  251. Namespace: namespace,
  252. },
  253. Spec: esv1beta1.SecretStoreSpec{
  254. Provider: &esv1beta1.SecretStoreProvider{
  255. Gitlab: &esv1beta1.GitlabProvider{},
  256. },
  257. },
  258. }
  259. provider, err := esv1beta1.GetProvider(store)
  260. tassert.Nil(t, err)
  261. k8sClient := clientfake.NewClientBuilder().Build()
  262. secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
  263. tassert.EqualError(t, err, errMissingCredentials)
  264. tassert.Nil(t, secretClient)
  265. store.Spec.Provider.Gitlab.Auth = esv1beta1.GitlabAuth{}
  266. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  267. tassert.EqualError(t, err, errMissingCredentials)
  268. tassert.Nil(t, secretClient)
  269. store.Spec.Provider.Gitlab.Auth.SecretRef = esv1beta1.GitlabSecretRef{}
  270. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  271. tassert.EqualError(t, err, errMissingCredentials)
  272. tassert.Nil(t, secretClient)
  273. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{}
  274. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  275. tassert.EqualError(t, err, errMissingCredentials)
  276. tassert.Nil(t, secretClient)
  277. const authorizedKeySecretName = "authorizedKeySecretName"
  278. const authorizedKeySecretKey = "authorizedKeySecretKey"
  279. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Name = authorizedKeySecretName
  280. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Key = authorizedKeySecretKey
  281. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  282. tassert.EqualError(t, err, "couldn't find secret on cluster: secrets \"authorizedKeySecretName\" not found")
  283. tassert.Nil(t, secretClient)
  284. err = createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, newFakeAuthorizedKey()))
  285. tassert.Nil(t, err)
  286. secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
  287. tassert.Nil(t, err)
  288. tassert.NotNil(t, secretClient)
  289. }
  290. func toJSON(t *testing.T, v interface{}) []byte {
  291. jsonBytes, err := json.Marshal(v)
  292. tassert.Nil(t, err)
  293. return jsonBytes
  294. }
  295. func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) error {
  296. err := k8sClient.Create(ctx, &corev1.Secret{
  297. ObjectMeta: metav1.ObjectMeta{
  298. Namespace: namespace,
  299. Name: secretName,
  300. },
  301. Data: map[string][]byte{secretKey: secretValue},
  302. })
  303. tassert.Nil(t, err)
  304. return nil
  305. }
  306. func newFakeAuthorizedKey() *iamkey.Key {
  307. uniqueLabel := uuid.NewString()
  308. return &iamkey.Key{
  309. Id: uniqueLabel,
  310. Subject: &iamkey.Key_ServiceAccountId{
  311. ServiceAccountId: uniqueLabel,
  312. },
  313. PrivateKey: uniqueLabel,
  314. }
  315. }
  316. // test the sm<->gcp interface
  317. // make sure correct values are passed and errors are handled accordingly.
  318. func TestGetSecret(t *testing.T) {
  319. // good case: default version is set
  320. // key is passed in, output is sent back
  321. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  322. smtc.projectAPIOutput.Value = projectvalue
  323. smtc.groupAPIResponse = nil
  324. smtc.groupAPIOutput = nil
  325. smtc.expectedSecret = smtc.projectAPIOutput.Value
  326. }
  327. groupSecretProjectOverride := func(smtc *secretManagerTestCase) {
  328. smtc.projectAPIOutput.Value = projectvalue
  329. smtc.groupAPIOutput.Key = "testkey"
  330. smtc.groupAPIOutput.Value = groupvalue
  331. smtc.expectedSecret = smtc.projectAPIOutput.Value
  332. }
  333. groupWithoutProjectOverride := func(smtc *secretManagerTestCase) {
  334. smtc.groupIDs = []string{groupid}
  335. smtc.projectAPIResponse.Response.StatusCode = 404
  336. smtc.groupAPIOutput.Key = "testkey"
  337. smtc.groupAPIOutput.Value = groupvalue
  338. smtc.expectedSecret = smtc.groupAPIOutput.Value
  339. }
  340. successCases := []*secretManagerTestCase{
  341. makeValidSecretManagerTestCaseCustom(onlyProjectSecret),
  342. makeValidSecretManagerTestCaseCustom(groupSecretProjectOverride),
  343. makeValidSecretManagerTestCaseCustom(groupWithoutProjectOverride),
  344. makeValidSecretManagerTestCaseCustom(setAPIErr),
  345. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  346. }
  347. sm := Gitlab{}
  348. for k, v := range successCases {
  349. sm.projectVariablesClient = v.mockProjectVarClient
  350. sm.groupVariablesClient = v.mockGroupVarClient
  351. sm.projectID = v.projectID
  352. sm.groupIDs = v.groupIDs
  353. out, err := sm.GetSecret(context.Background(), *v.ref)
  354. if !ErrorContains(err, v.expectError) {
  355. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  356. }
  357. if string(out) != v.expectedSecret {
  358. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out), v.expectedSecret)
  359. }
  360. }
  361. }
  362. func TestResolveGroupIds(t *testing.T) {
  363. v := makeValidSecretManagerTestCaseCustom()
  364. sm := Gitlab{}
  365. sm.projectsClient = v.mockProjectsClient
  366. sm.projectID = v.projectID
  367. sm.inheritFromGroups = true
  368. err := sm.ResolveGroupIds()
  369. if err != nil {
  370. t.Errorf(defaultErrorMessage, 0, err.Error(), "")
  371. }
  372. if !reflect.DeepEqual(sm.groupIDs, []string{"1", "10", "100"}) {
  373. t.Errorf("unexpected groupIds: %s, expected %s", sm.groupIDs, []string{"1", "10", "100"})
  374. }
  375. }
  376. func TestGetAllSecrets(t *testing.T) {
  377. // good case: default version is set
  378. // key is passed in, output is sent back
  379. setMissingFindRegex := func(smtc *secretManagerTestCase) {
  380. smtc.refFind.Name = nil
  381. smtc.expectError = "'find.name' is mandatory"
  382. }
  383. setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
  384. path := "path"
  385. smtc.refFind.Path = &path
  386. smtc.expectError = "'find.path' is not implemented in the Gitlab provider"
  387. }
  388. setUnsupportedFindTag := func(smtc *secretManagerTestCase) {
  389. smtc.expectError = "'find.tags' only supports 'environment_scope"
  390. smtc.refFind.Tags = map[string]string{"foo": ""}
  391. }
  392. setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
  393. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  394. Key: "testkey",
  395. Value: projectvalue,
  396. EnvironmentScope: environment,
  397. }
  398. smtc.expectedSecret = projectvalue
  399. smtc.refFind.Name = makeFindName(findTestPrefix)
  400. }
  401. setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
  402. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  403. Key: "testkey",
  404. Value: projectvalue,
  405. EnvironmentScope: "test",
  406. }
  407. smtc.expectedSecret = ""
  408. smtc.refFind.Name = makeFindName("foo.*")
  409. }
  410. setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) {
  411. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  412. Key: "testkey",
  413. Value: projectvalue,
  414. EnvironmentScope: "test",
  415. }
  416. smtc.expectedSecret = ""
  417. smtc.refFind.Name = makeFindName(findTestPrefix)
  418. }
  419. setMatchingSecretFindTags := func(smtc *secretManagerTestCase) {
  420. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  421. Key: "testkey",
  422. Value: projectvalue,
  423. EnvironmentScope: environment,
  424. }
  425. smtc.apiInputEnv = "*"
  426. smtc.expectedSecret = projectvalue
  427. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  428. }
  429. setEnvironmentConstrainedByStore := func(smtc *secretManagerTestCase) {
  430. smtc.expectedSecret = projectvalue
  431. smtc.expectError = "'find.tags' is constrained by 'environment_scope' of the store"
  432. smtc.refFind.Tags = map[string]string{"environment_scope": environment}
  433. }
  434. cases := []*secretManagerTestCase{
  435. makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
  436. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
  437. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTag),
  438. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
  439. makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
  440. makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
  441. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindTags),
  442. makeValidSecretManagerGetAllTestCaseCustom(setEnvironmentConstrainedByStore),
  443. makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
  444. makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
  445. }
  446. sm := Gitlab{}
  447. for k, v := range cases {
  448. sm.environment = v.apiInputEnv
  449. sm.projectVariablesClient = v.mockProjectVarClient
  450. sm.groupVariablesClient = v.mockGroupVarClient
  451. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  452. if !ErrorContains(err, v.expectError) {
  453. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  454. }
  455. if v.expectError == "" && string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  456. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  457. }
  458. }
  459. }
  460. func TestGetAllSecretsWithGroups(t *testing.T) {
  461. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  462. smtc.projectAPIOutput.Value = projectvalue
  463. smtc.refFind.Name = makeFindName(findTestPrefix)
  464. smtc.groupAPIResponse = nil
  465. smtc.groupAPIOutput = nil
  466. smtc.expectedSecret = smtc.projectAPIOutput.Value
  467. }
  468. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  469. smtc.groupIDs = []string{groupid}
  470. smtc.projectAPIOutput.Value = projectvalue
  471. smtc.groupAPIOutput.Value = groupvalue
  472. smtc.expectedData = map[string][]byte{"testKey": []byte(projectvalue), "groupKey": []byte(groupvalue)}
  473. smtc.refFind.Name = makeFindName(".*Key")
  474. }
  475. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  476. smtc.groupIDs = []string{groupid}
  477. smtc.projectAPIOutput.Value = projectvalue
  478. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  479. smtc.groupAPIOutput.Value = groupvalue
  480. smtc.expectedData = map[string][]byte{"testKey": []byte(projectvalue)}
  481. smtc.refFind.Name = makeFindName(".*Key")
  482. }
  483. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  484. smtc.groupIDs = []string{groupid}
  485. smtc.projectAPIOutput.Value = projectvalue
  486. smtc.projectAPIOutput.EnvironmentScope = "test"
  487. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  488. smtc.groupAPIOutput.Value = groupvalue
  489. smtc.expectedData = map[string][]byte{"testKey": []byte(groupvalue)}
  490. smtc.refFind.Name = makeFindName(".*Key")
  491. }
  492. cases := []*secretManagerTestCase{
  493. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  494. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  495. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  496. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  497. }
  498. sm := Gitlab{}
  499. sm.environment = "prod"
  500. for k, v := range cases {
  501. sm.projectVariablesClient = v.mockProjectVarClient
  502. sm.groupVariablesClient = v.mockGroupVarClient
  503. sm.projectID = v.projectID
  504. sm.groupIDs = v.groupIDs
  505. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  506. if !ErrorContains(err, v.expectError) {
  507. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  508. }
  509. if v.expectError == "" {
  510. if len(v.expectedData) > 0 {
  511. if !reflect.DeepEqual(v.expectedData, out) {
  512. t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData)
  513. }
  514. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  515. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  516. }
  517. }
  518. }
  519. }
  520. func TestValidate(t *testing.T) {
  521. successCases := []*secretManagerTestCase{
  522. makeValidSecretManagerTestCaseCustom(),
  523. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  524. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  525. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  526. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  527. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  528. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  529. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  530. }
  531. sm := Gitlab{}
  532. for k, v := range successCases {
  533. sm.projectsClient = v.mockProjectsClient
  534. sm.projectVariablesClient = v.mockProjectVarClient
  535. sm.groupVariablesClient = v.mockGroupVarClient
  536. sm.projectID = v.projectID
  537. sm.groupIDs = v.groupIDs
  538. sm.inheritFromGroups = v.inheritFromGroups
  539. t.Logf("%+v", v)
  540. validationResult, err := sm.Validate()
  541. if !ErrorContains(err, v.expectError) {
  542. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  543. }
  544. if validationResult != v.expectedValidationResult {
  545. t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
  546. }
  547. if sm.inheritFromGroups && sm.groupIDs[0] != "1" {
  548. t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.groupIDs[0])
  549. }
  550. }
  551. }
  552. func TestGetSecretMap(t *testing.T) {
  553. // good case: default version & deserialization
  554. setDeserialization := func(smtc *secretManagerTestCase) {
  555. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  556. smtc.expectedData["foo"] = []byte("bar")
  557. }
  558. // bad case: invalid json
  559. setInvalidJSON := func(smtc *secretManagerTestCase) {
  560. smtc.projectAPIOutput.Value = `-----------------`
  561. smtc.expectError = "unable to unmarshal secret"
  562. }
  563. successCases := []*secretManagerTestCase{
  564. makeValidSecretManagerTestCaseCustom(setDeserialization),
  565. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  566. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  567. makeValidSecretManagerTestCaseCustom(setAPIErr),
  568. }
  569. sm := Gitlab{}
  570. for k, v := range successCases {
  571. sm.projectVariablesClient = v.mockProjectVarClient
  572. sm.groupVariablesClient = v.mockGroupVarClient
  573. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  574. if !ErrorContains(err, v.expectError) {
  575. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  576. }
  577. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  578. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  579. }
  580. }
  581. }
  582. func ErrorContains(out error, want string) bool {
  583. if out == nil {
  584. return want == ""
  585. }
  586. if want == "" {
  587. return false
  588. }
  589. return strings.Contains(out.Error(), want)
  590. }
  591. type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
  592. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
  593. store := &esv1beta1.SecretStore{
  594. Spec: esv1beta1.SecretStoreSpec{
  595. Provider: &esv1beta1.SecretStoreProvider{
  596. Gitlab: &esv1beta1.GitlabProvider{
  597. Auth: esv1beta1.GitlabAuth{},
  598. ProjectID: projectID,
  599. Environment: environment,
  600. },
  601. },
  602. },
  603. }
  604. for _, f := range fn {
  605. store = f(store)
  606. }
  607. return store
  608. }
  609. func withAccessToken(name, key string, namespace *string) storeModifier {
  610. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  611. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  612. Name: name,
  613. Key: key,
  614. Namespace: namespace,
  615. }
  616. return store
  617. }
  618. }
  619. func withGroups(ids []string, inherit bool) storeModifier {
  620. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  621. store.Spec.Provider.Gitlab.GroupIDs = ids
  622. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  623. return store
  624. }
  625. }
  626. type ValidateStoreTestCase struct {
  627. store *esv1beta1.SecretStore
  628. err error
  629. }
  630. func TestValidateStore(t *testing.T) {
  631. namespace := "my-namespace"
  632. testCases := []ValidateStoreTestCase{
  633. {
  634. store: makeSecretStore("", environment),
  635. err: fmt.Errorf("projectID and groupIDs must not both be empty"),
  636. },
  637. {
  638. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  639. err: fmt.Errorf("defining groupIDs and inheritFromGroups = true is not allowed"),
  640. },
  641. {
  642. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  643. err: fmt.Errorf("accessToken.name cannot be empty"),
  644. },
  645. {
  646. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  647. err: fmt.Errorf("accessToken.key cannot be empty"),
  648. },
  649. {
  650. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  651. err: fmt.Errorf("namespace not allowed with namespaced SecretStore"),
  652. },
  653. {
  654. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  655. err: nil,
  656. },
  657. {
  658. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  659. err: nil,
  660. },
  661. }
  662. p := Gitlab{}
  663. for _, tc := range testCases {
  664. err := p.ValidateStore(tc.store)
  665. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  666. t.Errorf("test failed! want %v, got %v", tc.err, err)
  667. } else if tc.err == nil && err != nil {
  668. t.Errorf("want nil got err %v", err)
  669. } else if tc.err != nil && err == nil {
  670. t.Errorf("want err %v got nil", tc.err)
  671. }
  672. }
  673. }