gitlab_test.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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: expected %s, got %s", k, v.expectedSecret, string(out))
  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("Expected groupIds %s, got %s", []string{"1", "10", "100"}, sm.groupIDs)
  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. setUnsupportedFindTags := func(smtc *secretManagerTestCase) {
  384. smtc.refFind.Tags = map[string]string{}
  385. smtc.expectError = "'find.tags' is not currently supported by Gitlab provider"
  386. }
  387. setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
  388. path := "path"
  389. smtc.refFind.Path = &path
  390. smtc.expectError = "'find.path' is not implemented in the Gitlab provider"
  391. }
  392. setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
  393. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  394. Key: "testkey",
  395. Value: "changedvalue",
  396. EnvironmentScope: "test",
  397. }
  398. smtc.expectedSecret = "changedvalue"
  399. smtc.refFind.Name = makeFindName(findTestPrefix)
  400. }
  401. setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
  402. smtc.projectAPIOutput = &gitlab.ProjectVariable{
  403. Key: "testkey",
  404. Value: "changedvalue",
  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: "changedvalue",
  414. EnvironmentScope: "prod",
  415. }
  416. smtc.expectedSecret = ""
  417. smtc.refFind.Name = makeFindName(findTestPrefix)
  418. }
  419. cases := []*secretManagerTestCase{
  420. makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
  421. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTags),
  422. makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
  423. makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
  424. makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
  425. makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
  426. makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
  427. makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
  428. }
  429. sm := Gitlab{}
  430. sm.environment = "test"
  431. for k, v := range cases {
  432. sm.projectVariablesClient = v.mockProjectVarClient
  433. sm.groupVariablesClient = v.mockGroupVarClient
  434. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  435. if !ErrorContains(err, v.expectError) {
  436. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  437. }
  438. if v.expectError == "" && string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  439. t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out[v.projectAPIOutput.Key]))
  440. }
  441. }
  442. }
  443. func TestGetAllSecretsWithGroups(t *testing.T) {
  444. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  445. smtc.projectAPIOutput.Value = projectvalue
  446. smtc.refFind.Name = makeFindName(findTestPrefix)
  447. smtc.groupAPIResponse = nil
  448. smtc.groupAPIOutput = nil
  449. smtc.expectedSecret = smtc.projectAPIOutput.Value
  450. }
  451. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  452. smtc.groupIDs = []string{groupid}
  453. smtc.projectAPIOutput.Value = projectvalue
  454. smtc.groupAPIOutput.Value = groupvalue
  455. smtc.expectedData = map[string][]byte{"testKey": []byte(projectvalue), "groupKey": []byte(groupvalue)}
  456. smtc.refFind.Name = makeFindName(".*Key")
  457. }
  458. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  459. smtc.groupIDs = []string{groupid}
  460. smtc.projectAPIOutput.Value = projectvalue
  461. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  462. smtc.groupAPIOutput.Value = groupvalue
  463. smtc.expectedData = map[string][]byte{"testKey": []byte(projectvalue)}
  464. smtc.refFind.Name = makeFindName(".*Key")
  465. }
  466. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  467. smtc.groupIDs = []string{groupid}
  468. smtc.projectAPIOutput.Value = projectvalue
  469. smtc.projectAPIOutput.EnvironmentScope = "test"
  470. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  471. smtc.groupAPIOutput.Value = groupvalue
  472. smtc.expectedData = map[string][]byte{"testKey": []byte(groupvalue)}
  473. smtc.refFind.Name = makeFindName(".*Key")
  474. }
  475. cases := []*secretManagerTestCase{
  476. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  477. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  478. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  479. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  480. }
  481. sm := Gitlab{}
  482. sm.environment = "prod"
  483. for k, v := range cases {
  484. sm.projectVariablesClient = v.mockProjectVarClient
  485. sm.groupVariablesClient = v.mockGroupVarClient
  486. sm.projectID = v.projectID
  487. sm.groupIDs = v.groupIDs
  488. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  489. if !ErrorContains(err, v.expectError) {
  490. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  491. }
  492. if v.expectError == "" {
  493. if len(v.expectedData) > 0 {
  494. if !reflect.DeepEqual(v.expectedData, out) {
  495. t.Errorf("[%d] Unexpected secrets. Expected [%s], got [%s]", k, v.expectedData, out)
  496. }
  497. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  498. t.Errorf("[%d] Unexpected secret. Expected [%s], got [%s]", k, v.expectedSecret, string(out[v.projectAPIOutput.Key]))
  499. }
  500. }
  501. }
  502. }
  503. func TestValidate(t *testing.T) {
  504. successCases := []*secretManagerTestCase{
  505. makeValidSecretManagerTestCaseCustom(),
  506. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  507. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  508. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  509. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  510. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  511. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  512. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  513. }
  514. sm := Gitlab{}
  515. for k, v := range successCases {
  516. sm.projectsClient = v.mockProjectsClient
  517. sm.projectVariablesClient = v.mockProjectVarClient
  518. sm.groupVariablesClient = v.mockGroupVarClient
  519. sm.projectID = v.projectID
  520. sm.groupIDs = v.groupIDs
  521. sm.inheritFromGroups = v.inheritFromGroups
  522. t.Logf("%+v", v)
  523. validationResult, err := sm.Validate()
  524. if !ErrorContains(err, v.expectError) {
  525. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  526. }
  527. if validationResult != v.expectedValidationResult {
  528. t.Errorf("[%d], unexpected validationResult: %s, expected: '%s'", k, validationResult, v.expectedValidationResult)
  529. }
  530. if sm.inheritFromGroups && sm.groupIDs[0] != "1" {
  531. t.Errorf("[%d], Expected groupID '1'", k)
  532. }
  533. }
  534. }
  535. func TestGetSecretMap(t *testing.T) {
  536. // good case: default version & deserialization
  537. setDeserialization := func(smtc *secretManagerTestCase) {
  538. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  539. smtc.expectedData["foo"] = []byte("bar")
  540. }
  541. // bad case: invalid json
  542. setInvalidJSON := func(smtc *secretManagerTestCase) {
  543. smtc.projectAPIOutput.Value = `-----------------`
  544. smtc.expectError = "unable to unmarshal secret"
  545. }
  546. successCases := []*secretManagerTestCase{
  547. makeValidSecretManagerTestCaseCustom(setDeserialization),
  548. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  549. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  550. makeValidSecretManagerTestCaseCustom(setAPIErr),
  551. }
  552. sm := Gitlab{}
  553. for k, v := range successCases {
  554. sm.projectVariablesClient = v.mockProjectVarClient
  555. sm.groupVariablesClient = v.mockGroupVarClient
  556. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  557. if !ErrorContains(err, v.expectError) {
  558. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  559. }
  560. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  561. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
  562. }
  563. }
  564. }
  565. func ErrorContains(out error, want string) bool {
  566. if out == nil {
  567. return want == ""
  568. }
  569. if want == "" {
  570. return false
  571. }
  572. return strings.Contains(out.Error(), want)
  573. }
  574. type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
  575. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
  576. store := &esv1beta1.SecretStore{
  577. Spec: esv1beta1.SecretStoreSpec{
  578. Provider: &esv1beta1.SecretStoreProvider{
  579. Gitlab: &esv1beta1.GitlabProvider{
  580. Auth: esv1beta1.GitlabAuth{},
  581. ProjectID: projectID,
  582. Environment: environment,
  583. },
  584. },
  585. },
  586. }
  587. for _, f := range fn {
  588. store = f(store)
  589. }
  590. return store
  591. }
  592. func withAccessToken(name, key string, namespace *string) storeModifier {
  593. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  594. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  595. Name: name,
  596. Key: key,
  597. Namespace: namespace,
  598. }
  599. return store
  600. }
  601. }
  602. func withGroups(ids []string, inherit bool) storeModifier {
  603. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  604. store.Spec.Provider.Gitlab.GroupIDs = ids
  605. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  606. return store
  607. }
  608. }
  609. type ValidateStoreTestCase struct {
  610. store *esv1beta1.SecretStore
  611. err error
  612. }
  613. func TestValidateStore(t *testing.T) {
  614. namespace := "my-namespace"
  615. testCases := []ValidateStoreTestCase{
  616. {
  617. store: makeSecretStore("", environment),
  618. err: fmt.Errorf("projectID and groupIDs must not both be empty"),
  619. },
  620. {
  621. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  622. err: fmt.Errorf("defining groupIDs and inheritFromGroups = true is not allowed"),
  623. },
  624. {
  625. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  626. err: fmt.Errorf("accessToken.name cannot be empty"),
  627. },
  628. {
  629. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  630. err: fmt.Errorf("accessToken.key cannot be empty"),
  631. },
  632. {
  633. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  634. err: fmt.Errorf("namespace not allowed with namespaced SecretStore"),
  635. },
  636. {
  637. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  638. err: nil,
  639. },
  640. {
  641. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  642. err: nil,
  643. },
  644. }
  645. p := Gitlab{}
  646. for _, tc := range testCases {
  647. err := p.ValidateStore(tc.store)
  648. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  649. t.Errorf("test failed! want %v, got %v", tc.err, err)
  650. } else if tc.err == nil && err != nil {
  651. t.Errorf("want nil got err %v", err)
  652. } else if tc.err != nil && err == nil {
  653. t.Errorf("want err %v got nil", tc.err)
  654. }
  655. }
  656. }