gitlab_test.go 31 KB

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