| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- /*
- Copyright © 2025 ESO Maintainer Team
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package azure
- import (
- "os"
- "strings"
- "sync"
- "time"
- "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
- "github.com/Azure/go-autorest/autorest"
- "github.com/Azure/go-autorest/autorest/azure"
- kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
- // nolint
- . "github.com/onsi/ginkgo/v2"
- // nolint
- . "github.com/onsi/gomega"
- v1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- utilpointer "k8s.io/utils/pointer"
- "github.com/external-secrets/external-secrets-e2e/framework"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- esoazkv "github.com/external-secrets/external-secrets/providers/v1/azure/keyvault"
- )
- type azureProvider struct {
- clientID string
- clientSecret string
- tenantID string
- vaultURL string
- client *keyvault.BaseClient
- framework *framework.Framework
- }
- // newFromEnv creates a new Azure KeyVault e2e test provider
- // which uses client credentials flow to authenticate with azure.
- func newFromEnv(f *framework.Framework) *azureProvider {
- vaultURL := os.Getenv("TFC_VAULT_URL")
- tenantID := os.Getenv("TFC_AZURE_TENANT_ID")
- clientID := os.Getenv("TFC_AZURE_CLIENT_ID")
- clientSecret := os.Getenv("TFC_AZURE_CLIENT_SECRET")
- basicClient := keyvault.New()
- prov := &azureProvider{
- framework: f,
- clientID: clientID,
- tenantID: tenantID,
- vaultURL: vaultURL,
- client: &basicClient,
- clientSecret: clientSecret,
- }
- o := &sync.Once{}
- BeforeEach(func() {
- // run authorizor only if this spec is called
- // this allows us to run OTHER providers using GINKGO_LABELS without bailing out
- o.Do(func() {
- defer GinkgoRecover()
- clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
- clientCredentialsConfig.Resource = "https://vault.azure.net"
- authorizer, err := clientCredentialsConfig.Authorizer()
- if err != nil {
- Fail(err.Error())
- }
- prov.client.Authorizer = authorizer
- })
- prov.CreateSecretStore()
- prov.CreateReferentSecretStore()
- prov.CreateSecretStoreNewSDK()
- prov.CreateReferentSecretStoreNewSDK()
- })
- return prov
- }
- // create a new provider from workload identity
- // the azwi webhook injects `AZURE_*` env vars into the container.
- // we use these credentials to authenticate with azure using the federated token flow.
- // please see here for details: https://azure.github.io/azure-workload-identity/docs/quick-start.html
- func newFromWorkloadIdentity(f *framework.Framework) *azureProvider {
- // from azwi webhook
- tenantID := os.Getenv("AZURE_TENANT_ID")
- clientID := os.Getenv("AZURE_CLIENT_ID")
- tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
- // from run.sh
- vaultURL := "https://eso-testing.vault.azure.net/"
- basicClient := keyvault.New()
- prov := &azureProvider{
- framework: f,
- client: &basicClient,
- clientID: clientID,
- tenantID: tenantID,
- vaultURL: vaultURL,
- }
- o := &sync.Once{}
- BeforeEach(func() {
- prov.CreateSecretStoreWithWI()
- // run authorizor only if this spec is called
- o.Do(func() {
- defer GinkgoRecover()
- token, err := os.ReadFile(tokenFilePath)
- if err != nil {
- Fail(err.Error())
- }
- // exchange the federated token for an access token
- aadEndpoint := esoazkv.AadEndpointForType(esv1.AzureEnvironmentPublicCloud)
- kvResource := strings.TrimSuffix(azure.PublicCloud.KeyVaultEndpoint, "/")
- tokenProvider, err := esoazkv.NewTokenProvider(GinkgoT().Context(), string(token), clientID, tenantID, aadEndpoint, kvResource)
- if err != nil {
- Fail(err.Error())
- }
- basicClient.Authorizer = autorest.NewBearerAuthorizer(tokenProvider)
- })
- })
- return prov
- }
- func (s *azureProvider) CreateSecret(key string, val framework.SecretEntry) {
- _, err := s.client.SetSecret(
- GinkgoT().Context(),
- s.vaultURL,
- key,
- keyvault.SecretSetParameters{
- Value: &val.Value,
- SecretAttributes: &keyvault.SecretAttributes{
- RecoveryLevel: keyvault.Purgeable,
- Enabled: utilpointer.Bool(true),
- },
- })
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) DeleteSecret(key string) {
- _, err := s.client.DeleteSecret(
- GinkgoT().Context(),
- s.vaultURL,
- key)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateKey(key string) *keyvault.JSONWebKey {
- out, err := s.client.CreateKey(
- GinkgoT().Context(),
- s.vaultURL,
- key,
- keyvault.KeyCreateParameters{
- Kty: keyvault.RSA,
- KeyAttributes: &keyvault.KeyAttributes{
- RecoveryLevel: keyvault.Purgeable,
- Enabled: utilpointer.Bool(true),
- },
- },
- )
- Expect(err).ToNot(HaveOccurred())
- return out.Key
- }
- func (s *azureProvider) DeleteKey(key string) {
- _, err := s.client.DeleteKey(GinkgoT().Context(), s.vaultURL, key)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateCertificate(key string) {
- _, err := s.client.CreateCertificate(
- GinkgoT().Context(),
- s.vaultURL,
- key,
- keyvault.CertificateCreateParameters{
- CertificatePolicy: &keyvault.CertificatePolicy{
- X509CertificateProperties: &keyvault.X509CertificateProperties{
- Subject: utilpointer.String("CN=e2e.test"),
- ValidityInMonths: utilpointer.Int32(42),
- },
- IssuerParameters: &keyvault.IssuerParameters{
- Name: utilpointer.String("Self"),
- },
- Attributes: &keyvault.CertificateAttributes{
- RecoveryLevel: keyvault.Purgeable,
- Enabled: utilpointer.Bool(true),
- },
- },
- CertificateAttributes: &keyvault.CertificateAttributes{
- RecoveryLevel: keyvault.Purgeable,
- Enabled: utilpointer.Bool(true),
- },
- },
- )
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) GetCertificate(key string) []byte {
- attempts := 60
- for {
- out, err := s.client.GetCertificate(
- GinkgoT().Context(),
- s.vaultURL,
- key,
- "",
- )
- Expect(err).ToNot(HaveOccurred())
- if out.Cer != nil {
- return *out.Cer
- }
- attempts--
- if attempts <= 0 {
- Fail("failed fetching azkv certificate")
- }
- <-time.After(time.Second * 5)
- }
- }
- func (s *azureProvider) DeleteCertificate(key string) {
- _, err := s.client.DeleteCertificate(GinkgoT().Context(), s.vaultURL, key)
- Expect(err).ToNot(HaveOccurred())
- }
- const (
- staticSecretName = "provider-secret"
- referentSecretName = "referent-secret"
- workloadIdentityServiceAccountNme = "external-secrets-operator"
- credentialKeyClientID = "client-id"
- credentialKeyClientSecret = "client-secret"
- )
- func newProviderWithStaticCredentials(tenantID, vaultURL, secretName string) *esv1.AzureKVProvider {
- return &esv1.AzureKVProvider{
- TenantID: &tenantID,
- VaultURL: &vaultURL,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &esmeta.SecretKeySelector{
- Name: staticSecretName,
- Key: credentialKeyClientID,
- },
- ClientSecret: &esmeta.SecretKeySelector{
- Name: staticSecretName,
- Key: credentialKeyClientSecret,
- },
- },
- }
- }
- func newProviderWithStaticCredentialsNewSDK(tenantID, vaultURL, secretName string) *esv1.AzureKVProvider {
- useNewSDK := true
- return &esv1.AzureKVProvider{
- TenantID: &tenantID,
- VaultURL: &vaultURL,
- UseAzureSDK: &useNewSDK,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &esmeta.SecretKeySelector{
- Name: staticSecretName,
- Key: credentialKeyClientID,
- },
- ClientSecret: &esmeta.SecretKeySelector{
- Name: staticSecretName,
- Key: credentialKeyClientSecret,
- },
- },
- }
- }
- func newProviderWithServiceAccount(tenantID, vaultURL string, authType esv1.AzureAuthType, serviceAccountName string, serviceAccountNamespace *string) *esv1.AzureKVProvider {
- return &esv1.AzureKVProvider{
- TenantID: &tenantID,
- VaultURL: &vaultURL,
- AuthType: &authType,
- ServiceAccountRef: &esmeta.ServiceAccountSelector{
- Name: serviceAccountName,
- Namespace: serviceAccountNamespace,
- },
- }
- }
- func (s *azureProvider) CreateSecretStore() {
- azureCreds := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: staticSecretName,
- Namespace: s.framework.Namespace.Name,
- },
- StringData: map[string]string{
- credentialKeyClientID: s.clientID,
- credentialKeyClientSecret: s.clientSecret,
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
- Expect(err).ToNot(HaveOccurred())
- secretStore := &esv1.SecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: s.framework.Namespace.Name,
- Namespace: s.framework.Namespace.Name,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithStaticCredentials(s.tenantID, s.vaultURL, staticSecretName),
- },
- },
- }
- err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateSecretStoreNewSDK() {
- azureCreds := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: staticSecretName,
- Namespace: s.framework.Namespace.Name,
- },
- StringData: map[string]string{
- credentialKeyClientID: s.clientID,
- credentialKeyClientSecret: s.clientSecret,
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
- // Ignore AlreadyExists error since CreateSecretStore() might have already created this secret
- if err != nil && !apierrors.IsAlreadyExists(err) {
- Expect(err).ToNot(HaveOccurred())
- }
- secretStore := &esv1.SecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: s.framework.Namespace.Name + "-new-sdk",
- Namespace: s.framework.Namespace.Name,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, staticSecretName),
- },
- },
- }
- err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateReferentSecretStore() {
- azureCreds := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: referentSecretName,
- Namespace: s.framework.Namespace.Name,
- },
- StringData: map[string]string{
- credentialKeyClientID: s.clientID,
- credentialKeyClientSecret: s.clientSecret,
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
- Expect(err).ToNot(HaveOccurred())
- secretStore := &esv1.ClusterSecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: referentAuthName(s.framework),
- Namespace: s.framework.Namespace.Name,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithStaticCredentials(s.tenantID, s.vaultURL, referentSecretName),
- },
- },
- }
- err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateReferentSecretStoreNewSDK() {
- azureCreds := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: referentSecretName + "-new-sdk",
- Namespace: s.framework.Namespace.Name,
- },
- StringData: map[string]string{
- credentialKeyClientID: s.clientID,
- credentialKeyClientSecret: s.clientSecret,
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), azureCreds)
- Expect(err).ToNot(HaveOccurred())
- secretStore := &esv1.ClusterSecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: referentAuthName(s.framework) + "-new-sdk",
- Namespace: s.framework.Namespace.Name,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, referentSecretName+"-new-sdk"),
- },
- },
- }
- err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
- Expect(err).ToNot(HaveOccurred())
- }
- func referentAuthName(f *framework.Framework) string {
- return "referent-auth-" + f.Namespace.Name
- }
- func (s *azureProvider) CreateSecretStoreWithWI() {
- authType := esv1.AzureWorkloadIdentity
- namespace := "external-secrets-operator"
- ClusterSecretStore := &esv1.ClusterSecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: s.framework.Namespace.Name,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithServiceAccount(s.tenantID, s.vaultURL, authType, workloadIdentityServiceAccountNme, &namespace),
- },
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), ClusterSecretStore)
- Expect(err).ToNot(HaveOccurred())
- }
- func (s *azureProvider) CreateReferentSecretStoreWithWI() {
- authType := esv1.AzureWorkloadIdentity
- ClusterSecretStore := &esv1.ClusterSecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: referentAuthName(s.framework),
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: newProviderWithServiceAccount(s.tenantID, s.vaultURL, authType, workloadIdentityServiceAccountNme, nil),
- },
- },
- }
- err := s.framework.CRClient.Create(GinkgoT().Context(), ClusterSecretStore)
- Expect(err).ToNot(HaveOccurred())
- }
|