provider.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package vault
  14. import (
  15. "context"
  16. "fmt"
  17. "net/http"
  18. "time"
  19. vault "github.com/hashicorp/vault/api"
  20. //nolint
  21. . "github.com/onsi/ginkgo/v2"
  22. //nolint
  23. . "github.com/onsi/gomega"
  24. v1 "k8s.io/api/core/v1"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "github.com/external-secrets/external-secrets-e2e/framework"
  27. "github.com/external-secrets/external-secrets-e2e/framework/addon"
  28. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  30. )
  31. type vaultProvider struct {
  32. addon *addon.Vault
  33. url string
  34. mtlsUrl string
  35. client *vault.Client
  36. framework *framework.Framework
  37. }
  38. type StoreCustomizer = func(provider *vaultProvider, secret *v1.Secret, secretStore *metav1.ObjectMeta, secretStoreSpec *esv1.SecretStoreSpec, isClusterStore bool)
  39. const (
  40. clientTlsCertName = "vault-client-tls"
  41. certAuthProviderName = "cert-auth-provider"
  42. appRoleAuthProviderName = "app-role-provider"
  43. kvv1ProviderName = "kv-v1-provider"
  44. jwtProviderName = "jwt-provider"
  45. jwtProviderSecretName = "jwt-provider-credentials"
  46. jwtK8sProviderName = "jwt-k8s-provider"
  47. kubernetesProviderName = "kubernetes-provider"
  48. referentSecretName = "referent-secret"
  49. referentKey = "referent-secret-key"
  50. )
  51. var (
  52. secretStorePath = "secret"
  53. mtlsSuffix = "-mtls"
  54. invalidMtlSuffix = "-invalid-mtls"
  55. )
  56. func newVaultProvider(f *framework.Framework, addon *addon.Vault) *vaultProvider {
  57. prov := &vaultProvider{
  58. addon: addon,
  59. framework: f,
  60. }
  61. BeforeEach(prov.BeforeEach)
  62. AfterEach(prov.AfterEach)
  63. return prov
  64. }
  65. // CreateSecret creates a secret in both kv v1 and v2 provider.
  66. func (s *vaultProvider) CreateSecret(key string, val framework.SecretEntry) {
  67. req := s.client.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret/data/%s", key))
  68. req.BodyBytes = []byte(fmt.Sprintf(`{"data": %s}`, val.Value))
  69. _, err := s.client.RawRequestWithContext(GinkgoT().Context(), req) //nolint:staticcheck
  70. Expect(err).ToNot(HaveOccurred())
  71. req = s.client.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret_v1/%s", key))
  72. req.BodyBytes = []byte(val.Value)
  73. _, err = s.client.RawRequestWithContext(GinkgoT().Context(), req) //nolint:staticcheck
  74. Expect(err).ToNot(HaveOccurred())
  75. }
  76. func (s *vaultProvider) DeleteSecret(key string) {
  77. req := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/secret/data/%s", key))
  78. _, err := s.client.RawRequestWithContext(GinkgoT().Context(), req) //nolint:staticcheck
  79. Expect(err).ToNot(HaveOccurred())
  80. req = s.client.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/secret_v1/%s", key))
  81. _, err = s.client.RawRequestWithContext(GinkgoT().Context(), req) //nolint:staticcheck
  82. Expect(err).ToNot(HaveOccurred())
  83. }
  84. func WithMTLS(provider *vaultProvider, secret *v1.Secret, secretStore *metav1.ObjectMeta, secretStoreSpec *esv1.SecretStoreSpec, isClusterStore bool) {
  85. provider.CreateClientTlsCert()
  86. secret.Name = secret.Name + mtlsSuffix
  87. secretStore.Name = secretStore.Name + mtlsSuffix
  88. secretStoreSpec.Provider.Vault.Server = provider.mtlsUrl
  89. secretStoreSpec.Provider.Vault.ClientTLS = esv1.VaultClientTLS{
  90. CertSecretRef: &esmeta.SecretKeySelector{
  91. Name: clientTlsCertName,
  92. },
  93. KeySecretRef: &esmeta.SecretKeySelector{
  94. Name: clientTlsCertName,
  95. },
  96. }
  97. if isClusterStore {
  98. secretStoreSpec.Provider.Vault.ClientTLS.CertSecretRef.Namespace = &provider.framework.Namespace.Name
  99. secretStoreSpec.Provider.Vault.ClientTLS.KeySecretRef.Namespace = &provider.framework.Namespace.Name
  100. }
  101. }
  102. func WithInvalidMTLS(provider *vaultProvider, secret *v1.Secret, secretStore *metav1.ObjectMeta, secretStoreSpec *esv1.SecretStoreSpec, isClusterStore bool) {
  103. secret.Name = secret.Name + invalidMtlSuffix
  104. secretStore.Name = secretStore.Name + invalidMtlSuffix
  105. secretStoreSpec.Provider.Vault.Server = provider.mtlsUrl
  106. }
  107. func (s *vaultProvider) BeforeEach() {
  108. s.client = s.addon.VaultClient
  109. s.url = s.addon.VaultURL
  110. s.mtlsUrl = s.addon.VaultMtlsURL
  111. }
  112. func (s *vaultProvider) AfterEach() {
  113. }
  114. func makeStore(name, ns string, v *addon.Vault) *esv1.SecretStore {
  115. return &esv1.SecretStore{
  116. ObjectMeta: metav1.ObjectMeta{
  117. Name: name,
  118. Namespace: ns,
  119. },
  120. Spec: esv1.SecretStoreSpec{
  121. Provider: &esv1.SecretStoreProvider{
  122. Vault: &esv1.VaultProvider{
  123. Version: esv1.VaultKVStoreV2,
  124. Path: &secretStorePath,
  125. Server: v.VaultURL,
  126. CABundle: v.VaultServerCA,
  127. },
  128. },
  129. },
  130. }
  131. }
  132. func makeClusterStore(name, ns string, v *addon.Vault) *esv1.ClusterSecretStore {
  133. store := makeStore(name, ns, v)
  134. return &esv1.ClusterSecretStore{
  135. ObjectMeta: store.ObjectMeta,
  136. Spec: store.Spec,
  137. }
  138. }
  139. func (s *vaultProvider) CreateClientTlsCert() {
  140. By("creating a secret containing the Vault TLS client certificate")
  141. clientCert := s.addon.ClientCert
  142. clientKey := s.addon.ClientKey
  143. vaultClientCert := &v1.Secret{
  144. ObjectMeta: metav1.ObjectMeta{
  145. Name: clientTlsCertName,
  146. Namespace: s.framework.Namespace.Name,
  147. },
  148. Data: map[string][]byte{
  149. "tls.crt": clientCert,
  150. "tls.key": clientKey,
  151. },
  152. }
  153. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultClientCert)
  154. Expect(err).ToNot(HaveOccurred())
  155. }
  156. func (s *vaultProvider) CreateCertStore() {
  157. By("creating a vault secret")
  158. clientCert := s.addon.ClientCert
  159. clientKey := s.addon.ClientKey
  160. vaultCreds := &v1.Secret{
  161. ObjectMeta: metav1.ObjectMeta{
  162. Name: certAuthProviderName,
  163. Namespace: s.framework.Namespace.Name,
  164. },
  165. Data: map[string][]byte{
  166. "token": []byte(s.addon.RootToken),
  167. "client_cert": clientCert,
  168. "client_key": clientKey,
  169. },
  170. }
  171. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultCreds)
  172. Expect(err).ToNot(HaveOccurred())
  173. By("creating an secret store for vault")
  174. secretStore := makeStore(certAuthProviderName, s.framework.Namespace.Name, s.addon)
  175. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  176. Cert: &esv1.VaultCertAuth{
  177. ClientCert: esmeta.SecretKeySelector{
  178. Name: certAuthProviderName,
  179. Key: "client_cert",
  180. },
  181. SecretRef: esmeta.SecretKeySelector{
  182. Name: certAuthProviderName,
  183. Key: "client_key",
  184. },
  185. },
  186. }
  187. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  188. Expect(err).ToNot(HaveOccurred())
  189. }
  190. func (s vaultProvider) CreateTokenStore(customizers ...StoreCustomizer) {
  191. vaultCreds := &v1.Secret{
  192. ObjectMeta: metav1.ObjectMeta{
  193. Name: "token-provider",
  194. Namespace: s.framework.Namespace.Name,
  195. },
  196. Data: map[string][]byte{
  197. "token": []byte(s.addon.RootToken),
  198. },
  199. }
  200. secretStore := makeStore(s.framework.Namespace.Name, s.framework.Namespace.Name, s.addon)
  201. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  202. TokenSecretRef: &esmeta.SecretKeySelector{
  203. Name: vaultCreds.Name,
  204. Key: "token",
  205. },
  206. }
  207. for _, customizer := range customizers {
  208. customizer(&s, vaultCreds, &secretStore.ObjectMeta, &secretStore.Spec, false)
  209. }
  210. secretStore.Spec.Provider.Vault.Auth.TokenSecretRef.Name = vaultCreds.Name
  211. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultCreds)
  212. Expect(err).ToNot(HaveOccurred())
  213. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  214. Expect(err).ToNot(HaveOccurred())
  215. }
  216. // CreateReferentTokenStore creates a secret in the ExternalSecrets
  217. // namespace and creates a ClusterSecretStore with an empty namespace
  218. // that can be used to test the referent namespace feature.
  219. func (s vaultProvider) CreateReferentTokenStore(customizers ...StoreCustomizer) {
  220. referentSecret := &v1.Secret{
  221. ObjectMeta: metav1.ObjectMeta{
  222. Name: referentSecretName,
  223. Namespace: s.framework.Namespace.Name,
  224. },
  225. Data: map[string][]byte{
  226. referentKey: []byte(s.addon.RootToken),
  227. },
  228. }
  229. secretStore := makeClusterStore(referentSecretStoreName(s.framework), s.framework.Namespace.Name, s.addon)
  230. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  231. TokenSecretRef: &esmeta.SecretKeySelector{
  232. Name: referentSecret.Name,
  233. Key: referentKey,
  234. },
  235. }
  236. for _, customizer := range customizers {
  237. customizer(&s, referentSecret, &secretStore.ObjectMeta, &secretStore.Spec, true)
  238. }
  239. DeferCleanup(func() {
  240. // cannot use ginkgo context nested in DeferCleanup
  241. ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
  242. defer cancel()
  243. s.framework.CRClient.Delete(ctx, secretStore)
  244. })
  245. secretStore.Spec.Provider.Vault.Auth.TokenSecretRef.Name = referentSecret.Name
  246. _, err := s.framework.KubeClientSet.CoreV1().Secrets(s.framework.Namespace.Name).Create(GinkgoT().Context(), referentSecret, metav1.CreateOptions{})
  247. Expect(err).ToNot(HaveOccurred())
  248. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  249. Expect(err).ToNot(HaveOccurred())
  250. }
  251. func (s vaultProvider) CreateAppRoleStore() {
  252. By("creating a vault secret")
  253. vaultCreds := &v1.Secret{
  254. ObjectMeta: metav1.ObjectMeta{
  255. Name: appRoleAuthProviderName,
  256. Namespace: s.framework.Namespace.Name,
  257. },
  258. Data: map[string][]byte{
  259. "approle_secret": []byte(s.addon.AppRoleSecret),
  260. },
  261. }
  262. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultCreds)
  263. Expect(err).ToNot(HaveOccurred())
  264. By("creating an secret store for vault")
  265. secretStore := makeStore(appRoleAuthProviderName, s.framework.Namespace.Name, s.addon)
  266. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  267. AppRole: &esv1.VaultAppRole{
  268. Path: s.addon.AppRolePath,
  269. RoleID: s.addon.AppRoleID,
  270. SecretRef: esmeta.SecretKeySelector{
  271. Name: appRoleAuthProviderName,
  272. Key: "approle_secret",
  273. },
  274. },
  275. }
  276. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  277. Expect(err).ToNot(HaveOccurred())
  278. }
  279. func (s vaultProvider) CreateV1Store() {
  280. vaultCreds := &v1.Secret{
  281. ObjectMeta: metav1.ObjectMeta{
  282. Name: "v1-provider",
  283. Namespace: s.framework.Namespace.Name,
  284. },
  285. Data: map[string][]byte{
  286. "token": []byte(s.addon.RootToken),
  287. },
  288. }
  289. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultCreds)
  290. Expect(err).ToNot(HaveOccurred())
  291. secretStore := makeStore(kvv1ProviderName, s.framework.Namespace.Name, s.addon)
  292. secretV1StorePath := "secret_v1"
  293. secretStore.Spec.Provider.Vault.Version = esv1.VaultKVStoreV1
  294. secretStore.Spec.Provider.Vault.Path = &secretV1StorePath
  295. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  296. TokenSecretRef: &esmeta.SecretKeySelector{
  297. Name: "v1-provider",
  298. Key: "token",
  299. },
  300. }
  301. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  302. Expect(err).ToNot(HaveOccurred())
  303. }
  304. func (s vaultProvider) CreateJWTStore() {
  305. vaultCreds := &v1.Secret{
  306. ObjectMeta: metav1.ObjectMeta{
  307. Name: jwtProviderSecretName,
  308. Namespace: s.framework.Namespace.Name,
  309. },
  310. Data: map[string][]byte{
  311. "jwt": []byte(s.addon.JWTToken),
  312. },
  313. }
  314. err := s.framework.CRClient.Create(GinkgoT().Context(), vaultCreds)
  315. Expect(err).ToNot(HaveOccurred())
  316. secretStore := makeStore(jwtProviderName, s.framework.Namespace.Name, s.addon)
  317. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  318. Jwt: &esv1.VaultJwtAuth{
  319. Path: s.addon.JWTPath,
  320. Role: s.addon.JWTRole,
  321. SecretRef: &esmeta.SecretKeySelector{
  322. Name: jwtProviderSecretName,
  323. Key: "jwt",
  324. },
  325. },
  326. }
  327. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  328. Expect(err).ToNot(HaveOccurred())
  329. }
  330. func (s vaultProvider) CreateJWTK8sStore() {
  331. secretStore := makeStore(jwtK8sProviderName, s.framework.Namespace.Name, s.addon)
  332. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  333. Jwt: &esv1.VaultJwtAuth{
  334. Path: s.addon.JWTK8sPath,
  335. Role: s.addon.JWTRole,
  336. KubernetesServiceAccountToken: &esv1.VaultKubernetesServiceAccountTokenAuth{
  337. ServiceAccountRef: esmeta.ServiceAccountSelector{
  338. Name: "default",
  339. },
  340. Audiences: &[]string{
  341. "vault.client",
  342. },
  343. },
  344. },
  345. }
  346. err := s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  347. Expect(err).ToNot(HaveOccurred())
  348. }
  349. func (s vaultProvider) CreateKubernetesAuthStore() {
  350. secretStore := makeStore(kubernetesProviderName, s.framework.Namespace.Name, s.addon)
  351. secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
  352. Kubernetes: &esv1.VaultKubernetesAuth{
  353. Path: s.addon.KubernetesAuthPath,
  354. Role: s.addon.KubernetesAuthRole,
  355. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  356. Name: "default",
  357. },
  358. },
  359. }
  360. err := s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  361. Expect(err).ToNot(HaveOccurred())
  362. }
  363. func referentSecretStoreName(f *framework.Framework) string {
  364. return "referent-provider-" + f.Namespace.Name
  365. }