vault_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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 vault
  13. import (
  14. "bytes"
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "testing"
  22. "github.com/crossplane/crossplane-runtime/pkg/test"
  23. "github.com/google/go-cmp/cmp"
  24. vault "github.com/hashicorp/vault/api"
  25. corev1 "k8s.io/api/core/v1"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  28. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  29. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  30. "github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
  31. )
  32. func makeValidSecretStore() *esv1alpha1.SecretStore {
  33. return &esv1alpha1.SecretStore{
  34. ObjectMeta: metav1.ObjectMeta{
  35. Name: "vault-store",
  36. Namespace: "default",
  37. },
  38. Spec: esv1alpha1.SecretStoreSpec{
  39. Provider: &esv1alpha1.SecretStoreProvider{
  40. Vault: &esv1alpha1.VaultProvider{
  41. Server: "vault.example.com",
  42. Path: "secret",
  43. Version: esv1alpha1.VaultKVStoreV2,
  44. Auth: esv1alpha1.VaultAuth{
  45. Kubernetes: &esv1alpha1.VaultKubernetesAuth{
  46. Path: "kubernetes",
  47. Role: "kubernetes-auth-role",
  48. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  49. Name: "example-sa",
  50. },
  51. },
  52. },
  53. },
  54. },
  55. },
  56. }
  57. }
  58. func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore {
  59. return &esv1alpha1.SecretStore{
  60. ObjectMeta: metav1.ObjectMeta{
  61. Name: "vault-store",
  62. Namespace: "default",
  63. },
  64. Spec: esv1alpha1.SecretStoreSpec{
  65. Provider: &esv1alpha1.SecretStoreProvider{
  66. Vault: &esv1alpha1.VaultProvider{
  67. Server: "vault.example.com",
  68. Path: "secret",
  69. Version: esv1alpha1.VaultKVStoreV2,
  70. Auth: esv1alpha1.VaultAuth{
  71. Cert: &esv1alpha1.VaultCertAuth{
  72. ClientCert: esmeta.SecretKeySelector{
  73. Name: "tls-auth-certs",
  74. Key: "tls.crt",
  75. },
  76. SecretRef: esmeta.SecretKeySelector{
  77. Name: "tls-auth-certs",
  78. Key: "tls.key",
  79. },
  80. },
  81. },
  82. },
  83. },
  84. },
  85. }
  86. }
  87. type secretStoreTweakFn func(s *esv1alpha1.SecretStore)
  88. func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1alpha1.SecretStore {
  89. store := makeValidSecretStore()
  90. for _, fn := range tweaks {
  91. fn(store)
  92. }
  93. return store
  94. }
  95. func newVaultResponse(data *vault.Secret) *vault.Response {
  96. jsonData, _ := json.Marshal(data)
  97. return &vault.Response{
  98. Response: &http.Response{
  99. Body: ioutil.NopCloser(bytes.NewReader(jsonData)),
  100. },
  101. }
  102. }
  103. func newVaultTokenIDResponse(token string) *vault.Response {
  104. return newVaultResponse(&vault.Secret{
  105. Data: map[string]interface{}{
  106. "id": token,
  107. },
  108. })
  109. }
  110. func TestNewVault(t *testing.T) {
  111. errBoom := errors.New("boom")
  112. secretData := []byte("some-creds")
  113. secretClientKey := []byte(`-----BEGIN RSA PRIVATE KEY-----
  114. MIIEpAIBAAKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABAoIBAQCYPICQ8hVX+MNcpLrfZenycR7sBYNOMC0silbH5cUn6yzFfgHuRxi3pOnrCJnTb3cE0BvMbdMVAVdYReD2znSsR9NEdZvvjZ/GGSgH1SIQsI7t//+mDQ/jRLJb4KsXb4vJcLLwdpLrd22bMmhMXjzndrF8gSz8NLX9omozPM8RlLxjzPzYOdlX/Zw8V68qQH2Ic04KbtnCwyAUIgAJxYtn/uYB8lzILBkyzQqwhQKkDDZQ0wbZT0hP6z+HgsdifwQvHG1GZAgCuzzyXrL/4TgDaDhYdMVoBA4+HPmzqm5MkBvjH4oqroxjRofUroVix0OGXZJMI1OJ0z/ubzmwCq5BAoGBANqbwzAydUJs0P+GFL94K/Y6tXULKA2c9N0crbxoxheobRpuJvhpW1ZE/9UGpaYX1Rw3nW4x+Jwvt83YkgHAlR4LgEwDvdJPZobybfqifQDiraUO0t62Crn8mSxOsFCugtRIFniwnX67w3uKxiSdCZYbJGs9JEDTpxRG/PSWq3QlAoGBAMu3zOv1PJAhOky7VcxFxWQPEMY+t2PA/sneD01/qgGuhlTwL4QlpywmBqXcI070dcvcBkP0flnWI7y5cnuE1+55twmsrvfaS8s1+AYje0b35DsaF2vtKuJrXC0AGKP+/eiycd9cbvVW2GWOxE7Ui76Mj95MARK8ZNjt0wJagQhjAoGASm9dD80uhhadN1RFPkjB1054OMk6sx/tdFhug8e9I5MSyzwUguME2aQW5EcmIh7dToVVUo8rUqsgz7NdS8FyRM+vuLJRcQneJDbp4bxwCdwlOh2JCZI8psVutlp4yJATNgrxs9iXV+7BChDflNnvyK+nP+iKrpQiwNHHEdU3vg0CgYEAvEpwD4+loJn1psJn9NxwK6F5IaMKIhtZ4/9pKXpcCh3jb1JouL2MnFOxRVAJGor87aW57Mlol2RDt8W4OM56PqMlOL3xIokUEQka66GT6e5pdu8QwuJ9BrWwhq9WFw4yZQe6FHb836qbbJLegvYVC9QjjZW2UDjtBUwcAkrghH0CgYBUMmMOCwIfMEtMaWxZRGdxRabazLhn7TXhBpVTuv7WouPaXYd7ZGjCTMKAuVa/E4afBlxgemnqBuX90gHpK/dDmn9l+lp8GZey0grJ7G0x5HEMiKziaX5PrgAcKbQ70m9ZNZ1deYhsC05X8rHNexZB6ns7Yms9L7qnlAy51ZH2zw==
  115. -----END RSA PRIVATE KEY-----`)
  116. clientCrt := []byte(`-----BEGIN CERTIFICATE-----
  117. MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZhdWx0LWNhMB4XDTIxMDcyMDA4MTQxM1oXDTIyMDcyMDA4MTQxM1owFzEVMBMGA1UEAwwMdmF1bHQtY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAsDYKtzScIA7bqIOmqF8rr+oLSjRhPt5OfT+KGNdXk8G3VAy1ED2tyCHaRNC7dPLq4EvcxbIXQnXPy1iZMofriGbFPAcQ2fyWUesAD6bYSpI+bYxwz6Ebb93hU5nc/FyXg8yh0kgiGbY3MrACPjxqP2+z5kcOC3u3hx3SZylgW7TeOXDTdqSbNfH1b+1rR/bVNgQQshjhU9d+c4Yv/t0u07uykBhHLWZDSnYiAeOZ8+mWuOSDkcZHE1zznx74fWgtN0zRDtr0L0w9evT9R2CnNSZGxXcEQxAlQ7SL/Jyw82TFCGEw0L4jj7jjvx0N5J8KX/DulUDE9vuVyQEJ88Epe
  118. -----END CERTIFICATE-----
  119. `)
  120. type args struct {
  121. newClientFunc func(c *vault.Config) (Client, error)
  122. store esv1alpha1.GenericStore
  123. kube kclient.Client
  124. ns string
  125. }
  126. type want struct {
  127. err error
  128. }
  129. cases := map[string]struct {
  130. reason string
  131. args args
  132. want want
  133. }{
  134. "InvalidVaultStore": {
  135. reason: "Should return error if given an invalid vault store.",
  136. args: args{
  137. store: &esv1alpha1.SecretStore{},
  138. },
  139. want: want{
  140. err: errors.New(errVaultStore),
  141. },
  142. },
  143. "AddVaultStoreCertsError": {
  144. reason: "Should return error if given an invalid CA certificate.",
  145. args: args{
  146. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  147. s.Spec.Provider.Vault.CABundle = []byte("badcertdata")
  148. }),
  149. },
  150. want: want{
  151. err: errors.New(errVaultCert),
  152. },
  153. },
  154. "VaultAuthFormatError": {
  155. reason: "Should return error if no valid authentication method is given.",
  156. args: args{
  157. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  158. s.Spec.Provider.Vault.Auth = esv1alpha1.VaultAuth{}
  159. }),
  160. },
  161. want: want{
  162. err: errors.New(errAuthFormat),
  163. },
  164. },
  165. "GetKubeServiceAccountError": {
  166. reason: "Should return error if fetching kubernetes secret fails.",
  167. args: args{
  168. store: makeSecretStore(),
  169. kube: &test.MockClient{
  170. MockGet: test.NewMockGetFn(errBoom),
  171. },
  172. },
  173. want: want{
  174. err: fmt.Errorf(errGetKubeSA, "example-sa", errBoom),
  175. },
  176. },
  177. "GetKubeSecretError": {
  178. reason: "Should return error if fetching kubernetes secret fails.",
  179. args: args{
  180. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  181. s.Spec.Provider.Vault.Auth.Kubernetes.ServiceAccountRef = nil
  182. s.Spec.Provider.Vault.Auth.Kubernetes.SecretRef = &esmeta.SecretKeySelector{
  183. Name: "vault-secret",
  184. Key: "key",
  185. }
  186. }),
  187. kube: &test.MockClient{
  188. MockGet: test.NewMockGetFn(errBoom),
  189. },
  190. },
  191. want: want{
  192. err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom),
  193. },
  194. },
  195. "SuccessfulVaultStore": {
  196. reason: "Should return a Vault provider successfully",
  197. args: args{
  198. store: makeSecretStore(),
  199. kube: &test.MockClient{
  200. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  201. if o, ok := obj.(*corev1.ServiceAccount); ok {
  202. o.Secrets = []corev1.ObjectReference{
  203. {
  204. Name: "example-secret-token",
  205. },
  206. }
  207. return nil
  208. }
  209. if o, ok := obj.(*corev1.Secret); ok {
  210. o.Data = map[string][]byte{
  211. "token": secretData,
  212. }
  213. return nil
  214. }
  215. return nil
  216. }),
  217. },
  218. newClientFunc: func(c *vault.Config) (Client, error) {
  219. return &fake.VaultClient{
  220. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  221. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  222. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error {
  223. kubeRole := makeValidSecretStore().Spec.Provider.Vault.Auth.Kubernetes.Role
  224. want := kubeParameters(kubeRole, string(secretData))
  225. if diff := cmp.Diff(want, got.Obj); diff != "" {
  226. t.Errorf("RawRequestWithContext(...): -want, +got:\n%s", diff)
  227. }
  228. return nil
  229. }),
  230. MockSetToken: fake.NewSetTokenFn(),
  231. }, nil
  232. },
  233. },
  234. want: want{
  235. err: nil,
  236. },
  237. },
  238. "SuccessfulVaultStoreWithCertAuth": {
  239. reason: "Should return a Vault provider successfully",
  240. args: args{
  241. store: makeValidSecretStoreWithCerts(),
  242. kube: &test.MockClient{
  243. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  244. if o, ok := obj.(*corev1.Secret); ok {
  245. o.Data = map[string][]byte{
  246. "tls.key": secretClientKey,
  247. "tls.crt": clientCrt,
  248. }
  249. return nil
  250. }
  251. return nil
  252. }),
  253. },
  254. newClientFunc: func(c *vault.Config) (Client, error) {
  255. return &fake.VaultClient{
  256. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  257. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  258. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  259. MockSetToken: fake.NewSetTokenFn(),
  260. }, nil
  261. },
  262. },
  263. want: want{
  264. err: nil,
  265. },
  266. },
  267. "GetCertificateFormatError": {
  268. reason: "Should return error if client certificate is in wrong format.",
  269. args: args{
  270. store: makeValidSecretStoreWithCerts(),
  271. kube: &test.MockClient{
  272. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  273. if o, ok := obj.(*corev1.Secret); ok {
  274. o.Data = map[string][]byte{
  275. "tls.key": secretClientKey,
  276. "tls.crt": []byte("cert with mistak"),
  277. }
  278. return nil
  279. }
  280. return nil
  281. }),
  282. },
  283. newClientFunc: func(c *vault.Config) (Client, error) {
  284. return &fake.VaultClient{
  285. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  286. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  287. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  288. MockSetToken: fake.NewSetTokenFn(),
  289. }, nil
  290. },
  291. },
  292. want: want{
  293. err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in certificate input"),
  294. },
  295. },
  296. "GetKeyFormatError": {
  297. reason: "Should return error if client key is in wrong format.",
  298. args: args{
  299. store: makeValidSecretStoreWithCerts(),
  300. kube: &test.MockClient{
  301. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  302. if o, ok := obj.(*corev1.Secret); ok {
  303. o.Data = map[string][]byte{
  304. "tls.key": []byte("key with mistake"),
  305. "tls.crt": clientCrt,
  306. }
  307. return nil
  308. }
  309. return nil
  310. }),
  311. },
  312. newClientFunc: func(c *vault.Config) (Client, error) {
  313. return &fake.VaultClient{
  314. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  315. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  316. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  317. MockSetToken: fake.NewSetTokenFn(),
  318. }, nil
  319. },
  320. },
  321. want: want{
  322. err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in key input"),
  323. },
  324. },
  325. }
  326. for name, tc := range cases {
  327. t.Run(name, func(t *testing.T) {
  328. conn := &connector{
  329. newVaultClient: tc.args.newClientFunc,
  330. }
  331. if tc.args.newClientFunc == nil {
  332. conn.newVaultClient = newVaultClient
  333. }
  334. _, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
  335. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  336. t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
  337. }
  338. })
  339. }
  340. }
  341. func TestGetSecretMap(t *testing.T) {
  342. errBoom := errors.New("boom")
  343. type args struct {
  344. store *esv1alpha1.VaultProvider
  345. kube kclient.Client
  346. vClient Client
  347. ns string
  348. data esv1alpha1.ExternalSecretDataRemoteRef
  349. }
  350. type want struct {
  351. err error
  352. }
  353. cases := map[string]struct {
  354. reason string
  355. args args
  356. want want
  357. }{
  358. "ReadSecretError": {
  359. reason: "Should return error if vault client fails to read secret.",
  360. args: args{
  361. store: makeSecretStore().Spec.Provider.Vault,
  362. vClient: &fake.VaultClient{
  363. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  364. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
  365. },
  366. },
  367. want: want{
  368. err: fmt.Errorf(errReadSecret, errBoom),
  369. },
  370. },
  371. }
  372. for name, tc := range cases {
  373. t.Run(name, func(t *testing.T) {
  374. vStore := &client{
  375. kube: tc.args.kube,
  376. client: tc.args.vClient,
  377. store: tc.args.store,
  378. namespace: tc.args.ns,
  379. }
  380. _, err := vStore.GetSecretMap(context.Background(), tc.args.data)
  381. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  382. t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
  383. }
  384. })
  385. }
  386. }