Prechádzať zdrojové kódy

feat: add provider v2 gRPC store contract

Moritz Johner 2 mesiacov pred
rodič
commit
40cc9a33a1
41 zmenil súbory, kde vykonal 12027 pridanie a 0 odobranie
  1. 150 0
      providers/v2/adapter/store/client.go
  2. 749 0
      providers/v2/adapter/store/client_test.go
  3. 78 0
      providers/v2/adapter/store/compatibility_store.go
  4. 83 0
      providers/v2/adapter/store/go.mod
  5. 207 0
      providers/v2/adapter/store/go.sum
  6. 506 0
      providers/v2/adapter/store/server.go
  7. 1168 0
      providers/v2/adapter/store/server_test.go
  8. 138 0
      providers/v2/adapter/store/synthetic_store.go
  9. 84 0
      providers/v2/common/go.mod
  10. 208 0
      providers/v2/common/go.sum
  11. 597 0
      providers/v2/common/grpc/client.go
  12. 835 0
      providers/v2/common/grpc/client_test.go
  13. 18 0
      providers/v2/common/grpc/doc.go
  14. 128 0
      providers/v2/common/grpc/factory.go
  15. 54 0
      providers/v2/common/grpc/health.go
  16. 91 0
      providers/v2/common/grpc/health_test.go
  17. 292 0
      providers/v2/common/grpc/metrics.go
  18. 42 0
      providers/v2/common/grpc/metrics_test.go
  19. 395 0
      providers/v2/common/grpc/pool.go
  20. 252 0
      providers/v2/common/grpc/pool_test.go
  21. 274 0
      providers/v2/common/grpc/resilient.go
  22. 322 0
      providers/v2/common/grpc/retry.go
  23. 238 0
      providers/v2/common/grpc/retry_test.go
  24. 75 0
      providers/v2/common/grpc/server/go.mod
  25. 207 0
      providers/v2/common/grpc/server/go.sum
  26. 110 0
      providers/v2/common/grpc/server/http.go
  27. 118 0
      providers/v2/common/grpc/server/interceptors.go
  28. 146 0
      providers/v2/common/grpc/server/metrics.go
  29. 120 0
      providers/v2/common/grpc/server/server.go
  30. 110 0
      providers/v2/common/grpc/server/tls.go
  31. 148 0
      providers/v2/common/grpc/tls.go
  32. 320 0
      providers/v2/common/grpc/tls_test.go
  33. 448 0
      providers/v2/common/proto/generator/generator.pb.go
  34. 71 0
      providers/v2/common/proto/generator/generator.proto
  35. 192 0
      providers/v2/common/proto/generator/generator_grpc.pb.go
  36. 15 0
      providers/v2/common/proto/go.mod
  37. 38 0
      providers/v2/common/proto/go.sum
  38. 2137 0
      providers/v2/common/proto/provider/secretstore.pb.go
  39. 347 0
      providers/v2/common/proto/provider/secretstore.proto
  40. 422 0
      providers/v2/common/proto/provider/secretstore_grpc.pb.go
  41. 94 0
      providers/v2/common/types.go

+ 150 - 0
providers/v2/adapter/store/client.go

@@ -0,0 +1,150 @@
+/*
+Copyright © The ESO Authors
+
+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 store adapts v1 provider implementations to the v2 gRPC SecretStoreProvider interface.
+package store
+
+import (
+	"context"
+
+	corev1 "k8s.io/api/core/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+// Client wraps a v2.Provider (gRPC client) and exposes it as an esv1.SecretsClient.
+// This allows v2 providers to be used with the existing client manager infrastructure.
+type Client struct {
+	v2Provider         v2.Provider
+	providerRef        *pb.ProviderReference
+	compatibilityStore *pb.CompatibilityStore
+	sourceNamespace    string
+	closeFunc          func(context.Context) error
+}
+
+// Ensure Client implements SecretsClient interface.
+var _ esv1.SecretsClient = &Client{}
+
+// NewClient creates a new wrapper that adapts a v2.Provider to esv1.SecretsClient.
+func NewClient(v2Provider v2.Provider, providerRef *pb.ProviderReference, sourceNamespace string) esv1.SecretsClient {
+	return NewClientWithCloser(v2Provider, providerRef, sourceNamespace, nil)
+}
+
+// NewClientWithCloser creates a provider-ref based wrapper with a custom close function.
+func NewClientWithCloser(v2Provider v2.Provider, providerRef *pb.ProviderReference, sourceNamespace string, closeFunc func(context.Context) error) esv1.SecretsClient {
+	return &Client{
+		v2Provider:      v2Provider,
+		providerRef:     providerRef,
+		sourceNamespace: sourceNamespace,
+		closeFunc:       closeFunc,
+	}
+}
+
+// NewCompatibilityClient creates a compatibility-store based wrapper for runtimeRef reads.
+func NewCompatibilityClient(v2Provider v2.Provider, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) esv1.SecretsClient {
+	return NewCompatibilityClientWithCloser(v2Provider, compatibilityStore, sourceNamespace, nil)
+}
+
+// NewCompatibilityClientWithCloser creates a compatibility-store based wrapper with a custom close function.
+func NewCompatibilityClientWithCloser(v2Provider v2.Provider, compatibilityStore *pb.CompatibilityStore, sourceNamespace string, closeFunc func(context.Context) error) esv1.SecretsClient {
+	return &Client{
+		v2Provider:         v2Provider,
+		compatibilityStore: compatibilityStore,
+		sourceNamespace:    sourceNamespace,
+		closeFunc:          closeFunc,
+	}
+}
+
+// GetSecret retrieves a single secret from the provider.
+func (w *Client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	return w.v2Provider.GetSecret(ctx, ref, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// GetSecretMap retrieves a secret object and returns its key/value pairs.
+func (w *Client) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	return w.v2Provider.GetSecretMap(ctx, ref, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// GetAllSecrets retrieves multiple secrets based on find criteria.
+func (w *Client) GetAllSecrets(ctx context.Context, find esv1.ExternalSecretFind) (map[string][]byte, error) {
+	return w.v2Provider.GetAllSecrets(ctx, find, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// PushSecret writes a secret to the provider.
+func (w *Client) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
+	// Convert metadata from *apiextensionsv1.JSON to []byte
+	var metadata []byte
+	if data.GetMetadata() != nil {
+		metadata = data.GetMetadata().Raw
+	}
+
+	// Convert esv1.PushSecretData to pb.PushSecretData
+	pushSecretData := &pb.PushSecretData{
+		RemoteKey: data.GetRemoteKey(),
+		SecretKey: data.GetSecretKey(),
+		Property:  data.GetProperty(),
+		Metadata:  metadata,
+	}
+
+	return w.v2Provider.PushSecret(ctx, secret, pushSecretData, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// DeleteSecret deletes a secret from the provider.
+func (w *Client) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
+	// Convert esv1.PushSecretRemoteRef to pb.PushSecretRemoteRef
+	pbRemoteRef := &pb.PushSecretRemoteRef{
+		RemoteKey: remoteRef.GetRemoteKey(),
+		Property:  remoteRef.GetProperty(),
+	}
+
+	return w.v2Provider.DeleteSecret(ctx, pbRemoteRef, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// SecretExists checks if a secret exists in the provider.
+func (w *Client) SecretExists(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
+	// Convert esv1.PushSecretRemoteRef to pb.PushSecretRemoteRef
+	pbRemoteRef := &pb.PushSecretRemoteRef{
+		RemoteKey: remoteRef.GetRemoteKey(),
+		Property:  remoteRef.GetProperty(),
+	}
+
+	return w.v2Provider.SecretExists(ctx, pbRemoteRef, w.providerRef, w.compatibilityStore, w.sourceNamespace)
+}
+
+// Validate checks if the provider is properly configured.
+func (w *Client) Validate() (esv1.ValidationResult, error) {
+	if w.compatibilityStore != nil {
+		if _, err := CompatibilityStoreToSyntheticStore(w.compatibilityStore); err != nil {
+			return esv1.ValidationResultError, err
+		}
+	}
+
+	err := w.v2Provider.Validate(context.Background(), w.providerRef, w.compatibilityStore, w.sourceNamespace)
+	if err != nil {
+		return esv1.ValidationResultError, err
+	}
+	return esv1.ValidationResultReady, nil
+}
+
+// Close cleans up any resources held by the provider client.
+func (w *Client) Close(ctx context.Context) error {
+	if w.closeFunc != nil {
+		return w.closeFunc(ctx)
+	}
+	return w.v2Provider.Close(ctx)
+}

+ 749 - 0
providers/v2/adapter/store/client_test.go

@@ -0,0 +1,749 @@
+/*
+Copyright © The ESO Authors
+
+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 store
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+const (
+	testProperty        = "property"
+	testSecretValue     = "secret-value"
+	testSourceNamespace = "tenant-a"
+	testBarValue        = "bar"
+	testValue           = "value"
+)
+
+type fakeV2Provider struct {
+	getSecretResponse           []byte
+	getSecretErr                error
+	getSecretRef                esv1.ExternalSecretDataRemoteRef
+	getSecretProviderRef        *pb.ProviderReference
+	getSecretCompatibilityStore *pb.CompatibilityStore
+	getSecretNamespace          string
+
+	getSecretMapResponse           map[string][]byte
+	getSecretMapErr                error
+	getSecretMapRef                esv1.ExternalSecretDataRemoteRef
+	getSecretMapCompatibilityStore *pb.CompatibilityStore
+
+	getAllSecretsResponse           map[string][]byte
+	getAllSecretsErr                error
+	getAllSecretsFind               esv1.ExternalSecretFind
+	getAllSecretsCompatibilityStore *pb.CompatibilityStore
+
+	pushSecretErr                error
+	pushSecretData               map[string][]byte
+	pushSecretSecret             *corev1.Secret
+	pushSecretPayload            *pb.PushSecretData
+	pushSecretProviderRef        *pb.ProviderReference
+	pushSecretCompatibilityStore *pb.CompatibilityStore
+	pushSecretNamespace          string
+
+	deleteSecretErr                error
+	deleteSecretRemoteRef          *pb.PushSecretRemoteRef
+	deleteSecretProviderRef        *pb.ProviderReference
+	deleteSecretCompatibilityStore *pb.CompatibilityStore
+	deleteSecretNamespace          string
+
+	secretExistsResponse           bool
+	secretExistsErr                error
+	secretExistsRemoteRef          *pb.PushSecretRemoteRef
+	secretExistsProviderRef        *pb.ProviderReference
+	secretExistsCompatibilityStore *pb.CompatibilityStore
+	secretExistsNamespace          string
+
+	validateErr                error
+	validateProviderRef        *pb.ProviderReference
+	validateCompatibilityStore *pb.CompatibilityStore
+	validateNamespace          string
+	validateCalled             bool
+
+	closeErr    error
+	closeCalled bool
+}
+
+func (f *fakeV2Provider) GetSecret(
+	_ context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) ([]byte, error) {
+	f.getSecretRef = ref
+	f.getSecretProviderRef = providerRef
+	f.getSecretCompatibilityStore = compatibilityStore
+	f.getSecretNamespace = sourceNamespace
+	return f.getSecretResponse, f.getSecretErr
+}
+
+func (f *fakeV2Provider) GetSecretMap(
+	_ context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	f.getSecretMapRef = ref
+	f.getSecretProviderRef = providerRef
+	f.getSecretMapCompatibilityStore = compatibilityStore
+	f.getSecretNamespace = sourceNamespace
+	return f.getSecretMapResponse, f.getSecretMapErr
+}
+
+func (f *fakeV2Provider) GetAllSecrets(
+	_ context.Context,
+	find esv1.ExternalSecretFind,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	f.getAllSecretsFind = find
+	f.getSecretProviderRef = providerRef
+	f.getAllSecretsCompatibilityStore = compatibilityStore
+	f.getSecretNamespace = sourceNamespace
+	return f.getAllSecretsResponse, f.getAllSecretsErr
+}
+
+func (f *fakeV2Provider) PushSecret(
+	_ context.Context,
+	secret *corev1.Secret,
+	pushSecretData *pb.PushSecretData,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	f.pushSecretData = secret.Data
+	f.pushSecretSecret = secret.DeepCopy()
+	f.pushSecretPayload = pushSecretData
+	f.pushSecretProviderRef = providerRef
+	f.pushSecretCompatibilityStore = compatibilityStore
+	f.pushSecretNamespace = sourceNamespace
+	return f.pushSecretErr
+}
+
+func (f *fakeV2Provider) DeleteSecret(
+	_ context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	f.deleteSecretRemoteRef = remoteRef
+	f.deleteSecretProviderRef = providerRef
+	f.deleteSecretCompatibilityStore = compatibilityStore
+	f.deleteSecretNamespace = sourceNamespace
+	return f.deleteSecretErr
+}
+
+func (f *fakeV2Provider) SecretExists(
+	_ context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (bool, error) {
+	f.secretExistsRemoteRef = remoteRef
+	f.secretExistsProviderRef = providerRef
+	f.secretExistsCompatibilityStore = compatibilityStore
+	f.secretExistsNamespace = sourceNamespace
+	return f.secretExistsResponse, f.secretExistsErr
+}
+
+func (f *fakeV2Provider) Validate(_ context.Context, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) error {
+	f.validateCalled = true
+	f.validateProviderRef = providerRef
+	f.validateCompatibilityStore = compatibilityStore
+	f.validateNamespace = sourceNamespace
+	return f.validateErr
+}
+
+func (f *fakeV2Provider) Capabilities(context.Context, *pb.ProviderReference, string) (pb.SecretStoreCapabilities, error) {
+	return pb.SecretStoreCapabilities_READ_WRITE, nil
+}
+
+func (f *fakeV2Provider) Close(context.Context) error {
+	f.closeCalled = true
+	return f.closeErr
+}
+
+type fakePushSecretData struct {
+	property  string
+	secretKey string
+	remoteKey string
+	metadata  *apiextensionsv1.JSON
+}
+
+func (f fakePushSecretData) GetProperty() string {
+	return f.property
+}
+
+func (f fakePushSecretData) GetSecretKey() string {
+	return f.secretKey
+}
+
+func (f fakePushSecretData) GetRemoteKey() string {
+	return f.remoteKey
+}
+
+func (f fakePushSecretData) GetMetadata() *apiextensionsv1.JSON {
+	return f.metadata
+}
+
+type fakePushSecretRemoteRef struct {
+	remoteKey string
+	property  string
+}
+
+func (f fakePushSecretRemoteRef) GetRemoteKey() string {
+	return f.remoteKey
+}
+
+func (f fakePushSecretRemoteRef) GetProperty() string {
+	return f.property
+}
+
+func TestClientGetSecretDelegatesProviderReferenceAndNamespace(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	provider := &fakeV2Provider{getSecretResponse: []byte(testSecretValue)}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	ref := esv1.ExternalSecretDataRemoteRef{Key: "sample", Version: "v1", Property: "password"}
+	value, err := client.GetSecret(context.Background(), ref)
+	if err != nil {
+		t.Fatalf("GetSecret() error = %v", err)
+	}
+
+	if string(value) != testSecretValue {
+		t.Fatalf("expected %s, got %q", testSecretValue, string(value))
+	}
+	if provider.getSecretRef != ref {
+		t.Fatalf("unexpected ref: %#v", provider.getSecretRef)
+	}
+	if provider.getSecretProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
+	}
+	if provider.getSecretNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
+	}
+}
+
+func TestClientGetSecretMapDelegatesProviderReferenceAndNamespace(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	expected := map[string][]byte{
+		"foo": []byte(testBarValue),
+		"baz": []byte("qux"),
+	}
+	provider := &fakeV2Provider{getSecretMapResponse: expected}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	ref := esv1.ExternalSecretDataRemoteRef{Key: "sample"}
+	secretMap, err := client.GetSecretMap(context.Background(), ref)
+	if err != nil {
+		t.Fatalf("GetSecretMap() error = %v", err)
+	}
+
+	if string(secretMap["foo"]) != testBarValue || string(secretMap["baz"]) != "qux" {
+		t.Fatalf("unexpected secret map: %#v", secretMap)
+	}
+	if provider.getSecretMapRef != ref {
+		t.Fatalf("unexpected ref: %#v", provider.getSecretMapRef)
+	}
+	if provider.getSecretProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
+	}
+	if provider.getSecretNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
+	}
+}
+
+func TestClientGetAllSecretsDelegatesFindCriteria(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	path := "/team-a"
+	expected := map[string][]byte{"db-password": []byte(testValue)}
+	provider := &fakeV2Provider{getAllSecretsResponse: expected}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	find := esv1.ExternalSecretFind{
+		Tags: map[string]string{
+			"team": "a",
+		},
+		Path: &path,
+		Name: &esv1.FindName{RegExp: "db-.*"},
+	}
+
+	secrets, err := client.GetAllSecrets(context.Background(), find)
+	if err != nil {
+		t.Fatalf("GetAllSecrets() error = %v", err)
+	}
+
+	if string(secrets["db-password"]) != testValue {
+		t.Fatalf("unexpected secret value: %#v", secrets)
+	}
+	if provider.getAllSecretsFind.Tags["team"] != "a" {
+		t.Fatalf("unexpected find tags: %#v", provider.getAllSecretsFind)
+	}
+	if provider.getAllSecretsFind.Path == nil || *provider.getAllSecretsFind.Path != path {
+		t.Fatalf("unexpected find path: %#v", provider.getAllSecretsFind.Path)
+	}
+	if provider.getAllSecretsFind.Name == nil || provider.getAllSecretsFind.Name.RegExp != "db-.*" {
+		t.Fatalf("unexpected find name: %#v", provider.getAllSecretsFind.Name)
+	}
+	if provider.getSecretProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.getSecretProviderRef)
+	}
+	if provider.getSecretNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.getSecretNamespace)
+	}
+}
+
+func TestCompatibilityClientGetSecretDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{getSecretResponse: []byte(testSecretValue)}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	value, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"})
+	if err != nil {
+		t.Fatalf("GetSecret() error = %v", err)
+	}
+
+	if string(value) != testSecretValue {
+		t.Fatalf("expected %s, got %q", testSecretValue, string(value))
+	}
+	if provider.getSecretProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.getSecretProviderRef)
+	}
+	if provider.getSecretCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.getSecretCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientGetSecretMapDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{getSecretMapResponse: map[string][]byte{"foo": []byte(testBarValue)}}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	secretMap, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"})
+	if err != nil {
+		t.Fatalf("GetSecretMap() error = %v", err)
+	}
+
+	if string(secretMap["foo"]) != testBarValue {
+		t.Fatalf("unexpected secret map: %#v", secretMap)
+	}
+	if provider.getSecretProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.getSecretProviderRef)
+	}
+	if provider.getSecretMapCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.getSecretMapCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientGetAllSecretsDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{getAllSecretsResponse: map[string][]byte{"db-password": []byte(testValue)}}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	secrets, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{})
+	if err != nil {
+		t.Fatalf("GetAllSecrets() error = %v", err)
+	}
+
+	if string(secrets["db-password"]) != testValue {
+		t.Fatalf("unexpected secret value: %#v", secrets)
+	}
+	if provider.getSecretProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.getSecretProviderRef)
+	}
+	if provider.getAllSecretsCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.getAllSecretsCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientPushSecretDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	err := client.PushSecret(context.Background(), &corev1.Secret{
+		Data: map[string][]byte{"token": []byte(testValue)},
+	}, fakePushSecretData{remoteKey: serverTestRemoteKey, secretKey: "token"})
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	if provider.pushSecretProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.pushSecretProviderRef)
+	}
+	if provider.pushSecretCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.pushSecretCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientDeleteSecretDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	err := client.DeleteSecret(context.Background(), fakePushSecretRemoteRef{
+		remoteKey: serverTestRemoteKey,
+		property:  testProperty,
+	})
+	if err != nil {
+		t.Fatalf("DeleteSecret() error = %v", err)
+	}
+
+	if provider.deleteSecretProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.deleteSecretProviderRef)
+	}
+	if provider.deleteSecretCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.deleteSecretCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientSecretExistsDelegatesCompatibilityStore(t *testing.T) {
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"sample","value":"secret-value"}]}}}`),
+	}
+	provider := &fakeV2Provider{secretExistsResponse: true}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	exists, err := client.SecretExists(context.Background(), fakePushSecretRemoteRef{
+		remoteKey: serverTestRemoteKey,
+		property:  testProperty,
+	})
+	if err != nil {
+		t.Fatalf("SecretExists() error = %v", err)
+	}
+	if !exists {
+		t.Fatal("expected secret to exist")
+	}
+	if provider.secretExistsProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.secretExistsProviderRef)
+	}
+	if provider.secretExistsCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.secretExistsCompatibilityStore)
+	}
+}
+
+func TestClientPushSecretConvertsPayloadAndMetadata(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	provider := &fakeV2Provider{}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	metadata := []byte(`{"owner":"eso"}`)
+	secret := &corev1.Secret{
+		Data: map[string][]byte{
+			"token": []byte(testValue),
+		},
+	}
+	pushData := fakePushSecretData{
+		property:  testProperty,
+		secretKey: "token",
+		remoteKey: serverTestRemoteKey,
+		metadata:  &apiextensionsv1.JSON{Raw: metadata},
+	}
+
+	err := client.PushSecret(context.Background(), secret, pushData)
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	if string(provider.pushSecretData["token"]) != testValue {
+		t.Fatalf("unexpected secret data: %#v", provider.pushSecretData)
+	}
+	if provider.pushSecretPayload == nil {
+		t.Fatal("expected push payload to be recorded")
+	}
+	if provider.pushSecretPayload.RemoteKey != serverTestRemoteKey || provider.pushSecretPayload.SecretKey != "token" || provider.pushSecretPayload.Property != testProperty {
+		t.Fatalf("unexpected push payload: %#v", provider.pushSecretPayload)
+	}
+	if !bytes.Equal(provider.pushSecretPayload.Metadata, metadata) {
+		t.Fatalf("unexpected metadata: %q", string(provider.pushSecretPayload.Metadata))
+	}
+	if provider.pushSecretProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.pushSecretProviderRef)
+	}
+	if provider.pushSecretNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.pushSecretNamespace)
+	}
+}
+
+func TestClientPushSecretForwardsKubernetesSecretShape(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	provider := &fakeV2Provider{}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	metadata := []byte(`{"mergePolicy":"replace"}`)
+	secret := &corev1.Secret{
+		Type: corev1.SecretTypeDockerConfigJson,
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      map[string]string{"team": "platform"},
+			Annotations: map[string]string{"owner": "app-team"},
+		},
+		Data: map[string][]byte{
+			".dockerconfigjson": []byte("payload"),
+		},
+	}
+	pushData := fakePushSecretData{
+		property:  testProperty,
+		secretKey: ".dockerconfigjson",
+		remoteKey: serverTestRemoteKey,
+		metadata:  &apiextensionsv1.JSON{Raw: metadata},
+	}
+
+	err := client.PushSecret(context.Background(), secret, pushData)
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	if provider.pushSecretSecret == nil {
+		t.Fatal("expected pushed secret to be recorded")
+	}
+	if provider.pushSecretSecret.Type != corev1.SecretTypeDockerConfigJson {
+		t.Errorf("expected secret type %q, got %q", corev1.SecretTypeDockerConfigJson, provider.pushSecretSecret.Type)
+	}
+	if got, want := provider.pushSecretSecret.Labels["team"], "platform"; got != want {
+		t.Errorf("expected secret label team=%q, got %q", want, got)
+	}
+	if got, want := provider.pushSecretSecret.Annotations["owner"], "app-team"; got != want {
+		t.Errorf("expected secret annotation owner=%q, got %q", want, got)
+	}
+	if got, want := string(provider.pushSecretSecret.Data[".dockerconfigjson"]), "payload"; got != want {
+		t.Errorf("expected secret payload %q, got %q", want, got)
+	}
+	if provider.pushSecretPayload == nil {
+		t.Fatal("expected push payload to be recorded")
+	}
+	if !bytes.Equal(provider.pushSecretPayload.Metadata, metadata) {
+		t.Fatalf("unexpected metadata: %q", string(provider.pushSecretPayload.Metadata))
+	}
+}
+
+func TestClientDeleteSecretConvertsRemoteRef(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	provider := &fakeV2Provider{}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	err := client.DeleteSecret(context.Background(), fakePushSecretRemoteRef{
+		remoteKey: serverTestRemoteKey,
+		property:  testProperty,
+	})
+	if err != nil {
+		t.Fatalf("DeleteSecret() error = %v", err)
+	}
+
+	if provider.deleteSecretRemoteRef == nil {
+		t.Fatal("expected delete remote ref to be recorded")
+	}
+	if provider.deleteSecretRemoteRef.RemoteKey != serverTestRemoteKey || provider.deleteSecretRemoteRef.Property != testProperty {
+		t.Fatalf("unexpected remote ref: %#v", provider.deleteSecretRemoteRef)
+	}
+	if provider.deleteSecretProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.deleteSecretProviderRef)
+	}
+	if provider.deleteSecretNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.deleteSecretNamespace)
+	}
+}
+
+func TestClientSecretExistsConvertsRemoteRef(t *testing.T) {
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns"}
+	provider := &fakeV2Provider{secretExistsResponse: true}
+	client := NewClient(provider, providerRef, testSourceNamespace)
+
+	exists, err := client.SecretExists(context.Background(), fakePushSecretRemoteRef{
+		remoteKey: serverTestRemoteKey,
+		property:  testProperty,
+	})
+	if err != nil {
+		t.Fatalf("SecretExists() error = %v", err)
+	}
+
+	if !exists {
+		t.Fatal("expected secret to exist")
+	}
+	if provider.secretExistsRemoteRef == nil {
+		t.Fatal("expected exists remote ref to be recorded")
+	}
+	if provider.secretExistsRemoteRef.RemoteKey != serverTestRemoteKey || provider.secretExistsRemoteRef.Property != testProperty {
+		t.Fatalf("unexpected remote ref: %#v", provider.secretExistsRemoteRef)
+	}
+	if provider.secretExistsProviderRef != providerRef {
+		t.Fatalf("unexpected provider ref: %#v", provider.secretExistsProviderRef)
+	}
+	if provider.secretExistsNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", provider.secretExistsNamespace)
+	}
+}
+
+func TestClientValidateMapsProviderErrors(t *testing.T) {
+	t.Run("success", func(t *testing.T) {
+		providerRef := &pb.ProviderReference{
+			Name:         "provider",
+			Namespace:    "config-ns",
+			StoreRefKind: esv1.ProviderStoreKindStr,
+		}
+		provider := &fakeV2Provider{}
+		client := NewClient(provider, providerRef, testSourceNamespace)
+
+		result, err := client.Validate()
+		if err != nil {
+			t.Fatalf("Validate() error = %v", err)
+		}
+		if result != esv1.ValidationResultReady {
+			t.Fatalf("expected ValidationResultReady, got %q", result)
+		}
+		if provider.validateProviderRef != providerRef {
+			t.Fatalf("unexpected provider ref: %#v", provider.validateProviderRef)
+		}
+		if provider.validateProviderRef.StoreRefKind != esv1.ProviderStoreKindStr {
+			t.Fatalf("unexpected store_ref_kind: %q", provider.validateProviderRef.StoreRefKind)
+		}
+		if provider.validateNamespace != testSourceNamespace {
+			t.Fatalf("unexpected source namespace: %q", provider.validateNamespace)
+		}
+	})
+
+	t.Run("error", func(t *testing.T) {
+		validateErr := errors.New("invalid credentials")
+		provider := &fakeV2Provider{validateErr: validateErr}
+		client := NewClient(provider, &pb.ProviderReference{Name: "provider"}, testSourceNamespace)
+
+		result, err := client.Validate()
+		if !errors.Is(err, validateErr) {
+			t.Fatalf("expected %v, got %v", validateErr, err)
+		}
+		if result != esv1.ValidationResultError {
+			t.Fatalf("expected ValidationResultError, got %q", result)
+		}
+	})
+}
+
+func TestCompatibilityClientValidateUsesCompatibilityStore(t *testing.T) {
+	provider := &fakeV2Provider{}
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       "runtime-store",
+		StoreNamespace:  "tenant-a",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"db","value":"secret"}]}}}`),
+	}
+	client := NewCompatibilityClient(provider, compatibilityStore, testSourceNamespace)
+
+	result, err := client.Validate()
+	if err != nil {
+		t.Fatalf("Validate() error = %v", err)
+	}
+	if result != esv1.ValidationResultReady {
+		t.Fatalf("expected ValidationResultReady, got %q", result)
+	}
+	if !provider.validateCalled {
+		t.Fatal("expected provider Validate to be called")
+	}
+	if provider.validateProviderRef != nil {
+		t.Fatalf("expected provider ref to be nil, got %#v", provider.validateProviderRef)
+	}
+	if provider.validateCompatibilityStore != compatibilityStore {
+		t.Fatalf("unexpected compatibility store: %#v", provider.validateCompatibilityStore)
+	}
+}
+
+func TestCompatibilityClientValidateReturnsErrorWhenValidationHookFails(t *testing.T) {
+	validateErr := errors.New("runtime not serving")
+	client := NewCompatibilityClient(&fakeV2Provider{validateErr: validateErr}, &pb.CompatibilityStore{
+		StoreName:       "runtime-store",
+		StoreNamespace:  "tenant-a",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"db","value":"secret"}]}}}`),
+	}, testSourceNamespace)
+
+	result, err := client.Validate()
+	if !errors.Is(err, validateErr) {
+		t.Fatalf("expected %v, got %v", validateErr, err)
+	}
+	if result != esv1.ValidationResultError {
+		t.Fatalf("expected ValidationResultError, got %q", result)
+	}
+}
+
+func TestClientCloseDelegates(t *testing.T) {
+	closeErr := errors.New("close failed")
+	provider := &fakeV2Provider{closeErr: closeErr}
+	client := NewClient(provider, &pb.ProviderReference{Name: "provider"}, testSourceNamespace)
+
+	err := client.Close(context.Background())
+	if !errors.Is(err, closeErr) {
+		t.Fatalf("expected %v, got %v", closeErr, err)
+	}
+	if !provider.closeCalled {
+		t.Fatal("expected provider close to be called")
+	}
+}

+ 78 - 0
providers/v2/adapter/store/compatibility_store.go

@@ -0,0 +1,78 @@
+/*
+Copyright © The ESO Authors
+
+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 store
+
+import (
+	"encoding/json"
+	"fmt"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+// CompatibilityStoreToSyntheticStore materializes a v1 SecretStore/ClusterSecretStore payload
+// into a GenericStore that existing v1 providers can consume.
+func CompatibilityStoreToSyntheticStore(store *pb.CompatibilityStore) (*SyntheticStore, error) {
+	if store == nil {
+		return nil, fmt.Errorf("compatibility store is nil")
+	}
+	if len(store.GetStoreSpecJson()) == 0 {
+		return nil, fmt.Errorf("compatibility store spec is empty")
+	}
+
+	spec := &esv1.SecretStoreSpec{}
+	if err := json.Unmarshal(store.GetStoreSpecJson(), spec); err != nil {
+		return nil, fmt.Errorf("decode compatibility store spec: %w", err)
+	}
+	if spec.Provider == nil {
+		return nil, fmt.Errorf("compatibility store provider config is required")
+	}
+
+	switch store.GetStoreKind() {
+	case esv1.SecretStoreKind, esv1.ClusterSecretStoreKind:
+	default:
+		return nil, fmt.Errorf("unsupported compatibility store kind %q", store.GetStoreKind())
+	}
+
+	gvk := schema.GroupVersionKind{
+		Group:   esv1.SchemeGroupVersion.Group,
+		Version: esv1.SchemeGroupVersion.Version,
+		Kind:    store.GetStoreKind(),
+	}
+	syntheticStore := &SyntheticStore{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: esv1.SchemeGroupVersion.String(),
+			Kind:       store.GetStoreKind(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:       store.GetStoreName(),
+			Namespace:  store.GetStoreNamespace(),
+			UID:        types.UID(store.GetStoreUid()),
+			Generation: store.GetStoreGeneration(),
+		},
+		spec:   spec,
+		status: esv1.SecretStoreStatus{},
+		gvk:    gvk,
+	}
+	syntheticStore.SetGroupVersionKind(gvk)
+
+	return syntheticStore, nil
+}

+ 83 - 0
providers/v2/adapter/store/go.mod

@@ -0,0 +1,83 @@
+module github.com/external-secrets/external-secrets/providers/v2/adapter/store
+
+go 1.26.2
+
+replace (
+	github.com/external-secrets/external-secrets => ../../../..
+	github.com/external-secrets/external-secrets/apis => ../../../../apis
+	github.com/external-secrets/external-secrets/proto => ../../common/proto
+	github.com/external-secrets/external-secrets/providers/v2/common => ../../common
+)
+
+require (
+	github.com/external-secrets/external-secrets/apis v0.0.0
+	github.com/external-secrets/external-secrets/proto v0.0.0
+	github.com/external-secrets/external-secrets/providers/v2/common v0.0.0
+	k8s.io/api v0.35.2
+	k8s.io/apiextensions-apiserver v0.35.2
+	k8s.io/apimachinery v0.35.2
+	sigs.k8s.io/controller-runtime v0.23.3
+)
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+	github.com/go-logr/logr v1.4.3 // indirect
+	github.com/go-openapi/jsonpointer v0.22.5 // indirect
+	github.com/go-openapi/jsonreference v0.21.5 // indirect
+	github.com/go-openapi/swag v0.25.5 // indirect
+	github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
+	github.com/go-openapi/swag/conv v0.25.5 // indirect
+	github.com/go-openapi/swag/fileutils v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonname v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
+	github.com/go-openapi/swag/loading v0.25.5 // indirect
+	github.com/go-openapi/swag/mangling v0.25.5 // indirect
+	github.com/go-openapi/swag/netutils v0.25.5 // indirect
+	github.com/go-openapi/swag/stringutils v0.25.5 // indirect
+	github.com/go-openapi/swag/typeutils v0.25.5 // indirect
+	github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.7.1 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/prometheus/client_golang v1.23.2 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
+	github.com/prometheus/common v0.67.5 // indirect
+	github.com/prometheus/procfs v0.20.1 // indirect
+	github.com/spf13/pflag v1.0.10 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/net v0.52.0 // indirect
+	golang.org/x/oauth2 v0.36.0 // indirect
+	golang.org/x/sync v0.20.0 // indirect
+	golang.org/x/sys v0.42.0 // indirect
+	golang.org/x/term v0.41.0 // indirect
+	golang.org/x/text v0.35.0 // indirect
+	golang.org/x/time v0.15.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
+	google.golang.org/grpc v1.79.3 // indirect
+	google.golang.org/protobuf v1.36.11 // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	k8s.io/client-go v0.35.2 // indirect
+	k8s.io/klog/v2 v2.140.0 // indirect
+	k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
+	k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
+	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
+	sigs.k8s.io/randfill v1.0.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
+)

+ 207 - 0
providers/v2/adapter/store/go.sum

@@ -0,0 +1,207 @@
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
+github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
+github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
+github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
+github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
+github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
+github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
+github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
+github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
+github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
+github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
+github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
+github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
+github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
+github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
+github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
+github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
+github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
+github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
+github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
+github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
+github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
+github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
+github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
+github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
+github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
+github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
+github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
+github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
+github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
+github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
+github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
+github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
+github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
+github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
+github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc=
+github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
+github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
+github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
+gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
+gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
+k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
+k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=
+k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
+k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
+k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
+k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
+k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
+k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
+k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf h1:btPscg4cMql0XdYK2jLsJcNEKmACJz8l+U7geC06FiM=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
+sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

+ 506 - 0
providers/v2/adapter/store/server.go

@@ -0,0 +1,506 @@
+/*
+Copyright © The ESO Authors
+
+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 store
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	corev1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+const readIdentityRequiredErr = "provider reference or compatibility store is required for read operations"
+
+// Server wraps v1 providers and generators and exposes them as v2 gRPC services.
+// This allows existing v1 provider and generator implementations to be used in the v2 architecture.
+type Server struct {
+	pb.UnimplementedSecretStoreProviderServer
+	kubeClient client.Client
+
+	// we support multiple v1 providers, so we need to map the v2 provider
+	// with apiVersion+kind to the corresponding v1 provider
+	resourceMapping ProviderMapping
+	specMapper      SpecMapper
+	// compatibilityProvider handles read requests that carry a serialized v1 SecretStore payload.
+	compatibilityProvider esv1.ProviderInterface
+}
+
+// ProviderMapping maps Kubernetes resources to their provider implementations.
+type ProviderMapping map[schema.GroupVersionKind]esv1.ProviderInterface
+
+// SpecMapper maps a provider reference to a SecretStoreSpec.
+// This is used to create a synthetic store for the v1 provider.
+type SpecMapper func(ref *pb.ProviderReference, sourceNamespace string) (*esv1.SecretStoreSpec, error)
+
+// NewServer creates a new AdapterServer that wraps v1 providers and generators.
+func NewServer(kubeClient client.Client, resourceMapping ProviderMapping, specMapping SpecMapper) *Server {
+	return NewServerWithCompatibilityProvider(kubeClient, resourceMapping, specMapping, nil)
+}
+
+// NewServerWithCompatibilityProvider creates a new AdapterServer with an explicit provider
+// for compatibility-store based read requests.
+func NewServerWithCompatibilityProvider(
+	kubeClient client.Client,
+	resourceMapping ProviderMapping,
+	specMapping SpecMapper,
+	compatibilityProvider esv1.ProviderInterface,
+) *Server {
+	return &Server{
+		kubeClient:            kubeClient,
+		resourceMapping:       resourceMapping,
+		specMapper:            specMapping,
+		compatibilityProvider: compatibilityProvider,
+	}
+}
+
+func (s *Server) resolveProvider(ref *pb.ProviderReference) (esv1.ProviderInterface, error) {
+	if ref == nil {
+		return nil, fmt.Errorf("provider reference is nil")
+	}
+
+	splitted := strings.Split(ref.ApiVersion, "/")
+	if len(splitted) != 2 {
+		return nil, fmt.Errorf("invalid api version: %s", ref.ApiVersion)
+	}
+	group := splitted[0]
+	version := splitted[1]
+
+	key := schema.GroupVersionKind{
+		Group:   group,
+		Version: version,
+		Kind:    ref.Kind,
+	}
+	v1Provider, ok := s.resourceMapping[key]
+	if !ok {
+		return nil, fmt.Errorf("resource mapping not found for %q", key)
+	}
+	return v1Provider, nil
+}
+
+func (s *Server) getClient(ctx context.Context, ref *pb.ProviderReference, namespace string) (esv1.SecretsClient, error) {
+	if ref == nil {
+		return nil, fmt.Errorf("provider reference is required")
+	}
+
+	spec, err := s.specMapper(ref, namespace)
+	if err != nil {
+		return nil, fmt.Errorf("failed to map provider reference to spec: %w", err)
+	}
+	// namespace is the resolved authentication namespace (from Provider/ClusterProvider if applicable)
+	syntheticStore, err := NewSyntheticStore(spec, namespace, ref.GetStoreRefKind())
+	if err != nil {
+		return nil, fmt.Errorf("failed to create synthetic store: %w", err)
+	}
+	provider, err := s.resolveProvider(ref)
+	if err != nil {
+		return nil, fmt.Errorf("failed to resolve provider: %w", err)
+	}
+	clientNamespace := namespace
+	if ref.GetNamespace() != "" {
+		clientNamespace = ref.GetNamespace()
+	}
+	syntheticStore.Namespace = clientNamespace
+	return provider.NewClient(ctx, syntheticStore, s.kubeClient, clientNamespace)
+}
+
+func (s *Server) getCompatibilityClient(ctx context.Context, store *pb.CompatibilityStore, namespace string) (esv1.SecretsClient, error) {
+	if s.compatibilityProvider == nil {
+		return nil, fmt.Errorf("compatibility provider is not configured")
+	}
+
+	syntheticStore, err := CompatibilityStoreToSyntheticStore(store)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create synthetic store from compatibility payload: %w", err)
+	}
+
+	return s.compatibilityProvider.NewClient(ctx, syntheticStore, s.kubeClient, namespace)
+}
+
+func (s *Server) getReadClient(
+	ctx context.Context,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (esv1.SecretsClient, error) {
+	if err := validateReadIdentity(providerRef, compatibilityStore); err != nil {
+		return nil, err
+	}
+	if compatibilityStore != nil {
+		return s.getCompatibilityClient(ctx, compatibilityStore, sourceNamespace)
+	}
+
+	return s.getClient(ctx, providerRef, sourceNamespace)
+}
+
+func validateReadIdentity(providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore) error {
+	if providerRef == nil && compatibilityStore == nil {
+		return fmt.Errorf(readIdentityRequiredErr)
+	}
+	return nil
+}
+
+func (s *Server) getClientForRemoteRef(
+	ctx context.Context,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+	remoteRef *pb.ExternalSecretDataRemoteRef,
+) (esv1.SecretsClient, esv1.ExternalSecretDataRemoteRef, error) {
+	if remoteRef == nil {
+		return nil, esv1.ExternalSecretDataRemoteRef{}, fmt.Errorf("request or remote ref is nil")
+	}
+	if err := validateSourceNamespace(sourceNamespace); err != nil {
+		return nil, esv1.ExternalSecretDataRemoteRef{}, err
+	}
+
+	client, err := s.getReadClient(ctx, providerRef, compatibilityStore, sourceNamespace)
+	if err != nil {
+		if err.Error() == readIdentityRequiredErr {
+			return nil, esv1.ExternalSecretDataRemoteRef{}, err
+		}
+		return nil, esv1.ExternalSecretDataRemoteRef{}, fmt.Errorf("failed to get client: %w", err)
+	}
+
+	ref := esv1.ExternalSecretDataRemoteRef{
+		Key:      remoteRef.Key,
+		Version:  remoteRef.Version,
+		Property: remoteRef.Property,
+	}
+	if remoteRef.DecodingStrategy != "" {
+		ref.DecodingStrategy = esv1.ExternalSecretDecodingStrategy(remoteRef.DecodingStrategy)
+	}
+	if remoteRef.MetadataPolicy != "" {
+		ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicy(remoteRef.MetadataPolicy)
+	}
+
+	return client, ref, nil
+}
+
+// GetSecret retrieves a single secret from the provider.
+func (s *Server) GetSecret(ctx context.Context, req *pb.GetSecretRequest) (*pb.GetSecretResponse, error) {
+	if req == nil {
+		return nil, fmt.Errorf("request or remote ref is nil")
+	}
+	client, ref, err := s.getClientForRemoteRef(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace, req.RemoteRef)
+	if err != nil {
+		return nil, err
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	value, err := client.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get secret: %w", err)
+	}
+
+	return &pb.GetSecretResponse{
+		Value: value,
+	}, nil
+}
+
+// GetSecretMap retrieves multiple key/value pairs from a single secret object.
+func (s *Server) GetSecretMap(ctx context.Context, req *pb.GetSecretMapRequest) (*pb.GetSecretMapResponse, error) {
+	if req == nil {
+		return nil, fmt.Errorf("request or remote ref is nil")
+	}
+	client, ref, err := s.getClientForRemoteRef(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace, req.RemoteRef)
+	if err != nil {
+		return nil, err
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	secrets, err := client.GetSecretMap(ctx, ref)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get secret map: %w", err)
+	}
+
+	return &pb.GetSecretMapResponse{
+		Secrets: secrets,
+	}, nil
+}
+
+// PushSecret writes a secret to the provider.
+func (s *Server) PushSecret(ctx context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
+	if req == nil || req.PushSecretData == nil {
+		return nil, fmt.Errorf("request or push secret data is nil")
+	}
+	if err := validateSourceNamespace(req.SourceNamespace); err != nil {
+		return nil, err
+	}
+
+	client, err := s.getReadClient(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get client: %w", err)
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	// Convert map[string][]byte to *corev1.Secret
+	secret := &corev1.Secret{
+		Data: req.SecretData,
+		Type: corev1.SecretType(req.SecretType),
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      req.SecretLabels,
+			Annotations: req.SecretAnnotations,
+		},
+	}
+
+	// Convert protobuf PushSecretData to v1 PushSecretData
+	pushData := &pushSecretData{
+		property:  req.PushSecretData.Property,
+		secretKey: req.PushSecretData.SecretKey,
+		remoteKey: req.PushSecretData.RemoteKey,
+		metadata:  req.PushSecretData.Metadata,
+	}
+
+	// Call v1 PushSecret
+	if err := client.PushSecret(ctx, secret, pushData); err != nil {
+		return nil, fmt.Errorf("failed to push secret: %w", err)
+	}
+
+	return &pb.PushSecretResponse{}, nil
+}
+
+// DeleteSecret deletes a secret from the provider.
+func (s *Server) DeleteSecret(ctx context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
+	if req == nil || req.RemoteRef == nil {
+		return nil, fmt.Errorf("request or remote ref is nil")
+	}
+	if err := validateSourceNamespace(req.SourceNamespace); err != nil {
+		return nil, err
+	}
+
+	client, err := s.getReadClient(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get client: %w", err)
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	// Convert protobuf remote ref to v1 PushSecretRemoteRef
+	remoteRef := &pushSecretRemoteRef{
+		remoteKey: req.RemoteRef.RemoteKey,
+		property:  req.RemoteRef.Property,
+	}
+
+	// Call v1 DeleteSecret
+	if err := client.DeleteSecret(ctx, remoteRef); err != nil {
+		return nil, fmt.Errorf("failed to delete secret: %w", err)
+	}
+
+	return &pb.DeleteSecretResponse{}, nil
+}
+
+// SecretExists checks if a secret exists in the provider.
+func (s *Server) SecretExists(ctx context.Context, req *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
+	if req == nil || req.RemoteRef == nil {
+		return nil, fmt.Errorf("request or remote ref is nil")
+	}
+	if err := validateSourceNamespace(req.SourceNamespace); err != nil {
+		return nil, err
+	}
+
+	client, err := s.getReadClient(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get client: %w", err)
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	// Convert protobuf remote ref to v1 PushSecretRemoteRef
+	remoteRef := &pushSecretRemoteRef{
+		remoteKey: req.RemoteRef.RemoteKey,
+		property:  req.RemoteRef.Property,
+	}
+
+	// Call v1 SecretExists
+	exists, err := client.SecretExists(ctx, remoteRef)
+	if err != nil {
+		return nil, fmt.Errorf("failed to check if secret exists: %w", err)
+	}
+
+	return &pb.SecretExistsResponse{
+		Exists: exists,
+	}, nil
+}
+
+// GetAllSecrets retrieves multiple secrets from the provider.
+func (s *Server) GetAllSecrets(ctx context.Context, req *pb.GetAllSecretsRequest) (*pb.GetAllSecretsResponse, error) {
+	if req == nil || req.Find == nil {
+		return nil, fmt.Errorf("request or find criteria is nil")
+	}
+	if err := validateSourceNamespace(req.SourceNamespace); err != nil {
+		return nil, err
+	}
+	client, err := s.getReadClient(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace)
+	if err != nil {
+		if err.Error() == readIdentityRequiredErr {
+			return nil, err
+		}
+		return nil, fmt.Errorf("failed to get client: %w", err)
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	// Convert protobuf ExternalSecretFind to v1 ExternalSecretFind
+	find := esv1.ExternalSecretFind{
+		Tags:               req.Find.Tags,
+		ConversionStrategy: esv1.ExternalSecretConversionStrategy(req.Find.ConversionStrategy),
+		DecodingStrategy:   esv1.ExternalSecretDecodingStrategy(req.Find.DecodingStrategy),
+	}
+
+	// Convert Path from string to *string
+	if req.Find.Path != "" {
+		path := req.Find.Path
+		find.Path = &path
+	}
+
+	if req.Find.Name != nil {
+		find.Name = &esv1.FindName{
+			RegExp: req.Find.Name.Regexp,
+		}
+	}
+
+	// Call v1 GetAllSecrets
+	secrets, err := client.GetAllSecrets(ctx, find)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get all secrets: %w", err)
+	}
+
+	return &pb.GetAllSecretsResponse{
+		Secrets: secrets,
+	}, nil
+}
+
+// Validate checks if the provider configuration is valid.
+func (s *Server) Validate(ctx context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
+	if req == nil {
+		return nil, fmt.Errorf("request is nil")
+	}
+
+	client, err := s.getReadClient(ctx, req.ProviderRef, req.CompatibilityStore, req.SourceNamespace)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get client: %w", err)
+	}
+	defer func() { _ = client.Close(ctx) }()
+
+	result, err := client.Validate()
+	if err != nil {
+		return &pb.ValidateResponse{
+			Valid: false,
+			Error: err.Error(),
+		}, nil
+	}
+
+	var valid bool
+	switch result {
+	case esv1.ValidationResultReady:
+		valid = true
+	case esv1.ValidationResultUnknown:
+		valid = true // Unknown is treated as valid but warns
+	case esv1.ValidationResultError:
+		valid = false
+	}
+
+	return &pb.ValidateResponse{
+		Valid:    valid,
+		Warnings: []string{},
+	}, nil
+}
+
+// Capabilities returns the capabilities of the provider.
+// TODO: remove / rewrite capabilities:
+// the provider should advertise what providers/generators it supports.
+func (s *Server) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
+	if req == nil {
+		return nil, fmt.Errorf("request is nil")
+	}
+
+	provider, err := s.resolveProvider(req.ProviderRef)
+	if err != nil {
+		return nil, fmt.Errorf("failed to resolve provider: %w", err)
+	}
+	caps := provider.Capabilities()
+	var pbCaps pb.SecretStoreCapabilities
+	switch caps {
+	case esv1.SecretStoreReadOnly:
+		pbCaps = pb.SecretStoreCapabilities_READ_ONLY
+	case esv1.SecretStoreWriteOnly:
+		pbCaps = pb.SecretStoreCapabilities_WRITE_ONLY
+	case esv1.SecretStoreReadWrite:
+		pbCaps = pb.SecretStoreCapabilities_READ_WRITE
+	default:
+		pbCaps = pb.SecretStoreCapabilities_READ_ONLY
+	}
+
+	return &pb.CapabilitiesResponse{
+		Capabilities: pbCaps,
+	}, nil
+}
+
+func validateSourceNamespace(sourceNamespace string) error {
+	if sourceNamespace == "" {
+		return fmt.Errorf("source namespace is required")
+	}
+	return nil
+}
+
+// pushSecretData implements esv1.PushSecretData.
+type pushSecretData struct {
+	property  string
+	secretKey string
+	remoteKey string
+	metadata  []byte
+}
+
+func (p *pushSecretData) GetProperty() string {
+	return p.property
+}
+
+func (p *pushSecretData) GetSecretKey() string {
+	return p.secretKey
+}
+
+func (p *pushSecretData) GetRemoteKey() string {
+	return p.remoteKey
+}
+
+func (p *pushSecretData) GetMetadata() *apiextensionsv1.JSON {
+	if len(p.metadata) == 0 {
+		return nil
+	}
+	return &apiextensionsv1.JSON{
+		Raw: p.metadata,
+	}
+}
+
+// pushSecretRemoteRef implements esv1.PushSecretRemoteRef.
+type pushSecretRemoteRef struct {
+	remoteKey string
+	property  string
+}
+
+func (p *pushSecretRemoteRef) GetRemoteKey() string {
+	return p.remoteKey
+}
+
+func (p *pushSecretRemoteRef) GetProperty() string {
+	return p.property
+}

+ 1168 - 0
providers/v2/adapter/store/server_test.go

@@ -0,0 +1,1168 @@
+/*
+Copyright © The ESO Authors
+
+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 store
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"strings"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+const (
+	serverTestRemoteKey       = "remote/path"
+	serverTestProperty        = "property"
+	serverTestSourceNamespace = "tenant-a"
+	serverTestValue           = "value"
+)
+
+type fakeProviderInterface struct {
+	newClient func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error)
+	caps      esv1.SecretStoreCapabilities
+}
+
+func (f *fakeProviderInterface) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
+	return f.newClient(ctx, store, kube, namespace)
+}
+
+func (f *fakeProviderInterface) Capabilities() esv1.SecretStoreCapabilities {
+	return f.caps
+}
+
+func (f *fakeProviderInterface) ValidateStore(esv1.GenericStore) (admission.Warnings, error) {
+	return nil, nil
+}
+
+type fakeSecretsClient struct {
+	getSecretResponse []byte
+	getSecretErr      error
+	getSecretRef      esv1.ExternalSecretDataRemoteRef
+
+	getSecretMapResponse map[string][]byte
+	getSecretMapErr      error
+	getSecretMapRef      esv1.ExternalSecretDataRemoteRef
+
+	getAllSecretsResponse map[string][]byte
+	getAllSecretsErr      error
+	getAllSecretsFind     esv1.ExternalSecretFind
+
+	pushSecretErr    error
+	pushSecretSecret *corev1.Secret
+	pushSecretData   esv1.PushSecretData
+	deleteSecretErr  error
+	deleteSecretRef  esv1.PushSecretRemoteRef
+	secretExistsResp bool
+	secretExistsErr  error
+	secretExistsRef  esv1.PushSecretRemoteRef
+	validateResult   esv1.ValidationResult
+	validateErr      error
+	closeCalled      bool
+}
+
+func (f *fakeSecretsClient) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	f.getSecretRef = ref
+	return f.getSecretResponse, f.getSecretErr
+}
+
+func (f *fakeSecretsClient) GetSecretMap(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	f.getSecretMapRef = ref
+	return f.getSecretMapResponse, f.getSecretMapErr
+}
+
+func (f *fakeSecretsClient) GetAllSecrets(_ context.Context, find esv1.ExternalSecretFind) (map[string][]byte, error) {
+	f.getAllSecretsFind = find
+	return f.getAllSecretsResponse, f.getAllSecretsErr
+}
+
+func (f *fakeSecretsClient) PushSecret(_ context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
+	f.pushSecretSecret = secret
+	f.pushSecretData = data
+	return f.pushSecretErr
+}
+
+func (f *fakeSecretsClient) DeleteSecret(_ context.Context, remoteRef esv1.PushSecretRemoteRef) error {
+	f.deleteSecretRef = remoteRef
+	return f.deleteSecretErr
+}
+
+func (f *fakeSecretsClient) SecretExists(_ context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
+	f.secretExistsRef = remoteRef
+	return f.secretExistsResp, f.secretExistsErr
+}
+
+func (f *fakeSecretsClient) Validate() (esv1.ValidationResult, error) {
+	return f.validateResult, f.validateErr
+}
+
+func (f *fakeSecretsClient) Close(context.Context) error {
+	f.closeCalled = true
+	return nil
+}
+
+type specMapperRecorder struct {
+	ref             *pb.ProviderReference
+	sourceNamespace string
+	spec            *esv1.SecretStoreSpec
+	err             error
+}
+
+func (r *specMapperRecorder) mapRef(ref *pb.ProviderReference, sourceNamespace string) (*esv1.SecretStoreSpec, error) {
+	r.ref = ref
+	r.sourceNamespace = sourceNamespace
+	return r.spec, r.err
+}
+
+func mustCompatibilityStore(t *testing.T, spec *esv1.SecretStoreSpec) *pb.CompatibilityStore {
+	t.Helper()
+
+	specBytes, err := json.Marshal(spec)
+	if err != nil {
+		t.Fatalf("json.Marshal() error = %v", err)
+	}
+
+	return &pb.CompatibilityStore{
+		StoreName:       "compat-store",
+		StoreNamespace:  serverTestSourceNamespace,
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   specBytes,
+	}
+}
+
+func TestCompatibilityStoreToSyntheticStoreRejectsInvalidPayloads(t *testing.T) {
+	validSpec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+	validSpecBytes, err := json.Marshal(validSpec)
+	if err != nil {
+		t.Fatalf("json.Marshal() error = %v", err)
+	}
+
+	testCases := []struct {
+		name  string
+		store *pb.CompatibilityStore
+		want  string
+	}{
+		{
+			name:  "nil store",
+			store: nil,
+			want:  "compatibility store is nil",
+		},
+		{
+			name: "empty spec",
+			store: &pb.CompatibilityStore{
+				StoreKind: esv1.SecretStoreKind,
+			},
+			want: "compatibility store spec is empty",
+		},
+		{
+			name: "bad json",
+			store: &pb.CompatibilityStore{
+				StoreKind:     esv1.SecretStoreKind,
+				StoreSpecJson: []byte(`{`),
+			},
+			want: "decode compatibility store spec:",
+		},
+		{
+			name: "bad kind",
+			store: &pb.CompatibilityStore{
+				StoreKind:     "UnknownStore",
+				StoreSpecJson: validSpecBytes,
+			},
+			want: `unsupported compatibility store kind "UnknownStore"`,
+		},
+		{
+			name: "missing provider",
+			store: &pb.CompatibilityStore{
+				StoreKind:     esv1.SecretStoreKind,
+				StoreSpecJson: []byte(`{"retrySettings":{"maxRetries":1}}`),
+			},
+			want: "compatibility store provider config is required",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := CompatibilityStoreToSyntheticStore(tc.store)
+			if err == nil || !strings.Contains(err.Error(), tc.want) {
+				t.Fatalf("expected error containing %q, got %v", tc.want, err)
+			}
+		})
+	}
+}
+
+func TestServerGetSecretMapsRemoteRefAndSyntheticStoreNamespace(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				Fake: &esv1.FakeProvider{},
+			},
+		},
+	}
+	fakeClient := &fakeSecretsClient{getSecretResponse: []byte("secret-value")}
+
+	var receivedStore esv1.GenericStore
+	var receivedNamespace string
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(_ context.Context, store esv1.GenericStore, _ client.Client, namespace string) (esv1.SecretsClient, error) {
+				receivedStore = store
+				receivedNamespace = namespace
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	req := &pb.GetSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion:   "provider.external-secrets.io/v2alpha1",
+			Kind:         "Fake",
+			Name:         "backend",
+			Namespace:    "provider-config-ns",
+			StoreRefKind: esv1.ProviderStoreKindStr,
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		RemoteRef: &pb.ExternalSecretDataRemoteRef{
+			Key:              "sample",
+			Version:          "v1",
+			Property:         "password",
+			DecodingStrategy: string(esv1.ExternalSecretDecodeBase64),
+			MetadataPolicy:   string(esv1.ExternalSecretMetadataPolicyFetch),
+		},
+	}
+
+	resp, err := server.GetSecret(context.Background(), req)
+	if err != nil {
+		t.Fatalf("GetSecret() error = %v", err)
+	}
+
+	if string(resp.Value) != "secret-value" {
+		t.Fatalf("expected secret-value, got %q", string(resp.Value))
+	}
+	if mapper.ref != req.ProviderRef || mapper.sourceNamespace != serverTestSourceNamespace {
+		t.Fatalf("unexpected spec mapper input: ref=%#v namespace=%q", mapper.ref, mapper.sourceNamespace)
+	}
+	if receivedNamespace != "provider-config-ns" {
+		t.Fatalf("unexpected new client namespace: %q", receivedNamespace)
+	}
+	syntheticStore, ok := receivedStore.(*SyntheticStore)
+	if !ok {
+		t.Fatalf("expected SyntheticStore, got %T", receivedStore)
+	}
+	if syntheticStore.Namespace != "provider-config-ns" {
+		t.Fatalf("unexpected synthetic store namespace: %q", syntheticStore.Namespace)
+	}
+	if syntheticStore.Kind != esv1.SecretStoreKind {
+		t.Fatalf("unexpected synthetic store kind: %q", syntheticStore.Kind)
+	}
+	if syntheticStore.GetSpec() != mapper.spec {
+		t.Fatalf("unexpected synthetic spec: %#v", syntheticStore.GetSpec())
+	}
+	if fakeClient.getSecretRef.Key != "sample" || fakeClient.getSecretRef.Version != "v1" || fakeClient.getSecretRef.Property != "password" {
+		t.Fatalf("unexpected remote ref: %#v", fakeClient.getSecretRef)
+	}
+	if fakeClient.getSecretRef.DecodingStrategy != esv1.ExternalSecretDecodeBase64 {
+		t.Fatalf("unexpected decoding strategy: %q", fakeClient.getSecretRef.DecodingStrategy)
+	}
+	if fakeClient.getSecretRef.MetadataPolicy != esv1.ExternalSecretMetadataPolicyFetch {
+		t.Fatalf("unexpected metadata policy: %q", fakeClient.getSecretRef.MetadataPolicy)
+	}
+	if !fakeClient.closeCalled {
+		t.Fatal("expected secrets client to be closed")
+	}
+}
+
+func TestServerPushSecretMapsClusterProviderStoreKindToClusterSecretStore(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				Fake: &esv1.FakeProvider{},
+			},
+		},
+	}
+	fakeClient := &fakeSecretsClient{}
+
+	var receivedStore esv1.GenericStore
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(_ context.Context, store esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+				receivedStore = store
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	_, err := server.PushSecret(context.Background(), &pb.PushSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion:   "provider.external-secrets.io/v2alpha1",
+			Kind:         "Fake",
+			Name:         "backend",
+			StoreRefKind: esv1.ClusterProviderStoreKindStr,
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		SecretData: map[string][]byte{
+			serverTestValue: []byte("secret-value"),
+		},
+		PushSecretData: &pb.PushSecretData{
+			RemoteKey: "remote-secret",
+			SecretKey: serverTestValue,
+		},
+	})
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	syntheticStore, ok := receivedStore.(*SyntheticStore)
+	if !ok {
+		t.Fatalf("expected SyntheticStore, got %T", receivedStore)
+	}
+	if syntheticStore.Kind != esv1.ClusterSecretStoreKind {
+		t.Fatalf("unexpected synthetic store kind: %q", syntheticStore.Kind)
+	}
+}
+
+func TestServerGetSecretMapDelegates(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{Fake: &esv1.FakeProvider{}}},
+	}
+	fakeClient := &fakeSecretsClient{
+		getSecretMapResponse: map[string][]byte{"foo": []byte("bar")},
+	}
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	resp, err := server.GetSecretMap(context.Background(), &pb.GetSecretMapRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		RemoteRef:       &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+	})
+	if err != nil {
+		t.Fatalf("GetSecretMap() error = %v", err)
+	}
+
+	if string(resp.Secrets["foo"]) != "bar" {
+		t.Fatalf("unexpected response: %#v", resp.Secrets)
+	}
+	if fakeClient.getSecretMapRef.Key != "sample" {
+		t.Fatalf("unexpected ref: %#v", fakeClient.getSecretMapRef)
+	}
+}
+
+func TestServerGetSecretUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{getSecretResponse: []byte("secret-value")}
+
+	var receivedStore esv1.GenericStore
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, store esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			receivedStore = store
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps:      provider.caps,
+			newClient: provider.newClient,
+		},
+	}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{
+				Data: []esv1.FakeProviderData{
+					{Key: "sample", Value: "secret-value"},
+				},
+			},
+		},
+	}
+
+	resp, err := server.GetSecret(context.Background(), &pb.GetSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		RemoteRef:          &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+	})
+	if err != nil {
+		t.Fatalf("GetSecret() error = %v", err)
+	}
+
+	if string(resp.Value) != "secret-value" {
+		t.Fatalf("unexpected response: %q", string(resp.Value))
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+	syntheticStore, ok := receivedStore.(*SyntheticStore)
+	if !ok {
+		t.Fatalf("expected SyntheticStore, got %T", receivedStore)
+	}
+	if syntheticStore.Name != "compat-store" || syntheticStore.Namespace != serverTestSourceNamespace {
+		t.Fatalf("unexpected synthetic store metadata: %#v", syntheticStore.ObjectMeta)
+	}
+	if syntheticStore.GetSpec().Provider == nil || syntheticStore.GetSpec().Provider.Fake == nil {
+		t.Fatalf("expected fake provider config, got %#v", syntheticStore.GetSpec().Provider)
+	}
+}
+
+func TestServerCompatibilityReadsRequireExplicitCompatibilityProvider(t *testing.T) {
+	server := NewServer(nil, ProviderMapping{}, (&specMapperRecorder{}).mapRef)
+
+	_, err := server.GetSecret(context.Background(), &pb.GetSecretRequest{
+		CompatibilityStore: &pb.CompatibilityStore{
+			StoreName:       "compat-store",
+			StoreNamespace:  serverTestSourceNamespace,
+			StoreKind:       esv1.SecretStoreKind,
+			StoreSpecJson:   []byte(`{"provider":{"fake":{}}}`),
+			StoreGeneration: 1,
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		RemoteRef:       &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+	})
+	if err == nil || err.Error() != "failed to get client: compatibility provider is not configured" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestServerReadRequestsRequireReadIdentity(t *testing.T) {
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{}, (&specMapperRecorder{}).mapRef, &fakeProviderInterface{})
+
+	testCases := []struct {
+		name string
+		call func() error
+	}{
+		{
+			name: "get secret",
+			call: func() error {
+				_, err := server.GetSecret(context.Background(), &pb.GetSecretRequest{
+					SourceNamespace: serverTestSourceNamespace,
+					RemoteRef:       &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+				})
+				return err
+			},
+		},
+		{
+			name: "get secret map",
+			call: func() error {
+				_, err := server.GetSecretMap(context.Background(), &pb.GetSecretMapRequest{
+					SourceNamespace: serverTestSourceNamespace,
+					RemoteRef:       &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+				})
+				return err
+			},
+		},
+		{
+			name: "get all secrets",
+			call: func() error {
+				_, err := server.GetAllSecrets(context.Background(), &pb.GetAllSecretsRequest{
+					SourceNamespace: serverTestSourceNamespace,
+					Find:            &pb.ExternalSecretFind{},
+				})
+				return err
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := tc.call()
+			if err == nil || err.Error() != "provider reference or compatibility store is required for read operations" {
+				t.Fatalf("unexpected error: %v", err)
+			}
+		})
+	}
+}
+
+func TestServerGetSecretPrefersCompatibilityStoreOverProviderRef(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				Fake: &esv1.FakeProvider{
+					Data: []esv1.FakeProviderData{
+						{Key: "sample", Value: "mapped-value"},
+					},
+				},
+			},
+		},
+	}
+	fakeClient := &fakeSecretsClient{getSecretResponse: []byte("compat-value")}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps:      provider.caps,
+			newClient: provider.newClient,
+		},
+	}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{
+				Data: []esv1.FakeProviderData{
+					{Key: "sample", Value: "compat-value"},
+				},
+			},
+		},
+	}
+
+	resp, err := server.GetSecret(context.Background(), &pb.GetSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		RemoteRef:          &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+	})
+	if err != nil {
+		t.Fatalf("GetSecret() error = %v", err)
+	}
+
+	if string(resp.Value) != "compat-value" {
+		t.Fatalf("unexpected response: %q", string(resp.Value))
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected compatibility store to bypass provider ref mapping, got %#v", mapper.ref)
+	}
+}
+
+func TestServerGetSecretMapUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{
+		getSecretMapResponse: map[string][]byte{"foo": []byte("bar")},
+	}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps:      provider.caps,
+			newClient: provider.newClient,
+		},
+	}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	resp, err := server.GetSecretMap(context.Background(), &pb.GetSecretMapRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		RemoteRef:          &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+	})
+	if err != nil {
+		t.Fatalf("GetSecretMap() error = %v", err)
+	}
+
+	if string(resp.Secrets["foo"]) != "bar" {
+		t.Fatalf("unexpected response: %#v", resp.Secrets)
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+}
+
+func TestServerGetAllSecretsUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{
+		getAllSecretsResponse: map[string][]byte{"db-password": []byte(serverTestValue)},
+	}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps:      provider.caps,
+			newClient: provider.newClient,
+		},
+	}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	resp, err := server.GetAllSecrets(context.Background(), &pb.GetAllSecretsRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		Find:               &pb.ExternalSecretFind{Tags: map[string]string{"team": "a"}},
+	})
+	if err != nil {
+		t.Fatalf("GetAllSecrets() error = %v", err)
+	}
+
+	if string(resp.Secrets["db-password"]) != serverTestValue {
+		t.Fatalf("unexpected response: %#v", resp.Secrets)
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+}
+
+func TestServerPushSecretUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	_, err := server.PushSecret(context.Background(), &pb.PushSecretRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		SecretData:         map[string][]byte{"token": []byte(serverTestValue)},
+		PushSecretData:     &pb.PushSecretData{RemoteKey: serverTestRemoteKey, SecretKey: "token"},
+	})
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+	if fakeClient.pushSecretData.GetRemoteKey() != serverTestRemoteKey {
+		t.Fatalf("unexpected push data: %#v", fakeClient.pushSecretData)
+	}
+}
+
+func TestServerDeleteSecretUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	_, err := server.DeleteSecret(context.Background(), &pb.DeleteSecretRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		RemoteRef:          &pb.PushSecretRemoteRef{RemoteKey: serverTestRemoteKey, Property: serverTestProperty},
+	})
+	if err != nil {
+		t.Fatalf("DeleteSecret() error = %v", err)
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+	if fakeClient.deleteSecretRef.GetRemoteKey() != serverTestRemoteKey {
+		t.Fatalf("unexpected delete ref: %#v", fakeClient.deleteSecretRef)
+	}
+}
+
+func TestServerSecretExistsUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{secretExistsResp: true}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	resp, err := server.SecretExists(context.Background(), &pb.SecretExistsRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+		RemoteRef:          &pb.PushSecretRemoteRef{RemoteKey: serverTestRemoteKey, Property: serverTestProperty},
+	})
+	if err != nil {
+		t.Fatalf("SecretExists() error = %v", err)
+	}
+	if !resp.Exists {
+		t.Fatal("expected secret to exist")
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+	if fakeClient.secretExistsRef.GetRemoteKey() != serverTestRemoteKey {
+		t.Fatalf("unexpected secret exists ref: %#v", fakeClient.secretExistsRef)
+	}
+}
+
+func TestServerValidateUsesCompatibilityStore(t *testing.T) {
+	mapper := &specMapperRecorder{err: errors.New("spec mapper should not be called")}
+	fakeClient := &fakeSecretsClient{validateResult: esv1.ValidationResultReady}
+
+	provider := &fakeProviderInterface{
+		caps: esv1.SecretStoreReadWrite,
+		newClient: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
+			return fakeClient, nil
+		},
+	}
+	server := NewServerWithCompatibilityProvider(nil, ProviderMapping{}, mapper.mapRef, provider)
+
+	spec := &esv1.SecretStoreSpec{
+		Provider: &esv1.SecretStoreProvider{
+			Fake: &esv1.FakeProvider{},
+		},
+	}
+
+	resp, err := server.Validate(context.Background(), &pb.ValidateRequest{
+		CompatibilityStore: mustCompatibilityStore(t, spec),
+		SourceNamespace:    serverTestSourceNamespace,
+	})
+	if err != nil {
+		t.Fatalf("Validate() error = %v", err)
+	}
+	if !resp.Valid {
+		t.Fatalf("expected validate response to be valid: %#v", resp)
+	}
+	if mapper.ref != nil {
+		t.Fatalf("expected spec mapper to be bypassed, got %#v", mapper.ref)
+	}
+}
+
+func TestServerGetAllSecretsMapsFindCriteria(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{Fake: &esv1.FakeProvider{}}},
+	}
+	fakeClient := &fakeSecretsClient{
+		getAllSecretsResponse: map[string][]byte{"db-password": []byte(serverTestValue)},
+	}
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	resp, err := server.GetAllSecrets(context.Background(), &pb.GetAllSecretsRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		Find: &pb.ExternalSecretFind{
+			Tags:               map[string]string{"team": "a"},
+			Path:               "/team-a",
+			ConversionStrategy: string(esv1.ExternalSecretConversionDefault),
+			DecodingStrategy:   string(esv1.ExternalSecretDecodeBase64),
+			Name:               &pb.FindName{Regexp: "db-.*"},
+		},
+	})
+	if err != nil {
+		t.Fatalf("GetAllSecrets() error = %v", err)
+	}
+
+	if string(resp.Secrets["db-password"]) != serverTestValue {
+		t.Fatalf("unexpected response: %#v", resp.Secrets)
+	}
+	if fakeClient.getAllSecretsFind.Tags["team"] != "a" {
+		t.Fatalf("unexpected find tags: %#v", fakeClient.getAllSecretsFind)
+	}
+	if fakeClient.getAllSecretsFind.Path == nil || *fakeClient.getAllSecretsFind.Path != "/team-a" {
+		t.Fatalf("unexpected find path: %#v", fakeClient.getAllSecretsFind.Path)
+	}
+	if fakeClient.getAllSecretsFind.Name == nil || fakeClient.getAllSecretsFind.Name.RegExp != "db-.*" {
+		t.Fatalf("unexpected find name: %#v", fakeClient.getAllSecretsFind.Name)
+	}
+}
+
+func TestServerPushDeleteAndExistsMapWriteRequests(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{Fake: &esv1.FakeProvider{}}},
+	}
+	fakeClient := &fakeSecretsClient{secretExistsResp: true}
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	_, err := server.PushSecret(context.Background(), &pb.PushSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		SecretData: map[string][]byte{
+			"token": []byte(serverTestValue),
+		},
+		PushSecretData: &pb.PushSecretData{
+			RemoteKey: serverTestRemoteKey,
+			SecretKey: "token",
+			Property:  serverTestProperty,
+			Metadata:  []byte(`{"owner":"eso"}`),
+		},
+	})
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	if fakeClient.pushSecretSecret == nil || string(fakeClient.pushSecretSecret.Data["token"]) != serverTestValue {
+		t.Fatalf("unexpected pushed secret: %#v", fakeClient.pushSecretSecret)
+	}
+	if fakeClient.pushSecretSecret.Type != "" {
+		t.Fatalf("unexpected secret type: %q", fakeClient.pushSecretSecret.Type)
+	}
+	if fakeClient.pushSecretData.GetRemoteKey() != serverTestRemoteKey || fakeClient.pushSecretData.GetSecretKey() != "token" || fakeClient.pushSecretData.GetProperty() != serverTestProperty {
+		t.Fatalf("unexpected push data: %#v", fakeClient.pushSecretData)
+	}
+	if got := fakeClient.pushSecretData.GetMetadata(); got == nil || string(got.Raw) != `{"owner":"eso"}` {
+		t.Fatalf("unexpected metadata: %#v", got)
+	}
+
+	_, err = server.DeleteSecret(context.Background(), &pb.DeleteSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		RemoteRef: &pb.PushSecretRemoteRef{
+			RemoteKey: serverTestRemoteKey,
+			Property:  serverTestProperty,
+		},
+	})
+	if err != nil {
+		t.Fatalf("DeleteSecret() error = %v", err)
+	}
+
+	if fakeClient.deleteSecretRef.GetRemoteKey() != serverTestRemoteKey || fakeClient.deleteSecretRef.GetProperty() != serverTestProperty {
+		t.Fatalf("unexpected delete ref: %#v", fakeClient.deleteSecretRef)
+	}
+
+	resp, err := server.SecretExists(context.Background(), &pb.SecretExistsRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		RemoteRef: &pb.PushSecretRemoteRef{
+			RemoteKey: serverTestRemoteKey,
+			Property:  serverTestProperty,
+		},
+	})
+	if err != nil {
+		t.Fatalf("SecretExists() error = %v", err)
+	}
+
+	if !resp.Exists {
+		t.Fatal("expected exists response to be true")
+	}
+	if fakeClient.secretExistsRef.GetRemoteKey() != serverTestRemoteKey || fakeClient.secretExistsRef.GetProperty() != serverTestProperty {
+		t.Fatalf("unexpected exists ref: %#v", fakeClient.secretExistsRef)
+	}
+}
+
+func TestServerPushSecretForwardsKubernetesSecretMetadata(t *testing.T) {
+	mapper := &specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{Fake: &esv1.FakeProvider{}}},
+	}
+	fakeClient := &fakeSecretsClient{}
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+				return fakeClient, nil
+			},
+		},
+	}, mapper.mapRef)
+
+	req := &pb.PushSecretRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: serverTestSourceNamespace,
+		SecretData: map[string][]byte{
+			".dockerconfigjson": []byte("payload"),
+		},
+		SecretType:        string(corev1.SecretTypeDockerConfigJson),
+		SecretLabels:      map[string]string{"team": "platform"},
+		SecretAnnotations: map[string]string{"owner": "app-team"},
+		PushSecretData: &pb.PushSecretData{
+			RemoteKey: serverTestRemoteKey,
+			SecretKey: ".dockerconfigjson",
+			Property:  serverTestProperty,
+			Metadata:  []byte(`{"mergePolicy":"replace"}`),
+		},
+	}
+
+	_, err := server.PushSecret(context.Background(), req)
+	if err != nil {
+		t.Fatalf("PushSecret() error = %v", err)
+	}
+
+	if fakeClient.pushSecretSecret == nil {
+		t.Fatal("expected pushed secret to be recorded")
+	}
+	if got, want := string(fakeClient.pushSecretSecret.Data[".dockerconfigjson"]), "payload"; got != want {
+		t.Errorf("expected payload %q, got %q", want, got)
+	}
+	if got, want := fakeClient.pushSecretSecret.Type, corev1.SecretTypeDockerConfigJson; got != want {
+		t.Errorf("expected secret type %q, got %q", want, got)
+	}
+	if got, want := fakeClient.pushSecretSecret.Labels["team"], "platform"; got != want {
+		t.Errorf("expected secret label team=%q, got %q", want, got)
+	}
+	if got, want := fakeClient.pushSecretSecret.Annotations["owner"], "app-team"; got != want {
+		t.Errorf("expected secret annotation owner=%q, got %q", want, got)
+	}
+}
+
+func TestServerValidateMapsReadyUnknownAndErrorResults(t *testing.T) {
+	t.Run("ready", func(t *testing.T) {
+		resp := runValidateTest(t, esv1.ValidationResultReady, nil)
+		if !resp.Valid {
+			t.Fatalf("expected valid response, got %#v", resp)
+		}
+	})
+
+	t.Run("unknown", func(t *testing.T) {
+		resp := runValidateTest(t, esv1.ValidationResultUnknown, nil)
+		if !resp.Valid {
+			t.Fatalf("expected unknown to be treated as valid, got %#v", resp)
+		}
+	})
+
+	t.Run("error_result", func(t *testing.T) {
+		resp := runValidateTest(t, esv1.ValidationResultError, nil)
+		if resp.Valid {
+			t.Fatalf("expected invalid response, got %#v", resp)
+		}
+	})
+
+	t.Run("error", func(t *testing.T) {
+		validateErr := errors.New("invalid credentials")
+		resp := runValidateTest(t, esv1.ValidationResultError, validateErr)
+		if resp.Valid || resp.Error != "invalid credentials" {
+			t.Fatalf("unexpected response: %#v", resp)
+		}
+	})
+}
+
+func TestServerCapabilitiesMapsProviderCapabilities(t *testing.T) {
+	testCases := []struct {
+		name       string
+		caps       esv1.SecretStoreCapabilities
+		expectedPB pb.SecretStoreCapabilities
+	}{
+		{name: "read_only", caps: esv1.SecretStoreReadOnly, expectedPB: pb.SecretStoreCapabilities_READ_ONLY},
+		{name: "write_only", caps: esv1.SecretStoreWriteOnly, expectedPB: pb.SecretStoreCapabilities_WRITE_ONLY},
+		{name: "read_write", caps: esv1.SecretStoreReadWrite, expectedPB: pb.SecretStoreCapabilities_READ_WRITE},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			server := NewServer(nil, ProviderMapping{
+				schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+					caps: tc.caps,
+					newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+						return &fakeSecretsClient{}, nil
+					},
+				},
+			}, (&specMapperRecorder{}).mapRef)
+
+			resp, err := server.Capabilities(context.Background(), &pb.CapabilitiesRequest{
+				ProviderRef: &pb.ProviderReference{
+					ApiVersion: "provider.external-secrets.io/v2alpha1",
+					Kind:       "Fake",
+					Name:       "backend",
+				},
+			})
+			if err != nil {
+				t.Fatalf("Capabilities() error = %v", err)
+			}
+			if resp.Capabilities != tc.expectedPB {
+				t.Fatalf("expected %v, got %v", tc.expectedPB, resp.Capabilities)
+			}
+		})
+	}
+}
+
+func TestServerRejectsInvalidRequests(t *testing.T) {
+	server := NewServer(nil, ProviderMapping{}, (&specMapperRecorder{}).mapRef)
+
+	testCases := []struct {
+		name string
+		call func() error
+		want string
+	}{
+		{
+			name: "get_secret_nil_request",
+			call: func() error {
+				_, err := server.GetSecret(context.Background(), nil)
+				return err
+			},
+			want: "request or remote ref is nil",
+		},
+		{
+			name: "get_secret_empty_source_namespace",
+			call: func() error {
+				_, err := server.GetSecret(context.Background(), &pb.GetSecretRequest{
+					RemoteRef: &pb.ExternalSecretDataRemoteRef{Key: "sample"},
+				})
+				return err
+			},
+			want: "source namespace is required",
+		},
+		{
+			name: "push_secret_nil_payload",
+			call: func() error {
+				_, err := server.PushSecret(context.Background(), &pb.PushSecretRequest{
+					SourceNamespace: serverTestSourceNamespace,
+				})
+				return err
+			},
+			want: "request or push secret data is nil",
+		},
+		{
+			name: "validate_nil_request",
+			call: func() error {
+				_, err := server.Validate(context.Background(), nil)
+				return err
+			},
+			want: "request is nil",
+		},
+		{
+			name: "capabilities_nil_request",
+			call: func() error {
+				_, err := server.Capabilities(context.Background(), nil)
+				return err
+			},
+			want: "request is nil",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := tc.call()
+			if err == nil || err.Error() != tc.want {
+				t.Fatalf("expected %q, got %v", tc.want, err)
+			}
+		})
+	}
+}
+
+func runValidateTest(t *testing.T, result esv1.ValidationResult, validateErr error) *pb.ValidateResponse {
+	t.Helper()
+
+	server := NewServer(nil, ProviderMapping{
+		schema.GroupVersionKind{Group: "provider.external-secrets.io", Version: "v2alpha1", Kind: "Fake"}: &fakeProviderInterface{
+			caps: esv1.SecretStoreReadWrite,
+			newClient: func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
+				return &fakeSecretsClient{
+					validateResult: result,
+					validateErr:    validateErr,
+				}, nil
+			},
+		},
+	}, (&specMapperRecorder{
+		spec: &esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{Fake: &esv1.FakeProvider{}}},
+	}).mapRef)
+
+	resp, err := server.Validate(context.Background(), &pb.ValidateRequest{
+		ProviderRef: &pb.ProviderReference{
+			ApiVersion: "provider.external-secrets.io/v2alpha1",
+			Kind:       "Fake",
+			Name:       "backend",
+		},
+		SourceNamespace: "tenant-a",
+	})
+	if err != nil {
+		t.Fatalf("Validate() error = %v", err)
+	}
+	return resp
+}

+ 138 - 0
providers/v2/adapter/store/synthetic_store.go

@@ -0,0 +1,138 @@
+/*
+Copyright © The ESO Authors
+
+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 store
+
+import (
+	"fmt"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// SyntheticStore implements GenericStore to wrap provider config JSON
+// for use with v1 providers. This allows v1 NewClient() methods to work
+// with provider config passed as JSON bytes from v2 architecture.
+type SyntheticStore struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+	spec              *esv1.SecretStoreSpec
+	status            esv1.SecretStoreStatus
+	gvk               schema.GroupVersionKind
+}
+
+// NewSyntheticStore creates a new SyntheticStore from provider config JSON.
+// The providerConfig JSON should contain the provider-specific configuration
+// (e.g., KubernetesProvider, AWSProvider, etc.).
+func NewSyntheticStore(spec *esv1.SecretStoreSpec, namespace, storeRefKind string) (*SyntheticStore, error) {
+	if spec == nil {
+		return nil, fmt.Errorf("spec cannot be empty")
+	}
+
+	storeKind := esv1.SecretStoreKind
+	if storeRefKind == esv1.ClusterProviderStoreKindStr {
+		storeKind = esv1.ClusterSecretStoreKind
+	}
+
+	store := &SyntheticStore{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: esv1.SchemeGroupVersion.String(),
+			Kind:       storeKind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "synthetic-store",
+			Namespace: namespace,
+		},
+		spec:   spec,
+		status: esv1.SecretStoreStatus{},
+		gvk: schema.GroupVersionKind{
+			Group:   esv1.SchemeGroupVersion.Group,
+			Version: esv1.SchemeGroupVersion.Version,
+			Kind:    storeKind,
+		},
+	}
+	store.SetGroupVersionKind(store.gvk)
+	return store, nil
+}
+
+// GetSpec returns the SecretStoreSpec containing the provider configuration.
+func (s *SyntheticStore) GetSpec() *esv1.SecretStoreSpec {
+	return s.spec
+}
+
+// GetObjectKind returns the GroupVersionKind of this object.
+func (s *SyntheticStore) GetObjectKind() schema.ObjectKind {
+	return s
+}
+
+// DeepCopyObject creates a deep copy of the SyntheticStore.
+func (s *SyntheticStore) DeepCopyObject() runtime.Object {
+	return &SyntheticStore{
+		TypeMeta:   s.TypeMeta,
+		ObjectMeta: *s.ObjectMeta.DeepCopy(),
+		spec:       s.spec.DeepCopy(),
+		status:     s.status,
+		gvk:        s.gvk,
+	}
+}
+
+// GroupVersionKind returns the GVK of the synthetic store.
+func (s *SyntheticStore) GroupVersionKind() schema.GroupVersionKind {
+	return s.gvk
+}
+
+// SetGroupVersionKind sets the GVK of the synthetic store.
+func (s *SyntheticStore) SetGroupVersionKind(gvk schema.GroupVersionKind) {
+	s.gvk = gvk
+}
+
+// GetNamespacedName returns the name and namespace of the store.
+func (s *SyntheticStore) GetNamespacedName() string {
+	return fmt.Sprintf("%s/%s", s.Namespace, s.Name)
+}
+
+// GetStatus returns the status of the store.
+func (s *SyntheticStore) GetStatus() esv1.SecretStoreStatus {
+	return s.status
+}
+
+// SetStatus sets the status of the store.
+func (s *SyntheticStore) SetStatus(status esv1.SecretStoreStatus) {
+	s.status = status
+}
+
+// GetKind returns the kind of the store.
+func (s *SyntheticStore) GetKind() string {
+	return s.Kind
+}
+
+// GetObjectMeta returns the ObjectMeta of the store.
+func (s *SyntheticStore) GetObjectMeta() *metav1.ObjectMeta {
+	return &s.ObjectMeta
+}
+
+// GetTypeMeta returns the TypeMeta of the store.
+func (s *SyntheticStore) GetTypeMeta() *metav1.TypeMeta {
+	return &s.TypeMeta
+}
+
+// Copy returns a deep copy of the store.
+func (s *SyntheticStore) Copy() esv1.GenericStore {
+	return s.DeepCopyObject().(esv1.GenericStore)
+}

+ 84 - 0
providers/v2/common/go.mod

@@ -0,0 +1,84 @@
+module github.com/external-secrets/external-secrets/providers/v2/common
+
+go 1.26.2
+
+require (
+	github.com/external-secrets/external-secrets/apis v0.0.0
+	github.com/external-secrets/external-secrets/proto v0.0.0
+	github.com/go-logr/logr v1.4.3
+	github.com/prometheus/client_golang v1.23.2
+	github.com/stretchr/testify v1.11.1
+	google.golang.org/grpc v1.79.3
+	google.golang.org/protobuf v1.36.11
+	k8s.io/api v0.35.2
+	k8s.io/apimachinery v0.35.2
+	k8s.io/client-go v0.35.2
+	sigs.k8s.io/controller-runtime v0.23.3
+)
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+	github.com/go-openapi/jsonpointer v0.22.5 // indirect
+	github.com/go-openapi/jsonreference v0.21.5 // indirect
+	github.com/go-openapi/swag v0.25.5 // indirect
+	github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
+	github.com/go-openapi/swag/conv v0.25.5 // indirect
+	github.com/go-openapi/swag/fileutils v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonname v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
+	github.com/go-openapi/swag/loading v0.25.5 // indirect
+	github.com/go-openapi/swag/mangling v0.25.5 // indirect
+	github.com/go-openapi/swag/netutils v0.25.5 // indirect
+	github.com/go-openapi/swag/stringutils v0.25.5 // indirect
+	github.com/go-openapi/swag/typeutils v0.25.5 // indirect
+	github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.7.1 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/kr/text v0.2.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
+	github.com/prometheus/common v0.67.5 // indirect
+	github.com/prometheus/procfs v0.20.1 // indirect
+	github.com/spf13/pflag v1.0.10 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/net v0.52.0 // indirect
+	golang.org/x/oauth2 v0.36.0 // indirect
+	golang.org/x/sync v0.20.0 // indirect
+	golang.org/x/sys v0.42.0 // indirect
+	golang.org/x/term v0.41.0 // indirect
+	golang.org/x/text v0.35.0 // indirect
+	golang.org/x/time v0.15.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	k8s.io/apiextensions-apiserver v0.35.2 // indirect
+	k8s.io/klog/v2 v2.140.0 // indirect
+	k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
+	k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
+	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
+	sigs.k8s.io/randfill v1.0.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
+)
+
+replace (
+	github.com/external-secrets/external-secrets/apis => ../../../apis
+	github.com/external-secrets/external-secrets/proto => ./proto
+)

+ 208 - 0
providers/v2/common/go.sum

@@ -0,0 +1,208 @@
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
+github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
+github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
+github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
+github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
+github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
+github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
+github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
+github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
+github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
+github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
+github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
+github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
+github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
+github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
+github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
+github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
+github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
+github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
+github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
+github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
+github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
+github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
+github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
+github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
+github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
+github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
+github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
+github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
+github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
+github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
+github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
+github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
+github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
+github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
+github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc=
+github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
+github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
+github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
+gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
+gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
+k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
+k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=
+k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
+k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
+k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
+k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
+k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
+k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
+k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf h1:btPscg4cMql0XdYK2jLsJcNEKmACJz8l+U7geC06FiM=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
+sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

+ 597 - 0
providers/v2/common/grpc/client.go

@@ -0,0 +1,597 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/connectivity"
+	corev1 "k8s.io/api/core/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+const (
+	// defaultTimeout is the default timeout for gRPC calls.
+	defaultTimeout          = 30 * time.Second
+	getAllSecretsTimeout    = 2 * time.Minute
+	readIdentityRequiredErr = "provider reference or compatibility store is required for read operations"
+)
+
+// grpcProviderClient implements the v2.Provider interface using gRPC.
+type grpcProviderClient struct {
+	conn   *grpc.ClientConn
+	client pb.SecretStoreProviderClient
+	log    logr.Logger
+}
+
+// Ensure grpcProviderClient implements the Provider interface.
+var _ v2.Provider = &grpcProviderClient{}
+
+// GetSecret retrieves a single secret from the provider via gRPC.
+func (c *grpcProviderClient) GetSecret(
+	ctx context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) ([]byte, error) {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("GetSecret", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return nil, err
+	}
+
+	logFields := append([]any{
+		"key", ref.Key,
+		"version", ref.Version,
+		"property", ref.Property,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("getting secret via gRPC", logFields...)
+
+	// Check connection state before call
+	state := c.conn.GetState()
+	if state != connectivity.Ready && state != connectivity.Idle {
+		c.log.Info("connection not ready, attempting to reconnect",
+			"state", state.String(),
+			"target", c.conn.Target())
+	}
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Convert v1 reference to protobuf message
+	pbRef := &pb.ExternalSecretDataRemoteRef{
+		Key:              ref.Key,
+		Version:          ref.Version,
+		Property:         ref.Property,
+		DecodingStrategy: string(ref.DecodingStrategy),
+		MetadataPolicy:   string(ref.MetadataPolicy),
+	}
+
+	// Make gRPC call with provider reference
+	req := &pb.GetSecretRequest{
+		RemoteRef:          pbRef,
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling GetSecret RPC",
+		"target", c.conn.Target(),
+		"timeout", defaultTimeout.String())
+
+	resp, err := c.client.GetSecret(ctx, req)
+	if err != nil {
+		c.log.Error(err, "GetSecret RPC failed",
+			"key", ref.Key,
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to get secret via gRPC: %w", err)
+		return nil, err
+	}
+
+	c.log.V(1).Info("GetSecret RPC succeeded",
+		"key", ref.Key,
+		"valueLength", len(resp.Value))
+
+	return resp.Value, nil
+}
+
+// GetSecretMap retrieves multiple key/value pairs from a single provider object via gRPC.
+func (c *grpcProviderClient) GetSecretMap(
+	ctx context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("GetSecretMap", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return nil, err
+	}
+
+	logFields := append([]any{
+		"key", ref.Key,
+		"version", ref.Version,
+		"property", ref.Property,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("getting secret map via gRPC", logFields...)
+
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	pbRef := &pb.ExternalSecretDataRemoteRef{
+		Key:              ref.Key,
+		Version:          ref.Version,
+		Property:         ref.Property,
+		DecodingStrategy: string(ref.DecodingStrategy),
+		MetadataPolicy:   string(ref.MetadataPolicy),
+	}
+
+	req := &pb.GetSecretMapRequest{
+		RemoteRef:          pbRef,
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling GetSecretMap RPC",
+		"target", c.conn.Target())
+
+	resp, err := c.client.GetSecretMap(ctx, req)
+	if err != nil {
+		c.log.Error(err, "GetSecretMap RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to get secret map via gRPC: %w", err)
+		return nil, err
+	}
+
+	c.log.V(1).Info("GetSecretMap RPC succeeded",
+		"secretCount", len(resp.Secrets))
+
+	return resp.Secrets, nil
+}
+
+// Validate checks if the provider is properly configured via gRPC.
+func (c *grpcProviderClient) Validate(ctx context.Context, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) error {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("Validate", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return err
+	}
+
+	c.log.Info("validating provider via gRPC",
+		"target", c.conn.Target(),
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+		"compatibilityStore", compatibilityStore != nil)
+
+	// Check connection state before call
+	state := c.conn.GetState()
+	c.log.V(1).Info("connection details",
+		"state", state.String(),
+		"target", c.conn.Target(),
+		"authority", c.conn.GetMethodConfig("").WaitForReady)
+
+	if state != connectivity.Ready && state != connectivity.Idle {
+		c.log.Info("connection not in ready/idle state, will attempt to connect",
+			"state", state.String(),
+			"target", c.conn.Target())
+	}
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Make gRPC call with provider reference
+	req := &pb.ValidateRequest{
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling Validate RPC",
+		"target", c.conn.Target(),
+		"timeout", defaultTimeout.String())
+
+	resp, err := c.client.Validate(ctx, req)
+	if err != nil {
+		c.log.Error(err, "Validate RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target(),
+			"errorType", fmt.Sprintf("%T", err))
+		err = fmt.Errorf("failed to validate provider via gRPC: %w", err)
+		return err
+	}
+
+	c.log.V(1).Info("Validate RPC completed",
+		"valid", resp.Valid,
+		"error", resp.Error)
+
+	// Check for error in response
+	if !resp.Valid {
+		if resp.Error != "" {
+			c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
+				"message", resp.Error)
+			err = fmt.Errorf("provider validation failed: %s", resp.Error)
+			return err
+		}
+		c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
+			"message", "no error message provided")
+		err = fmt.Errorf("provider validation failed without error message")
+		return err
+	}
+
+	c.log.Info("provider validation succeeded")
+	return nil
+}
+
+// GetAllSecrets retrieves multiple secrets based on find criteria via gRPC.
+func (c *grpcProviderClient) GetAllSecrets(
+	ctx context.Context,
+	find esv1.ExternalSecretFind,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("GetAllSecrets", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return nil, err
+	}
+
+	logFields := append([]any{
+		"tags", find.Tags,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("getting all secrets via gRPC", logFields...)
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, getAllSecretsTimeout)
+	defer cancel()
+
+	// Convert find criteria to protobuf
+	pbFind := &pb.ExternalSecretFind{
+		Tags:               find.Tags,
+		ConversionStrategy: string(find.ConversionStrategy),
+		DecodingStrategy:   string(find.DecodingStrategy),
+	}
+
+	if find.Path != nil {
+		pbFind.Path = *find.Path
+	}
+
+	if find.Name != nil {
+		pbFind.Name = &pb.FindName{
+			Regexp: find.Name.RegExp,
+		}
+	}
+
+	// Make gRPC call
+	req := &pb.GetAllSecretsRequest{
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		Find:               pbFind,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling GetAllSecrets RPC",
+		"target", c.conn.Target(),
+		"timeout", getAllSecretsTimeout.String())
+
+	resp, err := c.client.GetAllSecrets(ctx, req)
+	if err != nil {
+		c.log.Error(err, "GetAllSecrets RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to get all secrets via gRPC: %w", err)
+		return nil, err
+	}
+
+	c.log.V(1).Info("GetAllSecrets RPC succeeded",
+		"secretCount", len(resp.Secrets))
+
+	return resp.Secrets, nil
+}
+
+func validateReadIdentity(providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore) error {
+	if providerRef == nil && compatibilityStore == nil {
+		return fmt.Errorf(readIdentityRequiredErr)
+	}
+	return nil
+}
+
+func compatibilityStoreLogFields(store *pb.CompatibilityStore) []any {
+	if store == nil {
+		return nil
+	}
+
+	return []any{
+		"compatibilityStoreKind", store.GetStoreKind(),
+		"compatibilityStoreName", store.GetStoreName(),
+		"compatibilityStoreNamespace", store.GetStoreNamespace(),
+		"compatibilityStoreUID", store.GetStoreUid(),
+		"compatibilityStoreGeneration", store.GetStoreGeneration(),
+		"compatibilityStoreSpecBytes", len(store.GetStoreSpecJson()),
+	}
+}
+
+// PushSecret writes a secret to the provider via gRPC.
+func (c *grpcProviderClient) PushSecret(
+	ctx context.Context,
+	secret *corev1.Secret,
+	pushSecretData *pb.PushSecretData,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("PushSecret", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return err
+	}
+
+	logFields := append([]any{
+		"remoteKey", pushSecretData.RemoteKey,
+		"property", pushSecretData.Property,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("pushing secret via gRPC", logFields...)
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Make gRPC call
+	req := &pb.PushSecretRequest{
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		SecretData:         secret.Data,
+		PushSecretData:     pushSecretData,
+		SourceNamespace:    sourceNamespace,
+		SecretType:         string(secret.Type),
+		SecretLabels:       secret.Labels,
+		SecretAnnotations:  secret.Annotations,
+	}
+
+	c.log.V(1).Info("calling PushSecret RPC",
+		"target", c.conn.Target())
+
+	_, err = c.client.PushSecret(ctx, req)
+	if err != nil {
+		c.log.Error(err, "PushSecret RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to push secret via gRPC: %w", err)
+		return err
+	}
+
+	c.log.V(1).Info("PushSecret RPC succeeded",
+		"remoteKey", pushSecretData.RemoteKey)
+
+	return nil
+}
+
+// DeleteSecret deletes a secret from the provider via gRPC.
+func (c *grpcProviderClient) DeleteSecret(
+	ctx context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("DeleteSecret", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return err
+	}
+
+	logFields := append([]any{
+		"remoteKey", remoteRef.RemoteKey,
+		"property", remoteRef.Property,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("deleting secret via gRPC", logFields...)
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Make gRPC call
+	req := &pb.DeleteSecretRequest{
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		RemoteRef:          remoteRef,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling DeleteSecret RPC",
+		"target", c.conn.Target())
+
+	_, err = c.client.DeleteSecret(ctx, req)
+	if err != nil {
+		c.log.Error(err, "DeleteSecret RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to delete secret via gRPC: %w", err)
+		return err
+	}
+
+	c.log.V(1).Info("DeleteSecret RPC succeeded",
+		"remoteKey", remoteRef.RemoteKey)
+
+	return nil
+}
+
+// SecretExists checks if a secret exists in the provider via gRPC.
+func (c *grpcProviderClient) SecretExists(
+	ctx context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (bool, error) {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("SecretExists", c.conn.Target(), err, time.Since(start))
+	}()
+	if validationErr := validateReadIdentity(providerRef, compatibilityStore); validationErr != nil {
+		err = validationErr
+		return false, err
+	}
+
+	logFields := append([]any{
+		"remoteKey", remoteRef.RemoteKey,
+		"property", remoteRef.Property,
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace,
+	}, compatibilityStoreLogFields(compatibilityStore)...)
+	c.log.V(1).Info("checking if secret exists via gRPC", logFields...)
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Make gRPC call
+	req := &pb.SecretExistsRequest{
+		ProviderRef:        providerRef,
+		CompatibilityStore: compatibilityStore,
+		RemoteRef:          remoteRef,
+		SourceNamespace:    sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling SecretExists RPC",
+		"target", c.conn.Target())
+
+	resp, err := c.client.SecretExists(ctx, req)
+	if err != nil {
+		c.log.Error(err, "SecretExists RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to check if secret exists via gRPC: %w", err)
+		return false, err
+	}
+
+	c.log.V(1).Info("SecretExists RPC succeeded",
+		"remoteKey", remoteRef.RemoteKey,
+		"exists", resp.Exists)
+
+	return resp.Exists, nil
+}
+
+// Capabilities retrieves the capabilities of the provider via gRPC.
+func (c *grpcProviderClient) Capabilities(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) (pb.SecretStoreCapabilities, error) {
+	start := time.Now()
+	var err error
+	defer func() {
+		clientMetrics.ObserveRequest("Capabilities", c.conn.Target(), err, time.Since(start))
+	}()
+
+	c.log.V(1).Info("getting provider capabilities via gRPC",
+		"target", c.conn.Target(),
+		"connectionState", c.conn.GetState().String(),
+		"providerRef", providerRef,
+		"sourceNamespace", sourceNamespace)
+
+	// Create context with timeout
+	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+	defer cancel()
+
+	// Make gRPC call with provider reference
+	req := &pb.CapabilitiesRequest{
+		ProviderRef:     providerRef,
+		SourceNamespace: sourceNamespace,
+	}
+
+	c.log.V(1).Info("calling Capabilities RPC",
+		"target", c.conn.Target())
+
+	resp, err := c.client.Capabilities(ctx, req)
+	if err != nil {
+		c.log.Error(err, "Capabilities RPC failed",
+			"connectionState", c.conn.GetState().String(),
+			"target", c.conn.Target())
+		err = fmt.Errorf("failed to get capabilities via gRPC: %w", err)
+		return pb.SecretStoreCapabilities_READ_ONLY, err
+	}
+
+	c.log.V(1).Info("Capabilities RPC succeeded",
+		"capabilities", resp.Capabilities)
+
+	return resp.Capabilities, nil
+}
+
+// Close closes the gRPC connection.
+func (c *grpcProviderClient) Close(_ context.Context) error {
+	if c.conn != nil {
+		c.log.V(1).Info("closing gRPC connection",
+			"target", c.conn.Target(),
+			"state", c.conn.GetState().String())
+		return c.conn.Close()
+	}
+	c.log.V(1).Info("no connection to close")
+	return nil
+}

+ 835 - 0
providers/v2/common/grpc/client_test.go

@@ -0,0 +1,835 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"bytes"
+	"context"
+	"net"
+	"testing"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+	"google.golang.org/grpc/test/bufconn"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+const (
+	bufSize                    = 1024 * 1024
+	testCompatibilityStoreName = "compat-store"
+	testSourceNamespace        = "tenant-a"
+)
+
+type mockServer struct {
+	pb.UnimplementedSecretStoreProviderServer
+
+	getSecretResponse *pb.GetSecretResponse
+	getSecretRequest  *pb.GetSecretRequest
+
+	getSecretMapResponse *pb.GetSecretMapResponse
+	getSecretMapRequest  *pb.GetSecretMapRequest
+
+	getAllSecretsResponse *pb.GetAllSecretsResponse
+	getAllSecretsRequest  *pb.GetAllSecretsRequest
+
+	pushSecretRequest *pb.PushSecretRequest
+	deleteRequest     *pb.DeleteSecretRequest
+	existsRequest     *pb.SecretExistsRequest
+	existsResponse    *pb.SecretExistsResponse
+
+	validateResponse *pb.ValidateResponse
+	validateRequest  *pb.ValidateRequest
+
+	capabilitiesResponse *pb.CapabilitiesResponse
+	capabilitiesRequest  *pb.CapabilitiesRequest
+
+	getAllSecretsDeadline time.Duration
+}
+
+func (m *mockServer) GetSecret(_ context.Context, req *pb.GetSecretRequest) (*pb.GetSecretResponse, error) {
+	m.getSecretRequest = req
+	if m.getSecretResponse != nil {
+		return m.getSecretResponse, nil
+	}
+	return &pb.GetSecretResponse{Value: []byte("test-secret-value")}, nil
+}
+
+func (m *mockServer) GetSecretMap(_ context.Context, req *pb.GetSecretMapRequest) (*pb.GetSecretMapResponse, error) {
+	m.getSecretMapRequest = req
+	if m.getSecretMapResponse != nil {
+		return m.getSecretMapResponse, nil
+	}
+	return &pb.GetSecretMapResponse{
+		Secrets: map[string][]byte{"foo": []byte("bar")},
+	}, nil
+}
+
+func (m *mockServer) GetAllSecrets(ctx context.Context, req *pb.GetAllSecretsRequest) (*pb.GetAllSecretsResponse, error) {
+	m.getAllSecretsRequest = req
+	if deadline, ok := ctx.Deadline(); ok {
+		m.getAllSecretsDeadline = time.Until(deadline)
+	}
+	if m.getAllSecretsResponse != nil {
+		return m.getAllSecretsResponse, nil
+	}
+	return &pb.GetAllSecretsResponse{
+		Secrets: map[string][]byte{"db-password": []byte("value")},
+	}, nil
+}
+
+func (m *mockServer) PushSecret(_ context.Context, req *pb.PushSecretRequest) (*pb.PushSecretResponse, error) {
+	m.pushSecretRequest = req
+	return &pb.PushSecretResponse{}, nil
+}
+
+func (m *mockServer) DeleteSecret(_ context.Context, req *pb.DeleteSecretRequest) (*pb.DeleteSecretResponse, error) {
+	m.deleteRequest = req
+	return &pb.DeleteSecretResponse{}, nil
+}
+
+func (m *mockServer) SecretExists(_ context.Context, req *pb.SecretExistsRequest) (*pb.SecretExistsResponse, error) {
+	m.existsRequest = req
+	if m.existsResponse != nil {
+		return m.existsResponse, nil
+	}
+	return &pb.SecretExistsResponse{Exists: true}, nil
+}
+
+func (m *mockServer) Validate(_ context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
+	m.validateRequest = req
+	if m.validateResponse != nil {
+		return m.validateResponse, nil
+	}
+	return &pb.ValidateResponse{Valid: true}, nil
+}
+
+func (m *mockServer) Capabilities(_ context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
+	m.capabilitiesRequest = req
+	if m.capabilitiesResponse != nil {
+		return m.capabilitiesResponse, nil
+	}
+	return &pb.CapabilitiesResponse{Capabilities: pb.SecretStoreCapabilities_READ_WRITE}, nil
+}
+
+func setupTestServer(t *testing.T, mock *mockServer) (*grpc.ClientConn, func()) {
+	t.Helper()
+
+	lis := bufconn.Listen(bufSize)
+	baseServer := grpc.NewServer()
+	pb.RegisterSecretStoreProviderServer(baseServer, mock)
+	go func() {
+		if err := baseServer.Serve(lis); err != nil {
+			t.Logf("Server exited with error: %v", err)
+		}
+	}()
+
+	conn, err := grpc.DialContext(context.Background(), "",
+		grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
+			return lis.Dial()
+		}),
+		grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		t.Fatalf("Failed to dial bufnet: %v", err)
+	}
+
+	cleanup := func() {
+		_ = conn.Close()
+		baseServer.Stop()
+		_ = lis.Close()
+	}
+
+	return conn, cleanup
+}
+
+func TestClientGetSecretSendsProviderReferenceAndNamespace(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+	ref := esv1.ExternalSecretDataRemoteRef{
+		Key:              "test-key",
+		Version:          "v1",
+		Property:         "password",
+		DecodingStrategy: esv1.ExternalSecretDecodeBase64,
+		MetadataPolicy:   esv1.ExternalSecretMetadataPolicyFetch,
+	}
+
+	value, err := client.GetSecret(context.Background(), ref, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetSecret failed: %v", err)
+	}
+
+	if string(value) != "test-secret-value" {
+		t.Fatalf("expected test-secret-value, got %q", string(value))
+	}
+	if mock.getSecretRequest == nil {
+		t.Fatal("expected get secret request to be recorded")
+	}
+	assertProviderRefEqual(t, mock.getSecretRequest.ProviderRef, providerRef)
+	if mock.getSecretRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected source namespace: %q", mock.getSecretRequest.SourceNamespace)
+	}
+	if mock.getSecretRequest.RemoteRef.Key != "test-key" || mock.getSecretRequest.RemoteRef.Version != "v1" || mock.getSecretRequest.RemoteRef.Property != "password" {
+		t.Fatalf("unexpected remote ref: %#v", mock.getSecretRequest.RemoteRef)
+	}
+	if mock.getSecretRequest.RemoteRef.DecodingStrategy != string(esv1.ExternalSecretDecodeBase64) {
+		t.Fatalf("unexpected decoding strategy: %q", mock.getSecretRequest.RemoteRef.DecodingStrategy)
+	}
+	if mock.getSecretRequest.RemoteRef.MetadataPolicy != string(esv1.ExternalSecretMetadataPolicyFetch) {
+		t.Fatalf("unexpected metadata policy: %q", mock.getSecretRequest.RemoteRef.MetadataPolicy)
+	}
+}
+
+func TestClientGetSecretMapSendsProviderReferenceAndNamespace(t *testing.T) {
+	mock := &mockServer{
+		getSecretMapResponse: &pb.GetSecretMapResponse{
+			Secrets: map[string][]byte{"a": []byte("b")},
+		},
+	}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+
+	value, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetSecretMap failed: %v", err)
+	}
+
+	if string(value["a"]) != "b" {
+		t.Fatalf("expected map[a]=b, got %#v", value)
+	}
+	if mock.getSecretMapRequest == nil {
+		t.Fatal("expected get secret map request to be recorded")
+	}
+	if mock.getSecretMapRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected request: %#v", mock.getSecretMapRequest)
+	}
+	assertProviderRefEqual(t, mock.getSecretMapRequest.ProviderRef, providerRef)
+}
+
+func TestClientGetAllSecretsSendsFindCriteria(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+	path := "/team-a"
+
+	secrets, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
+		Tags: map[string]string{"team": "a"},
+		Path: &path,
+		Name: &esv1.FindName{RegExp: "db-.*"},
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetAllSecrets failed: %v", err)
+	}
+
+	if string(secrets["db-password"]) != "value" {
+		t.Fatalf("unexpected secrets: %#v", secrets)
+	}
+	if mock.getAllSecretsRequest == nil {
+		t.Fatal("expected get all secrets request to be recorded")
+	}
+	if mock.getAllSecretsRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected request: %#v", mock.getAllSecretsRequest)
+	}
+	assertProviderRefEqual(t, mock.getAllSecretsRequest.ProviderRef, providerRef)
+	if mock.getAllSecretsRequest.Find.Path != "/team-a" {
+		t.Fatalf("unexpected path: %q", mock.getAllSecretsRequest.Find.Path)
+	}
+	if mock.getAllSecretsRequest.Find.Name == nil || mock.getAllSecretsRequest.Find.Name.Regexp != "db-.*" {
+		t.Fatalf("unexpected name matcher: %#v", mock.getAllSecretsRequest.Find.Name)
+	}
+}
+
+func TestClientGetAllSecretsUsesExtendedTimeout(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+
+	_, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
+		Name: &esv1.FindName{RegExp: "db-.*"},
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetAllSecrets failed: %v", err)
+	}
+
+	if mock.getAllSecretsDeadline <= defaultTimeout {
+		t.Fatalf("expected GetAllSecrets timeout to exceed default timeout %s, got %s", defaultTimeout, mock.getAllSecretsDeadline)
+	}
+}
+
+func TestClientGetSecretSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	value, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetSecret failed: %v", err)
+	}
+
+	if string(value) != "test-secret-value" {
+		t.Fatalf("expected test-secret-value, got %q", string(value))
+	}
+	if mock.getSecretRequest == nil {
+		t.Fatal("expected get secret request to be recorded")
+	}
+	if mock.getSecretRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.getSecretRequest.ProviderRef)
+	}
+	if mock.getSecretRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+	if mock.getSecretRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
+		t.Fatalf("unexpected compatibility store: %#v", mock.getSecretRequest.CompatibilityStore)
+	}
+}
+
+func TestClientGetSecretMapSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	_, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "test-key"}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetSecretMap failed: %v", err)
+	}
+
+	if mock.getSecretMapRequest == nil {
+		t.Fatal("expected get secret map request to be recorded")
+	}
+	if mock.getSecretMapRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.getSecretMapRequest.ProviderRef)
+	}
+	if mock.getSecretMapRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+	if mock.getSecretMapRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
+		t.Fatalf("unexpected compatibility store: %#v", mock.getSecretMapRequest.CompatibilityStore)
+	}
+}
+
+func TestClientGetAllSecretsSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	_, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("GetAllSecrets failed: %v", err)
+	}
+
+	if mock.getAllSecretsRequest == nil {
+		t.Fatal("expected get all secrets request to be recorded")
+	}
+	if mock.getAllSecretsRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.getAllSecretsRequest.ProviderRef)
+	}
+	if mock.getAllSecretsRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+	if mock.getAllSecretsRequest.CompatibilityStore.StoreName != testCompatibilityStoreName {
+		t.Fatalf("unexpected compatibility store: %#v", mock.getAllSecretsRequest.CompatibilityStore)
+	}
+}
+
+func TestClientValidateSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	err := client.Validate(context.Background(), nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("Validate failed: %v", err)
+	}
+
+	if mock.validateRequest == nil {
+		t.Fatal("expected validate request to be recorded")
+	}
+	if mock.validateRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.validateRequest.ProviderRef)
+	}
+	if mock.validateRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+}
+
+func TestClientPushSecretSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	err := client.PushSecret(context.Background(), &corev1.Secret{
+		Data: map[string][]byte{"token": []byte("value")},
+	}, &pb.PushSecretData{RemoteKey: "remote", SecretKey: "token"}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("PushSecret failed: %v", err)
+	}
+
+	if mock.pushSecretRequest == nil {
+		t.Fatal("expected push secret request to be recorded")
+	}
+	if mock.pushSecretRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.pushSecretRequest.ProviderRef)
+	}
+	if mock.pushSecretRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+}
+
+func TestClientDeleteSecretSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	err := client.DeleteSecret(context.Background(), &pb.PushSecretRemoteRef{RemoteKey: "remote"}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("DeleteSecret failed: %v", err)
+	}
+
+	if mock.deleteRequest == nil {
+		t.Fatal("expected delete secret request to be recorded")
+	}
+	if mock.deleteRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.deleteRequest.ProviderRef)
+	}
+	if mock.deleteRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+}
+
+func TestClientSecretExistsSendsCompatibilityStore(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	compatibilityStore := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "config-ns",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   []byte(`{"provider":{"fake":{"data":[{"key":"test-key","value":"test-secret-value"}]}}}`),
+	}
+
+	exists, err := client.SecretExists(context.Background(), &pb.PushSecretRemoteRef{RemoteKey: "remote"}, nil, compatibilityStore, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("SecretExists failed: %v", err)
+	}
+	if !exists {
+		t.Fatal("expected secret to exist")
+	}
+
+	if mock.existsRequest == nil {
+		t.Fatal("expected secret exists request to be recorded")
+	}
+	if mock.existsRequest.ProviderRef != nil {
+		t.Fatalf("expected provider ref to be omitted, got %#v", mock.existsRequest.ProviderRef)
+	}
+	if mock.existsRequest.CompatibilityStore == nil {
+		t.Fatal("expected compatibility store to be recorded")
+	}
+}
+
+func TestReadRequestIdentityValidationRejectsMissingReadIdentity(t *testing.T) {
+	testCases := []struct {
+		name string
+		call func(client v2.Provider) error
+	}{
+		{
+			name: "get secret",
+			call: func(client v2.Provider) error {
+				_, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"}, nil, nil, testSourceNamespace)
+				return err
+			},
+		},
+		{
+			name: "get secret map",
+			call: func(client v2.Provider) error {
+				_, err := client.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "sample"}, nil, nil, testSourceNamespace)
+				return err
+			},
+		},
+		{
+			name: "get all secrets",
+			call: func(client v2.Provider) error {
+				_, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{}, nil, nil, testSourceNamespace)
+				return err
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			mock := &mockServer{}
+			conn, cleanup := setupTestServer(t, mock)
+			defer cleanup()
+
+			err := tc.call(NewClientWithConn(conn))
+			if err == nil || err.Error() != "provider reference or compatibility store is required for read operations" {
+				t.Fatalf("unexpected error: %v", err)
+			}
+		})
+	}
+}
+
+func TestCompatibilityStoreLogFieldsRedactSpecPayload(t *testing.T) {
+	specPayload := []byte(`{"provider":{"fake":{"data":[{"key":"db","value":"secret-value"}]}}}`)
+	store := &pb.CompatibilityStore{
+		StoreName:       testCompatibilityStoreName,
+		StoreNamespace:  "team-a",
+		StoreKind:       esv1.SecretStoreKind,
+		StoreUid:        "uid-1",
+		StoreGeneration: 7,
+		StoreSpecJson:   specPayload,
+	}
+
+	fields := compatibilityStoreLogFields(store)
+	if len(fields) == 0 {
+		t.Fatal("expected compatibility store log fields")
+	}
+
+	found := map[string]bool{}
+	for i := 0; i < len(fields); i += 2 {
+		key, ok := fields[i].(string)
+		if !ok {
+			t.Fatalf("expected string log key, got %T", fields[i])
+		}
+		found[key] = true
+		if bytes.Equal([]byte(key), specPayload) {
+			t.Fatalf("unexpected spec payload key: %q", key)
+		}
+		value := fields[i+1]
+		if value == store {
+			t.Fatalf("unexpected raw compatibility store in log fields")
+		}
+		if payload, ok := value.([]byte); ok && bytes.Equal(payload, specPayload) {
+			t.Fatalf("unexpected raw spec payload in log fields")
+		}
+		if text, ok := value.(string); ok && text == string(specPayload) {
+			t.Fatalf("unexpected raw spec payload string in log fields")
+		}
+	}
+
+	for _, key := range []string{
+		"compatibilityStoreKind",
+		"compatibilityStoreName",
+		"compatibilityStoreNamespace",
+		"compatibilityStoreUID",
+		"compatibilityStoreGeneration",
+		"compatibilityStoreSpecBytes",
+	} {
+		if !found[key] {
+			t.Fatalf("expected log field %q, got %#v", key, fields)
+		}
+	}
+}
+
+func TestClientPushDeleteExistsAndCapabilitiesSendProviderReferenceAndNamespace(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+
+	err := client.PushSecret(context.Background(), &corev1.Secret{
+		Data: map[string][]byte{"token": []byte("value")},
+	}, &pb.PushSecretData{
+		RemoteKey: "remote/path",
+		SecretKey: "token",
+		Property:  "property",
+		Metadata:  []byte(`{"mergePolicy":"replace"}`),
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("PushSecret failed: %v", err)
+	}
+	if mock.pushSecretRequest == nil {
+		t.Fatal("expected push secret request to be recorded")
+	}
+	if mock.pushSecretRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected push request: %#v", mock.pushSecretRequest)
+	}
+	assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
+	if string(mock.pushSecretRequest.SecretData["token"]) != "value" {
+		t.Fatalf("unexpected pushed secret data: %#v", mock.pushSecretRequest.SecretData)
+	}
+
+	err = client.DeleteSecret(context.Background(), &pb.PushSecretRemoteRef{
+		RemoteKey: "remote/path",
+		Property:  "property",
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("DeleteSecret failed: %v", err)
+	}
+	if mock.deleteRequest == nil || mock.deleteRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected delete request: %#v", mock.deleteRequest)
+	}
+	assertProviderRefEqual(t, mock.deleteRequest.ProviderRef, providerRef)
+
+	exists, err := client.SecretExists(context.Background(), &pb.PushSecretRemoteRef{
+		RemoteKey: "remote/path",
+		Property:  "property",
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("SecretExists failed: %v", err)
+	}
+	if !exists {
+		t.Fatal("expected exists to be true")
+	}
+	if mock.existsRequest == nil || mock.existsRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected exists request: %#v", mock.existsRequest)
+	}
+	assertProviderRefEqual(t, mock.existsRequest.ProviderRef, providerRef)
+
+	caps, err := client.Capabilities(context.Background(), providerRef, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("Capabilities failed: %v", err)
+	}
+	if caps != pb.SecretStoreCapabilities_READ_WRITE {
+		t.Fatalf("expected READ_WRITE, got %v", caps)
+	}
+	if mock.capabilitiesRequest == nil || mock.capabilitiesRequest.SourceNamespace != testSourceNamespace {
+		t.Fatalf("unexpected capabilities request: %#v", mock.capabilitiesRequest)
+	}
+	assertProviderRefEqual(t, mock.capabilitiesRequest.ProviderRef, providerRef)
+}
+
+func TestClientPushSecretSendsExpandedKubernetesSecretFields(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+	providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ClusterProviderStoreKindStr}
+
+	err := client.PushSecret(context.Background(), &corev1.Secret{
+		Type: corev1.SecretTypeDockerConfigJson,
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      map[string]string{"team": "platform"},
+			Annotations: map[string]string{"owner": "app-team"},
+		},
+		Data: map[string][]byte{
+			".dockerconfigjson": []byte("payload"),
+		},
+	}, &pb.PushSecretData{
+		RemoteKey: "remote/path",
+		SecretKey: ".dockerconfigjson",
+		Property:  "property",
+		Metadata:  []byte(`{"mergePolicy":"replace"}`),
+	}, providerRef, nil, testSourceNamespace)
+	if err != nil {
+		t.Fatalf("PushSecret failed: %v", err)
+	}
+	if mock.pushSecretRequest == nil {
+		t.Fatal("expected push secret request to be recorded")
+	}
+	if got, want := string(mock.pushSecretRequest.SecretData[".dockerconfigjson"]), "payload"; got != want {
+		t.Errorf("expected request secret data %q, got %q", want, got)
+	}
+	assertProviderRefEqual(t, mock.pushSecretRequest.ProviderRef, providerRef)
+	if got, want := mock.pushSecretRequest.SourceNamespace, testSourceNamespace; got != want {
+		t.Errorf("expected source namespace %q, got %q", want, got)
+	}
+	if got, want := mock.pushSecretRequest.SecretType, string(corev1.SecretTypeDockerConfigJson); got != want {
+		t.Errorf("expected secret_type=%q, got %q", want, got)
+	}
+	if got, want := mock.pushSecretRequest.SecretLabels["team"], "platform"; got != want {
+		t.Errorf("expected secret_labels.team=%q, got %q", want, got)
+	}
+	if got, want := mock.pushSecretRequest.SecretAnnotations["owner"], "app-team"; got != want {
+		t.Errorf("expected secret_annotations.owner=%q, got %q", want, got)
+	}
+	if got, want := string(mock.pushSecretRequest.PushSecretData.Metadata), `{"mergePolicy":"replace"}`; got != want {
+		t.Errorf("expected metadata=%q, got %q", want, got)
+	}
+}
+
+func TestClientValidate(t *testing.T) {
+	t.Run("success", func(t *testing.T) {
+		mock := &mockServer{}
+		conn, cleanup := setupTestServer(t, mock)
+		defer cleanup()
+
+		client := NewClientWithConn(conn)
+		providerRef := &pb.ProviderReference{Name: "provider", Namespace: "config-ns", StoreRefKind: esv1.ProviderStoreKindStr}
+
+		err := client.Validate(context.Background(), providerRef, nil, testSourceNamespace)
+		if err != nil {
+			t.Fatalf("Validate failed: %v", err)
+		}
+		if mock.validateRequest == nil {
+			t.Fatal("expected validate request to be recorded")
+		}
+		if mock.validateRequest.SourceNamespace != testSourceNamespace {
+			t.Fatalf("unexpected validate request: %#v", mock.validateRequest)
+		}
+		assertProviderRefEqual(t, mock.validateRequest.ProviderRef, providerRef)
+	})
+
+	t.Run("validation_error", func(t *testing.T) {
+		mock := &mockServer{
+			validateResponse: &pb.ValidateResponse{
+				Valid: false,
+				Error: "invalid credentials",
+			},
+		}
+		conn, cleanup := setupTestServer(t, mock)
+		defer cleanup()
+
+		client := NewClientWithConn(conn)
+
+		err := client.Validate(context.Background(), &pb.ProviderReference{Name: "provider"}, nil, testSourceNamespace)
+		if err == nil {
+			t.Fatal("Expected validation to fail, but it succeeded")
+		}
+		if err.Error() != "provider validation failed: invalid credentials" {
+			t.Fatalf("unexpected error message: %v", err)
+		}
+	})
+}
+
+func TestClientClose(t *testing.T) {
+	mock := &mockServer{}
+	conn, cleanup := setupTestServer(t, mock)
+	defer cleanup()
+
+	client := NewClientWithConn(conn)
+
+	if err := client.Close(context.Background()); err != nil {
+		t.Fatalf("Close failed: %v", err)
+	}
+}
+
+func TestNewClientInvalidAddress(t *testing.T) {
+	_, err := NewClient("", nil)
+	if err == nil {
+		t.Fatal("expected error for empty address, got nil")
+	}
+}
+
+func assertProviderRefEqual(t *testing.T, got, want *pb.ProviderReference) {
+	t.Helper()
+
+	if got == nil || want == nil {
+		t.Fatalf("provider refs must not be nil: got=%#v want=%#v", got, want)
+	}
+	if got.ApiVersion != want.ApiVersion || got.Kind != want.Kind || got.Name != want.Name || got.Namespace != want.Namespace || got.StoreRefKind != want.StoreRefKind {
+		t.Fatalf("unexpected provider ref: got=%#v want=%#v", got, want)
+	}
+}
+
+func TestProtoCompatibilityRequestsExposeCompatibilityStoreField(t *testing.T) {
+	cases := []struct {
+		name string
+		msg  protoreflect.ProtoMessage
+	}{
+		{name: "GetSecretRequest", msg: &pb.GetSecretRequest{}},
+		{name: "GetSecretMapRequest", msg: &pb.GetSecretMapRequest{}},
+		{name: "GetAllSecretsRequest", msg: &pb.GetAllSecretsRequest{}},
+		{name: "ValidateRequest", msg: &pb.ValidateRequest{}},
+		{name: "PushSecretRequest", msg: &pb.PushSecretRequest{}},
+		{name: "DeleteSecretRequest", msg: &pb.DeleteSecretRequest{}},
+		{name: "SecretExistsRequest", msg: &pb.SecretExistsRequest{}},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			fields := tc.msg.ProtoReflect().Descriptor().Fields()
+			if fields.ByName("compatibility_store") == nil {
+				t.Fatalf("expected %s to have field compatibility_store", tc.name)
+			}
+		})
+	}
+}

+ 18 - 0
providers/v2/common/grpc/doc.go

@@ -0,0 +1,18 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc provides the v2 provider gRPC transport and client helpers.
+package grpc

+ 128 - 0
providers/v2/common/grpc/factory.go

@@ -0,0 +1,128 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/keepalive"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+// NewClient creates a new gRPC client that connects to the provider at the given address.
+// If tlsConfig is nil, an insecure connection is used (not recommended for production).
+// If log is nil, a default logger will be used.
+func NewClient(address string, tlsConfig *TLSConfig) (v2.Provider, error) {
+	return NewClientWithLogger(address, tlsConfig, ctrl.Log.WithName("grpc-client"))
+}
+
+// NewClientWithLogger creates a new gRPC client with a custom logger.
+func NewClientWithLogger(address string, tlsConfig *TLSConfig, log logr.Logger) (v2.Provider, error) {
+	if address == "" {
+		return nil, fmt.Errorf("provider address cannot be empty")
+	}
+
+	log.Info("creating gRPC client",
+		"address", address,
+		"tlsEnabled", tlsConfig != nil)
+
+	// Set up connection options.
+	opts := make([]grpc.DialOption, 0, 2)
+	opts = append(opts,
+		grpc.WithKeepaliveParams(keepalive.ClientParameters{
+			Time:                10 * time.Second, // Send keepalive pings every 10 seconds
+			Timeout:             5 * time.Second,  // Wait 5 seconds for ping ack
+			PermitWithoutStream: true,             // Allow pings when no streams are active
+		}),
+	)
+
+	log.V(1).Info("configured keepalive parameters",
+		"time", "10s",
+		"timeout", "5s",
+		"permitWithoutStream", true)
+
+	// Configure TLS or insecure credentials
+	if tlsConfig == nil {
+		return nil, fmt.Errorf("tlsConfig cannot be nil; insecure connections are not allowed in production")
+	}
+
+	log.V(1).Info("configuring TLS",
+		"serverName", tlsConfig.ServerName,
+		"hasCACert", len(tlsConfig.CACert) > 0,
+		"hasClientCert", len(tlsConfig.ClientCert) > 0,
+		"hasClientKey", len(tlsConfig.ClientKey) > 0)
+
+	grpcTLSConfig, err := tlsConfig.ToGRPCTLSConfig()
+	if err != nil {
+		log.Error(err, "failed to create TLS config")
+		return nil, fmt.Errorf("failed to create TLS config: %w", err)
+	}
+	opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(grpcTLSConfig)))
+	log.Info("TLS configured successfully")
+
+	// Dial the provider
+	log.Info("dialing provider", "address", address)
+	conn, err := grpc.Dial(address, opts...)
+	if err != nil {
+		log.Error(err, "failed to dial provider", "address", address)
+		return nil, fmt.Errorf("failed to dial provider at %s: %w", address, err)
+	}
+
+	log.Info("gRPC connection established",
+		"address", address,
+		"target", conn.Target(),
+		"state", conn.GetState().String())
+
+	// Create the gRPC client stub
+	grpcClient := pb.NewSecretStoreProviderClient(conn)
+
+	return &grpcProviderClient{
+		conn:   conn,
+		client: grpcClient,
+		log:    log.WithValues("target", conn.Target()),
+	}, nil
+}
+
+// NewClientWithConn creates a client from an existing gRPC connection.
+// This is useful for testing or when you need more control over connection setup.
+func NewClientWithConn(conn *grpc.ClientConn) v2.Provider {
+	return &grpcProviderClient{
+		conn:   conn,
+		client: pb.NewSecretStoreProviderClient(conn),
+		log:    ctrl.Log.WithName("grpc-client").WithValues("target", conn.Target()),
+	}
+}
+
+// NewResilientProviderClient creates a production-ready provider client with
+// connection pooling, retry logic, and circuit breaking.
+// This is the recommended way to create provider clients for production use.
+func NewResilientProviderClient(address string, tlsConfig *TLSConfig) (v2.Provider, error) {
+	config := DefaultResilientClientConfig(address, tlsConfig)
+	return NewResilientClient(config)
+}
+
+// NewResilientProviderClientWithConfig creates a resilient provider client with custom configuration.
+func NewResilientProviderClientWithConfig(config ResilientClientConfig) (v2.Provider, error) {
+	return NewResilientClient(config)
+}

+ 54 - 0
providers/v2/common/grpc/health.go

@@ -0,0 +1,54 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"fmt"
+
+	gogrpc "google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	grpc_health_v1 "google.golang.org/grpc/health/grpc_health_v1"
+)
+
+// CheckHealth verifies that a runtime is reachable and reports SERVING via gRPC health checks.
+func CheckHealth(ctx context.Context, address string, tlsConfig *TLSConfig) error {
+	if tlsConfig == nil {
+		return fmt.Errorf("tls config is required for health checks")
+	}
+
+	grpcTLSConfig, err := tlsConfig.ToGRPCTLSConfig()
+	if err != nil {
+		return fmt.Errorf("failed to create TLS config: %w", err)
+	}
+
+	conn, err := gogrpc.DialContext(ctx, address, gogrpc.WithTransportCredentials(credentials.NewTLS(grpcTLSConfig)))
+	if err != nil {
+		return fmt.Errorf("failed to dial runtime: %w", err)
+	}
+	defer func() { _ = conn.Close() }()
+
+	resp, err := grpc_health_v1.NewHealthClient(conn).Check(ctx, &grpc_health_v1.HealthCheckRequest{})
+	if err != nil {
+		return fmt.Errorf("health check failed: %w", err)
+	}
+	if resp.GetStatus() != grpc_health_v1.HealthCheckResponse_SERVING {
+		return fmt.Errorf("provider runtime is not serving: %s", resp.GetStatus().String())
+	}
+
+	return nil
+}

+ 91 - 0
providers/v2/common/grpc/health_test.go

@@ -0,0 +1,91 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	grpc_health "google.golang.org/grpc/health"
+	grpc_health_v1 "google.golang.org/grpc/health/grpc_health_v1"
+)
+
+func TestCheckHealthReturnsNilWhenServing(t *testing.T) {
+	address, tlsConfig := newHealthServer(t, grpc_health_v1.HealthCheckResponse_SERVING)
+
+	err := CheckHealth(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("CheckHealth() error = %v", err)
+	}
+}
+
+func TestCheckHealthReturnsErrorWhenNotServing(t *testing.T) {
+	address, tlsConfig := newHealthServer(t, grpc_health_v1.HealthCheckResponse_NOT_SERVING)
+
+	err := CheckHealth(context.Background(), address, tlsConfig)
+	if err == nil {
+		t.Fatal("expected non-serving runtime to fail health check")
+	}
+}
+
+func newHealthServer(t *testing.T, status grpc_health_v1.HealthCheckResponse_ServingStatus) (string, *TLSConfig) {
+	t.Helper()
+
+	serverCert, serverKey, clientCert, clientKey, caCert := newTLSArtifactsForTest(t, testLoopbackAddress)
+
+	caPool := x509.NewCertPool()
+	require.True(t, caPool.AppendCertsFromPEM(caCert))
+
+	tlsCert, err := tls.X509KeyPair(serverCert, serverKey)
+	require.NoError(t, err)
+
+	lis, err := net.Listen("tcp", testLoopbackAddress+":0")
+	require.NoError(t, err)
+
+	healthServer := grpc_health.NewServer()
+	healthServer.SetServingStatus("", status)
+
+	grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
+		MinVersion:   tls.VersionTLS12,
+		Certificates: []tls.Certificate{tlsCert},
+		ClientCAs:    caPool,
+		ClientAuth:   tls.RequireAndVerifyClientCert,
+	})))
+	grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+
+	go func() {
+		_ = grpcServer.Serve(lis)
+	}()
+
+	t.Cleanup(func() {
+		grpcServer.Stop()
+		_ = lis.Close()
+	})
+
+	return lis.Addr().String(), &TLSConfig{
+		CACert:     caCert,
+		ClientCert: clientCert,
+		ClientKey:  clientKey,
+		ServerName: testLoopbackAddress,
+	}
+}

+ 292 - 0
providers/v2/common/grpc/metrics.go

@@ -0,0 +1,292 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+	// gRPC latency buckets optimized for typical RPC call durations.
+	grpcLatencyBuckets = []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10, 30}
+
+	// Connection Pool gauges.
+	poolConnectionsActive = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "grpc_pool_connections_active",
+			Help: "Number of active gRPC connections in the pool with references > 0",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	poolConnectionsIdle = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "grpc_pool_connections_idle",
+			Help: "Number of idle gRPC connections in the pool with references = 0",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	poolConnectionsTotal = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "grpc_pool_connections_total",
+			Help: "Total number of gRPC connections in the pool",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	// Connection Pool histograms.
+	connectionAge = prometheus.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Name:    "grpc_connection_age_seconds",
+			Help:    "Age of gRPC connections in seconds",
+			Buckets: []float64{60, 300, 600, 900, 1800, 3600}, // 1m, 5m, 10m, 15m, 30m, 1h
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	connectionIdle = prometheus.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Name:    "grpc_connection_idle_seconds",
+			Help:    "Idle time of gRPC connections in seconds",
+			Buckets: []float64{30, 60, 120, 300, 600, 900}, // 30s, 1m, 2m, 5m, 10m, 15m
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	// Connection Pool counters.
+	poolHits = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_pool_hits_total",
+			Help: "Total number of connection pool cache hits (connection reused)",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	poolMisses = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_pool_misses_total",
+			Help: "Total number of connection pool cache misses (new connection created)",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	poolEvictions = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_pool_evictions_total",
+			Help: "Total number of connection pool evictions",
+		},
+		[]string{"address", "tls_enabled", "eviction_reason"},
+	)
+
+	poolConnectionErrors = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_pool_connection_errors_total",
+			Help: "Total number of failed connection attempts",
+		},
+		[]string{"address", "tls_enabled"},
+	)
+
+	// gRPC client metrics.
+	clientRequestDuration = prometheus.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Name:    "grpc_client_request_duration_seconds",
+			Help:    "Duration of gRPC client requests in seconds",
+			Buckets: grpcLatencyBuckets,
+		},
+		[]string{"method", "target", "status"},
+	)
+
+	clientRequestsTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_client_requests_total",
+			Help: "Total number of gRPC client requests",
+		},
+		[]string{"method", "target", "status"},
+	)
+
+	clientRequestErrors = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_client_request_errors_total",
+			Help: "Total number of failed gRPC client requests",
+		},
+		[]string{"method", "target", "error_type"},
+	)
+)
+
+// PoolMetrics records connection-pool metrics.
+type PoolMetrics interface {
+	RecordHit(address string, tlsEnabled bool)
+	RecordMiss(address string, tlsEnabled bool)
+	RecordEviction(address string, tlsEnabled bool, reason string)
+	RecordConnectionError(address string, tlsEnabled bool)
+	UpdatePoolState(address string, tlsEnabled bool, active, idle, total int)
+	RecordConnectionAge(address string, tlsEnabled bool, age time.Duration)
+	RecordConnectionIdle(address string, tlsEnabled bool, idle time.Duration)
+}
+
+// RequestObserver records client request metrics.
+type RequestObserver interface {
+	ObserveRequest(method, target string, err error, duration time.Duration)
+}
+
+// defaultPoolMetrics implements PoolMetrics using Prometheus.
+type defaultPoolMetrics struct{}
+
+// RecordHit records a connection pool cache hit.
+func (m *defaultPoolMetrics) RecordHit(address string, tlsEnabled bool) {
+	poolHits.WithLabelValues(address, strconv.FormatBool(tlsEnabled)).Inc()
+}
+
+// RecordMiss records a connection pool cache miss.
+func (m *defaultPoolMetrics) RecordMiss(address string, tlsEnabled bool) {
+	poolMisses.WithLabelValues(address, strconv.FormatBool(tlsEnabled)).Inc()
+}
+
+// RecordEviction records a connection eviction with reason.
+func (m *defaultPoolMetrics) RecordEviction(address string, tlsEnabled bool, reason string) {
+	poolEvictions.WithLabelValues(address, strconv.FormatBool(tlsEnabled), reason).Inc()
+}
+
+// RecordConnectionError records a failed connection attempt.
+func (m *defaultPoolMetrics) RecordConnectionError(address string, tlsEnabled bool) {
+	poolConnectionErrors.WithLabelValues(address, strconv.FormatBool(tlsEnabled)).Inc()
+}
+
+// UpdatePoolState updates the current pool state gauges.
+func (m *defaultPoolMetrics) UpdatePoolState(address string, tlsEnabled bool, active, idle, total int) {
+	labels := prometheus.Labels{"address": address, "tls_enabled": strconv.FormatBool(tlsEnabled)}
+	poolConnectionsActive.With(labels).Set(float64(active))
+	poolConnectionsIdle.With(labels).Set(float64(idle))
+	poolConnectionsTotal.With(labels).Set(float64(total))
+}
+
+// RecordConnectionAge records the age of a connection.
+func (m *defaultPoolMetrics) RecordConnectionAge(address string, tlsEnabled bool, age time.Duration) {
+	connectionAge.WithLabelValues(address, strconv.FormatBool(tlsEnabled)).Observe(age.Seconds())
+}
+
+// RecordConnectionIdle records the idle time of a connection.
+func (m *defaultPoolMetrics) RecordConnectionIdle(address string, tlsEnabled bool, idle time.Duration) {
+	connectionIdle.WithLabelValues(address, strconv.FormatBool(tlsEnabled)).Observe(idle.Seconds())
+}
+
+// defaultClientMetrics implements RequestObserver using Prometheus.
+type defaultClientMetrics struct{}
+
+// ObserveRequest records metrics for a client request.
+func (m *defaultClientMetrics) ObserveRequest(method, target string, err error, duration time.Duration) {
+	status := "success"
+	if err != nil {
+		status = "error"
+		errorType := classifyError(err)
+		clientRequestErrors.WithLabelValues(method, target, errorType).Inc()
+	}
+
+	clientRequestDuration.WithLabelValues(method, target, status).Observe(duration.Seconds())
+	clientRequestsTotal.WithLabelValues(method, target, status).Inc()
+}
+
+// classifyError extracts error type for metrics.
+func classifyError(err error) string {
+	if err == nil {
+		return "none"
+	}
+	errStr := err.Error()
+	// Classify common error patterns
+	switch {
+	case contains(errStr, "context deadline exceeded"):
+		return "timeout"
+	case contains(errStr, "connection refused"):
+		return "connection_refused"
+	case contains(errStr, "unavailable"):
+		return "unavailable"
+	case contains(errStr, "forbidden"):
+		return "forbidden"
+	case contains(errStr, "not found"):
+		return "not_found"
+	case contains(errStr, "unauthorized"):
+		return "unauthorized"
+	default:
+		return "unknown"
+	}
+}
+
+func contains(s, substr string) bool {
+	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || findSubstring(s, substr)))
+}
+
+func findSubstring(s, substr string) bool {
+	for i := 0; i <= len(s)-len(substr); i++ {
+		if s[i:i+len(substr)] == substr {
+			return true
+		}
+	}
+	return false
+}
+
+// Global instances.
+var (
+	poolMetrics   PoolMetrics     = &defaultPoolMetrics{}
+	clientMetrics RequestObserver = &defaultClientMetrics{}
+)
+
+// RegisterMetrics registers all gRPC metrics with Prometheus.
+func RegisterMetrics(registry prometheus.Registerer) error {
+	collectors := []prometheus.Collector{
+		poolConnectionsActive,
+		poolConnectionsIdle,
+		poolConnectionsTotal,
+		connectionAge,
+		connectionIdle,
+		poolHits,
+		poolMisses,
+		poolEvictions,
+		poolConnectionErrors,
+		clientRequestDuration,
+		clientRequestsTotal,
+		clientRequestErrors,
+	}
+
+	for _, collector := range collectors {
+		if err := registry.Register(collector); err != nil {
+			// Check if already registered.
+			var alreadyRegistered prometheus.AlreadyRegisteredError
+			if errors.As(err, &alreadyRegistered) {
+				continue
+			}
+			return fmt.Errorf("failed to register metric: %w", err)
+		}
+	}
+
+	return nil
+}
+
+// GetPoolMetrics returns the pool metrics instance for testing.
+func GetPoolMetrics() PoolMetrics {
+	return poolMetrics
+}
+
+// GetClientMetrics returns the client metrics instance for testing.
+func GetClientMetrics() RequestObserver {
+	return clientMetrics
+}

+ 42 - 0
providers/v2/common/grpc/metrics_test.go

@@ -0,0 +1,42 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+type wrappedAlreadyRegisteredRegisterer struct{}
+
+func (wrappedAlreadyRegisteredRegisterer) Register(prometheus.Collector) error {
+	return fmt.Errorf("wrapped: %w", prometheus.AlreadyRegisteredError{})
+}
+
+func (wrappedAlreadyRegisteredRegisterer) MustRegister(...prometheus.Collector) {}
+
+func (wrappedAlreadyRegisteredRegisterer) Unregister(prometheus.Collector) bool {
+	return false
+}
+
+func TestRegisterMetrics_AllowsWrappedAlreadyRegisteredError(t *testing.T) {
+	if err := RegisterMetrics(wrappedAlreadyRegisteredRegisterer{}); err != nil {
+		t.Fatalf("expected wrapped already registered error to be ignored, got %v", err)
+	}
+}

+ 395 - 0
providers/v2/common/grpc/pool.go

@@ -0,0 +1,395 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/go-logr/logr"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/connectivity"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+// ConnectionPool manages a pool of gRPC connections to providers.
+// It handles connection reuse, health checking, and graceful shutdown.
+type ConnectionPool struct {
+	mu          sync.RWMutex
+	connections map[string]*pooledConnection
+	maxIdle     time.Duration
+	maxLifetime time.Duration
+	healthCheck time.Duration
+	log         logr.Logger
+}
+
+// pooledConnection wraps a gRPC connection with metadata for pooling.
+type pooledConnection struct {
+	conn       *grpc.ClientConn
+	client     v2.Provider
+	created    time.Time
+	lastUsed   time.Time
+	references int32 // Number of active users
+	mu         sync.Mutex
+}
+
+// PoolConfig configures the connection pool.
+type PoolConfig struct {
+	// MaxIdleTime is how long a connection can be idle before being closed
+	MaxIdleTime time.Duration
+	// MaxLifetime is the maximum lifetime of a connection
+	MaxLifetime time.Duration
+	// HealthCheckInterval is how often to check connection health
+	HealthCheckInterval time.Duration
+}
+
+// DefaultPoolConfig returns sensible defaults for connection pooling.
+func DefaultPoolConfig() PoolConfig {
+	return PoolConfig{
+		MaxIdleTime:         5 * time.Minute,
+		MaxLifetime:         30 * time.Minute,
+		HealthCheckInterval: 30 * time.Second,
+	}
+}
+
+// NewConnectionPool creates a new connection pool with the given configuration.
+func NewConnectionPool(cfg PoolConfig) *ConnectionPool {
+	pool := &ConnectionPool{
+		connections: make(map[string]*pooledConnection),
+		maxIdle:     cfg.MaxIdleTime,
+		maxLifetime: cfg.MaxLifetime,
+		healthCheck: cfg.HealthCheckInterval,
+		log:         ctrl.Log.WithName("grpc-pool"),
+	}
+
+	pool.log.Info("connection pool initialized",
+		"maxIdleTime", cfg.MaxIdleTime.String(),
+		"maxLifetime", cfg.MaxLifetime.String(),
+		"healthCheckInterval", cfg.HealthCheckInterval.String())
+
+	// Start background goroutine for cleanup and health checks
+	go pool.maintenance()
+
+	return pool
+}
+
+// Get retrieves or creates a connection to the specified provider address.
+// The caller must call Release() when done with the connection.
+func (p *ConnectionPool) Get(_ context.Context, address string, tlsConfig *TLSConfig) (v2.Provider, error) {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+
+	key := p.connectionKey(address, tlsConfig)
+
+	p.log.V(1).Info("getting connection from pool",
+		"address", address,
+		"key", key)
+
+	// Check if we have a valid cached connection
+	if pooled, exists := p.connections[key]; exists {
+		pooled.mu.Lock()
+		defer pooled.mu.Unlock()
+
+		p.log.V(1).Info("found cached connection",
+			"address", address,
+			"state", pooled.conn.GetState().String(),
+			"references", pooled.references,
+			"age", time.Since(pooled.created).String(),
+			"idleTime", time.Since(pooled.lastUsed).String())
+
+		// Check if connection is still valid
+		if p.isConnectionValid(pooled) {
+			pooled.references++
+			pooled.lastUsed = time.Now()
+			p.log.Info("reusing cached connection",
+				"address", address,
+				"references", pooled.references)
+			// Record cache hit
+			poolMetrics.RecordHit(address, tlsConfig != nil)
+			return pooled.client, nil
+		}
+
+		// Connection is invalid, clean it up
+		p.log.Info("cached connection invalid, cleaning up",
+			"address", address,
+			"state", pooled.conn.GetState().String())
+		p.closeConn(pooled.conn, "failed to close invalid cached connection", "address", address)
+		delete(p.connections, key)
+	}
+
+	// Create new connection
+	p.log.Info("creating new connection",
+		"address", address,
+		"tlsEnabled", tlsConfig != nil)
+
+	// Record cache miss
+	poolMetrics.RecordMiss(address, tlsConfig != nil)
+
+	providerClient, err := NewClient(address, tlsConfig)
+	if err != nil {
+		p.log.Error(err, "failed to create new connection", "address", address)
+		// Record connection error
+		poolMetrics.RecordConnectionError(address, tlsConfig != nil)
+		return nil, fmt.Errorf("failed to create new connection: %w", err)
+	}
+
+	// Extract the underlying connection for pooling
+	grpcClient, ok := providerClient.(*grpcProviderClient)
+	if !ok {
+		return nil, fmt.Errorf("unexpected client type")
+	}
+
+	pooled := &pooledConnection{
+		conn:       grpcClient.conn,
+		client:     providerClient,
+		created:    time.Now(),
+		lastUsed:   time.Now(),
+		references: 1,
+	}
+
+	p.connections[key] = pooled
+
+	p.log.Info("new connection added to pool",
+		"address", address,
+		"state", grpcClient.conn.GetState().String(),
+		"target", grpcClient.conn.Target())
+
+	return providerClient, nil
+}
+
+// Release marks a connection as no longer in use.
+// This should be called in a defer after Get().
+func (p *ConnectionPool) Release(address string, tlsConfig *TLSConfig) {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+
+	key := p.connectionKey(address, tlsConfig)
+
+	if pooled, exists := p.connections[key]; exists {
+		pooled.mu.Lock()
+		defer pooled.mu.Unlock()
+
+		if pooled.references > 0 {
+			pooled.references--
+			p.log.V(1).Info("released connection",
+				"address", address,
+				"remainingReferences", pooled.references)
+		}
+	}
+}
+
+// Close shuts down the connection pool and closes all connections.
+func (p *ConnectionPool) Close() error {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+
+	for _, pooled := range p.connections {
+		pooled.mu.Lock()
+		if pooled.conn != nil {
+			p.closeConn(pooled.conn, "failed to close pooled connection during pool shutdown")
+		}
+		pooled.mu.Unlock()
+	}
+
+	p.connections = make(map[string]*pooledConnection)
+
+	return nil
+}
+
+// maintenance runs periodic cleanup and health checks.
+func (p *ConnectionPool) maintenance() {
+	ticker := time.NewTicker(p.healthCheck)
+	defer ticker.Stop()
+
+	for range ticker.C {
+		p.cleanupIdleConnections()
+		p.checkConnectionHealth()
+		p.updatePoolMetrics()
+	}
+}
+
+// cleanupIdleConnections removes connections that have been idle too long.
+func (p *ConnectionPool) cleanupIdleConnections() {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+
+	now := time.Now()
+	toRemove := make([]string, 0)
+	evictions := make(map[string]string) // key -> reason
+
+	for key, pooled := range p.connections {
+		pooled.mu.Lock()
+
+		// Skip connections that are in use
+		if pooled.references > 0 {
+			pooled.mu.Unlock()
+			continue
+		}
+
+		// Check if connection is too old or idle too long
+		idleTooLong := now.Sub(pooled.lastUsed) > p.maxIdle
+		tooOld := now.Sub(pooled.created) > p.maxLifetime
+
+		if idleTooLong {
+			p.closeConn(pooled.conn, "failed to close idle pooled connection", "connectionKey", key)
+			toRemove = append(toRemove, key)
+			evictions[key] = "idle_timeout"
+		} else if tooOld {
+			p.closeConn(pooled.conn, "failed to close expired pooled connection", "connectionKey", key)
+			toRemove = append(toRemove, key)
+			evictions[key] = "max_lifetime"
+		}
+
+		pooled.mu.Unlock()
+	}
+
+	for _, key := range toRemove {
+		address, tlsEnabled := p.parseConnectionKey(key)
+		poolMetrics.RecordEviction(address, tlsEnabled, evictions[key])
+		delete(p.connections, key)
+	}
+}
+
+// checkConnectionHealth verifies that pooled connections are still healthy.
+func (p *ConnectionPool) checkConnectionHealth() {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+
+	toRemove := make([]string, 0)
+
+	for key, pooled := range p.connections {
+		pooled.mu.Lock()
+
+		// Check connection state
+		state := pooled.conn.GetState()
+		if state == connectivity.TransientFailure || state == connectivity.Shutdown {
+			p.closeConn(pooled.conn, "failed to close unhealthy pooled connection", "connectionKey", key, "state", state.String())
+			toRemove = append(toRemove, key)
+		}
+
+		pooled.mu.Unlock()
+	}
+
+	for _, key := range toRemove {
+		address, tlsEnabled := p.parseConnectionKey(key)
+		poolMetrics.RecordEviction(address, tlsEnabled, "health_check")
+		delete(p.connections, key)
+	}
+}
+
+// isConnectionValid checks if a pooled connection is still usable.
+func (p *ConnectionPool) isConnectionValid(pooled *pooledConnection) bool {
+	// Check age
+	if time.Since(pooled.created) > p.maxLifetime {
+		return false
+	}
+
+	// Check connection state
+	state := pooled.conn.GetState()
+	if state == connectivity.Shutdown || state == connectivity.TransientFailure {
+		return false
+	}
+
+	return true
+}
+
+func (p *ConnectionPool) closeConn(conn *grpc.ClientConn, msg string, keysAndValues ...any) {
+	if conn == nil {
+		return
+	}
+	if err := conn.Close(); err != nil {
+		p.log.Error(err, msg, keysAndValues...)
+	}
+}
+
+// connectionKey generates a unique key for caching connections.
+func (p *ConnectionPool) connectionKey(address string, tlsConfig *TLSConfig) string {
+	if tlsConfig != nil {
+		sum := sha256.Sum256(append(append(append([]byte{}, tlsConfig.CACert...), tlsConfig.ClientCert...), tlsConfig.ClientKey...))
+		return fmt.Sprintf("%s|%s-tls", address, hex.EncodeToString(sum[:8]))
+	}
+	return fmt.Sprintf("%s-insecure", address)
+}
+
+// parseConnectionKey extracts address and TLS status from a connection key.
+func (p *ConnectionPool) parseConnectionKey(key string) (address string, tlsEnabled bool) {
+	if len(key) > 4 && key[len(key)-4:] == "-tls" {
+		trimmed := key[:len(key)-4]
+		for i := 0; i < len(trimmed); i++ {
+			if trimmed[i] == '|' {
+				return trimmed[:i], true
+			}
+		}
+		return trimmed, true
+	}
+	if len(key) > 9 && key[len(key)-9:] == "-insecure" {
+		return key[:len(key)-9], false
+	}
+	return key, false
+}
+
+// updatePoolMetrics updates pool state metrics.
+func (p *ConnectionPool) updatePoolMetrics() {
+	p.mu.RLock()
+	defer p.mu.RUnlock()
+
+	// Track stats per address/TLS combination
+	stats := make(map[string]struct {
+		active int
+		idle   int
+		total  int
+	})
+
+	now := time.Now()
+
+	for key, pooled := range p.connections {
+		pooled.mu.Lock()
+		address, tlsEnabled := p.parseConnectionKey(key)
+
+		statKey := key
+		s := stats[statKey]
+		s.total++
+
+		if pooled.references > 0 {
+			s.active++
+		} else {
+			s.idle++
+		}
+
+		stats[statKey] = s
+
+		// Record connection age and idle time
+		poolMetrics.RecordConnectionAge(address, tlsEnabled, now.Sub(pooled.created))
+		if pooled.references == 0 {
+			poolMetrics.RecordConnectionIdle(address, tlsEnabled, now.Sub(pooled.lastUsed))
+		}
+
+		pooled.mu.Unlock()
+	}
+
+	// Update gauges
+	for key, s := range stats {
+		address, tlsEnabled := p.parseConnectionKey(key)
+		poolMetrics.UpdatePoolState(address, tlsEnabled, s.active, s.idle, s.total)
+	}
+}

+ 252 - 0
providers/v2/common/grpc/pool_test.go

@@ -0,0 +1,252 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"net"
+	"testing"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+func TestConnectionPoolGetReleaseReuse(t *testing.T) {
+	address, tlsConfig := newPoolTestServer(t)
+
+	pool := NewConnectionPool(PoolConfig{
+		MaxIdleTime:         time.Minute,
+		MaxLifetime:         time.Minute,
+		HealthCheckInterval: time.Hour,
+	})
+	defer func() {
+		_ = pool.Close()
+	}()
+
+	client1, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("Get() error = %v", err)
+	}
+	client2, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("second Get() error = %v", err)
+	}
+
+	if client1 != client2 {
+		t.Fatal("expected pooled client to be reused")
+	}
+
+	key := pool.connectionKey(address, tlsConfig)
+	pooled := pool.connections[key]
+	if pooled == nil {
+		t.Fatalf("expected pooled connection for key %q", key)
+	}
+	if pooled.references != 2 {
+		t.Fatalf("expected references=2, got %d", pooled.references)
+	}
+
+	pool.Release(address, tlsConfig)
+	if pooled.references != 1 {
+		t.Fatalf("expected references=1 after release, got %d", pooled.references)
+	}
+	pool.Release(address, tlsConfig)
+	if pooled.references != 0 {
+		t.Fatalf("expected references=0 after second release, got %d", pooled.references)
+	}
+}
+
+func TestConnectionPoolGetReplacesExpiredConnection(t *testing.T) {
+	address, tlsConfig := newPoolTestServer(t)
+
+	pool := NewConnectionPool(PoolConfig{
+		MaxIdleTime:         time.Minute,
+		MaxLifetime:         time.Minute,
+		HealthCheckInterval: time.Hour,
+	})
+	defer func() {
+		_ = pool.Close()
+	}()
+
+	client1, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("Get() error = %v", err)
+	}
+	pool.Release(address, tlsConfig)
+
+	key := pool.connectionKey(address, tlsConfig)
+	pooled := pool.connections[key]
+	if pooled == nil {
+		t.Fatalf("expected pooled connection for key %q", key)
+	}
+	pooled.mu.Lock()
+	pooled.created = time.Now().Add(-2 * time.Hour)
+	pooled.mu.Unlock()
+
+	client2, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("second Get() error = %v", err)
+	}
+
+	if client1 == client2 {
+		t.Fatal("expected expired pooled client to be replaced")
+	}
+}
+
+func TestConnectionPoolCleanupIdleConnectionsRemovesReleasedConnection(t *testing.T) {
+	address, tlsConfig := newPoolTestServer(t)
+
+	pool := NewConnectionPool(PoolConfig{
+		MaxIdleTime:         time.Second,
+		MaxLifetime:         time.Minute,
+		HealthCheckInterval: time.Hour,
+	})
+	defer func() {
+		_ = pool.Close()
+	}()
+
+	_, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("Get() error = %v", err)
+	}
+	pool.Release(address, tlsConfig)
+
+	key := pool.connectionKey(address, tlsConfig)
+	pooled := pool.connections[key]
+	if pooled == nil {
+		t.Fatalf("expected pooled connection for key %q", key)
+	}
+	pooled.mu.Lock()
+	pooled.lastUsed = time.Now().Add(-2 * time.Second)
+	pooled.mu.Unlock()
+
+	pool.cleanupIdleConnections()
+
+	if _, ok := pool.connections[key]; ok {
+		t.Fatalf("expected idle pooled connection %q to be removed", key)
+	}
+}
+
+func TestConnectionPoolCheckConnectionHealthRemovesShutdownConnection(t *testing.T) {
+	address, tlsConfig := newPoolTestServer(t)
+
+	pool := NewConnectionPool(PoolConfig{
+		MaxIdleTime:         time.Minute,
+		MaxLifetime:         time.Minute,
+		HealthCheckInterval: time.Hour,
+	})
+	defer func() {
+		_ = pool.Close()
+	}()
+
+	_, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("Get() error = %v", err)
+	}
+	pool.Release(address, tlsConfig)
+
+	key := pool.connectionKey(address, tlsConfig)
+	pooled := pool.connections[key]
+	if pooled == nil {
+		t.Fatalf("expected pooled connection for key %q", key)
+	}
+
+	if err := pooled.conn.Close(); err != nil {
+		t.Fatalf("Close() error = %v", err)
+	}
+
+	pool.checkConnectionHealth()
+
+	if _, ok := pool.connections[key]; ok {
+		t.Fatalf("expected unhealthy pooled connection %q to be removed", key)
+	}
+}
+
+func TestConnectionPoolCloseClearsTrackedConnections(t *testing.T) {
+	address, tlsConfig := newPoolTestServer(t)
+
+	pool := NewConnectionPool(PoolConfig{
+		MaxIdleTime:         time.Minute,
+		MaxLifetime:         time.Minute,
+		HealthCheckInterval: time.Hour,
+	})
+
+	_, err := pool.Get(context.Background(), address, tlsConfig)
+	if err != nil {
+		t.Fatalf("Get() error = %v", err)
+	}
+	pool.Release(address, tlsConfig)
+
+	if len(pool.connections) != 1 {
+		t.Fatalf("expected one tracked connection, got %d", len(pool.connections))
+	}
+
+	if err := pool.Close(); err != nil {
+		t.Fatalf("Close() error = %v", err)
+	}
+
+	if len(pool.connections) != 0 {
+		t.Fatalf("expected no tracked connections after close, got %d", len(pool.connections))
+	}
+}
+
+func newPoolTestServer(t *testing.T) (string, *TLSConfig) {
+	t.Helper()
+
+	serverCert, serverKey, clientCert, clientKey, caCert := newTLSArtifactsForTest(t, "127.0.0.1")
+
+	caPool := x509.NewCertPool()
+	if !caPool.AppendCertsFromPEM(caCert) {
+		t.Fatal("failed to append CA cert")
+	}
+	tlsCert, err := tls.X509KeyPair(serverCert, serverKey)
+	if err != nil {
+		t.Fatalf("X509KeyPair() error = %v", err)
+	}
+
+	lis, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("Listen() error = %v", err)
+	}
+
+	server := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
+		MinVersion:   tls.VersionTLS12,
+		Certificates: []tls.Certificate{tlsCert},
+		ClientCAs:    caPool,
+		ClientAuth:   tls.RequireAndVerifyClientCert,
+	})))
+	pb.RegisterSecretStoreProviderServer(server, &mockServer{})
+	go func() {
+		_ = server.Serve(lis)
+	}()
+
+	t.Cleanup(func() {
+		server.Stop()
+		_ = lis.Close()
+	})
+
+	return lis.Addr().String(), &TLSConfig{
+		CACert:     caCert,
+		ClientCert: clientCert,
+		ClientKey:  clientKey,
+		ServerName: "127.0.0.1",
+	}
+}

+ 274 - 0
providers/v2/common/grpc/resilient.go

@@ -0,0 +1,274 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	corev1 "k8s.io/api/core/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+	v2 "github.com/external-secrets/external-secrets/providers/v2/common"
+)
+
+// ResilientClient wraps a gRPC provider client with connection pooling,
+// retry logic, and circuit breaking for production-ready reliability.
+type ResilientClient struct {
+	address        string
+	tlsConfig      *TLSConfig
+	pool           *ConnectionPool
+	circuitBreaker *CircuitBreaker
+	retryConfig    RetryConfig
+	log            logr.Logger
+}
+
+// ResilientClientConfig configures the resilient client.
+type ResilientClientConfig struct {
+	Address       string
+	TLSConfig     *TLSConfig
+	PoolConfig    PoolConfig
+	CircuitConfig CircuitBreakerConfig
+	RetryConfig   RetryConfig
+}
+
+// DefaultResilientClientConfig returns sensible defaults.
+func DefaultResilientClientConfig(address string, tlsConfig *TLSConfig) ResilientClientConfig {
+	return ResilientClientConfig{
+		Address:       address,
+		TLSConfig:     tlsConfig,
+		PoolConfig:    DefaultPoolConfig(),
+		CircuitConfig: DefaultCircuitBreakerConfig(),
+		RetryConfig:   DefaultRetryConfig(),
+	}
+}
+
+// NewResilientClient creates a new resilient client with connection pooling,
+// retry logic, and circuit breaking.
+func NewResilientClient(config ResilientClientConfig) (*ResilientClient, error) {
+	if config.Address == "" {
+		return nil, fmt.Errorf("provider address cannot be empty")
+	}
+
+	log := ctrl.Log.WithName("grpc-resilient").WithValues("address", config.Address)
+	log.Info("creating resilient client",
+		"poolMaxIdleTime", config.PoolConfig.MaxIdleTime.String(),
+		"poolMaxLifetime", config.PoolConfig.MaxLifetime.String(),
+		"retryMaxAttempts", config.RetryConfig.MaxAttempts,
+		"circuitMaxFailures", config.CircuitConfig.MaxFailures)
+
+	return &ResilientClient{
+		address:        config.Address,
+		tlsConfig:      config.TLSConfig,
+		pool:           NewConnectionPool(config.PoolConfig),
+		circuitBreaker: NewCircuitBreaker(config.CircuitConfig),
+		retryConfig:    config.RetryConfig,
+		log:            log,
+	}, nil
+}
+
+// Ensure ResilientClient implements the Provider interface.
+var _ v2.Provider = &ResilientClient{}
+
+// PushSecret writes a secret with retry logic and circuit breaking.
+func (rc *ResilientClient) PushSecret(
+	ctx context.Context,
+	secret *corev1.Secret,
+	pushSecretData *pb.PushSecretData,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	return rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		return client.PushSecret(ctx, secret, pushSecretData, providerRef, compatibilityStore, sourceNamespace)
+	})
+}
+
+// DeleteSecret deletes a secret with retry logic and circuit breaking.
+func (rc *ResilientClient) DeleteSecret(
+	ctx context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) error {
+	return rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		return client.DeleteSecret(ctx, remoteRef, providerRef, compatibilityStore, sourceNamespace)
+	})
+}
+
+// SecretExists checks if a secret exists with retry logic and circuit breaking.
+func (rc *ResilientClient) SecretExists(
+	ctx context.Context,
+	remoteRef *pb.PushSecretRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (bool, error) {
+	var result bool
+
+	err := rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		exists, err := client.SecretExists(ctx, remoteRef, providerRef, compatibilityStore, sourceNamespace)
+		if err != nil {
+			return err
+		}
+		result = exists
+		return nil
+	})
+
+	return result, err
+}
+
+// GetSecret retrieves a secret with retry logic and circuit breaking.
+func (rc *ResilientClient) GetSecret(
+	ctx context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) ([]byte, error) {
+	var result []byte
+
+	err := rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		secretData, err := client.GetSecret(ctx, ref, providerRef, compatibilityStore, sourceNamespace)
+		if err != nil {
+			return err
+		}
+		result = secretData
+		return nil
+	})
+
+	return result, err
+}
+
+// GetSecretMap retrieves multiple key/value pairs from a single provider object with retry logic and circuit breaking.
+func (rc *ResilientClient) GetSecretMap(
+	ctx context.Context,
+	ref esv1.ExternalSecretDataRemoteRef,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	var result map[string][]byte
+
+	err := rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		secretMap, err := client.GetSecretMap(ctx, ref, providerRef, compatibilityStore, sourceNamespace)
+		if err != nil {
+			return err
+		}
+		result = secretMap
+		return nil
+	})
+
+	return result, err
+}
+
+// GetAllSecrets retrieves multiple secrets with retry logic and circuit breaking.
+func (rc *ResilientClient) GetAllSecrets(
+	ctx context.Context,
+	find esv1.ExternalSecretFind,
+	providerRef *pb.ProviderReference,
+	compatibilityStore *pb.CompatibilityStore,
+	sourceNamespace string,
+) (map[string][]byte, error) {
+	var result map[string][]byte
+
+	err := rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		secrets, err := client.GetAllSecrets(ctx, find, providerRef, compatibilityStore, sourceNamespace)
+		if err != nil {
+			return err
+		}
+		result = secrets
+		return nil
+	})
+
+	return result, err
+}
+
+// Validate validates the provider configuration with retry logic.
+func (rc *ResilientClient) Validate(ctx context.Context, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) error {
+	return rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		return client.Validate(ctx, providerRef, compatibilityStore, sourceNamespace)
+	})
+}
+
+// Capabilities retrieves the provider's capabilities with retry logic.
+func (rc *ResilientClient) Capabilities(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) (pb.SecretStoreCapabilities, error) {
+	var result pb.SecretStoreCapabilities
+
+	err := rc.executeWithResilience(ctx, func(client v2.Provider) error {
+		caps, err := client.Capabilities(ctx, providerRef, sourceNamespace)
+		if err != nil {
+			return err
+		}
+		result = caps
+		return nil
+	})
+
+	return result, err
+}
+
+// Close closes the connection pool.
+func (rc *ResilientClient) Close(_ context.Context) error {
+	return rc.pool.Close()
+}
+
+// executeWithResilience executes a function with connection pooling, retry, and circuit breaking.
+func (rc *ResilientClient) executeWithResilience(ctx context.Context, fn func(v2.Provider) error) error {
+	rc.log.V(1).Info("executing with resilience",
+		"circuitState", rc.circuitBreaker.State())
+
+	// Check circuit breaker first
+	return rc.circuitBreaker.Call(ctx, func() error {
+		// Execute with retry
+		return WithRetry(ctx, rc.retryConfig, func(ctx context.Context, attempt int) error {
+			if attempt > 1 {
+				rc.log.Info("retrying operation",
+					"attempt", attempt,
+					"maxAttempts", rc.retryConfig.MaxAttempts)
+			}
+
+			// Get connection from pool
+			rc.log.V(1).Info("getting connection from pool")
+			client, err := rc.pool.Get(ctx, rc.address, rc.tlsConfig)
+			if err != nil {
+				rc.log.Error(err, "failed to get connection from pool",
+					"attempt", attempt)
+				return fmt.Errorf("failed to get connection from pool: %w", err)
+			}
+			defer rc.pool.Release(rc.address, rc.tlsConfig)
+
+			// Execute the function
+			rc.log.V(1).Info("executing provider operation", "attempt", attempt)
+			err = fn(client)
+			if err != nil {
+				rc.log.Error(err, "provider operation failed",
+					"attempt", attempt,
+					"willRetry", attempt < rc.retryConfig.MaxAttempts)
+			}
+			return err
+		})
+	})
+}
+
+// GetCircuitState returns the current state of the circuit breaker.
+func (rc *ResilientClient) GetCircuitState() CircuitState {
+	return rc.circuitBreaker.State()
+}

+ 322 - 0
providers/v2/common/grpc/retry.go

@@ -0,0 +1,322 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	cryptorand "crypto/rand"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+	"sync"
+	"time"
+
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+// RetryConfig configures retry behavior.
+type RetryConfig struct {
+	// MaxAttempts is the maximum number of retry attempts.
+	MaxAttempts int
+	// InitialBackoff is the initial backoff duration.
+	InitialBackoff time.Duration
+	// MaxBackoff is the maximum backoff duration.
+	MaxBackoff time.Duration
+	// BackoffMultiplier is the multiplier for exponential backoff.
+	BackoffMultiplier float64
+	// Jitter adds randomness to backoff to prevent thundering herd.
+	Jitter bool
+}
+
+// DefaultRetryConfig returns sensible defaults for retry behavior.
+func DefaultRetryConfig() RetryConfig {
+	return RetryConfig{
+		MaxAttempts:       3,
+		InitialBackoff:    100 * time.Millisecond,
+		MaxBackoff:        10 * time.Second,
+		BackoffMultiplier: 2.0,
+		Jitter:            true,
+	}
+}
+
+// CircuitBreakerConfig configures circuit breaker behavior.
+type CircuitBreakerConfig struct {
+	// MaxFailures is the number of consecutive failures before opening.
+	MaxFailures int
+	// Timeout is how long to wait in open state before trying again.
+	Timeout time.Duration
+	// HalfOpenMaxRequests is max requests allowed in half-open state.
+	HalfOpenMaxRequests int
+}
+
+// DefaultCircuitBreakerConfig returns sensible defaults.
+func DefaultCircuitBreakerConfig() CircuitBreakerConfig {
+	return CircuitBreakerConfig{
+		MaxFailures:         5,
+		Timeout:             30 * time.Second,
+		HalfOpenMaxRequests: 1,
+	}
+}
+
+// CircuitState represents the state of a circuit breaker.
+type CircuitState int
+
+const (
+	// StateClosed means the circuit is closed and requests flow normally.
+	StateClosed CircuitState = iota
+	// StateOpen means the circuit is open and requests fail fast.
+	StateOpen
+	// StateHalfOpen means the circuit is testing if the service has recovered.
+	StateHalfOpen
+)
+
+// CircuitBreaker implements the circuit breaker pattern.
+type CircuitBreaker struct {
+	mu              sync.RWMutex
+	state           CircuitState
+	failures        int
+	lastFailureTime time.Time
+	halfOpenReqs    int
+	config          CircuitBreakerConfig
+}
+
+// NewCircuitBreaker creates a new circuit breaker.
+func NewCircuitBreaker(config CircuitBreakerConfig) *CircuitBreaker {
+	return &CircuitBreaker{
+		state:  StateClosed,
+		config: config,
+	}
+}
+
+// Call executes the given function with circuit breaker protection.
+func (cb *CircuitBreaker) Call(_ context.Context, fn func() error) error {
+	// Check if we should allow the request.
+	if err := cb.beforeRequest(); err != nil {
+		return err
+	}
+
+	// Execute the function.
+	err := fn()
+
+	// Record the result.
+	cb.afterRequest(err)
+
+	return err
+}
+
+// beforeRequest checks if the request should be allowed.
+func (cb *CircuitBreaker) beforeRequest() error {
+	cb.mu.Lock()
+	defer cb.mu.Unlock()
+
+	switch cb.state {
+	case StateClosed:
+		return nil
+
+	case StateOpen:
+		// Check if timeout has elapsed
+		if time.Since(cb.lastFailureTime) > cb.config.Timeout {
+			cb.state = StateHalfOpen
+			cb.halfOpenReqs = 0
+			return nil
+		}
+		return fmt.Errorf("circuit breaker is open")
+
+	case StateHalfOpen:
+		// Allow limited requests in half-open state
+		if cb.halfOpenReqs >= cb.config.HalfOpenMaxRequests {
+			return fmt.Errorf("circuit breaker is half-open, max requests reached")
+		}
+		cb.halfOpenReqs++
+		return nil
+
+	default:
+		return fmt.Errorf("unknown circuit breaker state")
+	}
+}
+
+// afterRequest records the result of a request.
+func (cb *CircuitBreaker) afterRequest(err error) {
+	cb.mu.Lock()
+	defer cb.mu.Unlock()
+
+	if err == nil {
+		// Success
+		cb.onSuccess()
+	} else {
+		// Failure
+		cb.onFailure()
+	}
+}
+
+// onSuccess handles a successful request.
+func (cb *CircuitBreaker) onSuccess() {
+	switch cb.state {
+	case StateClosed:
+		cb.failures = 0
+
+	case StateOpen:
+		// Requests should not reach onSuccess while open, keep state unchanged.
+
+	case StateHalfOpen:
+		// Success in half-open state means we can close the circuit
+		cb.state = StateClosed
+		cb.failures = 0
+		cb.halfOpenReqs = 0
+	}
+}
+
+// onFailure handles a failed request.
+func (cb *CircuitBreaker) onFailure() {
+	cb.failures++
+	cb.lastFailureTime = time.Now()
+
+	switch cb.state {
+	case StateClosed:
+		if cb.failures >= cb.config.MaxFailures {
+			cb.state = StateOpen
+		}
+
+	case StateOpen:
+		// Keep tracking failure timing while the circuit remains open.
+
+	case StateHalfOpen:
+		// Failure in half-open state means we go back to open
+		cb.state = StateOpen
+		cb.halfOpenReqs = 0
+	}
+}
+
+// State returns the current state of the circuit breaker.
+func (cb *CircuitBreaker) State() CircuitState {
+	cb.mu.RLock()
+	defer cb.mu.RUnlock()
+	return cb.state
+}
+
+// RetryableFunc is a function that can be retried.
+type RetryableFunc func(ctx context.Context, attempt int) error
+
+// WithRetry executes a function with exponential backoff retry logic.
+func WithRetry(ctx context.Context, config RetryConfig, fn RetryableFunc) error {
+	var lastErr error
+
+	for attempt := 0; attempt < config.MaxAttempts; attempt++ {
+		// Execute the function
+		err := fn(ctx, attempt)
+		if err == nil {
+			return nil
+		}
+
+		lastErr = err
+
+		// Check if error is retryable
+		if !isRetryable(err) {
+			return err
+		}
+
+		// Last attempt, don't wait
+		if attempt == config.MaxAttempts-1 {
+			break
+		}
+
+		// Calculate backoff
+		backoff := calculateBackoff(attempt, config)
+
+		// Wait with context cancellation support
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-time.After(backoff):
+			// Continue to next attempt
+		}
+	}
+
+	return fmt.Errorf("max retry attempts (%d) exceeded: %w", config.MaxAttempts, lastErr)
+}
+
+// isRetryable determines if an error should trigger a retry.
+func isRetryable(err error) bool {
+	// Check gRPC status codes
+	st, ok := status.FromError(err)
+	if ok {
+		switch st.Code() {
+		case codes.Unavailable,
+			codes.DeadlineExceeded,
+			codes.ResourceExhausted,
+			codes.Aborted:
+			return true
+
+		case codes.OK,
+			codes.Canceled,
+			codes.InvalidArgument,
+			codes.NotFound,
+			codes.AlreadyExists,
+			codes.PermissionDenied,
+			codes.Unauthenticated,
+			codes.FailedPrecondition,
+			codes.OutOfRange,
+			codes.Unimplemented:
+			return false
+
+		case codes.Unknown,
+			codes.Internal,
+			codes.DataLoss:
+			// Retry transient or ambiguous failures.
+			return true
+		}
+	}
+
+	// For non-gRPC errors, retry network-related issues.
+	// Context errors should not be retried.
+	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+		return false
+	}
+
+	// Default: retry.
+	return true
+}
+
+// calculateBackoff calculates the backoff duration for a given attempt.
+func calculateBackoff(attempt int, config RetryConfig) time.Duration {
+	// Exponential backoff: initialBackoff * (multiplier ^ attempt).
+	backoff := float64(config.InitialBackoff) * math.Pow(config.BackoffMultiplier, float64(attempt))
+
+	// Apply max backoff.
+	if backoff > float64(config.MaxBackoff) {
+		backoff = float64(config.MaxBackoff)
+	}
+
+	// Add jitter if enabled.
+	if config.Jitter {
+		// Add random jitter between 0 and 25% of backoff.
+		jitter := randomFloat64() * backoff * 0.25
+		backoff += jitter
+	}
+
+	return time.Duration(backoff)
+}
+
+func randomFloat64() float64 {
+	var buf [8]byte
+	if _, err := cryptorand.Read(buf[:]); err != nil {
+		return 0
+	}
+	return float64(binary.BigEndian.Uint64(buf[:])) / float64(^uint64(0))
+}

+ 238 - 0
providers/v2/common/grpc/retry_test.go

@@ -0,0 +1,238 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+func TestCircuitBreaker_States(t *testing.T) {
+	cb := NewCircuitBreaker(CircuitBreakerConfig{
+		MaxFailures:         3,
+		Timeout:             100 * time.Millisecond,
+		HalfOpenMaxRequests: 1,
+	})
+
+	ctx := context.Background()
+
+	// Initially closed
+	if cb.State() != StateClosed {
+		t.Errorf("expected initial state to be Closed, got %v", cb.State())
+	}
+
+	// Simulate 3 failures to open the circuit
+	for range 3 {
+		err := cb.Call(ctx, func() error {
+			return status.Error(codes.Unavailable, "service unavailable")
+		})
+		if err == nil {
+			t.Fatal("expected error from failing call")
+		}
+	}
+
+	// Should now be open
+	if cb.State() != StateOpen {
+		t.Errorf("expected state to be Open after failures, got %v", cb.State())
+	}
+
+	// Requests should fail fast
+	err := cb.Call(ctx, func() error {
+		t.Fatal("should not execute function in open state")
+		return nil
+	})
+	if err == nil || err.Error() != "circuit breaker is open" {
+		t.Errorf("expected circuit open error, got: %v", err)
+	}
+
+	// Wait for timeout to transition to half-open
+	time.Sleep(150 * time.Millisecond)
+
+	// Should now be half-open
+	err = cb.Call(ctx, func() error {
+		return nil // Success
+	})
+	if err != nil {
+		t.Fatalf("expected success in half-open state, got: %v", err)
+	}
+
+	// Should transition back to closed
+	if cb.State() != StateClosed {
+		t.Errorf("expected state to be Closed after success, got %v", cb.State())
+	}
+}
+
+func TestRetry_Backoff(t *testing.T) {
+	attempts := 0
+	config := RetryConfig{
+		MaxAttempts:       3,
+		InitialBackoff:    10 * time.Millisecond,
+		MaxBackoff:        100 * time.Millisecond,
+		BackoffMultiplier: 2.0,
+		Jitter:            false, // Disable for predictable testing
+	}
+
+	ctx := context.Background()
+	start := time.Now()
+
+	err := WithRetry(ctx, config, func(ctx context.Context, attempt int) error {
+		attempts++
+		if attempt < 2 {
+			return status.Error(codes.Unavailable, "unavailable")
+		}
+		return nil // Success on 3rd attempt
+	})
+
+	elapsed := time.Since(start)
+
+	if err != nil {
+		t.Fatalf("expected success after retries, got: %v", err)
+	}
+
+	if attempts != 3 {
+		t.Errorf("expected 3 attempts, got %d", attempts)
+	}
+
+	// Expected: 10ms + 20ms = 30ms minimum
+	if elapsed < 30*time.Millisecond {
+		t.Errorf("expected at least 30ms elapsed, got %v", elapsed)
+	}
+}
+
+func TestRetry_NonRetryableError(t *testing.T) {
+	attempts := 0
+	config := DefaultRetryConfig()
+
+	ctx := context.Background()
+
+	err := WithRetry(ctx, config, func(ctx context.Context, attempt int) error {
+		attempts++
+		return status.Error(codes.NotFound, "not found")
+	})
+
+	if err == nil {
+		t.Fatal("expected error")
+	}
+
+	// Should not retry NotFound
+	if attempts != 1 {
+		t.Errorf("expected 1 attempt for non-retryable error, got %d", attempts)
+	}
+}
+
+func TestRetry_ContextCancellation(t *testing.T) {
+	config := DefaultRetryConfig()
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// Cancel immediately
+	cancel()
+
+	err := WithRetry(ctx, config, func(ctx context.Context, attempt int) error {
+		return status.Error(codes.Unavailable, "unavailable")
+	})
+
+	if !errors.Is(err, context.Canceled) {
+		t.Errorf("expected context.Canceled, got: %v", err)
+	}
+}
+
+func TestIsRetryable(t *testing.T) {
+	tests := []struct {
+		name      string
+		err       error
+		retryable bool
+	}{
+		{
+			name:      "Unavailable is retryable",
+			err:       status.Error(codes.Unavailable, "test"),
+			retryable: true,
+		},
+		{
+			name:      "DeadlineExceeded is retryable",
+			err:       status.Error(codes.DeadlineExceeded, "test"),
+			retryable: true,
+		},
+		{
+			name:      "NotFound is not retryable",
+			err:       status.Error(codes.NotFound, "test"),
+			retryable: false,
+		},
+		{
+			name:      "PermissionDenied is not retryable",
+			err:       status.Error(codes.PermissionDenied, "test"),
+			retryable: false,
+		},
+		{
+			name:      "Context canceled is not retryable",
+			err:       context.Canceled,
+			retryable: false,
+		},
+		{
+			name:      "Wrapped context canceled is not retryable",
+			err:       fmt.Errorf("wrapped: %w", context.Canceled),
+			retryable: false,
+		},
+		{
+			name:      "Wrapped context deadline exceeded is not retryable",
+			err:       fmt.Errorf("wrapped: %w", context.DeadlineExceeded),
+			retryable: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := isRetryable(tt.err)
+			if result != tt.retryable {
+				t.Errorf("expected retryable=%v, got %v", tt.retryable, result)
+			}
+		})
+	}
+}
+
+func TestCalculateBackoff(t *testing.T) {
+	config := RetryConfig{
+		InitialBackoff:    100 * time.Millisecond,
+		MaxBackoff:        1 * time.Second,
+		BackoffMultiplier: 2.0,
+		Jitter:            false,
+	}
+
+	tests := []struct {
+		attempt  int
+		expected time.Duration
+	}{
+		{0, 100 * time.Millisecond},
+		{1, 200 * time.Millisecond},
+		{2, 400 * time.Millisecond},
+		{3, 800 * time.Millisecond},
+		{4, 1 * time.Second}, // Capped at MaxBackoff
+		{5, 1 * time.Second}, // Still capped
+	}
+
+	for _, tt := range tests {
+		result := calculateBackoff(tt.attempt, config)
+		if result != tt.expected {
+			t.Errorf("attempt %d: expected %v, got %v", tt.attempt, tt.expected, result)
+		}
+	}
+}

+ 75 - 0
providers/v2/common/grpc/server/go.mod

@@ -0,0 +1,75 @@
+module github.com/external-secrets/external-secrets/providers/v2/common/grpc/server
+
+go 1.25.1
+
+require (
+	github.com/prometheus/client_golang v1.23.2
+	google.golang.org/grpc v1.79.3
+	sigs.k8s.io/controller-runtime v0.23.3
+)
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+	github.com/go-logr/logr v1.4.3 // indirect
+	github.com/go-openapi/jsonpointer v0.22.5 // indirect
+	github.com/go-openapi/jsonreference v0.21.5 // indirect
+	github.com/go-openapi/swag v0.25.5 // indirect
+	github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
+	github.com/go-openapi/swag/conv v0.25.5 // indirect
+	github.com/go-openapi/swag/fileutils v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonname v0.25.5 // indirect
+	github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
+	github.com/go-openapi/swag/loading v0.25.5 // indirect
+	github.com/go-openapi/swag/mangling v0.25.5 // indirect
+	github.com/go-openapi/swag/netutils v0.25.5 // indirect
+	github.com/go-openapi/swag/stringutils v0.25.5 // indirect
+	github.com/go-openapi/swag/typeutils v0.25.5 // indirect
+	github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.7.1 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/onsi/ginkgo/v2 v2.28.0 // indirect
+	github.com/onsi/gomega v1.39.1 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
+	github.com/prometheus/common v0.67.5 // indirect
+	github.com/prometheus/procfs v0.20.1 // indirect
+	github.com/spf13/pflag v1.0.10 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/net v0.52.0 // indirect
+	golang.org/x/oauth2 v0.36.0 // indirect
+	golang.org/x/sync v0.20.0 // indirect
+	golang.org/x/sys v0.42.0 // indirect
+	golang.org/x/term v0.41.0 // indirect
+	golang.org/x/text v0.35.0 // indirect
+	golang.org/x/time v0.15.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
+	google.golang.org/protobuf v1.36.11 // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	k8s.io/api v0.35.2 // indirect
+	k8s.io/apiextensions-apiserver v0.35.2 // indirect
+	k8s.io/apimachinery v0.35.2 // indirect
+	k8s.io/client-go v0.35.2 // indirect
+	k8s.io/klog/v2 v2.140.0 // indirect
+	k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
+	k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
+	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
+	sigs.k8s.io/randfill v1.0.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
+)

+ 207 - 0
providers/v2/common/grpc/server/go.sum

@@ -0,0 +1,207 @@
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
+github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
+github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
+github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
+github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
+github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
+github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
+github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
+github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
+github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
+github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
+github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
+github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
+github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
+github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
+github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
+github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
+github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
+github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
+github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
+github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
+github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
+github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
+github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
+github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
+github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
+github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
+github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
+github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
+github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
+github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
+github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
+github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
+github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
+github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
+github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
+github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
+github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc=
+github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
+github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
+github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
+gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
+gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
+k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
+k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=
+k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
+k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
+k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
+k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
+k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
+k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
+k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf h1:btPscg4cMql0XdYK2jLsJcNEKmACJz8l+U7geC06FiM=
+k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
+k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
+sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

+ 110 - 0
providers/v2/common/grpc/server/http.go

@@ -0,0 +1,110 @@
+/*
+Copyright © The ESO Authors
+
+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 server provides gRPC server infrastructure for v2 providers.
+package server
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	ctrl "sigs.k8s.io/controller-runtime"
+)
+
+const (
+	// DefaultMetricsPort is the default port for the HTTP metrics server.
+	DefaultMetricsPort = 8081
+	// DefaultMetricsPath is the default path for the metrics endpoint.
+	DefaultMetricsPath = "/metrics"
+)
+
+var metricsLog = ctrl.Log.WithName("metrics-server")
+
+// MetricsServer serves Prometheus metrics via HTTP.
+type MetricsServer struct {
+	server   *http.Server
+	registry *prometheus.Registry
+}
+
+// NewMetricsServer creates a new HTTP metrics server.
+func NewMetricsServer(port int, registry *prometheus.Registry) *MetricsServer {
+	if registry == nil {
+		registry = prometheus.NewRegistry()
+	}
+
+	mux := http.NewServeMux()
+	mux.Handle(DefaultMetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{
+		ErrorHandling: promhttp.ContinueOnError,
+	}))
+
+	// Add health check endpoint
+	mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
+		w.WriteHeader(http.StatusOK)
+		if _, err := w.Write([]byte("ok")); err != nil {
+			metricsLog.Error(err, "failed to write health response")
+		}
+	})
+
+	server := &http.Server{
+		Addr:              fmt.Sprintf(":%d", port),
+		Handler:           mux,
+		ReadHeaderTimeout: 10 * time.Second,
+		ReadTimeout:       30 * time.Second,
+		WriteTimeout:      30 * time.Second,
+		IdleTimeout:       90 * time.Second,
+	}
+
+	return &MetricsServer{
+		server:   server,
+		registry: registry,
+	}
+}
+
+// Start starts the HTTP metrics server.
+func (m *MetricsServer) Start(ctx context.Context) error {
+	metricsLog.Info("Starting metrics server", "addr", m.server.Addr, "path", DefaultMetricsPath)
+
+	// Start server in goroutine
+	errChan := make(chan error, 1)
+	go func() {
+		if err := m.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+			errChan <- fmt.Errorf("metrics server error: %w", err)
+		}
+	}()
+
+	// Wait for context cancellation or server error
+	select {
+	case <-ctx.Done():
+		metricsLog.Info("Shutting down metrics server")
+		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer cancel()
+		if err := m.server.Shutdown(shutdownCtx); err != nil {
+			return fmt.Errorf("metrics server shutdown error: %w", err)
+		}
+		return nil
+	case err := <-errChan:
+		return err
+	}
+}
+
+// GetRegistry returns the Prometheus registry.
+func (m *MetricsServer) GetRegistry() *prometheus.Registry {
+	return m.registry
+}

+ 118 - 0
providers/v2/common/grpc/server/interceptors.go

@@ -0,0 +1,118 @@
+/*
+Copyright © The ESO Authors
+
+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 server
+
+import (
+	"context"
+	"log"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/grpc/tap"
+)
+
+// LoggingUnaryInterceptor logs all RPC calls with connection details.
+func LoggingUnaryInterceptor(verbose bool) grpc.UnaryServerInterceptor {
+	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+		start := time.Now()
+
+		// Get peer information
+		p, ok := peer.FromContext(ctx)
+		var peerAddr string
+		if ok {
+			peerAddr = p.Addr.String()
+			if verbose {
+				LogTLSConnectionInfo(p)
+			}
+		} else {
+			peerAddr = "unknown"
+		}
+
+		log.Printf("[RPC] --> %s from %s", info.FullMethod, peerAddr)
+
+		// Call the handler
+		resp, err := handler(ctx, req)
+
+		duration := time.Since(start)
+		if err != nil {
+			log.Printf("[RPC] <-- %s failed in %v: %v", info.FullMethod, duration, err)
+		} else {
+			log.Printf("[RPC] <-- %s succeeded in %v", info.FullMethod, duration)
+		}
+
+		return resp, err
+	}
+}
+
+// ConnectionTapHandler logs every connection attempt with detailed information.
+func ConnectionTapHandler(ctx context.Context, info *tap.Info) (context.Context, error) {
+	log.Printf("[CONNECTION] New connection attempt: %+v", info)
+	return ctx, nil
+}
+
+// LogTLSConnectionInfo logs detailed TLS handshake information.
+func LogTLSConnectionInfo(p *peer.Peer) {
+	if p == nil {
+		log.Printf("[TLS] No peer information available")
+		return
+	}
+
+	log.Printf("[TLS] Connection from: %s", p.Addr.String())
+
+	authInfo := p.AuthInfo
+	if authInfo == nil {
+		log.Printf("[TLS] WARNING: No auth info - connection may not be using TLS")
+		return
+	}
+
+	tlsInfo, ok := authInfo.(credentials.TLSInfo)
+	if !ok {
+		log.Printf("[TLS] WARNING: Auth info is not TLS type: %T", authInfo)
+		return
+	}
+
+	state := tlsInfo.State
+	log.Printf("[TLS] Handshake complete: version=0x%04x (%s), cipher=0x%04x, resumed=%v",
+		state.Version, TLSVersionName(state.Version), state.CipherSuite, state.DidResume)
+
+	if state.ServerName != "" {
+		log.Printf("[TLS] SNI server name: %s", state.ServerName)
+	}
+
+	// Log peer certificates
+	if len(state.PeerCertificates) > 0 {
+		log.Printf("[TLS] Peer presented %d certificate(s)", len(state.PeerCertificates))
+		for i, cert := range state.PeerCertificates {
+			log.Printf("[TLS]   Cert %d: Subject=%s, Issuer=%s, NotBefore=%s, NotAfter=%s",
+				i, cert.Subject, cert.Issuer, cert.NotBefore, cert.NotAfter)
+			if len(cert.DNSNames) > 0 {
+				log.Printf("[TLS]   Cert %d: DNS names=%v", i, cert.DNSNames)
+			}
+		}
+	} else {
+		log.Printf("[TLS] WARNING: No peer certificates - mTLS may not be working")
+	}
+
+	// Log verified chains
+	if len(state.VerifiedChains) > 0 {
+		log.Printf("[TLS] Successfully verified %d certificate chain(s)", len(state.VerifiedChains))
+	} else {
+		log.Printf("[TLS] WARNING: No verified certificate chains")
+	}
+}

+ 146 - 0
providers/v2/common/grpc/server/metrics.go

@@ -0,0 +1,146 @@
+/*
+Copyright © The ESO Authors
+
+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 server
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"google.golang.org/grpc"
+)
+
+var (
+	// gRPC latency buckets optimized for typical RPC call durations.
+	grpcLatencyBuckets = []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10, 30}
+
+	// gRPC Server Counters.
+	serverRequestsTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "grpc_server_requests_total",
+			Help: "Total number of gRPC server requests",
+		},
+		[]string{"method", "status"},
+	)
+
+	// gRPC Server Histograms.
+	serverRequestDuration = prometheus.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Name:    "grpc_server_request_duration_seconds",
+			Help:    "Duration of gRPC server requests in seconds",
+			Buckets: grpcLatencyBuckets,
+		},
+		[]string{"method"},
+	)
+
+	// gRPC Server Gauges.
+	serverActiveConnections = prometheus.NewGauge(
+		prometheus.GaugeOpts{
+			Name: "grpc_server_active_connections",
+			Help: "Number of active gRPC server connections",
+		},
+	)
+)
+
+// Metrics provides the hooks used by the server interceptors.
+type Metrics interface {
+	RecordRequest(method string, err error, duration time.Duration)
+	IncrementActiveConnections()
+	DecrementActiveConnections()
+}
+
+// defaultServerMetrics implements Metrics using Prometheus.
+type defaultServerMetrics struct{}
+
+// RecordRequest records metrics for a server request.
+func (m *defaultServerMetrics) RecordRequest(method string, err error, duration time.Duration) {
+	status := "success"
+	if err != nil {
+		status = "error"
+	}
+
+	serverRequestDuration.WithLabelValues(method).Observe(duration.Seconds())
+	serverRequestsTotal.WithLabelValues(method, status).Inc()
+}
+
+// IncrementActiveConnections increments the active connections gauge.
+func (m *defaultServerMetrics) IncrementActiveConnections() {
+	serverActiveConnections.Inc()
+}
+
+// DecrementActiveConnections decrements the active connections gauge.
+func (m *defaultServerMetrics) DecrementActiveConnections() {
+	serverActiveConnections.Dec()
+}
+
+// Global instance.
+var serverMetrics Metrics = &defaultServerMetrics{}
+
+// MetricsUnaryInterceptor returns a gRPC unary server interceptor that records metrics.
+func MetricsUnaryInterceptor() grpc.UnaryServerInterceptor {
+	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+		start := time.Now()
+
+		// Call the handler
+		resp, err := handler(ctx, req)
+
+		// Record metrics
+		duration := time.Since(start)
+		serverMetrics.RecordRequest(info.FullMethod, err, duration)
+
+		return resp, err
+	}
+}
+
+// ConnectionCountingStreamServerInterceptor returns a gRPC stream server interceptor that tracks active connections.
+func ConnectionCountingStreamServerInterceptor() grpc.StreamServerInterceptor {
+	return func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
+		serverMetrics.IncrementActiveConnections()
+		defer serverMetrics.DecrementActiveConnections()
+
+		return handler(srv, ss)
+	}
+}
+
+// RegisterMetrics registers all server metrics with Prometheus.
+func RegisterMetrics(registry prometheus.Registerer) error {
+	collectors := []prometheus.Collector{
+		serverRequestsTotal,
+		serverRequestDuration,
+		serverActiveConnections,
+	}
+
+	for _, collector := range collectors {
+		if err := registry.Register(collector); err != nil {
+			// Ignore duplicate registration so shared registries can reuse the collectors.
+			var alreadyRegistered prometheus.AlreadyRegisteredError
+			if errors.As(err, &alreadyRegistered) {
+				continue
+			}
+			return fmt.Errorf("failed to register server metric: %w", err)
+		}
+	}
+
+	return nil
+}
+
+// GetServerMetrics returns the server metrics instance for testing.
+func GetServerMetrics() Metrics {
+	return serverMetrics
+}

+ 120 - 0
providers/v2/common/grpc/server/server.go

@@ -0,0 +1,120 @@
+/*
+Copyright © The ESO Authors
+
+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 server
+
+import (
+	"log"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/keepalive"
+)
+
+// Options holds configuration options for creating a gRPC server.
+type Options struct {
+	EnableTLS bool
+	Verbose   bool
+}
+
+// ServerOptions is kept as a compatibility alias for existing callers.
+//
+//nolint:revive // Compatibility alias for the previous exported API.
+type ServerOptions = Options
+
+// NewGRPCServer creates a new gRPC server with standard configuration.
+func NewGRPCServer(opts Options) (*grpc.Server, error) {
+	var grpcOpts []grpc.ServerOption
+
+	// Add keepalive parameters for better connection diagnostics
+	grpcOpts = append(grpcOpts,
+		grpc.KeepaliveParams(keepalive.ServerParameters{
+			MaxConnectionIdle:     15 * time.Minute,
+			MaxConnectionAge:      30 * time.Minute,
+			MaxConnectionAgeGrace: 5 * time.Minute,
+			Time:                  5 * time.Minute,
+			Timeout:               1 * time.Minute,
+		}),
+		grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
+			MinTime:             5 * time.Second, // Allow pings every 5s (client sends every 10s)
+			PermitWithoutStream: true,
+		}),
+	)
+
+	if opts.Verbose {
+		log.Printf("[CONFIG] Keepalive configured: idle=15m, age=30m, time=5m, timeout=1m, minPing=5s")
+	}
+
+	// Add connection tap handler for verbose mode
+	if opts.Verbose {
+		grpcOpts = append(grpcOpts, grpc.InTapHandle(ConnectionTapHandler))
+		log.Printf("[CONFIG] Connection tap handler enabled")
+	}
+
+	// Add RPC interceptors: metrics and logging
+	grpcOpts = append(grpcOpts, grpc.ChainUnaryInterceptor(
+		MetricsUnaryInterceptor(),
+		LoggingUnaryInterceptor(opts.Verbose),
+	))
+	log.Printf("[CONFIG] RPC metrics and logging interceptors enabled")
+
+	// Configure TLS if enabled
+	if opts.EnableTLS {
+		log.Printf("[TLS] Loading TLS configuration...")
+		tlsConfig, err := LoadTLSConfig(DefaultTLSConfig())
+		if err != nil {
+			return nil, err
+		}
+
+		// Log TLS configuration details
+		log.Printf("[TLS] Configuration loaded successfully")
+		log.Printf("[TLS]   Min TLS version: 0x%04x (%s)", tlsConfig.MinVersion, TLSVersionName(tlsConfig.MinVersion))
+		log.Printf("[TLS]   Max TLS version: 0x%04x (%s)", tlsConfig.MaxVersion, TLSVersionName(tlsConfig.MaxVersion))
+		log.Printf("[TLS]   Client auth required: %v", tlsConfig.ClientAuth == 4) // tls.RequireAndVerifyClientCert
+
+		if tlsConfig.ClientCAs != nil {
+			subjects := tlsConfig.ClientCAs.Subjects()
+			log.Printf("[TLS]   Client CA pool has %d certificate(s)", len(subjects))
+			if opts.Verbose {
+				for i, subject := range subjects {
+					log.Printf("[TLS]     CA %d: %s", i, string(subject))
+				}
+			}
+		} else {
+			log.Printf("[TLS]   WARNING: No client CA pool configured")
+		}
+
+		if len(tlsConfig.Certificates) > 0 {
+			log.Printf("[TLS]   Server has %d certificate(s)", len(tlsConfig.Certificates))
+			for i, cert := range tlsConfig.Certificates {
+				if len(cert.Certificate) > 0 {
+					log.Printf("[TLS]     Cert %d: raw certificate data present", i)
+				}
+			}
+		} else {
+			log.Printf("[TLS]   WARNING: No server certificates configured")
+		}
+
+		grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
+		log.Printf("[TLS] mTLS enabled for provider server")
+	} else {
+		log.Printf("[SECURITY] WARNING: TLS DISABLED - NOT SUITABLE FOR PRODUCTION")
+		log.Printf("[SECURITY] All traffic will be transmitted in PLAINTEXT")
+	}
+
+	return grpc.NewServer(grpcOpts...), nil
+}

+ 110 - 0
providers/v2/common/grpc/server/tls.go

@@ -0,0 +1,110 @@
+/*
+Copyright © The ESO Authors
+
+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 server
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"os"
+)
+
+const (
+	// DefaultCertDir is the default directory for provider TLS assets.
+	DefaultCertDir = "/etc/provider/certs"
+	// DefaultCACertFile is the default CA certificate filename.
+	DefaultCACertFile = "ca.crt"
+	// DefaultCertFile is the default server certificate filename.
+	DefaultCertFile = "tls.crt"
+	// DefaultKeyFile is the default server key filename.
+	DefaultKeyFile = "tls.key"
+)
+
+// TLSConfig holds configuration for provider server TLS.
+type TLSConfig struct {
+	CertDir    string
+	CACertFile string
+	CertFile   string
+	KeyFile    string
+}
+
+// DefaultTLSConfig returns a TLSConfig with default values.
+// Values can be overridden via TLS_CERT_DIR, TLS_CA_CERT_FILE, TLS_CERT_FILE, and TLS_KEY_FILE.
+func DefaultTLSConfig() *TLSConfig {
+	return &TLSConfig{
+		CertDir:    getEnvOrDefault("TLS_CERT_DIR", DefaultCertDir),
+		CACertFile: getEnvOrDefault("TLS_CA_CERT_FILE", DefaultCACertFile),
+		CertFile:   getEnvOrDefault("TLS_CERT_FILE", DefaultCertFile),
+		KeyFile:    getEnvOrDefault("TLS_KEY_FILE", DefaultKeyFile),
+	}
+}
+
+// LoadTLSConfig loads TLS configuration for a provider server.
+// This enables mTLS, requiring and verifying client certificates.
+func LoadTLSConfig(config *TLSConfig) (*tls.Config, error) {
+	// Load server certificate and key
+	certPath := fmt.Sprintf("%s/%s", config.CertDir, config.CertFile)
+	keyPath := fmt.Sprintf("%s/%s", config.CertDir, config.KeyFile)
+
+	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load server certificate: %w", err)
+	}
+
+	// Load CA certificate for client verification
+	caPath := fmt.Sprintf("%s/%s", config.CertDir, config.CACertFile)
+	// #nosec G304 -- TLSConfig paths are explicit operator-provided mount points.
+	caCert, err := os.ReadFile(caPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load CA certificate: %w", err)
+	}
+
+	caCertPool := x509.NewCertPool()
+	if !caCertPool.AppendCertsFromPEM(caCert) {
+		return nil, fmt.Errorf("failed to parse CA certificate")
+	}
+
+	return &tls.Config{
+		Certificates: []tls.Certificate{cert},
+		ClientCAs:    caCertPool,
+		ClientAuth:   tls.RequireAndVerifyClientCert,
+		MinVersion:   tls.VersionTLS12, // TLS 1.2 minimum for compatibility
+	}, nil
+}
+
+// TLSVersionName returns a human-readable name for a TLS version.
+func TLSVersionName(version uint16) string {
+	switch version {
+	case tls.VersionTLS10:
+		return "TLS 1.0"
+	case tls.VersionTLS11:
+		return "TLS 1.1"
+	case tls.VersionTLS12:
+		return "TLS 1.2"
+	case tls.VersionTLS13:
+		return "TLS 1.3"
+	default:
+		return "Unknown"
+	}
+}
+
+func getEnvOrDefault(key, defaultValue string) string {
+	if value := os.Getenv(key); value != "" {
+		return value
+	}
+	return defaultValue
+}

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

@@ -0,0 +1,148 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"net"
+	"strings"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// TLSConfig holds TLS configuration for connecting to a provider.
+type TLSConfig struct {
+	CACert     []byte
+	ClientCert []byte
+	ClientKey  []byte
+	// ServerName is optional. If empty, hostname verification is skipped
+	// but certificate chain validation is still performed (safe for mTLS).
+	ServerName string
+}
+
+// LoadClientTLSConfig loads TLS configuration for connecting to a provider.
+// It fetches the provider's certificate secret from Kubernetes.
+func LoadClientTLSConfig(
+	ctx context.Context,
+	k8sClient ctrlclient.Client,
+	address string,
+	namespace string,
+) (*TLSConfig, error) {
+	// parse the address to get the hostname, it is a url
+	hostname, _, err := net.SplitHostPort(address)
+	if err != nil {
+		hostname = address
+	}
+
+	secretName := "external-secrets-provider-tls"
+
+	// Fetch secret
+	var secret corev1.Secret
+	key := types.NamespacedName{
+		Name:      secretName,
+		Namespace: namespace,
+	}
+
+	if err := k8sClient.Get(ctx, key, &secret); err != nil {
+		return nil, fmt.Errorf("failed to get provider TLS secret %s: %w", secretName, err)
+	}
+
+	// Validate secret data
+	caCert, ok := secret.Data["ca.crt"]
+	if !ok || len(caCert) == 0 {
+		return nil, fmt.Errorf("ca.crt not found or empty in secret %s", secretName)
+	}
+
+	clientCert, ok := secret.Data["client.crt"]
+	if !ok || len(clientCert) == 0 {
+		return nil, fmt.Errorf("client.crt not found or empty in secret %s", secretName)
+	}
+
+	clientKey, ok := secret.Data["client.key"]
+	if !ok || len(clientKey) == 0 {
+		return nil, fmt.Errorf("client.key not found or empty in secret %s", secretName)
+	}
+
+	return &TLSConfig{
+		CACert:     caCert,
+		ClientCert: clientCert,
+		ClientKey:  clientKey,
+		ServerName: hostname,
+	}, 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
+}
+
+// ResolveTLSSecretNamespace determines the namespace of the TLS secret used to connect
+// to a provider. Service DNS addresses win, otherwise the namespace precedence is:
+// auth namespace, resource namespace, providerRef namespace.
+func ResolveTLSSecretNamespace(address, authNamespace, resourceNamespace, providerRefNamespace string) string {
+	fallbackNamespace := authNamespace
+	if fallbackNamespace == "" {
+		fallbackNamespace = resourceNamespace
+	}
+	if fallbackNamespace == "" {
+		fallbackNamespace = providerRefNamespace
+	}
+	return NamespaceFromAddress(address, fallbackNamespace)
+}
+
+// ToGRPCTLSConfig converts TLSConfig to a *tls.Config suitable for gRPC.
+func (t *TLSConfig) ToGRPCTLSConfig() (*tls.Config, error) {
+	// Load client certificate
+	cert, err := tls.X509KeyPair(t.ClientCert, t.ClientKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load client certificate: %w", err)
+	}
+
+	// Load CA certificate
+	caCertPool := x509.NewCertPool()
+	if !caCertPool.AppendCertsFromPEM(t.CACert) {
+		return nil, fmt.Errorf("failed to parse CA certificate")
+	}
+
+	tlsConfig := &tls.Config{
+		Certificates: []tls.Certificate{cert},
+		RootCAs:      caCertPool,
+		MinVersion:   tls.VersionTLS12, // TLS 1.2 minimum for compatibility
+		ServerName:   t.ServerName,
+	}
+
+	return tlsConfig, nil
+}

+ 320 - 0
providers/v2/common/grpc/tls_test.go

@@ -0,0 +1,320 @@
+/*
+Copyright © The ESO Authors
+
+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 grpc
+
+import (
+	"bytes"
+	"context"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"math/big"
+	"net"
+	"testing"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+const testLoopbackAddress = "127.0.0.1"
+
+func TestNamespaceFromAddress(t *testing.T) {
+	testCases := []struct {
+		name     string
+		address  string
+		fallback string
+		expected string
+	}{
+		{
+			name:     "service_dns_with_port",
+			address:  "provider.team-a.svc:9443",
+			fallback: "fallback",
+			expected: "team-a",
+		},
+		{
+			name:     "service_dns_cluster_local",
+			address:  "provider.team-b.svc.cluster.local:9443",
+			fallback: "fallback",
+			expected: "team-b",
+		},
+		{
+			name:     "non_service_address_uses_fallback",
+			address:  testLoopbackAddress + ":9443",
+			fallback: testSourceNamespace,
+			expected: testSourceNamespace,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			if got := NamespaceFromAddress(tc.address, tc.fallback); got != tc.expected {
+				t.Fatalf("expected %q, got %q", tc.expected, got)
+			}
+		})
+	}
+}
+
+func TestResolveTLSSecretNamespace(t *testing.T) {
+	testCases := []struct {
+		name                 string
+		address              string
+		authNamespace        string
+		resourceNamespace    string
+		providerRefNamespace string
+		expected             string
+	}{
+		{
+			name:                 "service_dns_takes_precedence",
+			address:              "provider.service-ns.svc:9443",
+			authNamespace:        "auth-ns",
+			resourceNamespace:    "resource-ns",
+			providerRefNamespace: "provider-ref-ns",
+			expected:             "service-ns",
+		},
+		{
+			name:                 "auth_namespace_used_before_other_fallbacks",
+			address:              testLoopbackAddress + ":9443",
+			authNamespace:        "auth-ns",
+			resourceNamespace:    "resource-ns",
+			providerRefNamespace: "provider-ref-ns",
+			expected:             "auth-ns",
+		},
+		{
+			name:                 "resource_namespace_used_before_provider_ref_namespace",
+			address:              testLoopbackAddress + ":9443",
+			resourceNamespace:    "resource-ns",
+			providerRefNamespace: "provider-ref-ns",
+			expected:             "resource-ns",
+		},
+		{
+			name:                 "provider_ref_namespace_is_final_fallback",
+			address:              testLoopbackAddress + ":9443",
+			providerRefNamespace: "provider-ref-ns",
+			expected:             "provider-ref-ns",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			if got := ResolveTLSSecretNamespace(tc.address, tc.authNamespace, tc.resourceNamespace, tc.providerRefNamespace); got != tc.expected {
+				t.Fatalf("expected %q, got %q", tc.expected, got)
+			}
+		})
+	}
+}
+
+func TestLoadClientTLSConfig(t *testing.T) {
+	t.Run("success", func(t *testing.T) {
+		serverName := testLoopbackAddress
+		_, _, clientCertPEM, clientKeyPEM, caCertPEM := newTLSArtifactsForTest(t, serverName)
+		kubeClient := newTLSSecretClient(t, map[string][]byte{
+			"ca.crt":     caCertPEM,
+			"client.crt": clientCertPEM,
+			"client.key": clientKeyPEM,
+		})
+
+		cfg, err := LoadClientTLSConfig(context.Background(), kubeClient, testLoopbackAddress+":9443", testSourceNamespace)
+		if err != nil {
+			t.Fatalf("LoadClientTLSConfig() error = %v", err)
+		}
+
+		if !bytes.Equal(cfg.CACert, caCertPEM) || !bytes.Equal(cfg.ClientCert, clientCertPEM) || !bytes.Equal(cfg.ClientKey, clientKeyPEM) {
+			t.Fatalf("unexpected tls config: %#v", cfg)
+		}
+		if cfg.ServerName != serverName {
+			t.Fatalf("expected server name %q, got %q", serverName, cfg.ServerName)
+		}
+	})
+
+	t.Run("missing_secret", func(t *testing.T) {
+		scheme := runtime.NewScheme()
+		utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+
+		kubeClient := fakeclient.NewClientBuilder().
+			WithScheme(scheme).
+			Build()
+
+		_, err := LoadClientTLSConfig(context.Background(), kubeClient, testLoopbackAddress+":9443", testSourceNamespace)
+		if err == nil || err.Error() == "" {
+			t.Fatalf("expected missing secret error, got %v", err)
+		}
+	})
+
+	t.Run("missing_secret_data", func(t *testing.T) {
+		kubeClient := newTLSSecretClient(t, map[string][]byte{
+			"ca.crt": []byte("ca"),
+		})
+
+		_, err := LoadClientTLSConfig(context.Background(), kubeClient, testLoopbackAddress+":9443", testSourceNamespace)
+		if err == nil || err.Error() != "client.crt not found or empty in secret external-secrets-provider-tls" {
+			t.Fatalf("unexpected error: %v", err)
+		}
+	})
+}
+
+func TestTLSConfigToGRPCTLSConfig(t *testing.T) {
+	t.Run("success", func(t *testing.T) {
+		serverName := testLoopbackAddress
+		_, _, clientCertPEM, clientKeyPEM, caCertPEM := newTLSArtifactsForTest(t, serverName)
+
+		cfg, err := (&TLSConfig{
+			CACert:     caCertPEM,
+			ClientCert: clientCertPEM,
+			ClientKey:  clientKeyPEM,
+			ServerName: serverName,
+		}).ToGRPCTLSConfig()
+		if err != nil {
+			t.Fatalf("ToGRPCTLSConfig() error = %v", err)
+		}
+
+		if cfg.MinVersion != tls.VersionTLS12 {
+			t.Fatalf("expected min version %v, got %v", tls.VersionTLS12, cfg.MinVersion)
+		}
+		if cfg.ServerName != serverName {
+			t.Fatalf("expected server name %q, got %q", serverName, cfg.ServerName)
+		}
+		if len(cfg.Certificates) != 1 {
+			t.Fatalf("expected one certificate, got %d", len(cfg.Certificates))
+		}
+		if cfg.RootCAs == nil {
+			t.Fatal("expected root CAs to be set")
+		}
+	})
+
+	t.Run("invalid_keypair", func(t *testing.T) {
+		_, err := (&TLSConfig{
+			CACert:     []byte("not-a-ca"),
+			ClientCert: []byte("not-a-cert"),
+			ClientKey:  []byte("not-a-key"),
+		}).ToGRPCTLSConfig()
+		if err == nil {
+			t.Fatal("expected invalid keypair to fail")
+		}
+	})
+
+	t.Run("invalid_ca", func(t *testing.T) {
+		serverName := testLoopbackAddress
+		_, _, clientCertPEM, clientKeyPEM, _ := newTLSArtifactsForTest(t, serverName)
+
+		_, err := (&TLSConfig{
+			CACert:     []byte("not-a-ca"),
+			ClientCert: clientCertPEM,
+			ClientKey:  clientKeyPEM,
+			ServerName: serverName,
+		}).ToGRPCTLSConfig()
+		if err == nil || err.Error() != "failed to parse CA certificate" {
+			t.Fatalf("unexpected error: %v", err)
+		}
+	})
+}
+
+func newTLSSecretClient(t *testing.T, data map[string][]byte) ctrlclient.Client {
+	t.Helper()
+
+	scheme := runtime.NewScheme()
+	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+
+	return fakeclient.NewClientBuilder().
+		WithScheme(scheme).
+		WithObjects(&corev1.Secret{
+			ObjectMeta: metav1ForTLS("external-secrets-provider-tls", testSourceNamespace),
+			Data:       data,
+		}).
+		Build()
+}
+
+func metav1ForTLS(name, namespace string) metav1.ObjectMeta {
+	return metav1.ObjectMeta{
+		Name:      name,
+		Namespace: namespace,
+	}
+}
+
+func newTLSArtifactsForTest(t *testing.T, host string) (serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM []byte) {
+	t.Helper()
+
+	caKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatalf("GenerateKey() error = %v", err)
+	}
+
+	caTemplate := &x509.Certificate{
+		SerialNumber:          big.NewInt(1),
+		Subject:               pkix.Name{CommonName: "grpc-test-ca"},
+		NotBefore:             time.Now().Add(-time.Hour),
+		NotAfter:              time.Now().Add(24 * time.Hour),
+		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+	}
+
+	caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
+	if err != nil {
+		t.Fatalf("CreateCertificate() error = %v", err)
+	}
+	caCert, err := x509.ParseCertificate(caDER)
+	if err != nil {
+		t.Fatalf("ParseCertificate() error = %v", err)
+	}
+
+	serverCertPEM, serverKeyPEM = newSignedTLSCertForTest(t, caCert, caKey, 2, host, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
+	clientCertPEM, clientKeyPEM = newSignedTLSCertForTest(t, caCert, caKey, 3, host, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
+	caCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER})
+	return serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM
+}
+
+func newSignedTLSCertForTest(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey, serial int64, host string, usages []x509.ExtKeyUsage) ([]byte, []byte) {
+	t.Helper()
+
+	key, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatalf("GenerateKey() error = %v", err)
+	}
+
+	template := &x509.Certificate{
+		SerialNumber: big.NewInt(serial),
+		Subject:      pkix.Name{CommonName: host},
+		NotBefore:    time.Now().Add(-time.Hour),
+		NotAfter:     time.Now().Add(24 * time.Hour),
+		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:  usages,
+	}
+
+	if ip := net.ParseIP(host); ip != nil {
+		template.IPAddresses = []net.IP{ip}
+	} else {
+		template.DNSNames = []string{host}
+	}
+
+	der, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
+	if err != nil {
+		t.Fatalf("CreateCertificate() error = %v", err)
+	}
+
+	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
+	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
+	return certPEM, keyPEM
+}

+ 448 - 0
providers/v2/common/proto/generator/generator.pb.go

@@ -0,0 +1,448 @@
+/*
+Copyright © The ESO Authors
+
+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.
+*/
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.35.1
+// 	protoc        v5.29.3
+// source: providers/v2/common/proto/generator/generator.proto
+
+package generator
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// GeneratorRef references a generator custom resource.
+type GeneratorRef struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Specify the apiVersion of the generator resource
+	ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
+	// Specify the Kind of the generator resource
+	Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
+	// Specify the name of the generator resource
+	Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+	// Namespace of the generator resource (optional for cluster-scoped generators)
+	Namespace string `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"`
+}
+
+func (x *GeneratorRef) Reset() {
+	*x = GeneratorRef{}
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[0]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GeneratorRef) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeneratorRef) ProtoMessage() {}
+
+func (x *GeneratorRef) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[0]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeneratorRef.ProtoReflect.Descriptor instead.
+func (*GeneratorRef) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GeneratorRef) GetApiVersion() string {
+	if x != nil {
+		return x.ApiVersion
+	}
+	return ""
+}
+
+func (x *GeneratorRef) GetKind() string {
+	if x != nil {
+		return x.Kind
+	}
+	return ""
+}
+
+func (x *GeneratorRef) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *GeneratorRef) GetNamespace() string {
+	if x != nil {
+		return x.Namespace
+	}
+	return ""
+}
+
+// GenerateRequest contains the information needed to generate secrets
+type GenerateRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the generator resource
+	GeneratorRef *GeneratorRef `protobuf:"bytes,1,opt,name=generator_ref,json=generatorRef,proto3" json:"generator_ref,omitempty"`
+	// Namespace of the ExternalSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,2,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *GenerateRequest) Reset() {
+	*x = GenerateRequest{}
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[1]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GenerateRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GenerateRequest) ProtoMessage() {}
+
+func (x *GenerateRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[1]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GenerateRequest.ProtoReflect.Descriptor instead.
+func (*GenerateRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GenerateRequest) GetGeneratorRef() *GeneratorRef {
+	if x != nil {
+		return x.GeneratorRef
+	}
+	return nil
+}
+
+func (x *GenerateRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// GenerateResponse contains the generated secrets and optional state
+type GenerateResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Map of secret keys to their values
+	Secrets map[string][]byte `protobuf:"bytes,1,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Optional state that can be used during cleanup
+	// This is provider-specific and opaque to the controller
+	State []byte `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"`
+}
+
+func (x *GenerateResponse) Reset() {
+	*x = GenerateResponse{}
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[2]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GenerateResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GenerateResponse) ProtoMessage() {}
+
+func (x *GenerateResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[2]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GenerateResponse.ProtoReflect.Descriptor instead.
+func (*GenerateResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GenerateResponse) GetSecrets() map[string][]byte {
+	if x != nil {
+		return x.Secrets
+	}
+	return nil
+}
+
+func (x *GenerateResponse) GetState() []byte {
+	if x != nil {
+		return x.State
+	}
+	return nil
+}
+
+// CleanupRequest contains the information needed to cleanup resources
+type CleanupRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the generator resource
+	GeneratorRef *GeneratorRef `protobuf:"bytes,1,opt,name=generator_ref,json=generatorRef,proto3" json:"generator_ref,omitempty"`
+	// State returned from the Generate call
+	State []byte `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"`
+	// Namespace of the ExternalSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *CleanupRequest) Reset() {
+	*x = CleanupRequest{}
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *CleanupRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CleanupRequest) ProtoMessage() {}
+
+func (x *CleanupRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[3]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CleanupRequest.ProtoReflect.Descriptor instead.
+func (*CleanupRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *CleanupRequest) GetGeneratorRef() *GeneratorRef {
+	if x != nil {
+		return x.GeneratorRef
+	}
+	return nil
+}
+
+func (x *CleanupRequest) GetState() []byte {
+	if x != nil {
+		return x.State
+	}
+	return nil
+}
+
+func (x *CleanupRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// CleanupResponse is the response from cleanup
+type CleanupResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *CleanupResponse) Reset() {
+	*x = CleanupResponse{}
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[4]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *CleanupResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CleanupResponse) ProtoMessage() {}
+
+func (x *CleanupResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_generator_generator_proto_msgTypes[4]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CleanupResponse.ProtoReflect.Descriptor instead.
+func (*CleanupResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP(), []int{4}
+}
+
+var File_providers_v2_common_proto_generator_generator_proto protoreflect.FileDescriptor
+
+var file_providers_v2_common_proto_generator_generator_proto_rawDesc = []byte{
+	0x0a, 0x33, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x65,
+	0x72, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
+	0x2e, 0x76, 0x31, 0x22, 0x75, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
+	0x52, 0x65, 0x66, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x7d, 0x0a, 0x0f, 0x47, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a,
+	0x0d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66,
+	0x52, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x12, 0x29,
+	0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x10, 0x47, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45,
+	0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x2b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47,
+	0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x65,
+	0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x3a, 0x0a, 0x0c, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
+	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, 0x01, 0x0a, 0x0e, 0x43, 0x6c, 0x65, 0x61,
+	0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x67, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x52, 0x0c, 0x67,
+	0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x73,
+	0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
+	0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x11, 0x0a, 0x0f,
+	0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32,
+	0xa6, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
+	0x65, 0x12, 0x1d, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x1e, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e,
+	0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x46, 0x0a, 0x07, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1c, 0x2e, 0x67, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e,
+	0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x65, 0x6e, 0x65,
+	0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2d,
+	0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67,
+	0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x3b, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
+	0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_providers_v2_common_proto_generator_generator_proto_rawDescOnce sync.Once
+	file_providers_v2_common_proto_generator_generator_proto_rawDescData = file_providers_v2_common_proto_generator_generator_proto_rawDesc
+)
+
+func file_providers_v2_common_proto_generator_generator_proto_rawDescGZIP() []byte {
+	file_providers_v2_common_proto_generator_generator_proto_rawDescOnce.Do(func() {
+		file_providers_v2_common_proto_generator_generator_proto_rawDescData = protoimpl.X.CompressGZIP(file_providers_v2_common_proto_generator_generator_proto_rawDescData)
+	})
+	return file_providers_v2_common_proto_generator_generator_proto_rawDescData
+}
+
+var file_providers_v2_common_proto_generator_generator_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_providers_v2_common_proto_generator_generator_proto_goTypes = []any{
+	(*GeneratorRef)(nil),     // 0: generator.v1.GeneratorRef
+	(*GenerateRequest)(nil),  // 1: generator.v1.GenerateRequest
+	(*GenerateResponse)(nil), // 2: generator.v1.GenerateResponse
+	(*CleanupRequest)(nil),   // 3: generator.v1.CleanupRequest
+	(*CleanupResponse)(nil),  // 4: generator.v1.CleanupResponse
+	nil,                      // 5: generator.v1.GenerateResponse.SecretsEntry
+}
+var file_providers_v2_common_proto_generator_generator_proto_depIdxs = []int32{
+	0, // 0: generator.v1.GenerateRequest.generator_ref:type_name -> generator.v1.GeneratorRef
+	5, // 1: generator.v1.GenerateResponse.secrets:type_name -> generator.v1.GenerateResponse.SecretsEntry
+	0, // 2: generator.v1.CleanupRequest.generator_ref:type_name -> generator.v1.GeneratorRef
+	1, // 3: generator.v1.GeneratorProvider.Generate:input_type -> generator.v1.GenerateRequest
+	3, // 4: generator.v1.GeneratorProvider.Cleanup:input_type -> generator.v1.CleanupRequest
+	2, // 5: generator.v1.GeneratorProvider.Generate:output_type -> generator.v1.GenerateResponse
+	4, // 6: generator.v1.GeneratorProvider.Cleanup:output_type -> generator.v1.CleanupResponse
+	5, // [5:7] is the sub-list for method output_type
+	3, // [3:5] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
+}
+
+func init() { file_providers_v2_common_proto_generator_generator_proto_init() }
+func file_providers_v2_common_proto_generator_generator_proto_init() {
+	if File_providers_v2_common_proto_generator_generator_proto != nil {
+		return
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_providers_v2_common_proto_generator_generator_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_providers_v2_common_proto_generator_generator_proto_goTypes,
+		DependencyIndexes: file_providers_v2_common_proto_generator_generator_proto_depIdxs,
+		MessageInfos:      file_providers_v2_common_proto_generator_generator_proto_msgTypes,
+	}.Build()
+	File_providers_v2_common_proto_generator_generator_proto = out.File
+	file_providers_v2_common_proto_generator_generator_proto_rawDesc = nil
+	file_providers_v2_common_proto_generator_generator_proto_goTypes = nil
+	file_providers_v2_common_proto_generator_generator_proto_depIdxs = nil
+}

+ 71 - 0
providers/v2/common/proto/generator/generator.proto

@@ -0,0 +1,71 @@
+syntax = "proto3";
+
+package generator.v1;
+
+option go_package = "github.com/external-secrets/external-secrets/proto/generator;generator";
+
+// GeneratorProvider is the service interface that generator implementations must satisfy.
+service GeneratorProvider {
+  // Generate creates a new secret or set of secrets.
+  // The returned map is a mapping of secret names to their respective values.
+  // The state is an optional field that can be used to store any generator-specific
+  // state which can be used during the Cleanup phase.
+  rpc Generate(GenerateRequest) returns (GenerateResponse);
+  
+  // Cleanup deletes any resources created during the Generate phase.
+  // Cleanup is idempotent and should not return an error if the resources
+  // have already been deleted.
+  rpc Cleanup(CleanupRequest) returns (CleanupResponse);
+}
+
+// GeneratorRef references a generator custom resource.
+message GeneratorRef {
+  // Specify the apiVersion of the generator resource
+  string api_version = 1;
+  
+  // Specify the Kind of the generator resource
+  string kind = 2;
+  
+  // Specify the name of the generator resource
+  string name = 3;
+  
+  // Namespace of the generator resource (optional for cluster-scoped generators)
+  string namespace = 4;
+}
+
+// GenerateRequest contains the information needed to generate secrets
+message GenerateRequest {
+  // Reference to the generator resource
+  GeneratorRef generator_ref = 1;
+  
+  // Namespace of the ExternalSecret making the request (for validation)
+  string source_namespace = 2;
+}
+
+// GenerateResponse contains the generated secrets and optional state
+message GenerateResponse {
+  // Map of secret keys to their values
+  map<string, bytes> secrets = 1;
+  
+  // Optional state that can be used during cleanup
+  // This is provider-specific and opaque to the controller
+  bytes state = 2;
+}
+
+// CleanupRequest contains the information needed to cleanup resources
+message CleanupRequest {
+  // Reference to the generator resource
+  GeneratorRef generator_ref = 1;
+  
+  // State returned from the Generate call
+  bytes state = 2;
+  
+  // Namespace of the ExternalSecret making the request (for validation)
+  string source_namespace = 3;
+}
+
+// CleanupResponse is the response from cleanup
+message CleanupResponse {
+  // Empty response - errors are communicated via gRPC status
+}
+

+ 192 - 0
providers/v2/common/proto/generator/generator_grpc.pb.go

@@ -0,0 +1,192 @@
+/*
+Copyright © The ESO Authors
+
+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.
+*/
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.5.1
+// - protoc             v5.29.3
+// source: providers/v2/common/proto/generator/generator.proto
+
+package generator
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.64.0 or later.
+const _ = grpc.SupportPackageIsVersion9
+
+const (
+	GeneratorProvider_Generate_FullMethodName = "/generator.v1.GeneratorProvider/Generate"
+	GeneratorProvider_Cleanup_FullMethodName  = "/generator.v1.GeneratorProvider/Cleanup"
+)
+
+// GeneratorProviderClient is the client API for GeneratorProvider service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+//
+// GeneratorProvider is the service interface that generator implementations must satisfy.
+type GeneratorProviderClient interface {
+	// Generate creates a new secret or set of secrets.
+	// The returned map is a mapping of secret names to their respective values.
+	// The state is an optional field that can be used to store any generator-specific
+	// state which can be used during the Cleanup phase.
+	Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error)
+	// Cleanup deletes any resources created during the Generate phase.
+	// Cleanup is idempotent and should not return an error if the resources
+	// have already been deleted.
+	Cleanup(ctx context.Context, in *CleanupRequest, opts ...grpc.CallOption) (*CleanupResponse, error)
+}
+
+type generatorProviderClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewGeneratorProviderClient(cc grpc.ClientConnInterface) GeneratorProviderClient {
+	return &generatorProviderClient{cc}
+}
+
+func (c *generatorProviderClient) Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(GenerateResponse)
+	err := c.cc.Invoke(ctx, GeneratorProvider_Generate_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *generatorProviderClient) Cleanup(ctx context.Context, in *CleanupRequest, opts ...grpc.CallOption) (*CleanupResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(CleanupResponse)
+	err := c.cc.Invoke(ctx, GeneratorProvider_Cleanup_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// GeneratorProviderServer is the server API for GeneratorProvider service.
+// All implementations must embed UnimplementedGeneratorProviderServer
+// for forward compatibility.
+//
+// GeneratorProvider is the service interface that generator implementations must satisfy.
+type GeneratorProviderServer interface {
+	// Generate creates a new secret or set of secrets.
+	// The returned map is a mapping of secret names to their respective values.
+	// The state is an optional field that can be used to store any generator-specific
+	// state which can be used during the Cleanup phase.
+	Generate(context.Context, *GenerateRequest) (*GenerateResponse, error)
+	// Cleanup deletes any resources created during the Generate phase.
+	// Cleanup is idempotent and should not return an error if the resources
+	// have already been deleted.
+	Cleanup(context.Context, *CleanupRequest) (*CleanupResponse, error)
+	mustEmbedUnimplementedGeneratorProviderServer()
+}
+
+// UnimplementedGeneratorProviderServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedGeneratorProviderServer struct{}
+
+func (UnimplementedGeneratorProviderServer) Generate(context.Context, *GenerateRequest) (*GenerateResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Generate not implemented")
+}
+func (UnimplementedGeneratorProviderServer) Cleanup(context.Context, *CleanupRequest) (*CleanupResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Cleanup not implemented")
+}
+func (UnimplementedGeneratorProviderServer) mustEmbedUnimplementedGeneratorProviderServer() {}
+func (UnimplementedGeneratorProviderServer) testEmbeddedByValue()                           {}
+
+// UnsafeGeneratorProviderServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to GeneratorProviderServer will
+// result in compilation errors.
+type UnsafeGeneratorProviderServer interface {
+	mustEmbedUnimplementedGeneratorProviderServer()
+}
+
+func RegisterGeneratorProviderServer(s grpc.ServiceRegistrar, srv GeneratorProviderServer) {
+	// If the following call pancis, it indicates UnimplementedGeneratorProviderServer was
+	// embedded by pointer and is nil.  This will cause panics if an
+	// unimplemented method is ever invoked, so we test this at initialization
+	// time to prevent it from happening at runtime later due to I/O.
+	if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+		t.testEmbeddedByValue()
+	}
+	s.RegisterService(&GeneratorProvider_ServiceDesc, srv)
+}
+
+func _GeneratorProvider_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GenerateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GeneratorProviderServer).Generate(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: GeneratorProvider_Generate_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GeneratorProviderServer).Generate(ctx, req.(*GenerateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _GeneratorProvider_Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CleanupRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GeneratorProviderServer).Cleanup(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: GeneratorProvider_Cleanup_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GeneratorProviderServer).Cleanup(ctx, req.(*CleanupRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// GeneratorProvider_ServiceDesc is the grpc.ServiceDesc for GeneratorProvider service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var GeneratorProvider_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "generator.v1.GeneratorProvider",
+	HandlerType: (*GeneratorProviderServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Generate",
+			Handler:    _GeneratorProvider_Generate_Handler,
+		},
+		{
+			MethodName: "Cleanup",
+			Handler:    _GeneratorProvider_Cleanup_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "providers/v2/common/proto/generator/generator.proto",
+}

+ 15 - 0
providers/v2/common/proto/go.mod

@@ -0,0 +1,15 @@
+module github.com/external-secrets/external-secrets/proto
+
+go 1.25.1
+
+require (
+	google.golang.org/grpc v1.79.3
+	google.golang.org/protobuf v1.36.11
+)
+
+require (
+	golang.org/x/net v0.52.0 // indirect
+	golang.org/x/sys v0.42.0 // indirect
+	golang.org/x/text v0.35.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
+)

+ 38 - 0
providers/v2/common/proto/go.sum

@@ -0,0 +1,38 @@
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=

+ 2137 - 0
providers/v2/common/proto/provider/secretstore.pb.go

@@ -0,0 +1,2137 @@
+/*
+Copyright © The ESO Authors
+
+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.
+*/
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.35.1
+// 	protoc        v5.29.3
+// source: providers/v2/common/proto/provider/secretstore.proto
+
+package provider
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// SecretStoreCapabilities defines what operations a provider supports
+type SecretStoreCapabilities int32
+
+const (
+	// Provider supports read operations only
+	SecretStoreCapabilities_READ_ONLY SecretStoreCapabilities = 0
+	// Provider supports write operations only
+	SecretStoreCapabilities_WRITE_ONLY SecretStoreCapabilities = 1
+	// Provider supports both read and write operations
+	SecretStoreCapabilities_READ_WRITE SecretStoreCapabilities = 2
+)
+
+// Enum value maps for SecretStoreCapabilities.
+var (
+	SecretStoreCapabilities_name = map[int32]string{
+		0: "READ_ONLY",
+		1: "WRITE_ONLY",
+		2: "READ_WRITE",
+	}
+	SecretStoreCapabilities_value = map[string]int32{
+		"READ_ONLY":  0,
+		"WRITE_ONLY": 1,
+		"READ_WRITE": 2,
+	}
+)
+
+func (x SecretStoreCapabilities) Enum() *SecretStoreCapabilities {
+	p := new(SecretStoreCapabilities)
+	*p = x
+	return p
+}
+
+func (x SecretStoreCapabilities) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (SecretStoreCapabilities) Descriptor() protoreflect.EnumDescriptor {
+	return file_providers_v2_common_proto_provider_secretstore_proto_enumTypes[0].Descriptor()
+}
+
+func (SecretStoreCapabilities) Type() protoreflect.EnumType {
+	return &file_providers_v2_common_proto_provider_secretstore_proto_enumTypes[0]
+}
+
+func (x SecretStoreCapabilities) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use SecretStoreCapabilities.Descriptor instead.
+func (SecretStoreCapabilities) EnumDescriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{0}
+}
+
+// ProviderReference identifies the backend/provider configuration object that
+// the runtime should load for this request.
+// This can originate from Provider/ClusterProvider or clean stores
+// (ProviderStore/ClusterProviderStore).
+type ProviderReference struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// APIVersion of the referenced resource.
+	// Example: "provider.external-secrets.io/v2alpha1"
+	ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
+	// Kind of the referenced resource.
+	// Example: "Fake", "Kubernetes", "AWSSecretsManager"
+	Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
+	// Name of the referenced resource.
+	Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+	// Namespace of the referenced resource.
+	// If empty, the resource is cluster-scoped or resolved relative to source_namespace.
+	Namespace string `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"`
+	// Kind of the originating ESO store reference.
+	// Example: "Provider", "ClusterProvider", "ProviderStore", "ClusterProviderStore"
+	StoreRefKind string `protobuf:"bytes,5,opt,name=store_ref_kind,json=storeRefKind,proto3" json:"store_ref_kind,omitempty"`
+}
+
+func (x *ProviderReference) Reset() {
+	*x = ProviderReference{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[0]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *ProviderReference) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ProviderReference) ProtoMessage() {}
+
+func (x *ProviderReference) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[0]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ProviderReference.ProtoReflect.Descriptor instead.
+func (*ProviderReference) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ProviderReference) GetApiVersion() string {
+	if x != nil {
+		return x.ApiVersion
+	}
+	return ""
+}
+
+func (x *ProviderReference) GetKind() string {
+	if x != nil {
+		return x.Kind
+	}
+	return ""
+}
+
+func (x *ProviderReference) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *ProviderReference) GetNamespace() string {
+	if x != nil {
+		return x.Namespace
+	}
+	return ""
+}
+
+func (x *ProviderReference) GetStoreRefKind() string {
+	if x != nil {
+		return x.StoreRefKind
+	}
+	return ""
+}
+
+// CompatibilityStore carries an existing v1 SecretStore/ClusterSecretStore payload over gRPC.
+// This is used by the runtimeRef compatibility flow to avoid Provider/ClusterProvider indirection.
+type CompatibilityStore struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Name of the originating SecretStore/ClusterSecretStore.
+	StoreName string `protobuf:"bytes,1,opt,name=store_name,json=storeName,proto3" json:"store_name,omitempty"`
+	// Namespace of the originating SecretStore. Empty for cluster-scoped stores.
+	StoreNamespace string `protobuf:"bytes,2,opt,name=store_namespace,json=storeNamespace,proto3" json:"store_namespace,omitempty"`
+	// Kind of the originating store. Example: "SecretStore", "ClusterSecretStore".
+	StoreKind string `protobuf:"bytes,3,opt,name=store_kind,json=storeKind,proto3" json:"store_kind,omitempty"`
+	// UID of the originating store.
+	StoreUid string `protobuf:"bytes,4,opt,name=store_uid,json=storeUid,proto3" json:"store_uid,omitempty"`
+	// Generation of the originating store.
+	StoreGeneration int64 `protobuf:"varint,5,opt,name=store_generation,json=storeGeneration,proto3" json:"store_generation,omitempty"`
+	// JSON-serialized esv1.SecretStoreSpec.
+	StoreSpecJson []byte `protobuf:"bytes,6,opt,name=store_spec_json,json=storeSpecJson,proto3" json:"store_spec_json,omitempty"`
+}
+
+func (x *CompatibilityStore) Reset() {
+	*x = CompatibilityStore{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[1]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *CompatibilityStore) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CompatibilityStore) ProtoMessage() {}
+
+func (x *CompatibilityStore) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[1]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CompatibilityStore.ProtoReflect.Descriptor instead.
+func (*CompatibilityStore) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CompatibilityStore) GetStoreName() string {
+	if x != nil {
+		return x.StoreName
+	}
+	return ""
+}
+
+func (x *CompatibilityStore) GetStoreNamespace() string {
+	if x != nil {
+		return x.StoreNamespace
+	}
+	return ""
+}
+
+func (x *CompatibilityStore) GetStoreKind() string {
+	if x != nil {
+		return x.StoreKind
+	}
+	return ""
+}
+
+func (x *CompatibilityStore) GetStoreUid() string {
+	if x != nil {
+		return x.StoreUid
+	}
+	return ""
+}
+
+func (x *CompatibilityStore) GetStoreGeneration() int64 {
+	if x != nil {
+		return x.StoreGeneration
+	}
+	return 0
+}
+
+func (x *CompatibilityStore) GetStoreSpecJson() []byte {
+	if x != nil {
+		return x.StoreSpecJson
+	}
+	return nil
+}
+
+// GetSecretRequest contains the information needed to fetch a secret
+type GetSecretRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The reference to the secret to retrieve
+	RemoteRef *ExternalSecretDataRemoteRef `protobuf:"bytes,1,opt,name=remote_ref,json=remoteRef,proto3" json:"remote_ref,omitempty"`
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,2,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,4,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+	// Namespace of the ExternalSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *GetSecretRequest) Reset() {
+	*x = GetSecretRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[2]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetSecretRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSecretRequest) ProtoMessage() {}
+
+func (x *GetSecretRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[2]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSecretRequest.ProtoReflect.Descriptor instead.
+func (*GetSecretRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GetSecretRequest) GetRemoteRef() *ExternalSecretDataRemoteRef {
+	if x != nil {
+		return x.RemoteRef
+	}
+	return nil
+}
+
+func (x *GetSecretRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *GetSecretRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+func (x *GetSecretRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// GetSecretResponse contains the retrieved secret data
+type GetSecretResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The secret value as bytes
+	Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
+}
+
+func (x *GetSecretResponse) Reset() {
+	*x = GetSecretResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetSecretResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSecretResponse) ProtoMessage() {}
+
+func (x *GetSecretResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[3]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSecretResponse.ProtoReflect.Descriptor instead.
+func (*GetSecretResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GetSecretResponse) GetValue() []byte {
+	if x != nil {
+		return x.Value
+	}
+	return nil
+}
+
+// GetSecretMapRequest contains the information needed to fetch a secret map
+type GetSecretMapRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The reference to the secret to retrieve
+	RemoteRef *ExternalSecretDataRemoteRef `protobuf:"bytes,1,opt,name=remote_ref,json=remoteRef,proto3" json:"remote_ref,omitempty"`
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,2,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,4,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+	// Namespace of the ExternalSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *GetSecretMapRequest) Reset() {
+	*x = GetSecretMapRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[4]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetSecretMapRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSecretMapRequest) ProtoMessage() {}
+
+func (x *GetSecretMapRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[4]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSecretMapRequest.ProtoReflect.Descriptor instead.
+func (*GetSecretMapRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *GetSecretMapRequest) GetRemoteRef() *ExternalSecretDataRemoteRef {
+	if x != nil {
+		return x.RemoteRef
+	}
+	return nil
+}
+
+func (x *GetSecretMapRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *GetSecretMapRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+func (x *GetSecretMapRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// GetSecretMapResponse contains the retrieved key/value pairs
+type GetSecretMapResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Map of secret keys to their values
+	Secrets map[string][]byte `protobuf:"bytes,1,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *GetSecretMapResponse) Reset() {
+	*x = GetSecretMapResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[5]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetSecretMapResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSecretMapResponse) ProtoMessage() {}
+
+func (x *GetSecretMapResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[5]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSecretMapResponse.ProtoReflect.Descriptor instead.
+func (*GetSecretMapResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *GetSecretMapResponse) GetSecrets() map[string][]byte {
+	if x != nil {
+		return x.Secrets
+	}
+	return nil
+}
+
+// ExternalSecretDataRemoteRef defines how to find the secret in the provider
+type ExternalSecretDataRemoteRef struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Key is the identifier for the secret in the provider's system
+	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// Version of the secret (optional, provider-specific)
+	Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
+	// Property to extract from the secret if it contains multiple values
+	Property string `protobuf:"bytes,3,opt,name=property,proto3" json:"property,omitempty"`
+	// DecodingStrategy specifies how to decode the secret value
+	DecodingStrategy string `protobuf:"bytes,4,opt,name=decoding_strategy,json=decodingStrategy,proto3" json:"decoding_strategy,omitempty"`
+	// MetadataPolicy specifies what metadata to fetch
+	MetadataPolicy string `protobuf:"bytes,5,opt,name=metadata_policy,json=metadataPolicy,proto3" json:"metadata_policy,omitempty"`
+}
+
+func (x *ExternalSecretDataRemoteRef) Reset() {
+	*x = ExternalSecretDataRemoteRef{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[6]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *ExternalSecretDataRemoteRef) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExternalSecretDataRemoteRef) ProtoMessage() {}
+
+func (x *ExternalSecretDataRemoteRef) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[6]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ExternalSecretDataRemoteRef.ProtoReflect.Descriptor instead.
+func (*ExternalSecretDataRemoteRef) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *ExternalSecretDataRemoteRef) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *ExternalSecretDataRemoteRef) GetVersion() string {
+	if x != nil {
+		return x.Version
+	}
+	return ""
+}
+
+func (x *ExternalSecretDataRemoteRef) GetProperty() string {
+	if x != nil {
+		return x.Property
+	}
+	return ""
+}
+
+func (x *ExternalSecretDataRemoteRef) GetDecodingStrategy() string {
+	if x != nil {
+		return x.DecodingStrategy
+	}
+	return ""
+}
+
+func (x *ExternalSecretDataRemoteRef) GetMetadataPolicy() string {
+	if x != nil {
+		return x.MetadataPolicy
+	}
+	return ""
+}
+
+// ValidateRequest contains the provider configuration to validate
+type ValidateRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// Namespace of the Provider making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,2,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,3,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+}
+
+func (x *ValidateRequest) Reset() {
+	*x = ValidateRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[7]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *ValidateRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ValidateRequest) ProtoMessage() {}
+
+func (x *ValidateRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[7]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ValidateRequest.ProtoReflect.Descriptor instead.
+func (*ValidateRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *ValidateRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *ValidateRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+func (x *ValidateRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+// ValidateResponse indicates whether the provider configuration is valid
+type ValidateResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Whether the validation was successful
+	Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
+	// Error message if validation failed
+	Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
+	// Warnings that don't prevent validation but should be surfaced to users
+	Warnings []string `protobuf:"bytes,3,rep,name=warnings,proto3" json:"warnings,omitempty"`
+}
+
+func (x *ValidateResponse) Reset() {
+	*x = ValidateResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[8]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *ValidateResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ValidateResponse) ProtoMessage() {}
+
+func (x *ValidateResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[8]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ValidateResponse.ProtoReflect.Descriptor instead.
+func (*ValidateResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *ValidateResponse) GetValid() bool {
+	if x != nil {
+		return x.Valid
+	}
+	return false
+}
+
+func (x *ValidateResponse) GetError() string {
+	if x != nil {
+		return x.Error
+	}
+	return ""
+}
+
+func (x *ValidateResponse) GetWarnings() []string {
+	if x != nil {
+		return x.Warnings
+	}
+	return nil
+}
+
+// Error represents a provider error with additional context
+type Error struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Error code for programmatic handling
+	Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
+	// Human-readable error message
+	Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+	// Whether the error is retryable
+	Retryable bool `protobuf:"varint,3,opt,name=retryable,proto3" json:"retryable,omitempty"`
+	// Additional context as key-value pairs
+	Details map[string]string `protobuf:"bytes,4,rep,name=details,proto3" json:"details,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *Error) Reset() {
+	*x = Error{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[9]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *Error) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Error) ProtoMessage() {}
+
+func (x *Error) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[9]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Error.ProtoReflect.Descriptor instead.
+func (*Error) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *Error) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *Error) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *Error) GetRetryable() bool {
+	if x != nil {
+		return x.Retryable
+	}
+	return false
+}
+
+func (x *Error) GetDetails() map[string]string {
+	if x != nil {
+		return x.Details
+	}
+	return nil
+}
+
+// PushSecretRequest contains the information needed to push a secret
+type PushSecretRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// The Kubernetes secret data as a map of key-value pairs
+	SecretData map[string][]byte `protobuf:"bytes,2,rep,name=secret_data,json=secretData,proto3" json:"secret_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// The push secret data configuration
+	PushSecretData *PushSecretData `protobuf:"bytes,3,opt,name=push_secret_data,json=pushSecretData,proto3" json:"push_secret_data,omitempty"`
+	// Namespace of the PushSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,4,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+	// Kubernetes Secret type from the source object
+	SecretType string `protobuf:"bytes,5,opt,name=secret_type,json=secretType,proto3" json:"secret_type,omitempty"`
+	// Kubernetes Secret labels from the source object
+	SecretLabels map[string]string `protobuf:"bytes,6,rep,name=secret_labels,json=secretLabels,proto3" json:"secret_labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Kubernetes Secret annotations from the source object
+	SecretAnnotations map[string]string `protobuf:"bytes,7,rep,name=secret_annotations,json=secretAnnotations,proto3" json:"secret_annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,8,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+}
+
+func (x *PushSecretRequest) Reset() {
+	*x = PushSecretRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[10]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *PushSecretRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PushSecretRequest) ProtoMessage() {}
+
+func (x *PushSecretRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[10]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PushSecretRequest.ProtoReflect.Descriptor instead.
+func (*PushSecretRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *PushSecretRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *PushSecretRequest) GetSecretData() map[string][]byte {
+	if x != nil {
+		return x.SecretData
+	}
+	return nil
+}
+
+func (x *PushSecretRequest) GetPushSecretData() *PushSecretData {
+	if x != nil {
+		return x.PushSecretData
+	}
+	return nil
+}
+
+func (x *PushSecretRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+func (x *PushSecretRequest) GetSecretType() string {
+	if x != nil {
+		return x.SecretType
+	}
+	return ""
+}
+
+func (x *PushSecretRequest) GetSecretLabels() map[string]string {
+	if x != nil {
+		return x.SecretLabels
+	}
+	return nil
+}
+
+func (x *PushSecretRequest) GetSecretAnnotations() map[string]string {
+	if x != nil {
+		return x.SecretAnnotations
+	}
+	return nil
+}
+
+func (x *PushSecretRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+// PushSecretResponse is the response from pushing a secret
+type PushSecretResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *PushSecretResponse) Reset() {
+	*x = PushSecretResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[11]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *PushSecretResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PushSecretResponse) ProtoMessage() {}
+
+func (x *PushSecretResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[11]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PushSecretResponse.ProtoReflect.Descriptor instead.
+func (*PushSecretResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{11}
+}
+
+// PushSecretData contains the configuration for pushing a secret
+type PushSecretData struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Metadata attached to the secret (provider-specific, stored as JSON)
+	Metadata []byte `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
+	// The key from the Kubernetes secret to push
+	SecretKey string `protobuf:"bytes,2,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
+	// The key name in the remote provider
+	RemoteKey string `protobuf:"bytes,3,opt,name=remote_key,json=remoteKey,proto3" json:"remote_key,omitempty"`
+	// Property name if the remote secret supports nested values
+	Property string `protobuf:"bytes,4,opt,name=property,proto3" json:"property,omitempty"`
+}
+
+func (x *PushSecretData) Reset() {
+	*x = PushSecretData{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[12]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *PushSecretData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PushSecretData) ProtoMessage() {}
+
+func (x *PushSecretData) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[12]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PushSecretData.ProtoReflect.Descriptor instead.
+func (*PushSecretData) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *PushSecretData) GetMetadata() []byte {
+	if x != nil {
+		return x.Metadata
+	}
+	return nil
+}
+
+func (x *PushSecretData) GetSecretKey() string {
+	if x != nil {
+		return x.SecretKey
+	}
+	return ""
+}
+
+func (x *PushSecretData) GetRemoteKey() string {
+	if x != nil {
+		return x.RemoteKey
+	}
+	return ""
+}
+
+func (x *PushSecretData) GetProperty() string {
+	if x != nil {
+		return x.Property
+	}
+	return ""
+}
+
+// DeleteSecretRequest contains the information needed to delete a secret
+type DeleteSecretRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// The reference to the secret to delete
+	RemoteRef *PushSecretRemoteRef `protobuf:"bytes,2,opt,name=remote_ref,json=remoteRef,proto3" json:"remote_ref,omitempty"`
+	// Namespace of the PushSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,4,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+}
+
+func (x *DeleteSecretRequest) Reset() {
+	*x = DeleteSecretRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[13]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteSecretRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteSecretRequest) ProtoMessage() {}
+
+func (x *DeleteSecretRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[13]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteSecretRequest.ProtoReflect.Descriptor instead.
+func (*DeleteSecretRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *DeleteSecretRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *DeleteSecretRequest) GetRemoteRef() *PushSecretRemoteRef {
+	if x != nil {
+		return x.RemoteRef
+	}
+	return nil
+}
+
+func (x *DeleteSecretRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+func (x *DeleteSecretRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+// DeleteSecretResponse is the response from deleting a secret
+type DeleteSecretResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *DeleteSecretResponse) Reset() {
+	*x = DeleteSecretResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[14]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteSecretResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteSecretResponse) ProtoMessage() {}
+
+func (x *DeleteSecretResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[14]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteSecretResponse.ProtoReflect.Descriptor instead.
+func (*DeleteSecretResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{14}
+}
+
+// PushSecretRemoteRef defines the remote reference for push/delete operations
+type PushSecretRemoteRef struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The key name in the remote provider
+	RemoteKey string `protobuf:"bytes,1,opt,name=remote_key,json=remoteKey,proto3" json:"remote_key,omitempty"`
+	// Property name if the remote secret supports nested values
+	Property string `protobuf:"bytes,2,opt,name=property,proto3" json:"property,omitempty"`
+}
+
+func (x *PushSecretRemoteRef) Reset() {
+	*x = PushSecretRemoteRef{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[15]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *PushSecretRemoteRef) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PushSecretRemoteRef) ProtoMessage() {}
+
+func (x *PushSecretRemoteRef) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[15]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PushSecretRemoteRef.ProtoReflect.Descriptor instead.
+func (*PushSecretRemoteRef) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *PushSecretRemoteRef) GetRemoteKey() string {
+	if x != nil {
+		return x.RemoteKey
+	}
+	return ""
+}
+
+func (x *PushSecretRemoteRef) GetProperty() string {
+	if x != nil {
+		return x.Property
+	}
+	return ""
+}
+
+// SecretExistsRequest contains the information needed to check if a secret exists
+type SecretExistsRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// The reference to the secret to check
+	RemoteRef *PushSecretRemoteRef `protobuf:"bytes,2,opt,name=remote_ref,json=remoteRef,proto3" json:"remote_ref,omitempty"`
+	// Namespace of the PushSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,4,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+}
+
+func (x *SecretExistsRequest) Reset() {
+	*x = SecretExistsRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[16]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *SecretExistsRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SecretExistsRequest) ProtoMessage() {}
+
+func (x *SecretExistsRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[16]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SecretExistsRequest.ProtoReflect.Descriptor instead.
+func (*SecretExistsRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{16}
+}
+
+func (x *SecretExistsRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *SecretExistsRequest) GetRemoteRef() *PushSecretRemoteRef {
+	if x != nil {
+		return x.RemoteRef
+	}
+	return nil
+}
+
+func (x *SecretExistsRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+func (x *SecretExistsRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+// SecretExistsResponse contains the result of checking if a secret exists
+type SecretExistsResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Whether the secret exists
+	Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"`
+}
+
+func (x *SecretExistsResponse) Reset() {
+	*x = SecretExistsResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[17]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *SecretExistsResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SecretExistsResponse) ProtoMessage() {}
+
+func (x *SecretExistsResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[17]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SecretExistsResponse.ProtoReflect.Descriptor instead.
+func (*SecretExistsResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *SecretExistsResponse) GetExists() bool {
+	if x != nil {
+		return x.Exists
+	}
+	return false
+}
+
+// GetAllSecretsRequest contains the information needed to retrieve multiple secrets
+type GetAllSecretsRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+	CompatibilityStore *CompatibilityStore `protobuf:"bytes,4,opt,name=compatibility_store,json=compatibilityStore,proto3" json:"compatibility_store,omitempty"`
+	// The find criteria to use for selecting secrets
+	Find *ExternalSecretFind `protobuf:"bytes,2,opt,name=find,proto3" json:"find,omitempty"`
+	// Namespace of the ExternalSecret making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,3,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *GetAllSecretsRequest) Reset() {
+	*x = GetAllSecretsRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[18]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetAllSecretsRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetAllSecretsRequest) ProtoMessage() {}
+
+func (x *GetAllSecretsRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[18]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetAllSecretsRequest.ProtoReflect.Descriptor instead.
+func (*GetAllSecretsRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{18}
+}
+
+func (x *GetAllSecretsRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *GetAllSecretsRequest) GetCompatibilityStore() *CompatibilityStore {
+	if x != nil {
+		return x.CompatibilityStore
+	}
+	return nil
+}
+
+func (x *GetAllSecretsRequest) GetFind() *ExternalSecretFind {
+	if x != nil {
+		return x.Find
+	}
+	return nil
+}
+
+func (x *GetAllSecretsRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// GetAllSecretsResponse contains the retrieved secrets
+type GetAllSecretsResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Map of secret keys to their values
+	Secrets map[string][]byte `protobuf:"bytes,1,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *GetAllSecretsResponse) Reset() {
+	*x = GetAllSecretsResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[19]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GetAllSecretsResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetAllSecretsResponse) ProtoMessage() {}
+
+func (x *GetAllSecretsResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[19]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetAllSecretsResponse.ProtoReflect.Descriptor instead.
+func (*GetAllSecretsResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *GetAllSecretsResponse) GetSecrets() map[string][]byte {
+	if x != nil {
+		return x.Secrets
+	}
+	return nil
+}
+
+// ExternalSecretFind defines criteria for finding multiple secrets
+type ExternalSecretFind struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// A root path to start the find operations
+	Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+	// Finds secrets based on the name
+	Name *FindName `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+	// Find secrets based on tags
+	Tags map[string]string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Conversion strategy for secret keys
+	ConversionStrategy string `protobuf:"bytes,4,opt,name=conversion_strategy,json=conversionStrategy,proto3" json:"conversion_strategy,omitempty"`
+	// Decoding strategy for secret values
+	DecodingStrategy string `protobuf:"bytes,5,opt,name=decoding_strategy,json=decodingStrategy,proto3" json:"decoding_strategy,omitempty"`
+}
+
+func (x *ExternalSecretFind) Reset() {
+	*x = ExternalSecretFind{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[20]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *ExternalSecretFind) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExternalSecretFind) ProtoMessage() {}
+
+func (x *ExternalSecretFind) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[20]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ExternalSecretFind.ProtoReflect.Descriptor instead.
+func (*ExternalSecretFind) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *ExternalSecretFind) GetPath() string {
+	if x != nil {
+		return x.Path
+	}
+	return ""
+}
+
+func (x *ExternalSecretFind) GetName() *FindName {
+	if x != nil {
+		return x.Name
+	}
+	return nil
+}
+
+func (x *ExternalSecretFind) GetTags() map[string]string {
+	if x != nil {
+		return x.Tags
+	}
+	return nil
+}
+
+func (x *ExternalSecretFind) GetConversionStrategy() string {
+	if x != nil {
+		return x.ConversionStrategy
+	}
+	return ""
+}
+
+func (x *ExternalSecretFind) GetDecodingStrategy() string {
+	if x != nil {
+		return x.DecodingStrategy
+	}
+	return ""
+}
+
+// FindName defines name-based criteria for finding secrets
+type FindName struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Regular expression to match secret names
+	Regexp string `protobuf:"bytes,1,opt,name=regexp,proto3" json:"regexp,omitempty"`
+}
+
+func (x *FindName) Reset() {
+	*x = FindName{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[21]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *FindName) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FindName) ProtoMessage() {}
+
+func (x *FindName) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[21]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FindName.ProtoReflect.Descriptor instead.
+func (*FindName) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{21}
+}
+
+func (x *FindName) GetRegexp() string {
+	if x != nil {
+		return x.Regexp
+	}
+	return ""
+}
+
+// CapabilitiesRequest requests the capabilities of the provider
+type CapabilitiesRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Reference to the provider configuration CRD
+	ProviderRef *ProviderReference `protobuf:"bytes,1,opt,name=provider_ref,json=providerRef,proto3" json:"provider_ref,omitempty"`
+	// Namespace of the Provider making the request (for validation)
+	SourceNamespace string `protobuf:"bytes,2,opt,name=source_namespace,json=sourceNamespace,proto3" json:"source_namespace,omitempty"`
+}
+
+func (x *CapabilitiesRequest) Reset() {
+	*x = CapabilitiesRequest{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[22]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *CapabilitiesRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CapabilitiesRequest) ProtoMessage() {}
+
+func (x *CapabilitiesRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[22]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CapabilitiesRequest.ProtoReflect.Descriptor instead.
+func (*CapabilitiesRequest) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *CapabilitiesRequest) GetProviderRef() *ProviderReference {
+	if x != nil {
+		return x.ProviderRef
+	}
+	return nil
+}
+
+func (x *CapabilitiesRequest) GetSourceNamespace() string {
+	if x != nil {
+		return x.SourceNamespace
+	}
+	return ""
+}
+
+// CapabilitiesResponse contains the provider's capabilities
+type CapabilitiesResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The capabilities of the provider
+	Capabilities SecretStoreCapabilities `protobuf:"varint,1,opt,name=capabilities,proto3,enum=provider.v1.SecretStoreCapabilities" json:"capabilities,omitempty"`
+}
+
+func (x *CapabilitiesResponse) Reset() {
+	*x = CapabilitiesResponse{}
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[23]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *CapabilitiesResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CapabilitiesResponse) ProtoMessage() {}
+
+func (x *CapabilitiesResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_providers_v2_common_proto_provider_secretstore_proto_msgTypes[23]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CapabilitiesResponse.ProtoReflect.Descriptor instead.
+func (*CapabilitiesResponse) Descriptor() ([]byte, []int) {
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP(), []int{23}
+}
+
+func (x *CapabilitiesResponse) GetCapabilities() SecretStoreCapabilities {
+	if x != nil {
+		return x.Capabilities
+	}
+	return SecretStoreCapabilities_READ_ONLY
+}
+
+var File_providers_v2_common_proto_provider_secretstore_proto protoreflect.FileDescriptor
+
+var file_providers_v2_common_proto_provider_secretstore_proto_rawDesc = []byte{
+	0x0a, 0x34, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x64, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x22, 0xa0, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
+	0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69,
+	0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
+	0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69,
+	0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x6b, 0x69,
+	0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52,
+	0x65, 0x66, 0x4b, 0x69, 0x6e, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x61,
+	0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a,
+	0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f,
+	0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6b,
+	0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65,
+	0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x75, 0x69,
+	0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x55, 0x69,
+	0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f,
+	0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63,
+	0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x9b, 0x02, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x0a, 0x72, 0x65, 0x6d,
+	0x6f, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65,
+	0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65,
+	0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52,
+	0x65, 0x66, 0x12, 0x41, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72,
+	0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52,
+	0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
+	0x65, 0x72, 0x52, 0x65, 0x66, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69,
+	0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74,
+	0x6f, 0x72, 0x65, 0x52, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69,
+	0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x22, 0x29, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9e, 0x02,
+	0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f,
+	0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+	0x52, 0x65, 0x66, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x12, 0x41,
+	0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e,
+	0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72,
+	0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65,
+	0x66, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69,
+	0x74, 0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d,
+	0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52,
+	0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74,
+	0x6f, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x9c,
+	0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
+	0x4d, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
+	0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbb, 0x01,
+	0x0a, 0x1b, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
+	0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x12, 0x10, 0x0a,
+	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+	0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f,
+	0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f,
+	0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e,
+	0x67, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x10, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70,
+	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0xd1, 0x01, 0x0a, 0x0f,
+	0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x41, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65,
+	0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52,
+	0x65, 0x66, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x50, 0x0a,
+	0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69,
+	0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x12, 0x63, 0x6f, 0x6d,
+	0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22,
+	0x5a, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72,
+	0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12,
+	0x1a, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xca, 0x01, 0x0a, 0x05,
+	0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73,
+	0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c,
+	0x65, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x3a, 0x0a, 0x0c,
+	0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8f, 0x06, 0x0a, 0x11, 0x50, 0x75, 0x73,
+	0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41,
+	0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e,
+	0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72,
+	0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65,
+	0x66, 0x12, 0x4f, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, 0x74,
+	0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61,
+	0x74, 0x61, 0x12, 0x45, 0x0a, 0x10, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x70, 0x75, 0x73, 0x68, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f,
+	0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x65, 0x63,
+	0x72, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c,
+	0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x64, 0x0a, 0x12,
+	0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x41,
+	0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
+	0x11, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c,
+	0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f,
+	0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65,
+	0x52, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53,
+	0x74, 0x6f, 0x72, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61,
+	0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
+	0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4c, 0x61, 0x62,
+	0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x41, 0x6e,
+	0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
+	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x14, 0x0a, 0x12, 0x50, 0x75,
+	0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0x86, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44,
+	0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1d,
+	0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a,
+	0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x22, 0x96, 0x02, 0x0a, 0x13, 0x44, 0x65,
+	0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x41, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65,
+	0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
+	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65,
+	0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
+	0x72, 0x52, 0x65, 0x66, 0x12, 0x3f, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x72,
+	0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f,
+	0x74, 0x65, 0x52, 0x65, 0x66, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74,
+	0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
+	0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x12,
+	0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f,
+	0x72, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x0a, 0x13, 0x50, 0x75,
+	0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65,
+	0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79,
+	0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x22, 0x96, 0x02, 0x0a,
+	0x13, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
+	0x5f, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
+	0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x12, 0x3f, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74,
+	0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65,
+	0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x52, 0x09, 0x72,
+	0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x66, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69,
+	0x6c, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43,
+	0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72,
+	0x65, 0x52, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
+	0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x2e, 0x0a, 0x14, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x45,
+	0x78, 0x69, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
+	0x06, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65,
+	0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x8b, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41,
+	0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e,
+	0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72,
+	0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65,
+	0x66, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69,
+	0x74, 0x79, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d,
+	0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52,
+	0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x74,
+	0x6f, 0x72, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x66, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
+	0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69,
+	0x6e, 0x64, 0x52, 0x04, 0x66, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x22, 0x9e, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65,
+	0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a,
+	0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
+	0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
+	0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x3a, 0x02, 0x38, 0x01, 0x22, 0xa9, 0x02, 0x0a, 0x12, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70,
+	0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
+	0x29, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x64,
+	0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x61,
+	0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e,
+	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69,
+	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65,
+	0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x53,
+	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45,
+	0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
+	0x22, 0x22, 0x0a, 0x08, 0x46, 0x69, 0x6e, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
+	0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65,
+	0x67, 0x65, 0x78, 0x70, 0x22, 0x83, 0x01, 0x0a, 0x13, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
+	0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x0c,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
+	0x63, 0x65, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x12,
+	0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x60, 0x0a, 0x14, 0x43, 0x61,
+	0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x48, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69,
+	0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x53, 0x74, 0x6f,
+	0x72, 0x65, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0c,
+	0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2a, 0x48, 0x0a, 0x17,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x70, 0x61, 0x62,
+	0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x45, 0x41, 0x44, 0x5f,
+	0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f,
+	0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x57,
+	0x52, 0x49, 0x54, 0x45, 0x10, 0x02, 0x32, 0xa5, 0x05, 0x0a, 0x13, 0x53, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x4a,
+	0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63,
+	0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x47, 0x65,
+	0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72,
+	0x65, 0x74, 0x4d, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65,
+	0x63, 0x72, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x4d, 0x0a, 0x0a, 0x50, 0x75, 0x73, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1e, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68,
+	0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53,
+	0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x20,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
+	0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44,
+	0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x45, 0x78, 0x69,
+	0x73, 0x74, 0x73, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76,
+	0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41,
+	0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65,
+	0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c,
+	0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x47, 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64,
+	0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x72, 0x6f,
+	0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x43, 0x61, 0x70,
+	0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69,
+	0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69,
+	0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x46,
+	0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x78, 0x74,
+	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x65, 0x78,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x3b, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_providers_v2_common_proto_provider_secretstore_proto_rawDescOnce sync.Once
+	file_providers_v2_common_proto_provider_secretstore_proto_rawDescData = file_providers_v2_common_proto_provider_secretstore_proto_rawDesc
+)
+
+func file_providers_v2_common_proto_provider_secretstore_proto_rawDescGZIP() []byte {
+	file_providers_v2_common_proto_provider_secretstore_proto_rawDescOnce.Do(func() {
+		file_providers_v2_common_proto_provider_secretstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_providers_v2_common_proto_provider_secretstore_proto_rawDescData)
+	})
+	return file_providers_v2_common_proto_provider_secretstore_proto_rawDescData
+}
+
+var file_providers_v2_common_proto_provider_secretstore_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_providers_v2_common_proto_provider_secretstore_proto_msgTypes = make([]protoimpl.MessageInfo, 31)
+var file_providers_v2_common_proto_provider_secretstore_proto_goTypes = []any{
+	(SecretStoreCapabilities)(0),        // 0: provider.v1.SecretStoreCapabilities
+	(*ProviderReference)(nil),           // 1: provider.v1.ProviderReference
+	(*CompatibilityStore)(nil),          // 2: provider.v1.CompatibilityStore
+	(*GetSecretRequest)(nil),            // 3: provider.v1.GetSecretRequest
+	(*GetSecretResponse)(nil),           // 4: provider.v1.GetSecretResponse
+	(*GetSecretMapRequest)(nil),         // 5: provider.v1.GetSecretMapRequest
+	(*GetSecretMapResponse)(nil),        // 6: provider.v1.GetSecretMapResponse
+	(*ExternalSecretDataRemoteRef)(nil), // 7: provider.v1.ExternalSecretDataRemoteRef
+	(*ValidateRequest)(nil),             // 8: provider.v1.ValidateRequest
+	(*ValidateResponse)(nil),            // 9: provider.v1.ValidateResponse
+	(*Error)(nil),                       // 10: provider.v1.Error
+	(*PushSecretRequest)(nil),           // 11: provider.v1.PushSecretRequest
+	(*PushSecretResponse)(nil),          // 12: provider.v1.PushSecretResponse
+	(*PushSecretData)(nil),              // 13: provider.v1.PushSecretData
+	(*DeleteSecretRequest)(nil),         // 14: provider.v1.DeleteSecretRequest
+	(*DeleteSecretResponse)(nil),        // 15: provider.v1.DeleteSecretResponse
+	(*PushSecretRemoteRef)(nil),         // 16: provider.v1.PushSecretRemoteRef
+	(*SecretExistsRequest)(nil),         // 17: provider.v1.SecretExistsRequest
+	(*SecretExistsResponse)(nil),        // 18: provider.v1.SecretExistsResponse
+	(*GetAllSecretsRequest)(nil),        // 19: provider.v1.GetAllSecretsRequest
+	(*GetAllSecretsResponse)(nil),       // 20: provider.v1.GetAllSecretsResponse
+	(*ExternalSecretFind)(nil),          // 21: provider.v1.ExternalSecretFind
+	(*FindName)(nil),                    // 22: provider.v1.FindName
+	(*CapabilitiesRequest)(nil),         // 23: provider.v1.CapabilitiesRequest
+	(*CapabilitiesResponse)(nil),        // 24: provider.v1.CapabilitiesResponse
+	nil,                                 // 25: provider.v1.GetSecretMapResponse.SecretsEntry
+	nil,                                 // 26: provider.v1.Error.DetailsEntry
+	nil,                                 // 27: provider.v1.PushSecretRequest.SecretDataEntry
+	nil,                                 // 28: provider.v1.PushSecretRequest.SecretLabelsEntry
+	nil,                                 // 29: provider.v1.PushSecretRequest.SecretAnnotationsEntry
+	nil,                                 // 30: provider.v1.GetAllSecretsResponse.SecretsEntry
+	nil,                                 // 31: provider.v1.ExternalSecretFind.TagsEntry
+}
+var file_providers_v2_common_proto_provider_secretstore_proto_depIdxs = []int32{
+	7,  // 0: provider.v1.GetSecretRequest.remote_ref:type_name -> provider.v1.ExternalSecretDataRemoteRef
+	1,  // 1: provider.v1.GetSecretRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	2,  // 2: provider.v1.GetSecretRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	7,  // 3: provider.v1.GetSecretMapRequest.remote_ref:type_name -> provider.v1.ExternalSecretDataRemoteRef
+	1,  // 4: provider.v1.GetSecretMapRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	2,  // 5: provider.v1.GetSecretMapRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	25, // 6: provider.v1.GetSecretMapResponse.secrets:type_name -> provider.v1.GetSecretMapResponse.SecretsEntry
+	1,  // 7: provider.v1.ValidateRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	2,  // 8: provider.v1.ValidateRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	26, // 9: provider.v1.Error.details:type_name -> provider.v1.Error.DetailsEntry
+	1,  // 10: provider.v1.PushSecretRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	27, // 11: provider.v1.PushSecretRequest.secret_data:type_name -> provider.v1.PushSecretRequest.SecretDataEntry
+	13, // 12: provider.v1.PushSecretRequest.push_secret_data:type_name -> provider.v1.PushSecretData
+	28, // 13: provider.v1.PushSecretRequest.secret_labels:type_name -> provider.v1.PushSecretRequest.SecretLabelsEntry
+	29, // 14: provider.v1.PushSecretRequest.secret_annotations:type_name -> provider.v1.PushSecretRequest.SecretAnnotationsEntry
+	2,  // 15: provider.v1.PushSecretRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	1,  // 16: provider.v1.DeleteSecretRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	16, // 17: provider.v1.DeleteSecretRequest.remote_ref:type_name -> provider.v1.PushSecretRemoteRef
+	2,  // 18: provider.v1.DeleteSecretRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	1,  // 19: provider.v1.SecretExistsRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	16, // 20: provider.v1.SecretExistsRequest.remote_ref:type_name -> provider.v1.PushSecretRemoteRef
+	2,  // 21: provider.v1.SecretExistsRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	1,  // 22: provider.v1.GetAllSecretsRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	2,  // 23: provider.v1.GetAllSecretsRequest.compatibility_store:type_name -> provider.v1.CompatibilityStore
+	21, // 24: provider.v1.GetAllSecretsRequest.find:type_name -> provider.v1.ExternalSecretFind
+	30, // 25: provider.v1.GetAllSecretsResponse.secrets:type_name -> provider.v1.GetAllSecretsResponse.SecretsEntry
+	22, // 26: provider.v1.ExternalSecretFind.name:type_name -> provider.v1.FindName
+	31, // 27: provider.v1.ExternalSecretFind.tags:type_name -> provider.v1.ExternalSecretFind.TagsEntry
+	1,  // 28: provider.v1.CapabilitiesRequest.provider_ref:type_name -> provider.v1.ProviderReference
+	0,  // 29: provider.v1.CapabilitiesResponse.capabilities:type_name -> provider.v1.SecretStoreCapabilities
+	3,  // 30: provider.v1.SecretStoreProvider.GetSecret:input_type -> provider.v1.GetSecretRequest
+	5,  // 31: provider.v1.SecretStoreProvider.GetSecretMap:input_type -> provider.v1.GetSecretMapRequest
+	11, // 32: provider.v1.SecretStoreProvider.PushSecret:input_type -> provider.v1.PushSecretRequest
+	14, // 33: provider.v1.SecretStoreProvider.DeleteSecret:input_type -> provider.v1.DeleteSecretRequest
+	17, // 34: provider.v1.SecretStoreProvider.SecretExists:input_type -> provider.v1.SecretExistsRequest
+	19, // 35: provider.v1.SecretStoreProvider.GetAllSecrets:input_type -> provider.v1.GetAllSecretsRequest
+	8,  // 36: provider.v1.SecretStoreProvider.Validate:input_type -> provider.v1.ValidateRequest
+	23, // 37: provider.v1.SecretStoreProvider.Capabilities:input_type -> provider.v1.CapabilitiesRequest
+	4,  // 38: provider.v1.SecretStoreProvider.GetSecret:output_type -> provider.v1.GetSecretResponse
+	6,  // 39: provider.v1.SecretStoreProvider.GetSecretMap:output_type -> provider.v1.GetSecretMapResponse
+	12, // 40: provider.v1.SecretStoreProvider.PushSecret:output_type -> provider.v1.PushSecretResponse
+	15, // 41: provider.v1.SecretStoreProvider.DeleteSecret:output_type -> provider.v1.DeleteSecretResponse
+	18, // 42: provider.v1.SecretStoreProvider.SecretExists:output_type -> provider.v1.SecretExistsResponse
+	20, // 43: provider.v1.SecretStoreProvider.GetAllSecrets:output_type -> provider.v1.GetAllSecretsResponse
+	9,  // 44: provider.v1.SecretStoreProvider.Validate:output_type -> provider.v1.ValidateResponse
+	24, // 45: provider.v1.SecretStoreProvider.Capabilities:output_type -> provider.v1.CapabilitiesResponse
+	38, // [38:46] is the sub-list for method output_type
+	30, // [30:38] is the sub-list for method input_type
+	30, // [30:30] is the sub-list for extension type_name
+	30, // [30:30] is the sub-list for extension extendee
+	0,  // [0:30] is the sub-list for field type_name
+}
+
+func init() { file_providers_v2_common_proto_provider_secretstore_proto_init() }
+func file_providers_v2_common_proto_provider_secretstore_proto_init() {
+	if File_providers_v2_common_proto_provider_secretstore_proto != nil {
+		return
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_providers_v2_common_proto_provider_secretstore_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   31,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_providers_v2_common_proto_provider_secretstore_proto_goTypes,
+		DependencyIndexes: file_providers_v2_common_proto_provider_secretstore_proto_depIdxs,
+		EnumInfos:         file_providers_v2_common_proto_provider_secretstore_proto_enumTypes,
+		MessageInfos:      file_providers_v2_common_proto_provider_secretstore_proto_msgTypes,
+	}.Build()
+	File_providers_v2_common_proto_provider_secretstore_proto = out.File
+	file_providers_v2_common_proto_provider_secretstore_proto_rawDesc = nil
+	file_providers_v2_common_proto_provider_secretstore_proto_goTypes = nil
+	file_providers_v2_common_proto_provider_secretstore_proto_depIdxs = nil
+}

+ 347 - 0
providers/v2/common/proto/provider/secretstore.proto

@@ -0,0 +1,347 @@
+syntax = "proto3";
+
+package provider.v1;
+
+option go_package = "github.com/external-secrets/external-secrets/proto/provider;provider";
+
+// SecretStoreProvider is the service interface that provider implementations must satisfy.
+service SecretStoreProvider {
+  // GetSecret retrieves a single secret from the provider
+  rpc GetSecret(GetSecretRequest) returns (GetSecretResponse);
+
+  // GetSecretMap retrieves multiple key/value pairs from a single provider object
+  rpc GetSecretMap(GetSecretMapRequest) returns (GetSecretMapResponse);
+  
+  // PushSecret writes a secret to the provider
+  rpc PushSecret(PushSecretRequest) returns (PushSecretResponse);
+  
+  // DeleteSecret deletes a secret from the provider
+  rpc DeleteSecret(DeleteSecretRequest) returns (DeleteSecretResponse);
+  
+  // SecretExists checks if a secret exists in the provider
+  rpc SecretExists(SecretExistsRequest) returns (SecretExistsResponse);
+  
+  // GetAllSecrets retrieves multiple secrets based on find criteria
+  rpc GetAllSecrets(GetAllSecretsRequest) returns (GetAllSecretsResponse);
+  
+  // Validate checks if the provider configuration is valid and the provider can be reached
+  rpc Validate(ValidateRequest) returns (ValidateResponse);
+  
+  // Capabilities returns what operations the provider supports
+  rpc Capabilities(CapabilitiesRequest) returns (CapabilitiesResponse);
+}
+
+// ProviderReference identifies the backend/provider configuration object that
+// the runtime should load for this request.
+// This can originate from Provider/ClusterProvider or clean stores
+// (ProviderStore/ClusterProviderStore).
+message ProviderReference {
+  // APIVersion of the referenced resource.
+  // Example: "provider.external-secrets.io/v2alpha1"
+  string api_version = 1;
+  
+  // Kind of the referenced resource.
+  // Example: "Fake", "Kubernetes", "AWSSecretsManager"
+  string kind = 2;
+  
+  // Name of the referenced resource.
+  string name = 3;
+  
+  // Namespace of the referenced resource.
+  // If empty, the resource is cluster-scoped or resolved relative to source_namespace.
+  string namespace = 4;
+
+  // Kind of the originating ESO store reference.
+  // Example: "Provider", "ClusterProvider", "ProviderStore", "ClusterProviderStore"
+  string store_ref_kind = 5;
+}
+
+// CompatibilityStore carries an existing v1 SecretStore/ClusterSecretStore payload over gRPC.
+// This is used by the runtimeRef compatibility flow to avoid Provider/ClusterProvider indirection.
+message CompatibilityStore {
+  // Name of the originating SecretStore/ClusterSecretStore.
+  string store_name = 1;
+
+  // Namespace of the originating SecretStore. Empty for cluster-scoped stores.
+  string store_namespace = 2;
+
+  // Kind of the originating store. Example: "SecretStore", "ClusterSecretStore".
+  string store_kind = 3;
+
+  // UID of the originating store.
+  string store_uid = 4;
+
+  // Generation of the originating store.
+  int64 store_generation = 5;
+
+  // JSON-serialized esv1.SecretStoreSpec.
+  bytes store_spec_json = 6;
+}
+
+// GetSecretRequest contains the information needed to fetch a secret
+message GetSecretRequest {
+  // The reference to the secret to retrieve
+  ExternalSecretDataRemoteRef remote_ref = 1;
+  
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 2;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 4;
+  
+  // Namespace of the ExternalSecret making the request (for validation)
+  string source_namespace = 3;
+}
+
+// GetSecretResponse contains the retrieved secret data
+message GetSecretResponse {
+  // The secret value as bytes
+  bytes value = 1;
+}
+
+// GetSecretMapRequest contains the information needed to fetch a secret map
+message GetSecretMapRequest {
+  // The reference to the secret to retrieve
+  ExternalSecretDataRemoteRef remote_ref = 1;
+
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 2;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 4;
+
+  // Namespace of the ExternalSecret making the request (for validation)
+  string source_namespace = 3;
+}
+
+// GetSecretMapResponse contains the retrieved key/value pairs
+message GetSecretMapResponse {
+  // Map of secret keys to their values
+  map<string, bytes> secrets = 1;
+}
+
+// ExternalSecretDataRemoteRef defines how to find the secret in the provider
+message ExternalSecretDataRemoteRef {
+  // Key is the identifier for the secret in the provider's system
+  string key = 1;
+  
+  // Version of the secret (optional, provider-specific)
+  string version = 2;
+  
+  // Property to extract from the secret if it contains multiple values
+  string property = 3;
+  
+  // DecodingStrategy specifies how to decode the secret value
+  string decoding_strategy = 4;
+  
+  // MetadataPolicy specifies what metadata to fetch
+  string metadata_policy = 5;
+}
+
+// ValidateRequest contains the provider configuration to validate
+message ValidateRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+  
+  // Namespace of the Provider making the request (for validation)
+  string source_namespace = 2;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 3;
+}
+
+// ValidateResponse indicates whether the provider configuration is valid
+message ValidateResponse {
+  // Whether the validation was successful
+  bool valid = 1;
+  
+  // Error message if validation failed
+  string error = 2;
+  
+  // Warnings that don't prevent validation but should be surfaced to users
+  repeated string warnings = 3;
+}
+
+// Error represents a provider error with additional context
+message Error {
+  // Error code for programmatic handling
+  string code = 1;
+  
+  // Human-readable error message
+  string message = 2;
+  
+  // Whether the error is retryable
+  bool retryable = 3;
+  
+  // Additional context as key-value pairs
+  map<string, string> details = 4;
+}
+
+// PushSecretRequest contains the information needed to push a secret
+message PushSecretRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+  
+  // The Kubernetes secret data as a map of key-value pairs
+  map<string, bytes> secret_data = 2;
+  
+  // The push secret data configuration
+  PushSecretData push_secret_data = 3;
+  
+  // Namespace of the PushSecret making the request (for validation)
+  string source_namespace = 4;
+
+  // Kubernetes Secret type from the source object
+  string secret_type = 5;
+
+  // Kubernetes Secret labels from the source object
+  map<string, string> secret_labels = 6;
+
+  // Kubernetes Secret annotations from the source object
+  map<string, string> secret_annotations = 7;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 8;
+}
+
+// PushSecretResponse is the response from pushing a secret
+message PushSecretResponse {
+  // Empty response - errors are communicated via gRPC status
+}
+
+// PushSecretData contains the configuration for pushing a secret
+message PushSecretData {
+  // Metadata attached to the secret (provider-specific, stored as JSON)
+  bytes metadata = 1;
+  
+  // The key from the Kubernetes secret to push
+  string secret_key = 2;
+  
+  // The key name in the remote provider
+  string remote_key = 3;
+  
+  // Property name if the remote secret supports nested values
+  string property = 4;
+}
+
+// DeleteSecretRequest contains the information needed to delete a secret
+message DeleteSecretRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+  
+  // The reference to the secret to delete
+  PushSecretRemoteRef remote_ref = 2;
+  
+  // Namespace of the PushSecret making the request (for validation)
+  string source_namespace = 3;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 4;
+}
+
+// DeleteSecretResponse is the response from deleting a secret
+message DeleteSecretResponse {
+  // Empty response - errors are communicated via gRPC status
+}
+
+// PushSecretRemoteRef defines the remote reference for push/delete operations
+message PushSecretRemoteRef {
+  // The key name in the remote provider
+  string remote_key = 1;
+  
+  // Property name if the remote secret supports nested values
+  string property = 2;
+}
+
+// SecretExistsRequest contains the information needed to check if a secret exists
+message SecretExistsRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+  
+  // The reference to the secret to check
+  PushSecretRemoteRef remote_ref = 2;
+  
+  // Namespace of the PushSecret making the request (for validation)
+  string source_namespace = 3;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 4;
+}
+
+// SecretExistsResponse contains the result of checking if a secret exists
+message SecretExistsResponse {
+  // Whether the secret exists
+  bool exists = 1;
+}
+
+// GetAllSecretsRequest contains the information needed to retrieve multiple secrets
+message GetAllSecretsRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+
+  // Optional: Full v1 SecretStore payload for runtimeRef compatibility.
+  CompatibilityStore compatibility_store = 4;
+  
+  // The find criteria to use for selecting secrets
+  ExternalSecretFind find = 2;
+  
+  // Namespace of the ExternalSecret making the request (for validation)
+  string source_namespace = 3;
+}
+
+// GetAllSecretsResponse contains the retrieved secrets
+message GetAllSecretsResponse {
+  // Map of secret keys to their values
+  map<string, bytes> secrets = 1;
+}
+
+// ExternalSecretFind defines criteria for finding multiple secrets
+message ExternalSecretFind {
+  // A root path to start the find operations
+  string path = 1;
+  
+  // Finds secrets based on the name
+  FindName name = 2;
+  
+  // Find secrets based on tags
+  map<string, string> tags = 3;
+  
+  // Conversion strategy for secret keys
+  string conversion_strategy = 4;
+  
+  // Decoding strategy for secret values
+  string decoding_strategy = 5;
+}
+
+// FindName defines name-based criteria for finding secrets
+message FindName {
+  // Regular expression to match secret names
+  string regexp = 1;
+}
+
+// CapabilitiesRequest requests the capabilities of the provider
+message CapabilitiesRequest {
+  // Reference to the provider configuration CRD
+  ProviderReference provider_ref = 1;
+  
+  // Namespace of the Provider making the request (for validation)
+  string source_namespace = 2;
+}
+
+// CapabilitiesResponse contains the provider's capabilities
+message CapabilitiesResponse {
+  // The capabilities of the provider
+  SecretStoreCapabilities capabilities = 1;
+}
+
+// SecretStoreCapabilities defines what operations a provider supports
+enum SecretStoreCapabilities {
+  // Provider supports read operations only
+  READ_ONLY = 0;
+  
+  // Provider supports write operations only
+  WRITE_ONLY = 1;
+  
+  // Provider supports both read and write operations
+  READ_WRITE = 2;
+}

+ 422 - 0
providers/v2/common/proto/provider/secretstore_grpc.pb.go

@@ -0,0 +1,422 @@
+/*
+Copyright © The ESO Authors
+
+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.
+*/
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.5.1
+// - protoc             v5.29.3
+// source: providers/v2/common/proto/provider/secretstore.proto
+
+package provider
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.64.0 or later.
+const _ = grpc.SupportPackageIsVersion9
+
+const (
+	SecretStoreProvider_GetSecret_FullMethodName     = "/provider.v1.SecretStoreProvider/GetSecret"
+	SecretStoreProvider_GetSecretMap_FullMethodName  = "/provider.v1.SecretStoreProvider/GetSecretMap"
+	SecretStoreProvider_PushSecret_FullMethodName    = "/provider.v1.SecretStoreProvider/PushSecret"
+	SecretStoreProvider_DeleteSecret_FullMethodName  = "/provider.v1.SecretStoreProvider/DeleteSecret"
+	SecretStoreProvider_SecretExists_FullMethodName  = "/provider.v1.SecretStoreProvider/SecretExists"
+	SecretStoreProvider_GetAllSecrets_FullMethodName = "/provider.v1.SecretStoreProvider/GetAllSecrets"
+	SecretStoreProvider_Validate_FullMethodName      = "/provider.v1.SecretStoreProvider/Validate"
+	SecretStoreProvider_Capabilities_FullMethodName  = "/provider.v1.SecretStoreProvider/Capabilities"
+)
+
+// SecretStoreProviderClient is the client API for SecretStoreProvider service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+//
+// SecretStoreProvider is the service interface that provider implementations must satisfy.
+type SecretStoreProviderClient interface {
+	// GetSecret retrieves a single secret from the provider
+	GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error)
+	// GetSecretMap retrieves multiple key/value pairs from a single provider object
+	GetSecretMap(ctx context.Context, in *GetSecretMapRequest, opts ...grpc.CallOption) (*GetSecretMapResponse, error)
+	// PushSecret writes a secret to the provider
+	PushSecret(ctx context.Context, in *PushSecretRequest, opts ...grpc.CallOption) (*PushSecretResponse, error)
+	// DeleteSecret deletes a secret from the provider
+	DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error)
+	// SecretExists checks if a secret exists in the provider
+	SecretExists(ctx context.Context, in *SecretExistsRequest, opts ...grpc.CallOption) (*SecretExistsResponse, error)
+	// GetAllSecrets retrieves multiple secrets based on find criteria
+	GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error)
+	// Validate checks if the provider configuration is valid and the provider can be reached
+	Validate(ctx context.Context, in *ValidateRequest, opts ...grpc.CallOption) (*ValidateResponse, error)
+	// Capabilities returns what operations the provider supports
+	Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error)
+}
+
+type secretStoreProviderClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewSecretStoreProviderClient(cc grpc.ClientConnInterface) SecretStoreProviderClient {
+	return &secretStoreProviderClient{cc}
+}
+
+func (c *secretStoreProviderClient) GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(GetSecretResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_GetSecret_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) GetSecretMap(ctx context.Context, in *GetSecretMapRequest, opts ...grpc.CallOption) (*GetSecretMapResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(GetSecretMapResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_GetSecretMap_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) PushSecret(ctx context.Context, in *PushSecretRequest, opts ...grpc.CallOption) (*PushSecretResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(PushSecretResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_PushSecret_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(DeleteSecretResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_DeleteSecret_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) SecretExists(ctx context.Context, in *SecretExistsRequest, opts ...grpc.CallOption) (*SecretExistsResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(SecretExistsResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_SecretExists_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(GetAllSecretsResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_GetAllSecrets_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) Validate(ctx context.Context, in *ValidateRequest, opts ...grpc.CallOption) (*ValidateResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(ValidateResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_Validate_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *secretStoreProviderClient) Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error) {
+	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+	out := new(CapabilitiesResponse)
+	err := c.cc.Invoke(ctx, SecretStoreProvider_Capabilities_FullMethodName, in, out, cOpts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// SecretStoreProviderServer is the server API for SecretStoreProvider service.
+// All implementations must embed UnimplementedSecretStoreProviderServer
+// for forward compatibility.
+//
+// SecretStoreProvider is the service interface that provider implementations must satisfy.
+type SecretStoreProviderServer interface {
+	// GetSecret retrieves a single secret from the provider
+	GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error)
+	// GetSecretMap retrieves multiple key/value pairs from a single provider object
+	GetSecretMap(context.Context, *GetSecretMapRequest) (*GetSecretMapResponse, error)
+	// PushSecret writes a secret to the provider
+	PushSecret(context.Context, *PushSecretRequest) (*PushSecretResponse, error)
+	// DeleteSecret deletes a secret from the provider
+	DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error)
+	// SecretExists checks if a secret exists in the provider
+	SecretExists(context.Context, *SecretExistsRequest) (*SecretExistsResponse, error)
+	// GetAllSecrets retrieves multiple secrets based on find criteria
+	GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error)
+	// Validate checks if the provider configuration is valid and the provider can be reached
+	Validate(context.Context, *ValidateRequest) (*ValidateResponse, error)
+	// Capabilities returns what operations the provider supports
+	Capabilities(context.Context, *CapabilitiesRequest) (*CapabilitiesResponse, error)
+	mustEmbedUnimplementedSecretStoreProviderServer()
+}
+
+// UnimplementedSecretStoreProviderServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedSecretStoreProviderServer struct{}
+
+func (UnimplementedSecretStoreProviderServer) GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetSecret not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) GetSecretMap(context.Context, *GetSecretMapRequest) (*GetSecretMapResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetSecretMap not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) PushSecret(context.Context, *PushSecretRequest) (*PushSecretResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PushSecret not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DeleteSecret not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) SecretExists(context.Context, *SecretExistsRequest) (*SecretExistsResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SecretExists not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetAllSecrets not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) Validate(context.Context, *ValidateRequest) (*ValidateResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Validate not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) Capabilities(context.Context, *CapabilitiesRequest) (*CapabilitiesResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Capabilities not implemented")
+}
+func (UnimplementedSecretStoreProviderServer) mustEmbedUnimplementedSecretStoreProviderServer() {}
+func (UnimplementedSecretStoreProviderServer) testEmbeddedByValue()                             {}
+
+// UnsafeSecretStoreProviderServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to SecretStoreProviderServer will
+// result in compilation errors.
+type UnsafeSecretStoreProviderServer interface {
+	mustEmbedUnimplementedSecretStoreProviderServer()
+}
+
+func RegisterSecretStoreProviderServer(s grpc.ServiceRegistrar, srv SecretStoreProviderServer) {
+	// If the following call pancis, it indicates UnimplementedSecretStoreProviderServer was
+	// embedded by pointer and is nil.  This will cause panics if an
+	// unimplemented method is ever invoked, so we test this at initialization
+	// time to prevent it from happening at runtime later due to I/O.
+	if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+		t.testEmbeddedByValue()
+	}
+	s.RegisterService(&SecretStoreProvider_ServiceDesc, srv)
+}
+
+func _SecretStoreProvider_GetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetSecretRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).GetSecret(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_GetSecret_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).GetSecret(ctx, req.(*GetSecretRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_GetSecretMap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetSecretMapRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).GetSecretMap(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_GetSecretMap_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).GetSecretMap(ctx, req.(*GetSecretMapRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_PushSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PushSecretRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).PushSecret(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_PushSecret_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).PushSecret(ctx, req.(*PushSecretRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_DeleteSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DeleteSecretRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).DeleteSecret(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_DeleteSecret_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).DeleteSecret(ctx, req.(*DeleteSecretRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_SecretExists_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SecretExistsRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).SecretExists(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_SecretExists_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).SecretExists(ctx, req.(*SecretExistsRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_GetAllSecrets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetAllSecretsRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).GetAllSecrets(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_GetAllSecrets_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).GetAllSecrets(ctx, req.(*GetAllSecretsRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_Validate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ValidateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).Validate(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_Validate_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).Validate(ctx, req.(*ValidateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _SecretStoreProvider_Capabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CapabilitiesRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SecretStoreProviderServer).Capabilities(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: SecretStoreProvider_Capabilities_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SecretStoreProviderServer).Capabilities(ctx, req.(*CapabilitiesRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// SecretStoreProvider_ServiceDesc is the grpc.ServiceDesc for SecretStoreProvider service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var SecretStoreProvider_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "provider.v1.SecretStoreProvider",
+	HandlerType: (*SecretStoreProviderServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "GetSecret",
+			Handler:    _SecretStoreProvider_GetSecret_Handler,
+		},
+		{
+			MethodName: "GetSecretMap",
+			Handler:    _SecretStoreProvider_GetSecretMap_Handler,
+		},
+		{
+			MethodName: "PushSecret",
+			Handler:    _SecretStoreProvider_PushSecret_Handler,
+		},
+		{
+			MethodName: "DeleteSecret",
+			Handler:    _SecretStoreProvider_DeleteSecret_Handler,
+		},
+		{
+			MethodName: "SecretExists",
+			Handler:    _SecretStoreProvider_SecretExists_Handler,
+		},
+		{
+			MethodName: "GetAllSecrets",
+			Handler:    _SecretStoreProvider_GetAllSecrets_Handler,
+		},
+		{
+			MethodName: "Validate",
+			Handler:    _SecretStoreProvider_Validate_Handler,
+		},
+		{
+			MethodName: "Capabilities",
+			Handler:    _SecretStoreProvider_Capabilities_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "providers/v2/common/proto/provider/secretstore.proto",
+}

+ 94 - 0
providers/v2/common/types.go

@@ -0,0 +1,94 @@
+/*
+Copyright © The ESO Authors
+
+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 common provides the v2 provider interface for out-of-tree providers communicating via gRPC.
+package common
+
+import (
+	"context"
+
+	corev1 "k8s.io/api/core/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	pb "github.com/external-secrets/external-secrets/proto/provider"
+)
+
+// Provider is the interface that v2 out-of-tree providers must satisfy.
+// Unlike v1 providers which are compiled into ESO, v2 providers run as separate services
+// and communicate with ESO via gRPC.
+type Provider interface {
+	// GetSecret retrieves a single secret from the provider.
+	// If the secret doesn't exist, it should return an error.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// If both are present, compatibilityStore takes precedence for read operations.
+	// sourceNamespace is the namespace of the ExternalSecret.
+	GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) ([]byte, error)
+
+	// GetSecretMap retrieves multiple key/value pairs from a single secret object.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// If both are present, compatibilityStore takes precedence for read operations.
+	// sourceNamespace is the namespace of the ExternalSecret.
+	GetSecretMap(
+		ctx context.Context,
+		ref esv1.ExternalSecretDataRemoteRef,
+		providerRef *pb.ProviderReference,
+		compatibilityStore *pb.CompatibilityStore,
+		sourceNamespace string,
+	) (map[string][]byte, error)
+
+	// GetAllSecrets retrieves multiple secrets based on find criteria.
+	// Returns a map of secret names to their byte values.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// If both are present, compatibilityStore takes precedence for read operations.
+	// sourceNamespace is the namespace of the ExternalSecret.
+	GetAllSecrets(ctx context.Context, find esv1.ExternalSecretFind, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) (map[string][]byte, error)
+
+	// PushSecret writes a secret to the provider.
+	// The secret is the Kubernetes Secret object to push, and pushSecretData contains the push configuration.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// sourceNamespace is the namespace of the PushSecret.
+	PushSecret(
+		ctx context.Context,
+		secret *corev1.Secret,
+		pushSecretData *pb.PushSecretData,
+		providerRef *pb.ProviderReference,
+		compatibilityStore *pb.CompatibilityStore,
+		sourceNamespace string,
+	) error
+
+	// DeleteSecret deletes a secret from the provider.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// sourceNamespace is the namespace of the PushSecret.
+	DeleteSecret(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) error
+
+	// SecretExists checks if a secret exists in the provider.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// sourceNamespace is the namespace of the PushSecret.
+	SecretExists(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) (bool, error)
+
+	// Validate checks if the provider is properly configured and can communicate with the backend.
+	// This is called by the SecretStore controller during reconciliation.
+	// At least one of providerRef or compatibilityStore must be provided.
+	// sourceNamespace is the namespace of the requesting store.
+	Validate(ctx context.Context, providerRef *pb.ProviderReference, compatibilityStore *pb.CompatibilityStore, sourceNamespace string) error
+
+	// Capabilities returns what operations the provider supports (ReadOnly, WriteOnly, ReadWrite).
+	// The providerRef references the provider configuration CRD, and sourceNamespace is the namespace of the Provider.
+	Capabilities(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) (pb.SecretStoreCapabilities, error)
+
+	// Close cleans up any resources held by the provider client.
+	Close(ctx context.Context) error
+}