Bladeren bron

feat: gate v2 providers behind explicit opt-in

Moritz Johner 3 maanden geleden
bovenliggende
commit
62ad1ff507

+ 5 - 5
Makefile

@@ -158,11 +158,11 @@ test.e2e.managed: generate ## Run e2e tests managed
 test.e2e.v2: generate manifests docker.build.e2e docker.build.providers ## Run V2 E2E tests
 	@$(INFO) Installing ESO V2 for E2E tests
 	./hack/install-eso-v2-e2e.sh
-#@$(INFO) Running V2 E2E tests
-#cd e2e && ginkgo -v --label-filter="v2" ./suites/v2/...
-#@$(INFO) Cleaning up ESO V2
-#./hack/uninstall-eso-v2-e2e.sh
-	#@$(OK) V2 E2E tests complete
+	@$(INFO) Running V2 E2E tests
+	cd e2e && ACK_GINKGO_DEPRECATIONS=2.9.5 ginkgo -v --label-filter="v2" ./suites/v2/...
+	@$(INFO) Cleaning up ESO V2
+	./hack/uninstall-eso-v2-e2e.sh
+	@$(OK) V2 E2E tests complete
 
 .PHONY: test.crds
 test.crds: cty crds.generate.tests ## Test CRDs for modification and backwards compatibility

+ 2 - 2
apis/externalsecrets/v1alpha1/pushsecret_types.go

@@ -45,10 +45,10 @@ type PushSecretStoreRef struct {
 	// +optional
 	LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
 
-	// Kind of the SecretStore resource (SecretStore, ClusterSecretStore, or Provider)
+	// Kind of the SecretStore resource (SecretStore, ClusterSecretStore, Provider, or ClusterProvider)
 	// +optional
 	// +kubebuilder:default="SecretStore"
-	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore;Provider
+	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore;Provider;ClusterProvider
 	Kind string `json:"kind,omitempty"`
 
 	// APIVersion of the SecretStore resource (external-secrets.io/v1 or secretstore.external-secrets.io/v2alpha1)

+ 43 - 25
cmd/controller/root.go

@@ -32,6 +32,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
@@ -57,6 +58,8 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore/cssmetrics"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore/ssmetrics"
+	grpccommon "github.com/external-secrets/external-secrets/providers/v2/common/grpc"
+	"github.com/external-secrets/external-secrets/runtime/clientmanager"
 	"github.com/external-secrets/external-secrets/runtime/feature"
 
 	// To allow using gcp auth.
@@ -107,6 +110,7 @@ var (
 	tlsMinVersion                         string
 	enableHTTP2                           bool
 	allowGenericTargets                   bool
+	enableV2Providers                     bool
 	providerNamespace                     string
 	providerServiceNames                  []string
 )
@@ -140,6 +144,16 @@ var rootCmd = &cobra.Command{
 
 		ctrlmetrics.SetUpLabelNames(enableExtendedMetricLabels)
 		esmetrics.SetUpMetrics()
+		if enableV2Providers {
+			if err := clientmanager.RegisterMetrics(); err != nil {
+				setupLog.Error(err, "unable to register clientmanager metrics")
+				os.Exit(1)
+			}
+			if err := grpccommon.RegisterMetrics(crmetrics.Registry); err != nil {
+				setupLog.Error(err, "unable to register grpc metrics")
+				os.Exit(1)
+			}
+		}
 		config := ctrl.GetConfigOrDie()
 		config.QPS = clientQPS
 		config.Burst = clientBurst
@@ -245,32 +259,34 @@ var rootCmd = &cobra.Command{
 				os.Exit(1)
 			}
 		}
-		provider.SetUpMetrics()
-		if err = (&provider.Reconciler{
-			Client:          mgr.GetClient(),
-			Log:             ctrl.Log.WithName("controllers").WithName("Provider"),
-			Scheme:          mgr.GetScheme(),
-			RequeueInterval: storeRequeueInterval,
-		}).SetupWithManager(mgr, controller.Options{
-			MaxConcurrentReconciles: concurrent,
-			RateLimiter:             ctrlcommon.BuildRateLimiter(),
-		}); err != nil {
-			setupLog.Error(err, errCreateController, "controller", "Provider")
-			os.Exit(1)
-		}
+		if enableV2Providers {
+			provider.SetUpMetrics()
+			if err = (&provider.Reconciler{
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("Provider"),
+				Scheme:          mgr.GetScheme(),
+				RequeueInterval: storeRequeueInterval,
+			}).SetupWithManager(mgr, controller.Options{
+				MaxConcurrentReconciles: concurrent,
+				RateLimiter:             ctrlcommon.BuildRateLimiter(),
+			}); err != nil {
+				setupLog.Error(err, errCreateController, "controller", "Provider")
+				os.Exit(1)
+			}
 
-		clusterprovider.SetUpMetrics()
-		if err = (&clusterprovider.Reconciler{
-			Client:          mgr.GetClient(),
-			Log:             ctrl.Log.WithName("controllers").WithName("ClusterProvider"),
-			Scheme:          mgr.GetScheme(),
-			RequeueInterval: storeRequeueInterval,
-		}).SetupWithManager(mgr, controller.Options{
-			MaxConcurrentReconciles: concurrent,
-			RateLimiter:             ctrlcommon.BuildRateLimiter(),
-		}); err != nil {
-			setupLog.Error(err, errCreateController, "controller", "ClusterProvider")
-			os.Exit(1)
+			clusterprovider.SetUpMetrics()
+			if err = (&clusterprovider.Reconciler{
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("ClusterProvider"),
+				Scheme:          mgr.GetScheme(),
+				RequeueInterval: storeRequeueInterval,
+			}).SetupWithManager(mgr, controller.Options{
+				MaxConcurrentReconciles: concurrent,
+				RateLimiter:             ctrlcommon.BuildRateLimiter(),
+			}); err != nil {
+				setupLog.Error(err, errCreateController, "controller", "ClusterProvider")
+				os.Exit(1)
+			}
 		}
 
 		if err = (&generatorstate.Reconciler{
@@ -412,6 +428,8 @@ func init() {
 	rootCmd.Flags().BoolVar(&enableExtendedMetricLabels, "enable-extended-metric-labels", false, "Enable recommended kubernetes annotations as labels in metrics.")
 	rootCmd.Flags().BoolVar(&enableHTTP2, "enable-http2", false,
 		"If set, HTTP/2 will be enabled for the metrics server")
+	rootCmd.Flags().BoolVar(&enableV2Providers, "enable-v2-providers", false,
+		"Enable experimental v2 Provider/ClusterProvider controllers and metrics.")
 	rootCmd.Flags().
 		BoolVar(&allowGenericTargets, "unsafe-allow-generic-targets", false, "Enable support for creating generic resources (ConfigMaps, Custom Resources). WARNING: Using generic resources, please sure all policies are correctly configured.")
 	fs := feature.Features()

+ 2 - 1
config/crds/bases/external-secrets.io_clusterpushsecrets.yaml

@@ -199,11 +199,12 @@ spec:
                         kind:
                           default: SecretStore
                           description: Kind of the SecretStore resource (SecretStore,
-                            ClusterSecretStore, or Provider)
+                            ClusterSecretStore, Provider, or ClusterProvider)
                           enum:
                           - SecretStore
                           - ClusterSecretStore
                           - Provider
+                          - ClusterProvider
                           type: string
                         labelSelector:
                           description: Optionally, sync to secret stores with label

+ 2 - 1
config/crds/bases/external-secrets.io_pushsecrets.yaml

@@ -121,11 +121,12 @@ spec:
                     kind:
                       default: SecretStore
                       description: Kind of the SecretStore resource (SecretStore,
-                        ClusterSecretStore, or Provider)
+                        ClusterSecretStore, Provider, or ClusterProvider)
                       enum:
                       - SecretStore
                       - ClusterSecretStore
                       - Provider
+                      - ClusterProvider
                       type: string
                     labelSelector:
                       description: Optionally, sync to secret stores with label selector

+ 8 - 0
deploy/charts/external-secrets/templates/cert-controller-deployment.yaml

@@ -92,6 +92,14 @@ spec:
           {{- if .Values.leaderElect }}
           - --enable-leader-election=true
           {{- end }}
+          {{- if and .Values.v2.enabled .Values.providers.enabled }}
+          - --provider-namespace={{ template "external-secrets.namespace" . }}
+            {{- range .Values.providers.list }}
+            {{- if .enabled }}
+          - --provider-service-names={{ include "external-secrets.provider.servicename" (dict "provider" . "root" $) }}
+            {{- end }}
+            {{- end }}
+          {{- end }}
           {{- range $key, $value := .Values.certController.extraArgs }}
             {{- if $value }}
           - --{{ $key }}={{ $value }}

+ 3 - 0
deploy/charts/external-secrets/templates/deployment.yaml

@@ -107,6 +107,9 @@ spec:
           {{- if .Values.enableHTTP2 }}
           - --enable-http2=true
           {{- end }}
+          {{- if .Values.v2.enabled }}
+          - --enable-v2-providers=true
+          {{- end }}
           {{- if .Values.concurrent }}
           - --concurrent={{ .Values.concurrent }}
           {{- end }}

+ 10 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -13,6 +13,7 @@ metadata:
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
 rules:
+  {{- if .Values.v2.enabled }}
   - apiGroups:
     - "provider.external-secrets.io"
     resources:
@@ -23,12 +24,15 @@ rules:
     - "get"
     - "list"
     - "watch"
+  {{- end }}
   - apiGroups:
     - "external-secrets.io"
     resources:
     - "secretstores"
+    {{- if .Values.v2.enabled }}
     - "providers"
     - "clusterproviders"
+    {{- end }}
     {{- if .Values.processClusterStore }}
     - "clustersecretstores"
     {{- end }}
@@ -49,10 +53,12 @@ rules:
   - apiGroups:
     - "external-secrets.io"
     resources:
+    {{- if .Values.v2.enabled }}
     - "providers"
     - "providers/status"
     - "clusterproviders"
     - "clusterproviders/status"
+    {{- end }}
     - "externalsecrets"
     - "externalsecrets/status"
     {{- if .Values.openshiftFinalizers }}
@@ -254,8 +260,10 @@ rules:
     resources:
       - "externalsecrets"
       - "secretstores"
+      {{- if .Values.v2.enabled }}
       - "providers"
       - "clusterproviders"
+      {{- end }}
       {{- if .Values.processClusterStore }}
       - "clustersecretstores"
       {{- end }}
@@ -318,8 +326,10 @@ rules:
     resources:
       - "externalsecrets"
       - "secretstores"
+      {{- if .Values.v2.enabled }}
       - "providers"
       - "clusterproviders"
+      {{- end }}
       {{- if .Values.processClusterStore }}
       - "clustersecretstores"
       {{- end }}

+ 5 - 0
deploy/charts/external-secrets/values-test.yaml

@@ -6,6 +6,11 @@ image:
   pullPolicy: IfNotPresent
 
 installCRDs: true
+v2:
+  enabled: true
+crds:
+  createProvider: true
+  createClusterProvider: true
 
 providers:
   enabled: true

+ 5 - 0
deploy/charts/external-secrets/values-with-providers-example.yaml

@@ -11,6 +11,11 @@ image:
 
 # Install CRDs
 installCRDs: true
+v2:
+  enabled: true
+crds:
+  createProvider: true
+  createClusterProvider: true
 
 # Enable provider deployments
 providers:

+ 8 - 1
deploy/charts/external-secrets/values.yaml

@@ -61,8 +61,10 @@ crds:
   createClusterExternalSecret: true
   # -- If true, create CRDs for Cluster Secret Store. If set to false you must also set processClusterStore: false.
   createClusterSecretStore: true
+  # -- If true, create CRDs for Provider.
+  createProvider: false
   # -- If true, create CRDs for Cluster Provider.
-  createClusterProvider: true
+  createClusterProvider: false
   # -- If true, create CRDs for Secret Store. If set to false you must also set processSecretStore: false.
   createSecretStore: true
   # -- If true, create CRDs for Cluster Generator. If set to false you must also set processClusterGenerator: false.
@@ -134,6 +136,11 @@ processClusterGenerator: true
 # -- if true, the operator will process push secret. Else, it will ignore them.
 processPushSecret: true
 
+# -- Experimental v2 Provider/ClusterProvider support.
+# Keep disabled unless you explicitly opt into v2 provider flows.
+v2:
+  enabled: false
+
 # -- Enable support for generic targets (ConfigMaps, Custom Resources).
 # Warning: Using generic target. Make sure access policies and encryption are properly configured.
 # When enabled, this grants the controller permissions to create/update/delete

+ 4 - 2
deploy/crds/bundle.yaml

@@ -1972,11 +1972,12 @@ spec:
                             type: string
                           kind:
                             default: SecretStore
-                            description: Kind of the SecretStore resource (SecretStore, ClusterSecretStore, or Provider)
+                            description: Kind of the SecretStore resource (SecretStore, ClusterSecretStore, Provider, or ClusterProvider)
                             enum:
                               - SecretStore
                               - ClusterSecretStore
                               - Provider
+                              - ClusterProvider
                             type: string
                           labelSelector:
                             description: Optionally, sync to secret stores with label selector
@@ -13841,11 +13842,12 @@ spec:
                         type: string
                       kind:
                         default: SecretStore
-                        description: Kind of the SecretStore resource (SecretStore, ClusterSecretStore, or Provider)
+                        description: Kind of the SecretStore resource (SecretStore, ClusterSecretStore, Provider, or ClusterProvider)
                         enum:
                           - SecretStore
                           - ClusterSecretStore
                           - Provider
+                          - ClusterProvider
                         type: string
                       labelSelector:
                         description: Optionally, sync to secret stores with label selector

+ 10 - 0
e2e/suites/v2/helpers.go

@@ -276,6 +276,16 @@ func CreateFakeGenerator(f *framework.Framework, namespace, name string, data ma
 // CreateClusterProvider creates a ClusterProvider pointing to the specified provider.
 // Returns the created ClusterProvider object.
 func CreateClusterProvider(f *framework.Framework, name, address, providerAPIVersion, providerKind, providerName, providerNamespace string, authScope v1.AuthenticationScope, conditions []v1.ClusterSecretStoreCondition) *v1.ClusterProvider {
+	existing := &v1.ClusterProvider{}
+	err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name}, existing)
+	if err == nil {
+		Expect(f.CRClient.Delete(context.Background(), existing)).To(Succeed())
+		Eventually(func() bool {
+			lookupErr := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name}, &v1.ClusterProvider{})
+			return lookupErr != nil && strings.Contains(lookupErr.Error(), "not found")
+		}, 15*time.Second, 500*time.Millisecond).Should(BeTrue(), "existing ClusterProvider should be deleted before recreation")
+	}
+
 	clusterProvider := &v1.ClusterProvider{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: name,

+ 6 - 0
hack/install-eso-v2-e2e.sh

@@ -150,6 +150,12 @@ install_external_secrets() {
 # Controller configuration
 installCRDs: true
 replicaCount: 1
+v2:
+  enabled: true
+
+crds:
+  createProvider: true
+  createClusterProvider: true
 
 image:
   repository: ghcr.io/external-secrets/external-secrets

+ 3 - 2
pkg/controllers/clusterprovider/controller.go

@@ -101,8 +101,10 @@ func (r *Reconciler) validateStoreAndGetCapabilities(ctx context.Context, store
 		return "", fmt.Errorf("provider address is required")
 	}
 
+	tlsSecretNamespace := grpc.NamespaceFromAddress(store.Spec.Config.Address, store.Spec.Config.ProviderRef.Namespace)
+
 	// Load TLS configuration
-	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, "external-secrets-system")
+	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, tlsSecretNamespace)
 	if err != nil {
 		return "", fmt.Errorf("failed to load TLS config: %w", err)
 	}
@@ -204,4 +206,3 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options)
 		Owns(&corev1.Secret{}). // Watch secrets that might be used for auth
 		Complete(r)
 }
-

+ 3 - 1
pkg/controllers/provider/controller.go

@@ -101,8 +101,10 @@ func (r *Reconciler) validateStoreAndGetCapabilities(ctx context.Context, store
 		return "", fmt.Errorf("provider address is required")
 	}
 
+	tlsSecretNamespace := grpc.NamespaceFromAddress(store.Spec.Config.Address, store.Namespace)
+
 	// Load TLS configuration
-	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, "external-secrets-system")
+	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, tlsSecretNamespace)
 	if err != nil {
 		return "", fmt.Errorf("failed to load TLS config: %w", err)
 	}

+ 55 - 186
pkg/controllers/pushsecret/pushsecret_controller_v2.go

@@ -24,125 +24,34 @@ import (
 
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-	pb "github.com/external-secrets/external-secrets/proto/provider"
-	"github.com/external-secrets/external-secrets/providers/v2/common/grpc"
 	"github.com/external-secrets/external-secrets/runtime/clientmanager"
 )
 
 // isV2SecretStore checks if the referenced SecretStore is a v2 API version.
 func (r *Reconciler) isV2SecretStore(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) bool {
+	if storeRef.Kind == esapi.ProviderKindStr || storeRef.Kind == esapi.ClusterProviderKindStr {
+		if storeRef.APIVersion == "" {
+			return true
+		}
+		return storeRef.APIVersion == esapi.SchemeGroupVersion.String()
+	}
+
 	// Check the apiVersion field first if specified
 	if storeRef.APIVersion != "" {
-		return storeRef.APIVersion == "external-secrets.io/v1"
+		return false
 	}
 
-	// For backwards compatibility, try to fetch as v2 Provider
-	var store esapi.Provider
-	storeKey := types.NamespacedName{
-		Name:      storeRef.Name,
-		Namespace: namespace,
+	if storeRef.Kind == esapi.ClusterProviderKindStr {
+		var store esapi.ClusterProvider
+		err := r.Client.Get(ctx, types.NamespacedName{Name: storeRef.Name}, &store)
+		return err == nil
 	}
-	err := r.Client.Get(ctx, storeKey, &store)
-	return err == nil
-}
 
-// pushSecretToProviderV2 pushes a secret to a v2 provider via gRPC.
-func (r *Reconciler) pushSecretToProviderV2(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, ps esv1alpha1.PushSecret, secret *corev1.Secret) (map[string]esv1alpha1.PushSecretData, error) {
-	// Get the v2 Provider
+	// For backwards compatibility, try to fetch as namespaced v2 Provider.
 	var store esapi.Provider
-	storeKey := types.NamespacedName{
-		Name:      storeRef.Name,
-		Namespace: ps.Namespace,
-	}
-	if err := r.Client.Get(ctx, storeKey, &store); err != nil {
-		return nil, fmt.Errorf("failed to get Provider: %w", err)
-	}
-
-	// Get provider address
-	address := store.Spec.Config.Address
-	if address == "" {
-		return nil, fmt.Errorf("provider address is required in Provider")
-	}
-
-	// Load TLS configuration
-	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, store.Spec.Config.Address, "external-secrets-system")
-	if err != nil {
-		return nil, fmt.Errorf("failed to load TLS config: %w", err)
-	}
-
-	// Create gRPC client with TLS
-	grpcClient, err := grpc.NewClient(address, tlsConfig)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create gRPC client: %w", err)
-	}
-	defer func() { _ = grpcClient.Close(ctx) }()
-
-	// Convert ProviderReference to protobuf format
-	providerRef := &pb.ProviderReference{
-		ApiVersion: store.Spec.Config.ProviderRef.APIVersion,
-		Kind:       store.Spec.Config.ProviderRef.Kind,
-		Name:       store.Spec.Config.ProviderRef.Name,
-		Namespace:  store.Spec.Config.ProviderRef.Namespace,
-	}
-
-	// Push secrets
-	syncedSecrets := make(map[string]esv1alpha1.PushSecretData)
-
-	// Convert secret data to map[string][]byte
-	secretData := make(map[string][]byte)
-	for k, v := range secret.Data {
-		secretData[k] = v
-	}
-
-	// Push each data item
-	for _, data := range ps.Spec.Data {
-		// Prepare push secret data
-		var metadataJSON []byte
-		if data.Metadata != nil {
-			metadataJSON = data.Metadata.Raw
-		}
-
-		pushData := &pb.PushSecretData{
-			Metadata:  metadataJSON,
-			SecretKey: data.Match.SecretKey,
-			RemoteKey: data.Match.RemoteRef.RemoteKey,
-			Property:  data.Match.RemoteRef.Property,
-		}
-
-		remoteRef := &pb.PushSecretRemoteRef{
-			RemoteKey: data.Match.RemoteRef.RemoteKey,
-			Property:  data.Match.RemoteRef.Property,
-		}
-
-		// Handle UpdatePolicy
-		switch ps.Spec.UpdatePolicy {
-		case esv1alpha1.PushSecretUpdatePolicyIfNotExists:
-			// Check if secret already exists
-			exists, err := grpcClient.SecretExists(ctx, remoteRef, providerRef, ps.Namespace)
-			if err != nil {
-				return syncedSecrets, fmt.Errorf("could not verify if secret exists: %w", err)
-			}
-			if exists {
-				// Secret exists, skip push but record it as synced
-				syncedSecrets[data.Match.RemoteRef.RemoteKey] = data
-				continue
-			}
-		case esv1alpha1.PushSecretUpdatePolicyReplace:
-			// Always push (replace existing)
-		default:
-			// Default to replace
-		}
-
-		// Push the secret
-		if err := grpcClient.PushSecret(ctx, secretData, pushData, providerRef, ps.Namespace); err != nil {
-			return syncedSecrets, fmt.Errorf("failed to push secret for key %s: %w", data.Match.RemoteRef.RemoteKey, err)
-		}
-
-		// Record successful push
-		syncedSecrets[data.Match.RemoteRef.RemoteKey] = data
-	}
-
-	return syncedSecrets, nil
+	storeKey := types.NamespacedName{Name: storeRef.Name, Namespace: namespace}
+	err := r.Client.Get(ctx, storeKey, &store)
+	return err == nil
 }
 
 // GetSecretStoresV2 retrieves both v1 and v2 Providers.
@@ -152,16 +61,23 @@ func (r *Reconciler) GetSecretStoresV2(ctx context.Context, ps esv1alpha1.PushSe
 	for _, refStore := range ps.Spec.SecretStoreRefs {
 		// Check if this is a v2 Provider
 		if r.isV2SecretStore(ctx, refStore, ps.Namespace) {
-			// Get v2 Provider
-			var store esapi.Provider
-			storeKey := types.NamespacedName{
-				Name:      refStore.Name,
-				Namespace: ps.Namespace,
+			if refStore.Kind == esapi.ClusterProviderKindStr {
+				var store esapi.ClusterProvider
+				storeKey := types.NamespacedName{Name: refStore.Name}
+				if err := r.Client.Get(ctx, storeKey, &store); err != nil {
+					return nil, fmt.Errorf("failed to get v2 ClusterProvider %s: %w", refStore.Name, err)
+				}
+				stores[refStore] = &store
+				continue
 			}
+
+			var store esapi.Provider
+			storeKey := types.NamespacedName{Name: refStore.Name, Namespace: ps.Namespace}
 			if err := r.Client.Get(ctx, storeKey, &store); err != nil {
 				return nil, fmt.Errorf("failed to get v2 Provider %s: %w", refStore.Name, err)
 			}
 			stores[refStore] = &store
+			continue
 		} else {
 			// Get v1 SecretStore (existing implementation)
 			store, err := r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
@@ -175,18 +91,6 @@ func (r *Reconciler) GetSecretStoresV2(ctx context.Context, ps esv1alpha1.PushSe
 	return stores, nil
 }
 
-// handleV2Push determines if a store is v2 and pushes accordingly.
-func (r *Reconciler) handleV2Push(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, store interface{}, ps esv1alpha1.PushSecret, secret *corev1.Secret) (map[string]esv1alpha1.PushSecretData, error) {
-	// Check if this is a v2 store
-	if _, ok := store.(*esapi.Provider); ok {
-		// Use v2 push
-		return r.pushSecretToProviderV2(ctx, storeRef, ps, secret)
-	}
-
-	// Not a v2 store, return nil to indicate v1 handling should be used
-	return nil, nil
-}
-
 // PushSecretToProvidersV2 pushes secret data to both v1 stores and v2 providers.
 func (r *Reconciler) PushSecretToProvidersV2(
 	ctx context.Context,
@@ -196,17 +100,8 @@ func (r *Reconciler) PushSecretToProvidersV2(
 	mgr *clientmanager.Manager,
 ) (esv1alpha1.SyncedPushSecretsMap, error) {
 	out := make(esv1alpha1.SyncedPushSecretsMap)
-	for ref, store := range stores {
-		v2Synced, err := r.handleV2Push(ctx, ref, store, ps, secret)
-		if err != nil {
-			return out, err
-		}
-		if v2Synced != nil {
-			storeKey := fmt.Sprintf("%v/%v", ref.Kind, ref.Name)
-			out[storeKey] = v2Synced
-			continue
-		}
-
+	for ref := range stores {
+		var err error
 		out, err = r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, ref.Name, ref.Kind)
 		if err != nil {
 			return out, err
@@ -218,6 +113,10 @@ func (r *Reconciler) PushSecretToProvidersV2(
 // DeleteSecretFromProvidersV2 removes secrets from v2 providers when they're no longer needed.
 func (r *Reconciler) DeleteSecretFromProvidersV2(ctx context.Context, ps *esv1alpha1.PushSecret, newMap esv1alpha1.SyncedPushSecretsMap, stores map[esv1alpha1.PushSecretStoreRef]interface{}) (esv1alpha1.SyncedPushSecretsMap, error) {
 	out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
+	mgr := clientmanager.NewManager(r.Client, r.ControllerClass, false)
+	defer func() {
+		_ = mgr.Close(ctx)
+	}()
 
 	for storeName, oldData := range ps.Status.SyncedPushSecrets {
 		// Parse store name format "Kind/Name"
@@ -229,11 +128,9 @@ func (r *Reconciler) DeleteSecretFromProvidersV2(ctx context.Context, ps *esv1al
 		storeNameOnly := parts[1]
 
 		// Find the matching store
-		var matchingStore interface{}
 		var found bool
-		for ref, store := range stores {
+		for ref := range stores {
 			if ref.Kind == storeKind && ref.Name == storeNameOnly {
-				matchingStore = store
 				found = true
 				break
 			}
@@ -244,60 +141,32 @@ func (r *Reconciler) DeleteSecretFromProvidersV2(ctx context.Context, ps *esv1al
 			continue
 		}
 
-		// Check if it's a v2 store
-		if v2Store, ok := matchingStore.(*esapi.Provider); ok {
-			// Create gRPC client
-			tlsConfig, err := grpc.LoadClientTLSConfig(ctx, r.Client, v2Store.Spec.Config.Address, "external-secrets-system")
-			if err != nil {
-				return out, fmt.Errorf("failed to load TLS config: %w", err)
-			}
+		secretClient, err := mgr.Get(ctx, esapi.SecretStoreRef{
+			Name: storeNameOnly,
+			Kind: storeKind,
+		}, ps.Namespace, nil)
+		if err != nil {
+			return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
+		}
 
-			grpcClient, err := grpc.NewClient(v2Store.Spec.Config.Address, tlsConfig)
+		newData, ok := newMap[storeName]
+		if !ok {
+			err = r.DeleteAllSecretsFromStore(ctx, secretClient, oldData)
 			if err != nil {
-				return out, fmt.Errorf("failed to create gRPC client: %w", err)
+				return out, err
 			}
-			defer func() { _ = grpcClient.Close(ctx) }()
-
-			// Convert ProviderReference to protobuf format
-			providerRef := &pb.ProviderReference{
-				ApiVersion: v2Store.Spec.Config.ProviderRef.APIVersion,
-				Kind:       v2Store.Spec.Config.ProviderRef.Kind,
-				Name:       v2Store.Spec.Config.ProviderRef.Name,
-				Namespace:  v2Store.Spec.Config.ProviderRef.Namespace,
-			}
-
-			// Check if store still exists in newMap
-			newData, ok := newMap[storeName]
-			if !ok {
-				// Store removed entirely, delete all secrets
-				for _, oldRef := range oldData {
-					remoteRef := &pb.PushSecretRemoteRef{
-						RemoteKey: oldRef.Match.RemoteRef.RemoteKey,
-						Property:  oldRef.Match.RemoteRef.Property,
-					}
-					if err := grpcClient.DeleteSecret(ctx, remoteRef, providerRef, ps.Namespace); err != nil {
-						return out, fmt.Errorf("failed to delete secret %s: %w", oldRef.Match.RemoteRef.RemoteKey, err)
-					}
-				}
-				delete(out, storeName)
-				continue
-			}
-
-			// Delete individual secrets that are no longer in the new data
-			for oldEntry, oldRef := range oldData {
-				if _, stillExists := newData[oldEntry]; !stillExists {
-					remoteRef := &pb.PushSecretRemoteRef{
-						RemoteKey: oldRef.Match.RemoteRef.RemoteKey,
-						Property:  oldRef.Match.RemoteRef.Property,
-					}
-					if err := grpcClient.DeleteSecret(ctx, remoteRef, providerRef, ps.Namespace); err != nil {
-						return out, fmt.Errorf("failed to delete secret %s: %w", oldRef.Match.RemoteRef.RemoteKey, err)
-					}
-					delete(out[storeName], oldRef.Match.RemoteRef.RemoteKey)
+			delete(out, storeName)
+			continue
+		}
+		for oldEntry, oldRef := range oldData {
+			if _, stillExists := newData[oldEntry]; !stillExists {
+				err = r.DeleteSecretFromStore(ctx, secretClient, oldRef)
+				if err != nil {
+					return out, err
 				}
+				delete(out[storeName], oldEntry)
 			}
 		}
-		// If not v2, the v1 deletion logic will handle it
 	}
 
 	return out, nil

+ 20 - 0
providers/v2/common/grpc/tls.go

@@ -20,6 +20,7 @@ import (
 	"crypto/x509"
 	"fmt"
 	"net"
+	"strings"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -87,6 +88,25 @@ func LoadClientTLSConfig(
 	}, nil
 }
 
+// NamespaceFromAddress extracts the service namespace from a Kubernetes service DNS address.
+// Expected address formats include:
+// - "<service>.<namespace>.svc:port"
+// - "<service>.<namespace>.svc.cluster.local:port"
+// If parsing fails, fallbackNamespace is returned.
+func NamespaceFromAddress(address, fallbackNamespace string) string {
+	host := address
+	if parsedHost, _, err := net.SplitHostPort(address); err == nil {
+		host = parsedHost
+	}
+
+	parts := strings.Split(host, ".")
+	if len(parts) >= 3 && parts[2] == "svc" && parts[1] != "" {
+		return parts[1]
+	}
+
+	return fallbackNamespace
+}
+
 // ToGRPCTLSConfig converts TLSConfig to a *tls.Config suitable for gRPC.
 func (t *TLSConfig) ToGRPCTLSConfig() (*tls.Config, error) {
 	// Load client certificate

+ 7 - 1
runtime/clientmanager/manager.go

@@ -263,8 +263,14 @@ func (m *Manager) getOrCreateV2Client(ctx context.Context, cfg v2ProviderConfig,
 		return nil, fmt.Errorf("provider address is required in %s %q", cfg.kindStr, cfg.name)
 	}
 
+	tlsSecretNamespace := cfg.resourceNamespace
+	if tlsSecretNamespace == "" {
+		tlsSecretNamespace = cfg.config.ProviderRef.Namespace
+	}
+	tlsSecretNamespace = grpc.NamespaceFromAddress(cfg.config.Address, tlsSecretNamespace)
+
 	// Load TLS configuration
-	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, m.client, cfg.config.Address, "external-secrets-system")
+	tlsConfig, err := grpc.LoadClientTLSConfig(ctx, m.client, cfg.config.Address, tlsSecretNamespace)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load TLS config for %s %q: %w", cfg.kindStr, cfg.name, err)
 	}