gitlab_test.go 31 KB

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