vault.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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. "time"
  18. apierrors "k8s.io/apimachinery/pkg/api/errors"
  19. "k8s.io/apimachinery/pkg/types"
  20. "k8s.io/apimachinery/pkg/util/wait"
  21. // nolint
  22. . "github.com/onsi/ginkgo/v2"
  23. . "github.com/onsi/gomega"
  24. v1 "k8s.io/api/core/v1"
  25. "github.com/external-secrets/external-secrets-e2e/framework"
  26. "github.com/external-secrets/external-secrets-e2e/framework/addon"
  27. "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
  28. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. )
  30. const (
  31. withTokenAuth = "with token auth"
  32. withTokenAuthAndMTLS = "with token auth and mTLS"
  33. withCertAuth = "with cert auth"
  34. withApprole = "with approle auth"
  35. withV1 = "with v1 provider"
  36. withJWT = "with jwt provider"
  37. withJWTK8s = "with jwt k8s provider"
  38. withK8s = "with kubernetes provider"
  39. withReferentAuth = "with referent provider"
  40. withReferentAuthAndMTLS = "with referent provider and mTLS"
  41. )
  42. var _ = Describe("[vault]", Label("vault"), Ordered, func() {
  43. f := framework.New("vault")
  44. vault := addon.NewVault()
  45. prov := newVaultProvider(f, vault)
  46. BeforeAll(func() {
  47. addon.InstallGlobalAddon(vault)
  48. })
  49. DescribeTable("sync secrets",
  50. framework.TableFuncWithExternalSecret(f, prov),
  51. // uses token auth
  52. framework.Compose(withTokenAuth, f, common.FindByName, useTokenAuth(prov)),
  53. framework.Compose(withTokenAuth, f, common.FindByNameAndRewrite, useTokenAuth(prov)),
  54. framework.Compose(withTokenAuth, f, common.JSONDataFromSync, useTokenAuth(prov)),
  55. framework.Compose(withTokenAuth, f, common.JSONDataFromRewrite, useTokenAuth(prov)),
  56. framework.Compose(withTokenAuth, f, common.JSONDataWithProperty, useTokenAuth(prov)),
  57. framework.Compose(withTokenAuth, f, common.JSONDataWithTemplate, useTokenAuth(prov)),
  58. framework.Compose(withTokenAuth, f, common.DataPropertyDockerconfigJSON, useTokenAuth(prov)),
  59. framework.Compose(withTokenAuth, f, common.JSONDataWithoutTargetName, useTokenAuth(prov)),
  60. framework.Compose(withTokenAuth, f, common.DecodingPolicySync, useTokenAuth(prov)),
  61. framework.Compose(withTokenAuth, f, common.JSONDataWithTemplateFromLiteral, useTokenAuth(prov)),
  62. framework.Compose(withTokenAuth, f, common.TemplateFromConfigmaps, useTokenAuth(prov)),
  63. // use cert auth
  64. framework.Compose(withCertAuth, f, common.FindByName, useCertAuth(prov)),
  65. framework.Compose(withCertAuth, f, common.FindByNameAndRewrite, useCertAuth(prov)),
  66. framework.Compose(withCertAuth, f, common.JSONDataFromSync, useCertAuth(prov)),
  67. framework.Compose(withCertAuth, f, common.JSONDataFromRewrite, useCertAuth(prov)),
  68. framework.Compose(withCertAuth, f, common.JSONDataWithProperty, useCertAuth(prov)),
  69. framework.Compose(withCertAuth, f, common.JSONDataWithTemplate, useCertAuth(prov)),
  70. framework.Compose(withCertAuth, f, common.DataPropertyDockerconfigJSON, useCertAuth(prov)),
  71. framework.Compose(withCertAuth, f, common.JSONDataWithoutTargetName, useCertAuth(prov)),
  72. // use approle auth
  73. framework.Compose(withApprole, f, common.FindByName, useApproleAuth(prov)),
  74. framework.Compose(withApprole, f, common.FindByNameAndRewrite, useApproleAuth(prov)),
  75. framework.Compose(withApprole, f, common.JSONDataFromSync, useApproleAuth(prov)),
  76. framework.Compose(withApprole, f, common.JSONDataFromRewrite, useApproleAuth(prov)),
  77. framework.Compose(withApprole, f, common.JSONDataWithProperty, useApproleAuth(prov)),
  78. framework.Compose(withApprole, f, common.JSONDataWithTemplate, useApproleAuth(prov)),
  79. framework.Compose(withApprole, f, common.DataPropertyDockerconfigJSON, useApproleAuth(prov)),
  80. framework.Compose(withApprole, f, common.JSONDataWithoutTargetName, useApproleAuth(prov)),
  81. // use v1 provider
  82. framework.Compose(withV1, f, common.FindByName, useV1Provider(prov)),
  83. framework.Compose(withV1, f, common.FindByNameAndRewrite, useV1Provider(prov)),
  84. framework.Compose(withV1, f, common.JSONDataFromSync, useV1Provider(prov)),
  85. framework.Compose(withV1, f, common.JSONDataFromRewrite, useV1Provider(prov)),
  86. framework.Compose(withV1, f, common.JSONDataWithProperty, useV1Provider(prov)),
  87. framework.Compose(withV1, f, common.JSONDataWithTemplate, useV1Provider(prov)),
  88. framework.Compose(withV1, f, common.DataPropertyDockerconfigJSON, useV1Provider(prov)),
  89. framework.Compose(withV1, f, common.JSONDataWithoutTargetName, useV1Provider(prov)),
  90. // use jwt provider
  91. framework.Compose(withJWT, f, common.FindByName, useJWTProvider(prov)),
  92. framework.Compose(withJWT, f, common.FindByNameAndRewrite, useJWTProvider(prov)),
  93. framework.Compose(withJWT, f, common.JSONDataFromSync, useJWTProvider(prov)),
  94. framework.Compose(withJWT, f, common.JSONDataFromRewrite, useJWTProvider(prov)),
  95. framework.Compose(withJWT, f, common.JSONDataWithProperty, useJWTProvider(prov)),
  96. framework.Compose(withJWT, f, common.JSONDataWithTemplate, useJWTProvider(prov)),
  97. framework.Compose(withJWT, f, common.DataPropertyDockerconfigJSON, useJWTProvider(prov)),
  98. framework.Compose(withJWT, f, common.JSONDataWithoutTargetName, useJWTProvider(prov)),
  99. // use jwt k8s provider
  100. framework.Compose(withJWTK8s, f, common.JSONDataFromSync, useJWTK8sProvider(prov)),
  101. framework.Compose(withJWTK8s, f, common.JSONDataFromRewrite, useJWTK8sProvider(prov)),
  102. framework.Compose(withJWTK8s, f, common.JSONDataWithProperty, useJWTK8sProvider(prov)),
  103. framework.Compose(withJWTK8s, f, common.JSONDataWithTemplate, useJWTK8sProvider(prov)),
  104. framework.Compose(withJWTK8s, f, common.DataPropertyDockerconfigJSON, useJWTK8sProvider(prov)),
  105. framework.Compose(withJWTK8s, f, common.JSONDataWithoutTargetName, useJWTK8sProvider(prov)),
  106. // use kubernetes provider
  107. framework.Compose(withK8s, f, common.FindByName, useKubernetesProvider(prov)),
  108. framework.Compose(withK8s, f, common.FindByNameAndRewrite, useKubernetesProvider(prov)),
  109. framework.Compose(withK8s, f, common.JSONDataFromSync, useKubernetesProvider(prov)),
  110. framework.Compose(withK8s, f, common.JSONDataFromRewrite, useKubernetesProvider(prov)),
  111. framework.Compose(withK8s, f, common.JSONDataWithProperty, useKubernetesProvider(prov)),
  112. framework.Compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider(prov)),
  113. framework.Compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider(prov)),
  114. framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider(prov)),
  115. // use referent auth
  116. framework.Compose(withReferentAuth, f, common.JSONDataFromSync, useReferentAuth(prov)),
  117. // vault-specific test cases
  118. Entry("secret value via data without property should return json-encoded string", Label("json"), testJSONWithoutProperty(prov)),
  119. Entry("secret value via data with property should return json-encoded string", Label("json"), testJSONWithProperty(prov)),
  120. Entry("dataFrom without property should extract key/value pairs", Label("json"), testDataFromJSONWithoutProperty(prov)),
  121. Entry("dataFrom with property should extract key/value pairs", Label("json"), testDataFromJSONWithProperty(prov)),
  122. // mTLS
  123. framework.Compose(withTokenAuthAndMTLS, f, common.FindByName, useMTLSAndTokenAuth(prov)),
  124. framework.Compose(withReferentAuthAndMTLS, f, common.JSONDataFromSync, useMTLSAndReferentAuth(prov)),
  125. Entry("store without clientTLS configuration should not be valid", Label("vault-invalid-store"), testInvalidMtlsStore(prov)),
  126. )
  127. })
  128. func useTokenAuth(prov *vaultProvider) func(*framework.TestCase) {
  129. return func(tc *framework.TestCase) {
  130. prov.CreateTokenStore()
  131. tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name
  132. }
  133. }
  134. func useMTLSAndTokenAuth(prov *vaultProvider) func(*framework.TestCase) {
  135. return func(tc *framework.TestCase) {
  136. prov.CreateTokenStore(WithMTLS)
  137. tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name + mtlsSuffix
  138. }
  139. }
  140. func useCertAuth(prov *vaultProvider) func(*framework.TestCase) {
  141. return func(tc *framework.TestCase) {
  142. prov.CreateCertStore()
  143. tc.ExternalSecret.Spec.SecretStoreRef.Name = certAuthProviderName
  144. }
  145. }
  146. func useApproleAuth(prov *vaultProvider) func(*framework.TestCase) {
  147. return func(tc *framework.TestCase) {
  148. prov.CreateAppRoleStore()
  149. tc.ExternalSecret.Spec.SecretStoreRef.Name = appRoleAuthProviderName
  150. }
  151. }
  152. func useV1Provider(prov *vaultProvider) func(*framework.TestCase) {
  153. return func(tc *framework.TestCase) {
  154. prov.CreateV1Store()
  155. tc.ExternalSecret.Spec.SecretStoreRef.Name = kvv1ProviderName
  156. }
  157. }
  158. func useJWTProvider(prov *vaultProvider) func(*framework.TestCase) {
  159. return func(tc *framework.TestCase) {
  160. prov.CreateJWTStore()
  161. tc.ExternalSecret.Spec.SecretStoreRef.Name = jwtProviderName
  162. }
  163. }
  164. func useJWTK8sProvider(prov *vaultProvider) func(*framework.TestCase) {
  165. return func(tc *framework.TestCase) {
  166. prov.CreateJWTK8sStore()
  167. tc.ExternalSecret.Spec.SecretStoreRef.Name = jwtK8sProviderName
  168. }
  169. }
  170. func useKubernetesProvider(prov *vaultProvider) func(*framework.TestCase) {
  171. return func(tc *framework.TestCase) {
  172. prov.CreateKubernetesAuthStore()
  173. tc.ExternalSecret.Spec.SecretStoreRef.Name = kubernetesProviderName
  174. }
  175. }
  176. func useReferentAuth(prov *vaultProvider) func(*framework.TestCase) {
  177. return func(tc *framework.TestCase) {
  178. prov.CreateReferentTokenStore()
  179. tc.ExternalSecret.Spec.SecretStoreRef.Name = referentSecretStoreName(tc.Framework)
  180. tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
  181. }
  182. }
  183. func useMTLSAndReferentAuth(prov *vaultProvider) func(*framework.TestCase) {
  184. return func(tc *framework.TestCase) {
  185. prov.CreateReferentTokenStore(WithMTLS)
  186. tc.ExternalSecret.Spec.SecretStoreRef.Name = referentSecretStoreName(tc.Framework) + mtlsSuffix
  187. tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
  188. }
  189. }
  190. const jsonVal = `{"foo":{"nested":{"bar":"mysecret","baz":"bang"}}}`
  191. // when no property is set it should return the json-encoded at path.
  192. func testJSONWithoutProperty(prov *vaultProvider) func(*framework.TestCase) {
  193. return func(tc *framework.TestCase) {
  194. prov.CreateTokenStore()
  195. secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
  196. tc.Secrets = map[string]framework.SecretEntry{
  197. secretKey: {Value: jsonVal},
  198. }
  199. tc.ExpectedSecret = &v1.Secret{
  200. Type: v1.SecretTypeOpaque,
  201. Data: map[string][]byte{
  202. secretKey: []byte(jsonVal),
  203. },
  204. }
  205. tc.ExternalSecret.Spec.Data = []esapi.ExternalSecretData{
  206. {
  207. SecretKey: secretKey,
  208. RemoteRef: esapi.ExternalSecretDataRemoteRef{
  209. Key: secretKey,
  210. },
  211. },
  212. }
  213. }
  214. }
  215. // when property is set it should return the json-encoded at path.
  216. func testJSONWithProperty(prov *vaultProvider) func(*framework.TestCase) {
  217. return func(tc *framework.TestCase) {
  218. prov.CreateTokenStore()
  219. secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
  220. expectedVal := `{"bar":"mysecret","baz":"bang"}`
  221. tc.Secrets = map[string]framework.SecretEntry{
  222. secretKey: {Value: jsonVal},
  223. }
  224. tc.ExpectedSecret = &v1.Secret{
  225. Type: v1.SecretTypeOpaque,
  226. Data: map[string][]byte{
  227. secretKey: []byte(expectedVal),
  228. },
  229. }
  230. tc.ExternalSecret.Spec.Data = []esapi.ExternalSecretData{
  231. {
  232. SecretKey: secretKey,
  233. RemoteRef: esapi.ExternalSecretDataRemoteRef{
  234. Key: secretKey,
  235. Property: "foo.nested",
  236. },
  237. },
  238. }
  239. }
  240. }
  241. // when no property is set it should extract the key/value pairs at the given path
  242. // note: it should json-encode if a value contains nested data
  243. func testDataFromJSONWithoutProperty(prov *vaultProvider) func(*framework.TestCase) {
  244. return func(tc *framework.TestCase) {
  245. prov.CreateTokenStore()
  246. secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
  247. tc.Secrets = map[string]framework.SecretEntry{
  248. secretKey: {Value: jsonVal},
  249. }
  250. tc.ExpectedSecret = &v1.Secret{
  251. Type: v1.SecretTypeOpaque,
  252. Data: map[string][]byte{
  253. "foo": []byte(`{"nested":{"bar":"mysecret","baz":"bang"}}`),
  254. },
  255. }
  256. tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
  257. {
  258. Extract: &esapi.ExternalSecretDataRemoteRef{
  259. Key: secretKey,
  260. },
  261. },
  262. }
  263. }
  264. }
  265. // when property is set it should extract values with dataFrom at the given path.
  266. func testDataFromJSONWithProperty(prov *vaultProvider) func(*framework.TestCase) {
  267. return func(tc *framework.TestCase) {
  268. prov.CreateTokenStore()
  269. secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
  270. tc.Secrets = map[string]framework.SecretEntry{
  271. secretKey: {Value: jsonVal},
  272. }
  273. tc.ExpectedSecret = &v1.Secret{
  274. Type: v1.SecretTypeOpaque,
  275. Data: map[string][]byte{
  276. "bar": []byte(`mysecret`),
  277. "baz": []byte(`bang`),
  278. },
  279. }
  280. tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
  281. {
  282. Extract: &esapi.ExternalSecretDataRemoteRef{
  283. Key: secretKey,
  284. Property: "foo.nested",
  285. },
  286. },
  287. }
  288. }
  289. }
  290. func testInvalidMtlsStore(prov *vaultProvider) func(*framework.TestCase) {
  291. return func(tc *framework.TestCase) {
  292. prov.CreateTokenStore(WithInvalidMTLS)
  293. tc.ExternalSecret = nil
  294. tc.ExpectedSecret = nil
  295. err := wait.PollUntilContextTimeout(GinkgoT().Context(), time.Second*10, time.Minute, true, func(ctx context.Context) (bool, error) {
  296. var ss esapi.SecretStore
  297. err := tc.Framework.CRClient.Get(ctx, types.NamespacedName{
  298. Namespace: tc.Framework.Namespace.Name,
  299. Name: tc.Framework.Namespace.Name + invalidMtlSuffix,
  300. }, &ss)
  301. if apierrors.IsNotFound(err) {
  302. return false, nil
  303. }
  304. if len(ss.Status.Conditions) == 0 {
  305. return false, nil
  306. }
  307. Expect(string(ss.Status.Conditions[0].Type)).Should(Equal("Ready"))
  308. Expect(string(ss.Status.Conditions[0].Status)).Should(Equal("False"))
  309. Expect(ss.Status.Conditions[0].Reason).Should(Equal("InvalidProviderConfig"))
  310. Expect(ss.Status.Conditions[0].Message).Should(ContainSubstring("unable to validate store"))
  311. return true, nil
  312. })
  313. Expect(err).ToNot(HaveOccurred())
  314. }
  315. }