vault_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. MockToken: fake.NewTokenFn(""),
  232. MockClearToken: fake.NewClearTokenFn(),
  233. }, nil
  234. },
  235. },
  236. want: want{
  237. err: nil,
  238. },
  239. },
  240. "SuccessfulVaultStoreWithCertAuth": {
  241. reason: "Should return a Vault provider successfully",
  242. args: args{
  243. store: makeValidSecretStoreWithCerts(),
  244. kube: &test.MockClient{
  245. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  246. if o, ok := obj.(*corev1.Secret); ok {
  247. o.Data = map[string][]byte{
  248. "tls.key": secretClientKey,
  249. "tls.crt": clientCrt,
  250. }
  251. return nil
  252. }
  253. return nil
  254. }),
  255. },
  256. newClientFunc: func(c *vault.Config) (Client, error) {
  257. return &fake.VaultClient{
  258. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  259. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  260. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  261. MockSetToken: fake.NewSetTokenFn(),
  262. }, nil
  263. },
  264. },
  265. want: want{
  266. err: nil,
  267. },
  268. },
  269. "GetCertificateFormatError": {
  270. reason: "Should return error if client certificate is in wrong format.",
  271. args: args{
  272. store: makeValidSecretStoreWithCerts(),
  273. kube: &test.MockClient{
  274. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  275. if o, ok := obj.(*corev1.Secret); ok {
  276. o.Data = map[string][]byte{
  277. "tls.key": secretClientKey,
  278. "tls.crt": []byte("cert with mistak"),
  279. }
  280. return nil
  281. }
  282. return nil
  283. }),
  284. },
  285. newClientFunc: func(c *vault.Config) (Client, error) {
  286. return &fake.VaultClient{
  287. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  288. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  289. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  290. MockSetToken: fake.NewSetTokenFn(),
  291. }, nil
  292. },
  293. },
  294. want: want{
  295. err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in certificate input"),
  296. },
  297. },
  298. "GetKeyFormatError": {
  299. reason: "Should return error if client key is in wrong format.",
  300. args: args{
  301. store: makeValidSecretStoreWithCerts(),
  302. kube: &test.MockClient{
  303. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  304. if o, ok := obj.(*corev1.Secret); ok {
  305. o.Data = map[string][]byte{
  306. "tls.key": []byte("key with mistake"),
  307. "tls.crt": clientCrt,
  308. }
  309. return nil
  310. }
  311. return nil
  312. }),
  313. },
  314. newClientFunc: func(c *vault.Config) (Client, error) {
  315. return &fake.VaultClient{
  316. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  317. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  318. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
  319. MockSetToken: fake.NewSetTokenFn(),
  320. }, nil
  321. },
  322. },
  323. want: want{
  324. err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in key input"),
  325. },
  326. },
  327. }
  328. for name, tc := range cases {
  329. t.Run(name, func(t *testing.T) {
  330. conn := &connector{
  331. newVaultClient: tc.args.newClientFunc,
  332. }
  333. if tc.args.newClientFunc == nil {
  334. conn.newVaultClient = newVaultClient
  335. }
  336. _, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
  337. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  338. t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
  339. }
  340. })
  341. }
  342. }
  343. func TestGetSecretMap(t *testing.T) {
  344. errBoom := errors.New("boom")
  345. type args struct {
  346. store *esv1alpha1.VaultProvider
  347. kube kclient.Client
  348. vClient Client
  349. ns string
  350. data esv1alpha1.ExternalSecretDataRemoteRef
  351. }
  352. type want struct {
  353. err error
  354. }
  355. cases := map[string]struct {
  356. reason string
  357. args args
  358. want want
  359. }{
  360. "ReadSecretError": {
  361. reason: "Should return error if vault client fails to read secret.",
  362. args: args{
  363. store: makeSecretStore().Spec.Provider.Vault,
  364. vClient: &fake.VaultClient{
  365. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  366. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
  367. },
  368. },
  369. want: want{
  370. err: fmt.Errorf(errReadSecret, errBoom),
  371. },
  372. },
  373. }
  374. for name, tc := range cases {
  375. t.Run(name, func(t *testing.T) {
  376. vStore := &client{
  377. kube: tc.args.kube,
  378. client: tc.args.vClient,
  379. store: tc.args.store,
  380. namespace: tc.args.ns,
  381. }
  382. _, err := vStore.GetSecretMap(context.Background(), tc.args.data)
  383. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  384. t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
  385. }
  386. })
  387. }
  388. }