gitlab_test.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package gitlab
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "reflect"
  20. "strings"
  21. "testing"
  22. "github.com/google/uuid"
  23. tassert "github.com/stretchr/testify/assert"
  24. "github.com/xanzy/go-gitlab"
  25. "github.com/yandex-cloud/go-sdk/iamkey"
  26. corev1 "k8s.io/api/core/v1"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
  29. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  30. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  31. esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1"
  32. prov "github.com/external-secrets/external-secrets/apis/providers/v1alpha1"
  33. fakegitlab "github.com/external-secrets/external-secrets/pkg/provider/gitlab/fake"
  34. )
  35. const (
  36. project = "my-Project"
  37. username = "user-name"
  38. userkey = "user-key"
  39. environment = "prod"
  40. environmentTest = "test"
  41. projectvalue = "projectvalue"
  42. groupvalue = "groupvalue"
  43. groupid = "groupId"
  44. defaultErrorMessage = "[%d] unexpected error: [%s], expected: [%s]"
  45. errMissingCredentials = "cannot get Kubernetes secret \"\": secrets \"\" not found"
  46. testKey = "testKey"
  47. findTestPrefix = "test.*"
  48. )
  49. type secretManagerTestCase struct {
  50. mockProjectsClient *fakegitlab.GitlabMockProjectsClient
  51. mockProjectVarClient *fakegitlab.GitlabMockProjectVariablesClient
  52. mockGroupVarClient *fakegitlab.GitlabMockGroupVariablesClient
  53. apiInputProjectID string
  54. apiInputKey string
  55. apiInputEnv string
  56. projectAPIOutput *gitlab.ProjectVariable
  57. projectAPIResponse *gitlab.Response
  58. projectAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.ProjectVariable]
  59. projectGroupsAPIOutput []*gitlab.ProjectGroup
  60. projectGroupsAPIResponse *gitlab.Response
  61. groupAPIOutputs []*fakegitlab.APIResponse[[]*gitlab.GroupVariable]
  62. groupAPIOutput *gitlab.GroupVariable
  63. groupAPIResponse *gitlab.Response
  64. ref *esv1beta1.ExternalSecretDataRemoteRef
  65. refFind *esv1beta1.ExternalSecretFind
  66. projectID string
  67. groupIDs []string
  68. inheritFromGroups bool
  69. apiErr error
  70. expectError string
  71. expectedSecret string
  72. expectedValidationResult esv1beta1.ValidationResult
  73. // for testing secretmap
  74. expectedData map[string][]byte
  75. }
  76. func makeValidSecretManagerTestCase() *secretManagerTestCase {
  77. smtc := secretManagerTestCase{
  78. mockProjectsClient: &fakegitlab.GitlabMockProjectsClient{},
  79. mockProjectVarClient: &fakegitlab.GitlabMockProjectVariablesClient{},
  80. mockGroupVarClient: &fakegitlab.GitlabMockGroupVariablesClient{},
  81. apiInputProjectID: makeValidAPIInputProjectID(),
  82. apiInputKey: makeValidAPIInputKey(),
  83. apiInputEnv: makeValidEnvironment(),
  84. ref: makeValidRef(),
  85. refFind: makeValidFindRef(),
  86. projectID: makeValidProjectID(),
  87. groupIDs: makeEmptyGroupIds(),
  88. projectAPIOutput: makeValidProjectAPIOutput(),
  89. projectAPIResponse: makeValidProjectAPIResponse(),
  90. projectGroupsAPIOutput: makeValidProjectGroupsAPIOutput(),
  91. projectGroupsAPIResponse: makeValidProjectGroupsAPIResponse(),
  92. groupAPIOutput: makeValidGroupAPIOutput(),
  93. groupAPIResponse: makeValidGroupAPIResponse(),
  94. apiErr: nil,
  95. expectError: "",
  96. expectedSecret: "",
  97. expectedValidationResult: esv1beta1.ValidationResultReady,
  98. expectedData: map[string][]byte{},
  99. }
  100. prepareMockProjectVarClient(&smtc)
  101. prepareMockGroupVarClient(&smtc)
  102. return &smtc
  103. }
  104. func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
  105. return &esv1beta1.ExternalSecretDataRemoteRef{
  106. Key: testKey,
  107. Version: "default",
  108. }
  109. }
  110. func makeValidFindRef() *esv1beta1.ExternalSecretFind {
  111. return &esv1beta1.ExternalSecretFind{}
  112. }
  113. func makeValidProjectID() string {
  114. return "projectId"
  115. }
  116. func makeEmptyGroupIds() []string {
  117. return []string{}
  118. }
  119. func makeFindName(regexp string) *esv1beta1.FindName {
  120. return &esv1beta1.FindName{
  121. RegExp: regexp,
  122. }
  123. }
  124. func makeValidAPIInputProjectID() string {
  125. return "testID"
  126. }
  127. func makeValidAPIInputKey() string {
  128. return testKey
  129. }
  130. func makeValidEnvironment() string {
  131. return environment
  132. }
  133. func makeValidProjectAPIResponse() *gitlab.Response {
  134. return &gitlab.Response{
  135. Response: &http.Response{
  136. StatusCode: http.StatusOK,
  137. },
  138. CurrentPage: 1,
  139. TotalPages: 1,
  140. }
  141. }
  142. func makeValidProjectGroupsAPIResponse() *gitlab.Response {
  143. return &gitlab.Response{
  144. Response: &http.Response{
  145. StatusCode: http.StatusOK,
  146. },
  147. CurrentPage: 1,
  148. TotalPages: 1,
  149. }
  150. }
  151. func makeValidGroupAPIResponse() *gitlab.Response {
  152. return &gitlab.Response{
  153. Response: &http.Response{
  154. StatusCode: http.StatusOK,
  155. },
  156. CurrentPage: 1,
  157. TotalPages: 1,
  158. }
  159. }
  160. func makeValidProjectAPIOutput() *gitlab.ProjectVariable {
  161. return &gitlab.ProjectVariable{
  162. Key: testKey,
  163. Value: "",
  164. EnvironmentScope: environment,
  165. }
  166. }
  167. func makeValidProjectGroupsAPIOutput() []*gitlab.ProjectGroup {
  168. return []*gitlab.ProjectGroup{{
  169. ID: 1,
  170. Name: "Group (1)",
  171. FullPath: "foo",
  172. }, {
  173. ID: 100,
  174. Name: "Group (100)",
  175. FullPath: "foo/bar/baz",
  176. }, {
  177. ID: 10,
  178. Name: "Group (10)",
  179. FullPath: "foo/bar",
  180. }}
  181. }
  182. func makeValidGroupAPIOutput() *gitlab.GroupVariable {
  183. return &gitlab.GroupVariable{
  184. Key: "groupKey",
  185. Value: "",
  186. EnvironmentScope: environment,
  187. }
  188. }
  189. func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  190. smtc := makeValidSecretManagerTestCase()
  191. for _, fn := range tweaks {
  192. fn(smtc)
  193. }
  194. smtc.mockProjectsClient.WithValue(smtc.projectGroupsAPIOutput, smtc.projectGroupsAPIResponse, smtc.apiErr)
  195. prepareMockProjectVarClient(smtc)
  196. prepareMockGroupVarClient(smtc)
  197. return smtc
  198. }
  199. func makeValidSecretManagerGetAllTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  200. smtc := makeValidSecretManagerTestCase()
  201. smtc.ref = nil
  202. smtc.refFind.Name = makeFindName(".*")
  203. for _, fn := range tweaks {
  204. fn(smtc)
  205. }
  206. prepareMockProjectVarClient(smtc)
  207. prepareMockGroupVarClient(smtc)
  208. return smtc
  209. }
  210. func prepareMockProjectVarClient(smtc *secretManagerTestCase) {
  211. responses := make([]fakegitlab.APIResponse[[]*gitlab.ProjectVariable], 0)
  212. if smtc.projectAPIOutput != nil {
  213. responses = append(responses, fakegitlab.APIResponse[[]*gitlab.ProjectVariable]{Output: []*gitlab.ProjectVariable{smtc.projectAPIOutput}, Response: smtc.projectAPIResponse, Error: smtc.apiErr})
  214. }
  215. for _, response := range smtc.projectAPIOutputs {
  216. responses = append(responses, *response)
  217. }
  218. smtc.mockProjectVarClient.WithValues(responses)
  219. }
  220. func prepareMockGroupVarClient(smtc *secretManagerTestCase) {
  221. responses := make([]fakegitlab.APIResponse[[]*gitlab.GroupVariable], 0)
  222. if smtc.projectAPIOutput != nil {
  223. responses = append(responses, fakegitlab.APIResponse[[]*gitlab.GroupVariable]{Output: []*gitlab.GroupVariable{smtc.groupAPIOutput}, Response: smtc.groupAPIResponse, Error: smtc.apiErr})
  224. }
  225. for _, response := range smtc.groupAPIOutputs {
  226. responses = append(responses, *response)
  227. }
  228. smtc.mockGroupVarClient.WithValues(responses)
  229. }
  230. // This case can be shared by both GetSecret and GetSecretMap tests.
  231. // bad case: set apiErr.
  232. var setAPIErr = func(smtc *secretManagerTestCase) {
  233. smtc.apiErr = errors.New("oh no")
  234. smtc.expectError = "oh no"
  235. smtc.projectAPIResponse.Response.StatusCode = http.StatusInternalServerError
  236. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  237. }
  238. var setListAPIErr = func(smtc *secretManagerTestCase) {
  239. err := errors.New("oh no")
  240. smtc.apiErr = err
  241. smtc.expectError = fmt.Errorf(errList, err).Error()
  242. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  243. }
  244. var setProjectListAPIRespNil = func(smtc *secretManagerTestCase) {
  245. smtc.projectAPIResponse = nil
  246. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  247. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  248. }
  249. var setGroupListAPIRespNil = func(smtc *secretManagerTestCase) {
  250. smtc.groupIDs = []string{groupid}
  251. smtc.groupAPIResponse = nil
  252. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  253. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  254. }
  255. var setProjectAndGroup = func(smtc *secretManagerTestCase) {
  256. smtc.groupIDs = []string{groupid}
  257. }
  258. var setProjectAndInheritFromGroups = func(smtc *secretManagerTestCase) {
  259. smtc.groupIDs = nil
  260. smtc.inheritFromGroups = true
  261. }
  262. var setProjectListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  263. smtc.projectAPIResponse.StatusCode = http.StatusUnauthorized
  264. smtc.expectError = fmt.Errorf(errProjectAuth, smtc.projectID).Error()
  265. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  266. }
  267. var setGroupListAPIRespBadCode = func(smtc *secretManagerTestCase) {
  268. smtc.groupIDs = []string{groupid}
  269. smtc.groupAPIResponse.StatusCode = http.StatusUnauthorized
  270. smtc.expectError = fmt.Errorf(errGroupAuth, groupid).Error()
  271. smtc.expectedValidationResult = esv1beta1.ValidationResultError
  272. }
  273. var setNilMockClient = func(smtc *secretManagerTestCase) {
  274. smtc.mockProjectVarClient = nil
  275. smtc.mockGroupVarClient = nil
  276. smtc.expectError = errUninitializedGitlabProvider
  277. }
  278. func TestNewClient(t *testing.T) {
  279. ctx := context.Background()
  280. const namespace = "namespace"
  281. store := &prov.Gitlab{
  282. ObjectMeta: metav1.ObjectMeta{
  283. Namespace: namespace,
  284. },
  285. Spec: prov.GitlabSpec{},
  286. }
  287. provider, ok := esv1beta1.GetProviderByName("gitlab")
  288. tassert.True(t, ok)
  289. k8sClient := clientfake.NewClientBuilder().Build()
  290. secretClient, err := provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  291. tassert.EqualError(t, err, errMissingCredentials)
  292. tassert.Nil(t, secretClient)
  293. store.Spec.Auth = prov.GitlabAuth{}
  294. secretClient, err = provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  295. tassert.EqualError(t, err, errMissingCredentials)
  296. tassert.Nil(t, secretClient)
  297. store.Spec.Auth.SecretRef = prov.GitlabSecretRef{}
  298. secretClient, err = provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  299. tassert.EqualError(t, err, errMissingCredentials)
  300. tassert.Nil(t, secretClient)
  301. store.Spec.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{}
  302. secretClient, err = provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  303. tassert.EqualError(t, err, errMissingCredentials)
  304. tassert.Nil(t, secretClient)
  305. const authorizedKeySecretName = "authorizedKeySecretName"
  306. const authorizedKeySecretKey = "authorizedKeySecretKey"
  307. store.Spec.Auth.SecretRef.AccessToken.Name = authorizedKeySecretName
  308. store.Spec.Auth.SecretRef.AccessToken.Key = authorizedKeySecretKey
  309. secretClient, err = provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  310. tassert.EqualError(t, err, "cannot get Kubernetes secret \"authorizedKeySecretName\": secrets \"authorizedKeySecretName\" not found")
  311. tassert.Nil(t, secretClient)
  312. err = createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, newFakeAuthorizedKey()))
  313. tassert.Nil(t, err)
  314. secretClient, err = provider.NewClientFromObj(context.Background(), store, k8sClient, namespace)
  315. tassert.Nil(t, err)
  316. tassert.NotNil(t, secretClient)
  317. }
  318. func toJSON(t *testing.T, v any) []byte {
  319. jsonBytes, err := json.Marshal(v)
  320. tassert.Nil(t, err)
  321. return jsonBytes
  322. }
  323. func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) error {
  324. err := k8sClient.Create(ctx, &corev1.Secret{
  325. ObjectMeta: metav1.ObjectMeta{
  326. Namespace: namespace,
  327. Name: secretName,
  328. },
  329. Data: map[string][]byte{secretKey: secretValue},
  330. })
  331. tassert.Nil(t, err)
  332. return nil
  333. }
  334. func newFakeAuthorizedKey() *iamkey.Key {
  335. uniqueLabel := uuid.NewString()
  336. return &iamkey.Key{
  337. Id: uniqueLabel,
  338. Subject: &iamkey.Key_ServiceAccountId{
  339. ServiceAccountId: uniqueLabel,
  340. },
  341. PrivateKey: uniqueLabel,
  342. }
  343. }
  344. // test the sm<->gcp interface
  345. // make sure correct values are passed and errors are handled accordingly.
  346. func TestGetSecret(t *testing.T) {
  347. // good case: default version is set
  348. // key is passed in, output is sent back
  349. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  350. smtc.projectAPIOutput.Value = projectvalue
  351. smtc.groupAPIResponse = nil
  352. smtc.groupAPIOutput = nil
  353. smtc.expectedSecret = smtc.projectAPIOutput.Value
  354. }
  355. onlyWildcardSecret := func(smtc *secretManagerTestCase) {
  356. smtc.projectAPIOutput.Value = ""
  357. smtc.projectAPIResponse.Response.StatusCode = 404
  358. smtc.groupAPIResponse = nil
  359. smtc.groupAPIOutput = nil
  360. smtc.expectedSecret = smtc.projectAPIOutput.Value
  361. }
  362. groupSecretProjectOverride := func(smtc *secretManagerTestCase) {
  363. smtc.projectAPIOutput.Value = projectvalue
  364. smtc.groupAPIOutput.Key = testKey
  365. smtc.groupAPIOutput.Value = groupvalue
  366. smtc.expectedSecret = smtc.projectAPIOutput.Value
  367. }
  368. groupWithoutProjectOverride := func(smtc *secretManagerTestCase) {
  369. smtc.groupIDs = []string{groupid}
  370. smtc.projectAPIResponse.Response.StatusCode = 404
  371. smtc.groupAPIOutput.Key = testKey
  372. smtc.groupAPIOutput.Value = groupvalue
  373. smtc.expectedSecret = smtc.groupAPIOutput.Value
  374. }
  375. successCases := []*secretManagerTestCase{
  376. makeValidSecretManagerTestCaseCustom(onlyProjectSecret),
  377. makeValidSecretManagerTestCaseCustom(onlyWildcardSecret),
  378. makeValidSecretManagerTestCaseCustom(groupSecretProjectOverride),
  379. makeValidSecretManagerTestCaseCustom(groupWithoutProjectOverride),
  380. makeValidSecretManagerTestCaseCustom(setAPIErr),
  381. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  382. }
  383. sm := gitlabBase{}
  384. sm.store = &prov.GitlabSpec{}
  385. for k, v := range successCases {
  386. sm.projectVariablesClient = v.mockProjectVarClient
  387. sm.groupVariablesClient = v.mockGroupVarClient
  388. sm.store.ProjectID = v.projectID
  389. sm.store.GroupIDs = v.groupIDs
  390. sm.store.Environment = v.apiInputEnv
  391. out, err := sm.GetSecret(context.Background(), *v.ref)
  392. if !ErrorContains(err, v.expectError) {
  393. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  394. }
  395. if string(out) != v.expectedSecret {
  396. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out), v.expectedSecret)
  397. }
  398. }
  399. }
  400. func TestResolveGroupIds(t *testing.T) {
  401. v := makeValidSecretManagerTestCaseCustom()
  402. sm := gitlabBase{}
  403. sm.store = &prov.GitlabSpec{}
  404. sm.projectsClient = v.mockProjectsClient
  405. sm.store.ProjectID = v.projectID
  406. sm.store.InheritFromGroups = true
  407. err := sm.ResolveGroupIds()
  408. if err != nil {
  409. t.Errorf(defaultErrorMessage, 0, err.Error(), "")
  410. }
  411. if !reflect.DeepEqual(sm.store.GroupIDs, []string{"1", "10", "100"}) {
  412. t.Errorf("unexpected groupIds: %s, expected %s", sm.store.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 := gitlabBase{}
  591. sm.store = &prov.GitlabSpec{}
  592. for k, v := range cases {
  593. sm.projectVariablesClient = v.mockProjectVarClient
  594. sm.groupVariablesClient = v.mockGroupVarClient
  595. sm.store.Environment = v.apiInputEnv
  596. sm.store.GroupIDs = v.groupIDs
  597. if v.expectedSecret != "" {
  598. v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)}
  599. }
  600. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  601. if !ErrorContains(err, v.expectError) {
  602. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  603. }
  604. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  605. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  606. }
  607. }
  608. }
  609. func TestGetAllSecretsWithGroups(t *testing.T) {
  610. onlyProjectSecret := func(smtc *secretManagerTestCase) {
  611. smtc.projectAPIOutput.Value = projectvalue
  612. smtc.refFind.Name = makeFindName(findTestPrefix)
  613. smtc.groupAPIResponse = nil
  614. smtc.groupAPIOutput = nil
  615. smtc.expectedSecret = smtc.projectAPIOutput.Value
  616. }
  617. groupAndProjectSecrets := func(smtc *secretManagerTestCase) {
  618. smtc.groupIDs = []string{groupid}
  619. smtc.projectAPIOutput.Value = projectvalue
  620. smtc.groupAPIOutput.Value = groupvalue
  621. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue), "groupKey": []byte(groupvalue)}
  622. smtc.refFind.Name = makeFindName(".*Key")
  623. }
  624. groupAndOverrideProjectSecrets := func(smtc *secretManagerTestCase) {
  625. smtc.groupIDs = []string{groupid}
  626. smtc.projectAPIOutput.Value = projectvalue
  627. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  628. smtc.groupAPIOutput.Value = groupvalue
  629. smtc.expectedData = map[string][]byte{testKey: []byte(projectvalue)}
  630. smtc.refFind.Name = makeFindName(".*Key")
  631. }
  632. groupAndProjectWithDifferentEnvSecrets := func(smtc *secretManagerTestCase) {
  633. smtc.groupIDs = []string{groupid}
  634. smtc.projectAPIOutput.Value = projectvalue
  635. smtc.projectAPIOutput.EnvironmentScope = environmentTest
  636. smtc.groupAPIOutput.Key = smtc.projectAPIOutput.Key
  637. smtc.groupAPIOutput.Value = groupvalue
  638. smtc.expectedData = map[string][]byte{testKey: []byte(groupvalue)}
  639. smtc.refFind.Name = makeFindName(".*Key")
  640. }
  641. cases := []*secretManagerTestCase{
  642. makeValidSecretManagerGetAllTestCaseCustom(onlyProjectSecret),
  643. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectSecrets),
  644. makeValidSecretManagerGetAllTestCaseCustom(groupAndOverrideProjectSecrets),
  645. makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
  646. }
  647. sm := gitlabBase{}
  648. sm.store = &prov.GitlabSpec{}
  649. sm.store.Environment = environment
  650. for k, v := range cases {
  651. sm.projectVariablesClient = v.mockProjectVarClient
  652. sm.groupVariablesClient = v.mockGroupVarClient
  653. sm.store.ProjectID = v.projectID
  654. sm.store.GroupIDs = v.groupIDs
  655. out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
  656. if !ErrorContains(err, v.expectError) {
  657. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  658. }
  659. if v.expectError == "" {
  660. if len(v.expectedData) > 0 {
  661. if !reflect.DeepEqual(v.expectedData, out) {
  662. t.Errorf("[%d] unexpected secrets: [%s], expected [%s]", k, out, v.expectedData)
  663. }
  664. } else if string(out[v.projectAPIOutput.Key]) != v.expectedSecret {
  665. t.Errorf("[%d] unexpected secret: [%s], expected [%s]", k, string(out[v.projectAPIOutput.Key]), v.expectedSecret)
  666. }
  667. }
  668. }
  669. }
  670. func TestValidate(t *testing.T) {
  671. successCases := []*secretManagerTestCase{
  672. makeValidSecretManagerTestCaseCustom(),
  673. makeValidSecretManagerTestCaseCustom(setProjectAndInheritFromGroups),
  674. makeValidSecretManagerTestCaseCustom(setProjectAndGroup),
  675. makeValidSecretManagerTestCaseCustom(setListAPIErr),
  676. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespNil),
  677. makeValidSecretManagerTestCaseCustom(setProjectListAPIRespBadCode),
  678. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
  679. makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
  680. }
  681. sm := gitlabBase{}
  682. sm.store = &prov.GitlabSpec{}
  683. for k, v := range successCases {
  684. sm.projectsClient = v.mockProjectsClient
  685. sm.projectVariablesClient = v.mockProjectVarClient
  686. sm.groupVariablesClient = v.mockGroupVarClient
  687. sm.store.ProjectID = v.projectID
  688. sm.store.GroupIDs = v.groupIDs
  689. sm.store.InheritFromGroups = v.inheritFromGroups
  690. t.Logf("%+v", v)
  691. validationResult, err := sm.Validate()
  692. if !ErrorContains(err, v.expectError) {
  693. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  694. }
  695. if validationResult != v.expectedValidationResult {
  696. t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
  697. }
  698. if sm.store.InheritFromGroups && sm.store.GroupIDs[0] != "1" {
  699. t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.store.GroupIDs[0])
  700. }
  701. }
  702. }
  703. func TestGetSecretMap(t *testing.T) {
  704. // good case: default version & deserialization
  705. setDeserialization := func(smtc *secretManagerTestCase) {
  706. smtc.projectAPIOutput.Value = `{"foo":"bar"}`
  707. smtc.expectedData["foo"] = []byte("bar")
  708. }
  709. // bad case: invalid json
  710. setInvalidJSON := func(smtc *secretManagerTestCase) {
  711. smtc.projectAPIOutput.Value = `-----------------`
  712. smtc.expectError = "unable to unmarshal secret"
  713. }
  714. successCases := []*secretManagerTestCase{
  715. makeValidSecretManagerTestCaseCustom(setDeserialization),
  716. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  717. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  718. makeValidSecretManagerTestCaseCustom(setAPIErr),
  719. }
  720. sm := gitlabBase{}
  721. sm.store = &prov.GitlabSpec{}
  722. for k, v := range successCases {
  723. sm.projectVariablesClient = v.mockProjectVarClient
  724. sm.groupVariablesClient = v.mockGroupVarClient
  725. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  726. if !ErrorContains(err, v.expectError) {
  727. t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
  728. }
  729. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  730. t.Errorf("[%d] unexpected secret data: [%#v], expected [%#v]", k, out, v.expectedData)
  731. }
  732. }
  733. }
  734. func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
  735. store := &esv1beta1.SecretStore{
  736. Spec: esv1beta1.SecretStoreSpec{
  737. Provider: &esv1beta1.SecretStoreProvider{
  738. Gitlab: &esv1beta1.GitlabProvider{
  739. Auth: esv1beta1.GitlabAuth{},
  740. ProjectID: projectID,
  741. Environment: environment,
  742. },
  743. },
  744. },
  745. }
  746. for _, f := range fn {
  747. store = f(store)
  748. }
  749. return store
  750. }
  751. func withAccessToken(name, key string, namespace *string) storeModifier {
  752. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  753. store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
  754. Name: name,
  755. Key: key,
  756. Namespace: namespace,
  757. }
  758. return store
  759. }
  760. }
  761. func withGroups(ids []string, inherit bool) storeModifier {
  762. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  763. store.Spec.Provider.Gitlab.GroupIDs = ids
  764. store.Spec.Provider.Gitlab.InheritFromGroups = inherit
  765. return store
  766. }
  767. }
  768. type ValidateStoreTestCase struct {
  769. store *esv1beta1.SecretStore
  770. err error
  771. }
  772. func TestValidateStore(t *testing.T) {
  773. namespace := "my-namespace"
  774. testCases := []ValidateStoreTestCase{
  775. {
  776. store: makeSecretStore("", environment),
  777. err: errors.New("projectID and groupIDs must not both be empty"),
  778. },
  779. {
  780. store: makeSecretStore(project, environment, withGroups([]string{"group1"}, true)),
  781. err: errors.New("defining groupIDs and inheritFromGroups = true is not allowed"),
  782. },
  783. {
  784. store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
  785. err: errors.New("accessToken.name cannot be empty"),
  786. },
  787. {
  788. store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
  789. err: errors.New("accessToken.key cannot be empty"),
  790. },
  791. {
  792. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
  793. err: errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  794. },
  795. {
  796. store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
  797. err: nil,
  798. },
  799. {
  800. store: makeSecretStore("", environment, withGroups([]string{"group1"}, false), withAccessToken("userName", "userKey", nil)),
  801. err: nil,
  802. },
  803. }
  804. p := Provider{}
  805. for _, tc := range testCases {
  806. _, err := p.ValidateStore(tc.store)
  807. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  808. t.Errorf("test failed! want %v, got %v", tc.err, err)
  809. } else if tc.err == nil && err != nil {
  810. t.Errorf("want nil got err %v", err)
  811. } else if tc.err != nil && err == nil {
  812. t.Errorf("want err %v got nil", tc.err)
  813. }
  814. }
  815. }
  816. func ErrorContains(out error, want string) bool {
  817. if out == nil {
  818. return want == ""
  819. }
  820. if want == "" {
  821. return false
  822. }
  823. return strings.Contains(out.Error(), want)
  824. }
  825. type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore