provider.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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 azure
  14. import (
  15. "os"
  16. "strings"
  17. "sync"
  18. "time"
  19. "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
  20. "github.com/Azure/go-autorest/autorest"
  21. "github.com/Azure/go-autorest/autorest/azure"
  22. kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
  23. // nolint
  24. . "github.com/onsi/ginkgo/v2"
  25. // nolint
  26. . "github.com/onsi/gomega"
  27. v1 "k8s.io/api/core/v1"
  28. apierrors "k8s.io/apimachinery/pkg/api/errors"
  29. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  30. utilpointer "k8s.io/utils/pointer"
  31. "github.com/external-secrets/external-secrets-e2e/framework"
  32. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  33. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  34. esoazkv "github.com/external-secrets/external-secrets/providers/v1/azure/keyvault"
  35. )
  36. type azureProvider struct {
  37. clientID string
  38. clientSecret string
  39. tenantID string
  40. vaultURL string
  41. client *keyvault.BaseClient
  42. framework *framework.Framework
  43. }
  44. // newFromEnv creates a new Azure KeyVault e2e test provider
  45. // which uses client credentials flow to authenticate with azure.
  46. func newFromEnv(f *framework.Framework) *azureProvider {
  47. vaultURL := os.Getenv("TFC_VAULT_URL")
  48. tenantID := os.Getenv("TFC_AZURE_TENANT_ID")
  49. clientID := os.Getenv("TFC_AZURE_CLIENT_ID")
  50. clientSecret := os.Getenv("TFC_AZURE_CLIENT_SECRET")
  51. basicClient := keyvault.New()
  52. prov := &azureProvider{
  53. framework: f,
  54. clientID: clientID,
  55. tenantID: tenantID,
  56. vaultURL: vaultURL,
  57. client: &basicClient,
  58. clientSecret: clientSecret,
  59. }
  60. o := &sync.Once{}
  61. BeforeEach(func() {
  62. // run authorizor only if this spec is called
  63. // this allows us to run OTHER providers using GINKGO_LABELS without bailing out
  64. o.Do(func() {
  65. defer GinkgoRecover()
  66. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
  67. clientCredentialsConfig.Resource = "https://vault.azure.net"
  68. authorizer, err := clientCredentialsConfig.Authorizer()
  69. if err != nil {
  70. Fail(err.Error())
  71. }
  72. prov.client.Authorizer = authorizer
  73. })
  74. prov.CreateSecretStore()
  75. prov.CreateReferentSecretStore()
  76. prov.CreateSecretStoreNewSDK()
  77. prov.CreateReferentSecretStoreNewSDK()
  78. })
  79. return prov
  80. }
  81. // create a new provider from workload identity
  82. // the azwi webhook injects `AZURE_*` env vars into the container.
  83. // we use these credentials to authenticate with azure using the federated token flow.
  84. // please see here for details: https://azure.github.io/azure-workload-identity/docs/quick-start.html
  85. func newFromWorkloadIdentity(f *framework.Framework) *azureProvider {
  86. // from azwi webhook
  87. tenantID := os.Getenv("AZURE_TENANT_ID")
  88. clientID := os.Getenv("AZURE_CLIENT_ID")
  89. tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
  90. // from run.sh
  91. vaultURL := "https://eso-testing.vault.azure.net/"
  92. basicClient := keyvault.New()
  93. prov := &azureProvider{
  94. framework: f,
  95. client: &basicClient,
  96. clientID: clientID,
  97. tenantID: tenantID,
  98. vaultURL: vaultURL,
  99. }
  100. o := &sync.Once{}
  101. BeforeEach(func() {
  102. prov.CreateSecretStoreWithWI()
  103. // run authorizor only if this spec is called
  104. o.Do(func() {
  105. defer GinkgoRecover()
  106. token, err := os.ReadFile(tokenFilePath)
  107. if err != nil {
  108. Fail(err.Error())
  109. }
  110. // exchange the federated token for an access token
  111. aadEndpoint := esoazkv.AadEndpointForType(esv1.AzureEnvironmentPublicCloud)
  112. kvResource := strings.TrimSuffix(azure.PublicCloud.KeyVaultEndpoint, "/")
  113. tokenProvider, err := esoazkv.NewTokenProvider(GinkgoT().Context(), string(token), clientID, tenantID, aadEndpoint, kvResource)
  114. if err != nil {
  115. Fail(err.Error())
  116. }
  117. basicClient.Authorizer = autorest.NewBearerAuthorizer(tokenProvider)
  118. })
  119. })
  120. return prov
  121. }
  122. func (s *azureProvider) CreateSecret(key string, val framework.SecretEntry) {
  123. _, err := s.client.SetSecret(
  124. GinkgoT().Context(),
  125. s.vaultURL,
  126. key,
  127. keyvault.SecretSetParameters{
  128. Value: &val.Value,
  129. SecretAttributes: &keyvault.SecretAttributes{
  130. RecoveryLevel: keyvault.Purgeable,
  131. Enabled: utilpointer.Bool(true),
  132. },
  133. })
  134. Expect(err).ToNot(HaveOccurred())
  135. }
  136. func (s *azureProvider) DeleteSecret(key string) {
  137. _, err := s.client.DeleteSecret(
  138. GinkgoT().Context(),
  139. s.vaultURL,
  140. key)
  141. Expect(err).ToNot(HaveOccurred())
  142. }
  143. func (s *azureProvider) CreateKey(key string) *keyvault.JSONWebKey {
  144. out, err := s.client.CreateKey(
  145. GinkgoT().Context(),
  146. s.vaultURL,
  147. key,
  148. keyvault.KeyCreateParameters{
  149. Kty: keyvault.RSA,
  150. KeyAttributes: &keyvault.KeyAttributes{
  151. RecoveryLevel: keyvault.Purgeable,
  152. Enabled: utilpointer.Bool(true),
  153. },
  154. },
  155. )
  156. Expect(err).ToNot(HaveOccurred())
  157. return out.Key
  158. }
  159. func (s *azureProvider) DeleteKey(key string) {
  160. _, err := s.client.DeleteKey(GinkgoT().Context(), s.vaultURL, key)
  161. Expect(err).ToNot(HaveOccurred())
  162. }
  163. func (s *azureProvider) CreateCertificate(key string) {
  164. _, err := s.client.CreateCertificate(
  165. GinkgoT().Context(),
  166. s.vaultURL,
  167. key,
  168. keyvault.CertificateCreateParameters{
  169. CertificatePolicy: &keyvault.CertificatePolicy{
  170. X509CertificateProperties: &keyvault.X509CertificateProperties{
  171. Subject: utilpointer.String("CN=e2e.test"),
  172. ValidityInMonths: utilpointer.Int32(42),
  173. },
  174. IssuerParameters: &keyvault.IssuerParameters{
  175. Name: utilpointer.String("Self"),
  176. },
  177. Attributes: &keyvault.CertificateAttributes{
  178. RecoveryLevel: keyvault.Purgeable,
  179. Enabled: utilpointer.Bool(true),
  180. },
  181. },
  182. CertificateAttributes: &keyvault.CertificateAttributes{
  183. RecoveryLevel: keyvault.Purgeable,
  184. Enabled: utilpointer.Bool(true),
  185. },
  186. },
  187. )
  188. Expect(err).ToNot(HaveOccurred())
  189. }
  190. func (s *azureProvider) GetCertificate(key string) []byte {
  191. attempts := 60
  192. for {
  193. out, err := s.client.GetCertificate(
  194. GinkgoT().Context(),
  195. s.vaultURL,
  196. key,
  197. "",
  198. )
  199. Expect(err).ToNot(HaveOccurred())
  200. if out.Cer != nil {
  201. return *out.Cer
  202. }
  203. attempts--
  204. if attempts <= 0 {
  205. Fail("failed fetching azkv certificate")
  206. }
  207. <-time.After(time.Second * 5)
  208. }
  209. }
  210. func (s *azureProvider) DeleteCertificate(key string) {
  211. _, err := s.client.DeleteCertificate(GinkgoT().Context(), s.vaultURL, key)
  212. Expect(err).ToNot(HaveOccurred())
  213. }
  214. const (
  215. staticSecretName = "provider-secret"
  216. referentSecretName = "referent-secret"
  217. workloadIdentityServiceAccountNme = "external-secrets-operator"
  218. credentialKeyClientID = "client-id"
  219. credentialKeyClientSecret = "client-secret"
  220. )
  221. func newProviderWithStaticCredentials(tenantID, vaultURL, secretName string) *esv1.AzureKVProvider {
  222. return &esv1.AzureKVProvider{
  223. TenantID: &tenantID,
  224. VaultURL: &vaultURL,
  225. AuthSecretRef: &esv1.AzureKVAuth{
  226. ClientID: &esmeta.SecretKeySelector{
  227. Name: staticSecretName,
  228. Key: credentialKeyClientID,
  229. },
  230. ClientSecret: &esmeta.SecretKeySelector{
  231. Name: staticSecretName,
  232. Key: credentialKeyClientSecret,
  233. },
  234. },
  235. }
  236. }
  237. func newProviderWithStaticCredentialsNewSDK(tenantID, vaultURL, secretName string) *esv1.AzureKVProvider {
  238. useNewSDK := true
  239. return &esv1.AzureKVProvider{
  240. TenantID: &tenantID,
  241. VaultURL: &vaultURL,
  242. UseAzureSDK: &useNewSDK,
  243. AuthSecretRef: &esv1.AzureKVAuth{
  244. ClientID: &esmeta.SecretKeySelector{
  245. Name: staticSecretName,
  246. Key: credentialKeyClientID,
  247. },
  248. ClientSecret: &esmeta.SecretKeySelector{
  249. Name: staticSecretName,
  250. Key: credentialKeyClientSecret,
  251. },
  252. },
  253. }
  254. }
  255. func newProviderWithServiceAccount(tenantID, vaultURL string, authType esv1.AzureAuthType, serviceAccountName string, serviceAccountNamespace *string) *esv1.AzureKVProvider {
  256. return &esv1.AzureKVProvider{
  257. TenantID: &tenantID,
  258. VaultURL: &vaultURL,
  259. AuthType: &authType,
  260. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  261. Name: serviceAccountName,
  262. Namespace: serviceAccountNamespace,
  263. },
  264. }
  265. }
  266. func (s *azureProvider) CreateSecretStore() {
  267. azureCreds := &v1.Secret{
  268. ObjectMeta: metav1.ObjectMeta{
  269. Name: staticSecretName,
  270. Namespace: s.framework.Namespace.Name,
  271. },
  272. StringData: map[string]string{
  273. credentialKeyClientID: s.clientID,
  274. credentialKeyClientSecret: s.clientSecret,
  275. },
  276. }
  277. err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
  278. Expect(err).ToNot(HaveOccurred())
  279. secretStore := &esv1.SecretStore{
  280. ObjectMeta: metav1.ObjectMeta{
  281. Name: s.framework.Namespace.Name,
  282. Namespace: s.framework.Namespace.Name,
  283. },
  284. Spec: esv1.SecretStoreSpec{
  285. Provider: &esv1.SecretStoreProvider{
  286. AzureKV: newProviderWithStaticCredentials(s.tenantID, s.vaultURL, staticSecretName),
  287. },
  288. },
  289. }
  290. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  291. Expect(err).ToNot(HaveOccurred())
  292. }
  293. func (s *azureProvider) CreateSecretStoreNewSDK() {
  294. azureCreds := &v1.Secret{
  295. ObjectMeta: metav1.ObjectMeta{
  296. Name: staticSecretName,
  297. Namespace: s.framework.Namespace.Name,
  298. },
  299. StringData: map[string]string{
  300. credentialKeyClientID: s.clientID,
  301. credentialKeyClientSecret: s.clientSecret,
  302. },
  303. }
  304. err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
  305. // Ignore AlreadyExists error since CreateSecretStore() might have already created this secret
  306. if err != nil && !apierrors.IsAlreadyExists(err) {
  307. Expect(err).ToNot(HaveOccurred())
  308. }
  309. secretStore := &esv1.SecretStore{
  310. ObjectMeta: metav1.ObjectMeta{
  311. Name: s.framework.Namespace.Name + "-new-sdk",
  312. Namespace: s.framework.Namespace.Name,
  313. },
  314. Spec: esv1.SecretStoreSpec{
  315. Provider: &esv1.SecretStoreProvider{
  316. AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, staticSecretName),
  317. },
  318. },
  319. }
  320. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  321. Expect(err).ToNot(HaveOccurred())
  322. }
  323. func (s *azureProvider) CreateReferentSecretStore() {
  324. azureCreds := &v1.Secret{
  325. ObjectMeta: metav1.ObjectMeta{
  326. Name: referentSecretName,
  327. Namespace: s.framework.Namespace.Name,
  328. },
  329. StringData: map[string]string{
  330. credentialKeyClientID: s.clientID,
  331. credentialKeyClientSecret: s.clientSecret,
  332. },
  333. }
  334. err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
  335. Expect(err).ToNot(HaveOccurred())
  336. secretStore := &esv1.ClusterSecretStore{
  337. ObjectMeta: metav1.ObjectMeta{
  338. Name: referentAuthName(s.framework),
  339. Namespace: s.framework.Namespace.Name,
  340. },
  341. Spec: esv1.SecretStoreSpec{
  342. Provider: &esv1.SecretStoreProvider{
  343. AzureKV: newProviderWithStaticCredentials(s.tenantID, s.vaultURL, referentSecretName),
  344. },
  345. },
  346. }
  347. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  348. Expect(err).ToNot(HaveOccurred())
  349. }
  350. func (s *azureProvider) CreateReferentSecretStoreNewSDK() {
  351. azureCreds := &v1.Secret{
  352. ObjectMeta: metav1.ObjectMeta{
  353. Name: referentSecretName + "-new-sdk",
  354. Namespace: s.framework.Namespace.Name,
  355. },
  356. StringData: map[string]string{
  357. credentialKeyClientID: s.clientID,
  358. credentialKeyClientSecret: s.clientSecret,
  359. },
  360. }
  361. err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
  362. Expect(err).ToNot(HaveOccurred())
  363. secretStore := &esv1.ClusterSecretStore{
  364. ObjectMeta: metav1.ObjectMeta{
  365. Name: referentAuthName(s.framework) + "-new-sdk",
  366. Namespace: s.framework.Namespace.Name,
  367. },
  368. Spec: esv1.SecretStoreSpec{
  369. Provider: &esv1.SecretStoreProvider{
  370. AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, referentSecretName+"-new-sdk"),
  371. },
  372. },
  373. }
  374. err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
  375. Expect(err).ToNot(HaveOccurred())
  376. }
  377. func referentAuthName(f *framework.Framework) string {
  378. return "referent-auth-" + f.Namespace.Name
  379. }
  380. func (s *azureProvider) CreateSecretStoreWithWI() {
  381. authType := esv1.AzureWorkloadIdentity
  382. namespace := "external-secrets-operator"
  383. ClusterSecretStore := &esv1.ClusterSecretStore{
  384. ObjectMeta: metav1.ObjectMeta{
  385. Name: s.framework.Namespace.Name,
  386. },
  387. Spec: esv1.SecretStoreSpec{
  388. Provider: &esv1.SecretStoreProvider{
  389. AzureKV: newProviderWithServiceAccount(s.tenantID, s.vaultURL, authType, workloadIdentityServiceAccountNme, &namespace),
  390. },
  391. },
  392. }
  393. err := s.framework.CRClient.Create(GinkgoT().Context(), ClusterSecretStore)
  394. Expect(err).ToNot(HaveOccurred())
  395. }
  396. func (s *azureProvider) CreateReferentSecretStoreWithWI() {
  397. authType := esv1.AzureWorkloadIdentity
  398. ClusterSecretStore := &esv1.ClusterSecretStore{
  399. ObjectMeta: metav1.ObjectMeta{
  400. Name: referentAuthName(s.framework),
  401. },
  402. Spec: esv1.SecretStoreSpec{
  403. Provider: &esv1.SecretStoreProvider{
  404. AzureKV: newProviderWithServiceAccount(s.tenantID, s.vaultURL, authType, workloadIdentityServiceAccountNme, nil),
  405. },
  406. },
  407. }
  408. err := s.framework.CRClient.Create(GinkgoT().Context(), ClusterSecretStore)
  409. Expect(err).ToNot(HaveOccurred())
  410. }