chef_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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 chef
  13. import (
  14. "bytes"
  15. "context"
  16. "errors"
  17. "fmt"
  18. "strings"
  19. "testing"
  20. "time"
  21. "github.com/go-chef/chef"
  22. corev1 "k8s.io/api/core/v1"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  25. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  26. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. fake "github.com/external-secrets/external-secrets/pkg/provider/chef/fake"
  28. "github.com/external-secrets/external-secrets/pkg/utils"
  29. )
  30. const (
  31. name = "chef-demo-user"
  32. baseURL = "https://chef.cloudant.com/organizations/myorg/"
  33. noEndSlashInvalidBaseURL = "no end slash invalid base URL"
  34. baseInvalidURL = "invalid base URL/"
  35. authName = "chef-demo-auth-name"
  36. authKey = "chef-demo-auth-key"
  37. authNamespace = "chef-demo-auth-namespace"
  38. kind = "SecretStore"
  39. apiversion = "external-secrets.io/v1beta1"
  40. databagName = "databag01"
  41. )
  42. type chefTestCase struct {
  43. mockClient *fake.ChefMockClient
  44. databagName string
  45. databagItemName string
  46. property string
  47. ref *esv1beta1.ExternalSecretDataRemoteRef
  48. apiErr error
  49. expectError string
  50. expectedData map[string][]byte
  51. expectedByte []byte
  52. }
  53. type ValidateStoreTestCase struct {
  54. store *esv1beta1.SecretStore
  55. err error
  56. }
  57. // type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
  58. func makeValidChefTestCase() *chefTestCase {
  59. smtc := chefTestCase{
  60. mockClient: &fake.ChefMockClient{},
  61. databagName: "databag01",
  62. databagItemName: "item01",
  63. property: "",
  64. apiErr: nil,
  65. expectError: "",
  66. expectedData: map[string][]byte{"item01": []byte(`"https://chef.com/organizations/dev/data/databag01/item01"`)},
  67. expectedByte: []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`),
  68. }
  69. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
  70. smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
  71. smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
  72. return &smtc
  73. }
  74. func makeInValidChefTestCase() *chefTestCase {
  75. smtc := chefTestCase{
  76. mockClient: &fake.ChefMockClient{},
  77. databagName: "databag01",
  78. databagItemName: "item03",
  79. property: "",
  80. apiErr: errors.New("unable to convert databagItem into JSON"),
  81. expectError: "unable to convert databagItem into JSON",
  82. expectedData: nil,
  83. expectedByte: nil,
  84. }
  85. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
  86. smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
  87. smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
  88. return &smtc
  89. }
  90. func makeValidRef(databag, dataitem, property string) *esv1beta1.ExternalSecretDataRemoteRef {
  91. return &esv1beta1.ExternalSecretDataRemoteRef{
  92. Key: databag + "/" + dataitem,
  93. Property: property,
  94. }
  95. }
  96. func makeinValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
  97. return &esv1beta1.ExternalSecretDataRemoteRef{
  98. Key: "",
  99. }
  100. }
  101. func makeValidRefForGetSecretMap(databag string) *esv1beta1.ExternalSecretDataRemoteRef {
  102. return &esv1beta1.ExternalSecretDataRemoteRef{
  103. Key: databag,
  104. }
  105. }
  106. func makeValidChefTestCaseCustom(tweaks ...func(smtc *chefTestCase)) *chefTestCase {
  107. smtc := makeValidChefTestCase()
  108. for _, fn := range tweaks {
  109. fn(smtc)
  110. }
  111. return smtc
  112. }
  113. func TestChefGetSecret(t *testing.T) {
  114. nilClient := func(smtc *chefTestCase) {
  115. smtc.mockClient = nil
  116. smtc.expectedByte = nil
  117. smtc.expectError = "chef provider is not initialized"
  118. }
  119. invalidDatabagName := func(smtc *chefTestCase) {
  120. smtc.databagName = "databag02"
  121. smtc.expectedByte = nil
  122. smtc.ref = makeinValidRef()
  123. smtc.expectError = "invalid key format in data section. Expected value 'databagName/databagItemName'"
  124. }
  125. invalidDatabagItemName := func(smtc *chefTestCase) {
  126. smtc.expectError = "data bag item item02 not found in data bag databag01"
  127. smtc.databagName = databagName
  128. smtc.databagItemName = "item02"
  129. smtc.expectedByte = nil
  130. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "")
  131. }
  132. noProperty := func(smtc *chefTestCase) {
  133. smtc.expectError = "property findProperty not found in data bag item"
  134. smtc.databagName = databagName
  135. smtc.databagItemName = "item01"
  136. smtc.expectedByte = nil
  137. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
  138. }
  139. withProperty := func(smtc *chefTestCase) {
  140. smtc.expectedByte = []byte("foundProperty")
  141. smtc.databagName = "databag03"
  142. smtc.databagItemName = "item03"
  143. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
  144. }
  145. successCases := []*chefTestCase{
  146. makeValidChefTestCase(),
  147. makeValidChefTestCaseCustom(nilClient),
  148. makeValidChefTestCaseCustom(invalidDatabagName),
  149. makeValidChefTestCaseCustom(invalidDatabagItemName),
  150. makeValidChefTestCaseCustom(noProperty),
  151. makeValidChefTestCaseCustom(withProperty),
  152. makeInValidChefTestCase(),
  153. }
  154. sm := Providerchef{
  155. databagService: &chef.DataBagService{},
  156. }
  157. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  158. defer cancel()
  159. for k, v := range successCases {
  160. sm.databagService = v.mockClient
  161. out, err := sm.GetSecret(ctx, *v.ref)
  162. if err != nil && !utils.ErrorContains(err, v.expectError) {
  163. t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
  164. } else if v.expectError != "" && err == nil {
  165. t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
  166. }
  167. if !bytes.Equal(out, v.expectedByte) {
  168. t.Errorf("[case %d] expected secret: %s, got: %s", k, v.expectedByte, out)
  169. }
  170. }
  171. }
  172. func TestChefGetSecretMap(t *testing.T) {
  173. nilClient := func(smtc *chefTestCase) {
  174. smtc.mockClient = nil
  175. smtc.expectedByte = nil
  176. smtc.expectError = "chef provider is not initialized"
  177. }
  178. databagHasSlash := func(smtc *chefTestCase) {
  179. smtc.expectedByte = nil
  180. smtc.ref = makeinValidRef()
  181. smtc.ref.Key = "data/Bag02"
  182. smtc.expectError = "invalid key format in dataForm section. Expected only 'databagName'"
  183. }
  184. withProperty := func(smtc *chefTestCase) {
  185. smtc.expectedByte = []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`)
  186. smtc.databagName = databagName
  187. smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
  188. }
  189. withProperty2 := func(smtc *chefTestCase) {
  190. smtc.expectError = "unable to list items in data bag 123, may be given data bag doesn't exists or it is empty"
  191. smtc.expectedByte = nil
  192. smtc.databagName = "123"
  193. smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
  194. }
  195. successCases := []*chefTestCase{
  196. makeValidChefTestCaseCustom(nilClient),
  197. makeValidChefTestCaseCustom(databagHasSlash),
  198. makeValidChefTestCaseCustom(withProperty),
  199. makeValidChefTestCaseCustom(withProperty2),
  200. }
  201. pc := Providerchef{
  202. databagService: &chef.DataBagService{},
  203. }
  204. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  205. defer cancel()
  206. for k, v := range successCases {
  207. pc.databagService = v.mockClient
  208. out, err := pc.GetSecretMap(ctx, *v.ref)
  209. if err != nil && !utils.ErrorContains(err, v.expectError) {
  210. t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
  211. } else if v.expectError != "" && err == nil {
  212. t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
  213. }
  214. if !bytes.Equal(out["item01"], v.expectedByte) {
  215. t.Errorf("[case %d] unexpected secret: expected %s, got %s", k, v.expectedByte, out)
  216. }
  217. }
  218. }
  219. func makeSecretStore(name, baseURL string, auth *esv1beta1.ChefAuth) *esv1beta1.SecretStore {
  220. store := &esv1beta1.SecretStore{
  221. Spec: esv1beta1.SecretStoreSpec{
  222. Provider: &esv1beta1.SecretStoreProvider{
  223. Chef: &esv1beta1.ChefProvider{
  224. UserName: name,
  225. ServerURL: baseURL,
  226. Auth: auth,
  227. },
  228. },
  229. },
  230. }
  231. return store
  232. }
  233. func makeAuth(name, namespace, key string) *esv1beta1.ChefAuth {
  234. return &esv1beta1.ChefAuth{
  235. SecretRef: esv1beta1.ChefAuthSecretRef{
  236. SecretKey: v1.SecretKeySelector{
  237. Name: name,
  238. Key: key,
  239. Namespace: &namespace,
  240. },
  241. },
  242. }
  243. }
  244. func TestValidateStore(t *testing.T) {
  245. testCases := []ValidateStoreTestCase{
  246. {
  247. store: makeSecretStore("", baseURL, makeAuth(authName, authNamespace, authKey)),
  248. err: fmt.Errorf("received invalid Chef SecretStore resource: missing username"),
  249. },
  250. {
  251. store: makeSecretStore(name, "", makeAuth(authName, authNamespace, authKey)),
  252. err: fmt.Errorf("received invalid Chef SecretStore resource: missing serverurl"),
  253. },
  254. {
  255. store: makeSecretStore(name, baseURL, nil),
  256. err: fmt.Errorf("received invalid Chef SecretStore resource: cannot initialize Chef Client: no valid authType was specified"),
  257. },
  258. {
  259. store: makeSecretStore(name, baseInvalidURL, makeAuth(authName, authNamespace, authKey)),
  260. err: fmt.Errorf("received invalid Chef SecretStore resource: invalid serverurl: parse \"invalid base URL/\": invalid URI for request"),
  261. },
  262. {
  263. store: makeSecretStore(name, noEndSlashInvalidBaseURL, makeAuth(authName, authNamespace, authKey)),
  264. err: fmt.Errorf("received invalid Chef SecretStore resource: serverurl does not end with slash(/)"),
  265. },
  266. {
  267. store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, "")),
  268. err: fmt.Errorf("received invalid Chef SecretStore resource: missing Secret Key"),
  269. },
  270. {
  271. store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, authKey)),
  272. err: fmt.Errorf("received invalid Chef SecretStore resource: namespace not allowed with namespaced SecretStore"),
  273. },
  274. {
  275. store: &esv1beta1.SecretStore{
  276. Spec: esv1beta1.SecretStoreSpec{
  277. Provider: nil,
  278. },
  279. },
  280. err: fmt.Errorf("received invalid Chef SecretStore resource: missing provider"),
  281. },
  282. {
  283. store: &esv1beta1.SecretStore{
  284. Spec: esv1beta1.SecretStoreSpec{
  285. Provider: &esv1beta1.SecretStoreProvider{
  286. Chef: nil,
  287. },
  288. },
  289. },
  290. err: fmt.Errorf("received invalid Chef SecretStore resource: missing chef provider"),
  291. },
  292. }
  293. pc := Providerchef{}
  294. for _, tc := range testCases {
  295. _, err := pc.ValidateStore(tc.store)
  296. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  297. t.Errorf("test failed! want: %v, got: %v", tc.err, err)
  298. } else if tc.err == nil && err != nil {
  299. t.Errorf("want: nil got: err %v", err)
  300. } else if tc.err != nil && err == nil {
  301. t.Errorf("want: err %v got: nil", tc.err)
  302. }
  303. }
  304. }
  305. func TestNewClient(t *testing.T) {
  306. store := &esv1beta1.SecretStore{TypeMeta: metav1.TypeMeta{Kind: "ClusterSecretStore"},
  307. Spec: esv1beta1.SecretStoreSpec{
  308. Provider: &esv1beta1.SecretStoreProvider{
  309. Chef: &esv1beta1.ChefProvider{
  310. Auth: makeAuth(authName, authNamespace, authKey),
  311. UserName: name,
  312. ServerURL: baseURL,
  313. },
  314. },
  315. },
  316. }
  317. expected := fmt.Sprintf("could not fetch SecretKey Secret: secrets %q not found", authName)
  318. expectedMissingStore := "missing or invalid spec: missing store"
  319. ctx := context.TODO()
  320. kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
  321. ObjectMeta: metav1.ObjectMeta{
  322. Name: "creds",
  323. Namespace: "default",
  324. }, TypeMeta: metav1.TypeMeta{
  325. Kind: kind,
  326. APIVersion: apiversion,
  327. },
  328. }).Build()
  329. pc := Providerchef{databagService: &fake.ChefMockClient{}}
  330. _, errMissingStore := pc.NewClient(ctx, nil, kube, "default")
  331. if !ErrorContains(errMissingStore, expectedMissingStore) {
  332. t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", errMissingStore.Error(), expectedMissingStore)
  333. }
  334. _, err := pc.NewClient(ctx, store, kube, "default")
  335. if !ErrorContains(err, expected) {
  336. t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", err.Error(), expected)
  337. }
  338. }
  339. func ErrorContains(out error, want string) bool {
  340. if out == nil {
  341. return want == ""
  342. }
  343. if want == "" {
  344. return false
  345. }
  346. return strings.Contains(out.Error(), want)
  347. }
  348. func TestValidate(t *testing.T) {
  349. pc := Providerchef{}
  350. var mockClient *fake.ChefMockClient
  351. pc.userService = mockClient
  352. pc.clientName = "correctUser"
  353. _, err := pc.Validate()
  354. t.Log("Error: ", err)
  355. pc.clientName = "wrongUser"
  356. _, err = pc.Validate()
  357. t.Log("Error: ", err)
  358. }
  359. func TestCapabilities(t *testing.T) {
  360. pc := Providerchef{}
  361. capabilities := pc.Capabilities()
  362. t.Log(capabilities)
  363. }
  364. // Test Cases To be added when Close function is implemented.
  365. func TestClose(_ *testing.T) {
  366. pc := Providerchef{}
  367. pc.Close(context.Background())
  368. }
  369. // Test Cases To be added when GetAllSecrets function is implemented.
  370. func TestGetAllSecrets(_ *testing.T) {
  371. pc := Providerchef{}
  372. pc.GetAllSecrets(context.Background(), esv1beta1.ExternalSecretFind{})
  373. }
  374. // Test Cases To be implemented when DeleteSecret function is implemented.
  375. func TestDeleteSecret(_ *testing.T) {
  376. pc := Providerchef{}
  377. pc.DeleteSecret(context.Background(), nil)
  378. }
  379. // Test Cases To be implemented when PushSecret function is implemented.
  380. func TestPushSecret(_ *testing.T) {
  381. pc := Providerchef{}
  382. pc.PushSecret(context.Background(), &corev1.Secret{}, nil)
  383. }