Browse Source

feat: implement validating webhook

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 4 years ago
parent
commit
8fc4484cc6
52 changed files with 1903 additions and 402 deletions
  1. 1 1
      apis/externalsecrets/v1alpha1/externalsecret_conversion.go
  2. 1 1
      apis/externalsecrets/v1alpha1/externalsecret_conversion_test.go
  3. 6 2
      apis/externalsecrets/v1beta1/externalsecret_types.go
  4. 18 7
      apis/externalsecrets/v1beta1/provider.go
  5. 11 11
      apis/externalsecrets/v1beta1/provider_schema.go
  6. 32 31
      apis/externalsecrets/v1beta1/provider_schema_test.go
  7. 62 0
      apis/externalsecrets/v1beta1/secretstore_validator.go
  8. 2 0
      apis/externalsecrets/v1beta1/secretstore_webhook.go
  9. 25 2
      apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
  10. 43 23
      cmd/certcontroller.go
  11. 4 1
      cmd/root.go
  12. 42 7
      cmd/webhook.go
  13. 4 0
      config/crds/bases/external-secrets.io_externalsecrets.yaml
  14. 6 0
      deploy/charts/external-secrets/templates/cert-controller-deployment.yaml
  15. 25 0
      deploy/charts/external-secrets/templates/cert-controller-rbac.yaml
  16. 41 0
      deploy/charts/external-secrets/templates/validatingwebhook.yaml
  17. 3 2
      deploy/charts/external-secrets/templates/webhook-deployment.yaml
  18. 1 0
      deploy/charts/external-secrets/templates/webhook-secret.yaml
  19. 1 0
      deploy/charts/external-secrets/templates/webhook-service.yaml
  20. 4 0
      deploy/crds/bundle.yaml
  21. 55 55
      pkg/controllers/crds/common_test.go
  22. 99 81
      pkg/controllers/crds/crds_controller.go
  23. 7 70
      pkg/controllers/crds/crds_controller_test.go
  24. 6 13
      pkg/controllers/crds/suite_test.go
  25. 6 8
      pkg/controllers/externalsecret/externalsecret_controller.go
  26. 11 11
      pkg/controllers/externalsecret/externalsecret_controller_test.go
  27. 1 2
      pkg/controllers/secretstore/common.go
  28. 100 0
      pkg/controllers/webhookconfig/suite_test.go
  29. 186 0
      pkg/controllers/webhookconfig/webhookconfig.go
  30. 324 0
      pkg/controllers/webhookconfig/webhookconfig_test.go
  31. 7 5
      pkg/provider/akeyless/akeyless.go
  32. 6 4
      pkg/provider/alibaba/kms.go
  33. 53 5
      pkg/provider/aws/provider.go
  34. 197 0
      pkg/provider/aws/provider_test.go
  35. 4 0
      pkg/provider/aws/secretsmanager/fake/fake.go
  36. 41 5
      pkg/provider/azure/keyvault/keyvault.go
  37. 64 3
      pkg/provider/azure/keyvault/keyvault_test.go
  38. 6 4
      pkg/provider/fake/fake.go
  39. 37 4
      pkg/provider/gcp/secretmanager/secretsmanager.go
  40. 64 0
      pkg/provider/gcp/secretmanager/secretsmanager_test.go
  41. 6 4
      pkg/provider/gitlab/gitlab.go
  42. 6 4
      pkg/provider/ibm/provider.go
  43. 7 5
      pkg/provider/kubernetes/kubernetes.go
  44. 6 4
      pkg/provider/oracle/oracle.go
  45. 10 8
      pkg/provider/testing/fake/fake.go
  46. 76 6
      pkg/provider/vault/vault.go
  47. 140 0
      pkg/provider/vault/vault_test.go
  48. 6 4
      pkg/provider/webhook/webhook.go
  49. 2 3
      pkg/provider/webhook/webhook_test.go
  50. 6 4
      pkg/provider/yandex/lockbox/lockbox.go
  51. 1 2
      pkg/provider/yandex/lockbox/lockbox_test.go
  52. 31 0
      pkg/utils/utils.go

+ 1 - 1
apis/externalsecrets/v1alpha1/externalsecret_conversion.go

@@ -28,7 +28,7 @@ func (alpha *ExternalSecret) ConvertTo(betaRaw conversion.Hub) error {
 	v1beta1DataFrom := make([]esv1beta1.ExternalSecretDataFromRemoteRef, 0)
 	v1beta1DataFrom := make([]esv1beta1.ExternalSecretDataFromRemoteRef, 0)
 	for _, v1alpha1RemoteRef := range alpha.Spec.DataFrom {
 	for _, v1alpha1RemoteRef := range alpha.Spec.DataFrom {
 		v1beta1RemoteRef := esv1beta1.ExternalSecretDataFromRemoteRef{
 		v1beta1RemoteRef := esv1beta1.ExternalSecretDataFromRemoteRef{
-			Extract: esv1beta1.ExternalSecretDataRemoteRef{
+			Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 				Key:      v1alpha1RemoteRef.Key,
 				Key:      v1alpha1RemoteRef.Key,
 				Property: v1alpha1RemoteRef.Property,
 				Property: v1alpha1RemoteRef.Property,
 				Version:  v1alpha1RemoteRef.Version,
 				Version:  v1alpha1RemoteRef.Version,

+ 1 - 1
apis/externalsecrets/v1alpha1/externalsecret_conversion_test.go

@@ -183,7 +183,7 @@ func newExternalSecretV1Beta1() *esv1beta1.ExternalSecret {
 			},
 			},
 			DataFrom: []esv1beta1.ExternalSecretDataFromRemoteRef{
 			DataFrom: []esv1beta1.ExternalSecretDataFromRemoteRef{
 				{
 				{
-					Extract: esv1beta1.ExternalSecretDataRemoteRef{
+					Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 						Key:      "key",
 						Key:      "key",
 						Property: "property",
 						Property: "property",
 						Version:  "version",
 						Version:  "version",

+ 6 - 2
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -161,15 +161,19 @@ type ExternalSecretDataRemoteRef struct {
 	Property string `json:"property,omitempty"`
 	Property string `json:"property,omitempty"`
 }
 }
 
 
+// +kubebuilder:validation:MinProperties=1
+// +kubebuilder:validation:MaxProperties=1
 type ExternalSecretDataFromRemoteRef struct {
 type ExternalSecretDataFromRemoteRef struct {
 	// Used to extract multiple key/value pairs from one secret
 	// Used to extract multiple key/value pairs from one secret
 	// +optional
 	// +optional
-	Extract ExternalSecretDataRemoteRef `json:"extract,omitempty"`
+	Extract *ExternalSecretDataRemoteRef `json:"extract,omitempty"`
 	// Used to find secrets based on tags or regular expressions
 	// Used to find secrets based on tags or regular expressions
 	// +optional
 	// +optional
-	Find ExternalSecretFind `json:"find,omitempty"`
+	Find *ExternalSecretFind `json:"find,omitempty"`
 }
 }
 
 
+// +kubebuilder:validation:MinProperties=1
+// +kubebuilder:validation:MaxProperties=1
 type ExternalSecretFind struct {
 type ExternalSecretFind struct {
 	// Finds secrets based on the name.
 	// Finds secrets based on the name.
 	// +optional
 	// +optional

+ 18 - 7
pkg/provider/provider.go → apis/externalsecrets/v1beta1/provider.go

@@ -12,36 +12,47 @@ See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 */
 */
 
 
-package provider
+package v1beta1
 
 
 import (
 import (
 	"context"
 	"context"
 
 
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 )
 )
 
 
+// +kubebuilder:object:root=false
+// +kubebuilder:object:generate:false
+// +k8s:deepcopy-gen:interfaces=nil
+// +k8s:deepcopy-gen=nil
+
 // Provider is a common interface for interacting with secret backends.
 // Provider is a common interface for interacting with secret backends.
 type Provider interface {
 type Provider interface {
 	// NewClient constructs a SecretsManager Provider
 	// NewClient constructs a SecretsManager Provider
-	NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (SecretsClient, error)
+	NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error)
+
+	// ValidateStore checks if the provided store is valid
+	ValidateStore(store GenericStore) error
 }
 }
 
 
+// +kubebuilder:object:root=false
+// +kubebuilder:object:generate:false
+// +k8s:deepcopy-gen:interfaces=nil
+// +k8s:deepcopy-gen=nil
+
 // SecretsClient provides access to secrets.
 // SecretsClient provides access to secrets.
 type SecretsClient interface {
 type SecretsClient interface {
 	// GetSecret returns a single secret from the provider
 	// GetSecret returns a single secret from the provider
-	GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
+	GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error)
 
 
 	// Validate checks if the client is configured correctly
 	// Validate checks if the client is configured correctly
 	// and is able to retrieve secrets from the provider
 	// and is able to retrieve secrets from the provider
 	Validate() error
 	Validate() error
 
 
 	// GetSecretMap returns multiple k/v pairs from the provider
 	// GetSecretMap returns multiple k/v pairs from the provider
-	GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
+	GetSecretMap(ctx context.Context, ref ExternalSecretDataRemoteRef) (map[string][]byte, error)
 
 
 	// GetAllSecrets returns multiple k/v pairs from the provider
 	// GetAllSecrets returns multiple k/v pairs from the provider
-	GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error)
+	GetAllSecrets(ctx context.Context, ref ExternalSecretFind) (map[string][]byte, error)
 
 
 	Close(ctx context.Context) error
 	Close(ctx context.Context) error
 }
 }

+ 11 - 11
pkg/provider/schema/schema.go → apis/externalsecrets/v1beta1/provider_schema.go

@@ -12,27 +12,24 @@ See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 */
 */
 
 
-package schema
+package v1beta1
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"sync"
 	"sync"
-
-	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 )
 )
 
 
-var builder map[string]provider.Provider
+var builder map[string]Provider
 var buildlock sync.RWMutex
 var buildlock sync.RWMutex
 
 
 func init() {
 func init() {
-	builder = make(map[string]provider.Provider)
+	builder = make(map[string]Provider)
 }
 }
 
 
 // Register a store backend type. Register panics if a
 // Register a store backend type. Register panics if a
 // backend with the same store is already registered.
 // backend with the same store is already registered.
-func Register(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
+func Register(s Provider, storeSpec *SecretStoreProvider) {
 	storeName, err := getProviderName(storeSpec)
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -50,7 +47,7 @@ func Register(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
 
 
 // ForceRegister adds to store schema, overwriting a store if
 // ForceRegister adds to store schema, overwriting a store if
 // already registered. Should only be used for testing.
 // already registered. Should only be used for testing.
-func ForceRegister(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
+func ForceRegister(s Provider, storeSpec *SecretStoreProvider) {
 	storeName, err := getProviderName(storeSpec)
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -62,7 +59,7 @@ func ForceRegister(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider
 }
 }
 
 
 // GetProviderByName returns the provider implementation by name.
 // GetProviderByName returns the provider implementation by name.
-func GetProviderByName(name string) (provider.Provider, bool) {
+func GetProviderByName(name string) (Provider, bool) {
 	buildlock.RLock()
 	buildlock.RLock()
 	f, ok := builder[name]
 	f, ok := builder[name]
 	buildlock.RUnlock()
 	buildlock.RUnlock()
@@ -70,8 +67,11 @@ func GetProviderByName(name string) (provider.Provider, bool) {
 }
 }
 
 
 // GetProvider returns the provider from the generic store.
 // GetProvider returns the provider from the generic store.
-func GetProvider(s esv1beta1.GenericStore) (provider.Provider, error) {
+func GetProvider(s GenericStore) (Provider, error) {
 	spec := s.GetSpec()
 	spec := s.GetSpec()
+	if spec == nil {
+		return nil, fmt.Errorf("no spec found in %#v", s)
+	}
 	storeName, err := getProviderName(spec.Provider)
 	storeName, err := getProviderName(spec.Provider)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("store error for %s: %w", s.GetName(), err)
 		return nil, fmt.Errorf("store error for %s: %w", s.GetName(), err)
@@ -90,7 +90,7 @@ func GetProvider(s esv1beta1.GenericStore) (provider.Provider, error) {
 
 
 // getProviderName returns the name of the configured provider
 // getProviderName returns the name of the configured provider
 // or an error if the provider is not configured.
 // or an error if the provider is not configured.
-func getProviderName(storeSpec *esv1beta1.SecretStoreProvider) (string, error) {
+func getProviderName(storeSpec *SecretStoreProvider) (string, error) {
 	storeBytes, err := json.Marshal(storeSpec)
 	storeBytes, err := json.Marshal(storeSpec)
 	if err != nil || storeBytes == nil {
 	if err != nil || storeBytes == nil {
 		return "", fmt.Errorf("failed to marshal store spec: %w", err)
 		return "", fmt.Errorf("failed to marshal store spec: %w", err)

+ 32 - 31
pkg/provider/schema/schema_test.go → apis/externalsecrets/v1beta1/provider_schema_test.go

@@ -11,7 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 */
 */
-package schema
+package v1beta1
 
 
 import (
 import (
 	"context"
 	"context"
@@ -19,9 +19,6 @@ import (
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 )
 )
 
 
 type PP struct{}
 type PP struct{}
@@ -29,22 +26,22 @@ type PP struct{}
 const shouldBeRegistered = "provider should be registered"
 const shouldBeRegistered = "provider should be registered"
 
 
 // New constructs a SecretsManager Provider.
 // New constructs a SecretsManager Provider.
-func (p *PP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *PP) NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error) {
 	return p, nil
 	return p, nil
 }
 }
 
 
 // GetSecret returns a single secret from the provider.
 // GetSecret returns a single secret from the provider.
-func (p *PP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+func (p *PP) GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error) {
 	return []byte("NOOP"), nil
 	return []byte("NOOP"), nil
 }
 }
 
 
 // GetSecretMap returns multiple k/v pairs from the provider.
 // GetSecretMap returns multiple k/v pairs from the provider.
-func (p *PP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+func (p *PP) GetSecretMap(ctx context.Context, ref ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	return map[string][]byte{}, nil
 	return map[string][]byte{}, nil
 }
 }
 
 
 // Empty GetAllSecrets.
 // Empty GetAllSecrets.
-func (p *PP) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (p *PP) GetAllSecrets(ctx context.Context, ref ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
 	// TO be implemented
 	return map[string][]byte{}, nil
 	return map[string][]byte{}, nil
 }
 }
@@ -57,6 +54,10 @@ func (p *PP) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (p *PP) ValidateStore(store GenericStore) error {
+	return nil
+}
+
 // TestRegister tests if the Register function
 // TestRegister tests if the Register function
 // (1) panics if it tries to register something invalid
 // (1) panics if it tries to register something invalid
 // (2) stores the correct provider.
 // (2) stores the correct provider.
@@ -66,22 +67,22 @@ func TestRegister(t *testing.T) {
 		name      string
 		name      string
 		expPanic  bool
 		expPanic  bool
 		expExists bool
 		expExists bool
-		provider  *esv1beta1.SecretStoreProvider
+		provider  *SecretStoreProvider
 	}{
 	}{
 		{
 		{
 			test:      "should panic when given an invalid provider",
 			test:      "should panic when given an invalid provider",
 			name:      "aws",
 			name:      "aws",
 			expPanic:  true,
 			expPanic:  true,
 			expExists: false,
 			expExists: false,
-			provider:  &esv1beta1.SecretStoreProvider{},
+			provider:  &SecretStoreProvider{},
 		},
 		},
 		{
 		{
 			test:      "should register an correct provider",
 			test:      "should register an correct provider",
 			name:      "aws",
 			name:      "aws",
 			expExists: false,
 			expExists: false,
-			provider: &esv1beta1.SecretStoreProvider{
-				AWS: &esv1beta1.AWSProvider{
-					Service: esv1beta1.AWSServiceSecretsManager,
+			provider: &SecretStoreProvider{
+				AWS: &AWSProvider{
+					Service: AWSServiceSecretsManager,
 				},
 				},
 			},
 			},
 		},
 		},
@@ -90,9 +91,9 @@ func TestRegister(t *testing.T) {
 			name:      "aws",
 			name:      "aws",
 			expPanic:  true,
 			expPanic:  true,
 			expExists: true,
 			expExists: true,
-			provider: &esv1beta1.SecretStoreProvider{
-				AWS: &esv1beta1.AWSProvider{
-					Service: esv1beta1.AWSServiceSecretsManager,
+			provider: &SecretStoreProvider{
+				AWS: &AWSProvider{
+					Service: AWSServiceSecretsManager,
 				},
 				},
 			},
 			},
 		},
 		},
@@ -109,10 +110,10 @@ func TestRegister(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func runTest(t *testing.T, name string, provider *esv1beta1.SecretStoreProvider, expPanic bool) {
+func runTest(t *testing.T, name string, provider *SecretStoreProvider, expPanic bool) {
 	testProvider := &PP{}
 	testProvider := &PP{}
-	secretStore := &esv1beta1.SecretStore{
-		Spec: esv1beta1.SecretStoreSpec{
+	secretStore := &SecretStore{
+		Spec: SecretStoreSpec{
 			Provider: provider,
 			Provider: provider,
 		},
 		},
 	}
 	}
@@ -135,19 +136,19 @@ func runTest(t *testing.T, name string, provider *esv1beta1.SecretStoreProvider,
 // ForceRegister is used by other tests, we should ensure it works as expected.
 // ForceRegister is used by other tests, we should ensure it works as expected.
 func TestForceRegister(t *testing.T) {
 func TestForceRegister(t *testing.T) {
 	testProvider := &PP{}
 	testProvider := &PP{}
-	provider := &esv1beta1.SecretStoreProvider{
-		AWS: &esv1beta1.AWSProvider{
-			Service: esv1beta1.AWSServiceParameterStore,
+	provider := &SecretStoreProvider{
+		AWS: &AWSProvider{
+			Service: AWSServiceParameterStore,
 		},
 		},
 	}
 	}
-	secretStore := &esv1beta1.SecretStore{
-		Spec: esv1beta1.SecretStoreSpec{
+	secretStore := &SecretStore{
+		Spec: SecretStoreSpec{
 			Provider: provider,
 			Provider: provider,
 		},
 		},
 	}
 	}
-	ForceRegister(testProvider, &esv1beta1.SecretStoreProvider{
-		AWS: &esv1beta1.AWSProvider{
-			Service: esv1beta1.AWSServiceParameterStore,
+	ForceRegister(testProvider, &SecretStoreProvider{
+		AWS: &AWSProvider{
+			Service: AWSServiceParameterStore,
 		},
 		},
 	})
 	})
 	p1, ok := GetProviderByName("aws")
 	p1, ok := GetProviderByName("aws")
@@ -164,10 +165,10 @@ func TestRegisterGCP(t *testing.T) {
 	assert.False(t, ok, "provider should not be registered")
 	assert.False(t, ok, "provider should not be registered")
 
 
 	testProvider := &PP{}
 	testProvider := &PP{}
-	secretStore := &esv1beta1.SecretStore{
-		Spec: esv1beta1.SecretStoreSpec{
-			Provider: &esv1beta1.SecretStoreProvider{
-				GCPSM: &esv1beta1.GCPSMProvider{},
+	secretStore := &SecretStore{
+		Spec: SecretStoreSpec{
+			Provider: &SecretStoreProvider{
+				GCPSM: &GCPSMProvider{},
 			},
 			},
 		},
 		},
 	}
 	}

+ 62 - 0
apis/externalsecrets/v1beta1/secretstore_validator.go

@@ -0,0 +1,62 @@
+/*
+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
+
+    http://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 v1beta1
+
+import (
+	"context"
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+var _ admission.CustomValidator = &GenericStoreValidator{}
+
+const (
+	errInvalidStore = "invalid store"
+)
+
+type GenericStoreValidator struct{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
+func (r *GenericStoreValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
+	st, ok := obj.(GenericStore)
+	if !ok {
+		return fmt.Errorf(errInvalidStore)
+	}
+	return validateStore(st)
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
+func (r *GenericStoreValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
+	st, ok := newObj.(GenericStore)
+	if !ok {
+		return fmt.Errorf(errInvalidStore)
+	}
+	return validateStore(st)
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
+func (r *GenericStoreValidator) ValidateDelete(ctx context.Context, obj runtime.Object) error {
+	return nil
+}
+
+func validateStore(store GenericStore) error {
+	provider, err := GetProvider(store)
+	if err != nil {
+		return err
+	}
+	return provider.ValidateStore(store)
+}

+ 2 - 0
apis/externalsecrets/v1beta1/secretstore_webhook.go

@@ -21,11 +21,13 @@ import (
 func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr).
 	return ctrl.NewWebhookManagedBy(mgr).
 		For(c).
 		For(c).
+		WithValidator(&GenericStoreValidator{}).
 		Complete()
 		Complete()
 }
 }
 
 
 func (c *ClusterSecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 func (c *ClusterSecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr).
 	return ctrl.NewWebhookManagedBy(mgr).
 		For(c).
 		For(c).
+		WithValidator(&GenericStoreValidator{}).
 		Complete()
 		Complete()
 }
 }

+ 25 - 2
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -422,8 +422,16 @@ func (in *ExternalSecretData) DeepCopy() *ExternalSecretData {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ExternalSecretDataFromRemoteRef) DeepCopyInto(out *ExternalSecretDataFromRemoteRef) {
 func (in *ExternalSecretDataFromRemoteRef) DeepCopyInto(out *ExternalSecretDataFromRemoteRef) {
 	*out = *in
 	*out = *in
-	out.Extract = in.Extract
-	in.Find.DeepCopyInto(&out.Find)
+	if in.Extract != nil {
+		in, out := &in.Extract, &out.Extract
+		*out = new(ExternalSecretDataRemoteRef)
+		**out = **in
+	}
+	if in.Find != nil {
+		in, out := &in.Find, &out.Find
+		*out = new(ExternalSecretFind)
+		(*in).DeepCopyInto(*out)
+	}
 }
 }
 
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretDataFromRemoteRef.
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretDataFromRemoteRef.
@@ -794,6 +802,21 @@ func (in *GCPWorkloadIdentity) DeepCopy() *GCPWorkloadIdentity {
 	return out
 	return out
 }
 }
 
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GenericStoreValidator) DeepCopyInto(out *GenericStoreValidator) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericStoreValidator.
+func (in *GenericStoreValidator) DeepCopy() *GenericStoreValidator {
+	if in == nil {
+		return nil
+	}
+	out := new(GenericStoreValidator)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GitlabAuth) DeepCopyInto(out *GitlabAuth) {
 func (in *GitlabAuth) DeepCopyInto(out *GitlabAuth) {
 	*out = *in
 	*out = *in

+ 43 - 23
cmd/certcontroller.go

@@ -28,12 +28,13 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 
 
 	"github.com/external-secrets/external-secrets/pkg/controllers/crds"
 	"github.com/external-secrets/external-secrets/pkg/controllers/crds"
+	"github.com/external-secrets/external-secrets/pkg/controllers/webhookconfig"
 )
 )
 
 
 var certcontrollerCmd = &cobra.Command{
 var certcontrollerCmd = &cobra.Command{
 	Use:   "certcontroller",
 	Use:   "certcontroller",
-	Short: "Controller to manage certificates for external secrets CRDs",
-	Long: `Controller to manage certificates for external secrets CRDs.
+	Short: "Controller to manage certificates for external secrets CRDs and ValidatingWebhookConfigs",
+	Long: `Controller to manage certificates for external secrets CRDs and ValidatingWebhookConfigs.
 	For more information visit https://external-secrets.io`,
 	For more information visit https://external-secrets.io`,
 	Run: func(cmd *cobra.Command, args []string) {
 	Run: func(cmd *cobra.Command, args []string) {
 		var lvl zapcore.Level
 		var lvl zapcore.Level
@@ -46,11 +47,12 @@ var certcontrollerCmd = &cobra.Command{
 		ctrl.SetLogger(logger)
 		ctrl.SetLogger(logger)
 
 
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
-			Scheme:             scheme,
-			MetricsBindAddress: metricsAddr,
-			Port:               9443,
-			LeaderElection:     enableLeaderElection,
-			LeaderElectionID:   "crd-certs-controller",
+			Scheme:                 scheme,
+			MetricsBindAddress:     metricsAddr,
+			HealthProbeBindAddress: healthzAddr,
+			Port:                   9443,
+			LeaderElection:         enableLeaderElection,
+			LeaderElectionID:       "crd-certs-controller",
 			ClientDisableCacheFor: []client.Object{
 			ClientDisableCacheFor: []client.Object{
 				// the client creates a ListWatch for all resource kinds that
 				// the client creates a ListWatch for all resource kinds that
 				// are requested with .Get().
 				// are requested with .Get().
@@ -59,31 +61,48 @@ var certcontrollerCmd = &cobra.Command{
 				// that he owns.
 				// that he owns.
 				// see #721
 				// see #721
 				&v1.Secret{},
 				&v1.Secret{},
-			}})
+			},
+		})
 		if err != nil {
 		if err != nil {
 			setupLog.Error(err, "unable to start manager")
 			setupLog.Error(err, "unable to start manager")
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
-		crds := &crds.Reconciler{
-			Client:                 mgr.GetClient(),
-			Log:                    ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
-			Scheme:                 mgr.GetScheme(),
-			SvcName:                serviceName,
-			SvcNamespace:           serviceNamespace,
-			SecretName:             secretName,
-			SecretNamespace:        secretNamespace,
-			RequeueInterval:        crdRequeueInterval,
-			CrdResources:           []string{"externalsecrets.external-secrets.io", "clustersecretstores.external-secrets.io", "secretstores.external-secrets.io"},
-			CAName:                 "external-secrets",
-			CAOrganization:         "external-secrets",
-			RestartOnSecretRefresh: false,
-		}
-		if err := crds.SetupWithManager(mgr, controller.Options{
+		crdctrl := crds.New(mgr.GetClient(), mgr.GetScheme(),
+			ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
+			crdRequeueInterval, serviceName, serviceNamespace, secretName, secretNamespace, []string{
+				"externalsecrets.external-secrets.io",
+				"clustersecretstores.external-secrets.io",
+				"secretstores.external-secrets.io",
+			})
+		if err := crdctrl.SetupWithManager(mgr, controller.Options{
 			MaxConcurrentReconciles: concurrent,
 			MaxConcurrentReconciles: concurrent,
 		}); err != nil {
 		}); err != nil {
 			setupLog.Error(err, errCreateController, "controller", "CustomResourceDefinition")
 			setupLog.Error(err, errCreateController, "controller", "CustomResourceDefinition")
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
+
+		whc := webhookconfig.New(mgr.GetClient(), mgr.GetScheme(),
+			ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
+			serviceName, serviceNamespace,
+			secretName, secretNamespace, crdRequeueInterval)
+		if err := whc.SetupWithManager(mgr, controller.Options{
+			MaxConcurrentReconciles: concurrent,
+		}); err != nil {
+			setupLog.Error(err, errCreateController, "controller", "WebhookConfig")
+			os.Exit(1)
+		}
+
+		err = mgr.AddReadyzCheck("crd-inject", crdctrl.ReadyCheck)
+		if err != nil {
+			setupLog.Error(err, "unable to add crd readyz check")
+			os.Exit(1)
+		}
+		err = mgr.AddReadyzCheck("validation-webhook-inject", whc.ReadyCheck)
+		if err != nil {
+			setupLog.Error(err, "unable to add webhook readyz check")
+			os.Exit(1)
+		}
+
 		setupLog.Info("starting manager")
 		setupLog.Info("starting manager")
 		if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
 		if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
 			setupLog.Error(err, "problem running manager")
 			setupLog.Error(err, "problem running manager")
@@ -96,6 +115,7 @@ func init() {
 	rootCmd.AddCommand(certcontrollerCmd)
 	rootCmd.AddCommand(certcontrollerCmd)
 
 
 	certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
 	certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	certcontrollerCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
 	certcontrollerCmd.Flags().StringVar(&serviceName, "service-name", "external-secrets-webhook", "Webhook service name")
 	certcontrollerCmd.Flags().StringVar(&serviceName, "service-name", "external-secrets-webhook", "Webhook service name")
 	certcontrollerCmd.Flags().StringVar(&serviceNamespace, "service-namespace", "default", "Webhook service namespace")
 	certcontrollerCmd.Flags().StringVar(&serviceNamespace, "service-namespace", "default", "Webhook service namespace")
 	certcontrollerCmd.Flags().StringVar(&secretName, "secret-name", "external-secrets-webhook", "Secret to store certs for webhook")
 	certcontrollerCmd.Flags().StringVar(&secretName, "secret-name", "external-secrets-webhook", "Secret to store certs for webhook")

+ 4 - 1
cmd/root.go

@@ -22,11 +22,12 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"go.uber.org/zap/zapcore"
 	"go.uber.org/zap/zapcore"
 	v1 "k8s.io/api/core/v1"
 	v1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 
 
 	// To allow using gcp auth.
 	// To allow using gcp auth.
-	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
+	_ "k8s.io/client-go/plugin/pkg/client/auth"
 	ctrl "sigs.k8s.io/controller-runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -44,6 +45,7 @@ var (
 	dnsName                       string
 	dnsName                       string
 	certDir                       string
 	certDir                       string
 	metricsAddr                   string
 	metricsAddr                   string
+	healthzAddr                   string
 	controllerClass               string
 	controllerClass               string
 	enableLeaderElection          bool
 	enableLeaderElection          bool
 	concurrent                    int
 	concurrent                    int
@@ -64,6 +66,7 @@ func init() {
 	_ = clientgoscheme.AddToScheme(scheme)
 	_ = clientgoscheme.AddToScheme(scheme)
 	_ = esv1beta1.AddToScheme(scheme)
 	_ = esv1beta1.AddToScheme(scheme)
 	_ = esv1alpha1.AddToScheme(scheme)
 	_ = esv1alpha1.AddToScheme(scheme)
+	_ = apiextensionsv1.AddToScheme(scheme)
 }
 }
 
 
 var rootCmd = &cobra.Command{
 var rootCmd = &cobra.Command{

+ 42 - 7
cmd/webhook.go

@@ -17,6 +17,7 @@ package cmd
 
 
 import (
 import (
 	"context"
 	"context"
+	"net/http"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
 	"syscall"
 	"syscall"
@@ -65,12 +66,12 @@ var webhookCmd = &cobra.Command{
 		logger := zap.New(zap.Level(lvl))
 		logger := zap.New(zap.Level(lvl))
 		ctrl.SetLogger(logger)
 		ctrl.SetLogger(logger)
 
 
-		setupLog.Info("validating certs")
-		err = crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
+		err = waitForCerts(c, time.Minute*2)
 		if err != nil {
 		if err != nil {
-			setupLog.Error(err, "error checking certs")
+			setupLog.Error(err, "unable to validate certificates")
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
+
 		ctx, cancel := context.WithCancel(context.Background())
 		ctx, cancel := context.WithCancel(context.Background())
 		go func(c crds.CertInfo, dnsName string, every time.Duration) {
 		go func(c crds.CertInfo, dnsName string, every time.Duration) {
 			sigs := make(chan os.Signal, 1)
 			sigs := make(chan os.Signal, 1)
@@ -92,10 +93,11 @@ var webhookCmd = &cobra.Command{
 		}(c, dnsName, certCheckInterval)
 		}(c, dnsName, certCheckInterval)
 
 
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
-			Scheme:             scheme,
-			MetricsBindAddress: metricsAddr,
-			Port:               9443,
-			CertDir:            certDir,
+			Scheme:                 scheme,
+			MetricsBindAddress:     metricsAddr,
+			HealthProbeBindAddress: healthzAddr,
+			Port:                   9443,
+			CertDir:                certDir,
 		})
 		})
 		if err != nil {
 		if err != nil {
 			setupLog.Error(err, "unable to start manager")
 			setupLog.Error(err, "unable to start manager")
@@ -125,6 +127,15 @@ var webhookCmd = &cobra.Command{
 			setupLog.Error(err, errCreateWebhook, "webhook", "ClusterSecretStore-v1alpha1")
 			setupLog.Error(err, errCreateWebhook, "webhook", "ClusterSecretStore-v1alpha1")
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
+
+		err = mgr.AddReadyzCheck("certs", func(_ *http.Request) error {
+			return crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
+		})
+		if err != nil {
+			setupLog.Error(err, "unable to add certs readyz check")
+			os.Exit(1)
+		}
+
 		setupLog.Info("starting manager")
 		setupLog.Info("starting manager")
 		if err := mgr.Start(ctx); err != nil {
 		if err := mgr.Start(ctx); err != nil {
 			setupLog.Error(err, "problem running manager")
 			setupLog.Error(err, "problem running manager")
@@ -133,9 +144,33 @@ var webhookCmd = &cobra.Command{
 	},
 	},
 }
 }
 
 
+// waitForCerts waits until the certificates become ready.
+// If they don't become ready within a given time duration
+// this function returns an error.
+// certs are generated by the certcontroller.
+func waitForCerts(c crds.CertInfo, timeout time.Duration) error {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+	for {
+		setupLog.Info("validating certs")
+		err := crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
+		if err == nil {
+			return nil
+		}
+		if err != nil {
+			setupLog.Error(err, "invalid certs. retrying...")
+			<-time.After(time.Second * 10)
+		}
+		if ctx.Err() != nil {
+			return ctx.Err()
+		}
+	}
+}
+
 func init() {
 func init() {
 	rootCmd.AddCommand(webhookCmd)
 	rootCmd.AddCommand(webhookCmd)
 	webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
 	webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	webhookCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
 	webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")
 	webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")
 	webhookCmd.Flags().StringVar(&certDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs", "path to check for certs")
 	webhookCmd.Flags().StringVar(&certDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs", "path to check for certs")
 	webhookCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")
 	webhookCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")

+ 4 - 0
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -320,6 +320,8 @@ spec:
                   Provider data If multiple entries are specified, the Secret keys
                   Provider data If multiple entries are specified, the Secret keys
                   are merged in the specified order
                   are merged in the specified order
                 items:
                 items:
+                  maxProperties: 1
+                  minProperties: 1
                   properties:
                   properties:
                     extract:
                     extract:
                       description: Used to extract multiple key/value pairs from one
                       description: Used to extract multiple key/value pairs from one
@@ -341,6 +343,8 @@ spec:
                       type: object
                       type: object
                     find:
                     find:
                       description: Used to find secrets based on tags or regular expressions
                       description: Used to find secrets based on tags or regular expressions
+                      maxProperties: 1
+                      minProperties: 1
                       properties:
                       properties:
                         name:
                         name:
                           description: Finds secrets based on the name.
                           description: Finds secrets based on the name.

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

@@ -61,6 +61,12 @@ spec:
             - containerPort: {{ .Values.certController.prometheus.service.port }}
             - containerPort: {{ .Values.certController.prometheus.service.port }}
               protocol: TCP
               protocol: TCP
               name: metrics
               name: metrics
+          readinessProbe:
+            httpGet:
+              port: 8081
+              path: /readyz
+            initialDelaySeconds: 20
+            periodSeconds: 5
           {{- with .Values.certController.extraEnv }}
           {{- with .Values.certController.extraEnv }}
           env:
           env:
             {{- toYaml . | nindent 12 }}
             {{- toYaml . | nindent 12 }}

+ 25 - 0
deploy/charts/external-secrets/templates/cert-controller-rbac.yaml

@@ -16,6 +16,31 @@ rules:
     - "watch"
     - "watch"
     - "update"
     - "update"
     - "patch"
     - "patch"
+  - apiGroups:
+    - "admissionregistration.k8s.io"
+    resources:
+    - "validatingwebhookconfigurations"
+    verbs:
+    - "get"
+    - "list"
+    - "watch"
+    - "update"
+    - "patch"
+  - apiGroups:
+    - ""
+    resources:
+    - "endpoints"
+    verbs:
+    - "list"
+    - "get"
+    - "watch"
+  - apiGroups:
+    - ""
+    resources:
+    - "events"
+    verbs:
+    - "create"
+    - "patch"
   - apiGroups:
   - apiGroups:
     - ""
     - ""
     resources:
     resources:

+ 41 - 0
deploy/charts/external-secrets/templates/validatingwebhook.yaml

@@ -0,0 +1,41 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: secretstore-validate
+  labels:
+    external-secrets.io/component: webhook
+webhooks:
+- name: "validate.secretstore.external-secrets.io"
+  rules:
+  - apiGroups:   ["external-secrets.io"]
+    apiVersions: ["v1beta1"]
+    operations:  ["CREATE", "UPDATE", "DELETE"]
+    resources:   ["secretstores"]
+    scope:       "Namespaced"
+  clientConfig:
+    service:
+      namespace: {{ .Release.Namespace | quote }}
+      name: {{ include "external-secrets.fullname" . }}-webhook
+      path: /validate-external-secrets-io-v1beta1-secretstore
+    # will be set by controller
+    caBundle: Cg==
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  timeoutSeconds: 5
+
+- name: "validate.clustersecretstore.external-secrets.io"
+  rules:
+  - apiGroups:   ["external-secrets.io"]
+    apiVersions: ["v1beta1"]
+    operations:  ["CREATE", "UPDATE", "DELETE"]
+    resources:   ["clustersecretstores"]
+    scope:       "Cluster"
+  clientConfig:
+    service:
+      namespace: {{ .Release.Namespace | quote }}
+      name: {{ include "external-secrets.fullname" . }}-webhook
+      path: /validate-external-secrets-io-v1beta1-clustersecretstore
+    caBundle: Cg== # will be set by controller
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  timeoutSeconds: 5

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

@@ -63,8 +63,9 @@ spec:
               protocol: TCP
               protocol: TCP
               name: webhook
               name: webhook
           readinessProbe:
           readinessProbe:
-            tcpSocket:
-              port: 9443
+            httpGet:
+              port: 8081
+              path: /readyz
             initialDelaySeconds: 20
             initialDelaySeconds: 20
             periodSeconds: 5
             periodSeconds: 5
           {{- with .Values.webhook.extraEnv }}
           {{- with .Values.webhook.extraEnv }}

+ 1 - 0
deploy/charts/external-secrets/templates/webhook-secret.yaml

@@ -2,6 +2,7 @@ apiVersion: v1
 kind: Secret
 kind: Secret
 metadata:
 metadata:
   name: {{ include "external-secrets.fullname" . }}-webhook
   name: {{ include "external-secrets.fullname" . }}-webhook
+  namespace: {{ .Release.Namespace | quote }}
   labels:
   labels:
     {{- include "external-secrets-webhook.labels" . | nindent 4 }}
     {{- include "external-secrets-webhook.labels" . | nindent 4 }}
     external-secrets.io/component : webhook
     external-secrets.io/component : webhook

+ 1 - 0
deploy/charts/external-secrets/templates/webhook-service.yaml

@@ -2,6 +2,7 @@ apiVersion: v1
 kind: Service
 kind: Service
 metadata:
 metadata:
   name: {{ include "external-secrets.fullname" . }}-webhook
   name: {{ include "external-secrets.fullname" . }}-webhook
+  namespace: {{ .Release.Namespace | quote }}
   labels:
   labels:
     {{- include "external-secrets-webhook.labels" . | nindent 4 }}
     {{- include "external-secrets-webhook.labels" . | nindent 4 }}
     external-secrets.io/component : webhook
     external-secrets.io/component : webhook

+ 4 - 0
deploy/crds/bundle.yaml

@@ -2244,6 +2244,8 @@ spec:
                 dataFrom:
                 dataFrom:
                   description: DataFrom is used to fetch all properties from a specific Provider data If multiple entries are specified, the Secret keys are merged in the specified order
                   description: DataFrom is used to fetch all properties from a specific Provider data If multiple entries are specified, the Secret keys are merged in the specified order
                   items:
                   items:
+                    maxProperties: 1
+                    minProperties: 1
                     properties:
                     properties:
                       extract:
                       extract:
                         description: Used to extract multiple key/value pairs from one secret
                         description: Used to extract multiple key/value pairs from one secret
@@ -2262,6 +2264,8 @@ spec:
                         type: object
                         type: object
                       find:
                       find:
                         description: Used to find secrets based on tags or regular expressions
                         description: Used to find secrets based on tags or regular expressions
+                        maxProperties: 1
+                        minProperties: 1
                         properties:
                         properties:
                           name:
                           name:
                             description: Finds secrets based on the name.
                             description: Finds secrets based on the name.

+ 55 - 55
pkg/controllers/crds/common_test.go

@@ -15,28 +15,22 @@ package crds
 
 
 import (
 import (
 	"context"
 	"context"
-	"encoding/json"
 	"time"
 	"time"
 
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/types"
-)
-
-const (
-	crdGroup   = "apiextensions.k8s.io"
-	crdKind    = "CustomResourceDefinition"
-	crdVersion = "v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 )
 )
 
 
 type testCase struct {
 type testCase struct {
-	crd     unstructured.Unstructured
-	crd2    unstructured.Unstructured
+	crd     *apiextensions.CustomResourceDefinition
+	crd2    *apiextensions.CustomResourceDefinition
 	service corev1.Service
 	service corev1.Service
 	secret  corev1.Secret
 	secret  corev1.Secret
 	assert  func()
 	assert  func()
@@ -50,31 +44,27 @@ var _ = Describe("CRD reconcile", func() {
 	})
 	})
 
 
 	AfterEach(func() {
 	AfterEach(func() {
-		// To improve later on with proper clean up.
+		ctx := context.Background()
+		k8sClient.Delete(ctx, &test.secret)
+		k8sClient.Delete(ctx, &test.service)
+		deleteCRD(test.crd)
+		deleteCRD(test.crd2)
 	})
 	})
 
 
 	// a invalid provider config should be reflected
 	// a invalid provider config should be reflected
 	// in the store status condition
 	// in the store status condition
 	PatchesCRD := func(tc *testCase) {
 	PatchesCRD := func(tc *testCase) {
 		tc.assert = func() {
 		tc.assert = func() {
-			Consistently(func() bool {
-				ss := unstructured.Unstructured{}
-				ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: crdVersion, Group: crdGroup})
+			Eventually(func() bool {
+				crd := &apiextensions.CustomResourceDefinition{}
 				err := k8sClient.Get(context.Background(), types.NamespacedName{
 				err := k8sClient.Get(context.Background(), types.NamespacedName{
 					Name: "secretstores.test.io",
 					Name: "secretstores.test.io",
-				}, &ss)
+				}, crd)
 				if err != nil {
 				if err != nil {
 					return false
 					return false
 				}
 				}
-				val, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
-				if err != nil || !ok {
-					return false
-				}
-				want, ok, err := unstructured.NestedString(tc.crd.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
-				if err != nil || !ok {
-					return false
-				}
-				return want != val
+				return crd.Spec.Conversion.Webhook.ClientConfig.Service.Name !=
+					tc.crd.Spec.Conversion.Webhook.ClientConfig.Service.Name
 			}).
 			}).
 				WithTimeout(time.Second * 10).
 				WithTimeout(time.Second * 10).
 				WithPolling(time.Second).
 				WithPolling(time.Second).
@@ -87,23 +77,15 @@ var _ = Describe("CRD reconcile", func() {
 	ignoreNonTargetCRDs := func(tc *testCase) {
 	ignoreNonTargetCRDs := func(tc *testCase) {
 		tc.assert = func() {
 		tc.assert = func() {
 			Consistently(func() bool {
 			Consistently(func() bool {
-				ss := unstructured.Unstructured{}
-				ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: crdVersion, Group: crdGroup})
+				crd := &apiextensions.CustomResourceDefinition{}
 				err := k8sClient.Get(context.Background(), types.NamespacedName{
 				err := k8sClient.Get(context.Background(), types.NamespacedName{
 					Name: "some-other.test.io",
 					Name: "some-other.test.io",
-				}, &ss)
+				}, crd)
 				if err != nil {
 				if err != nil {
 					return false
 					return false
 				}
 				}
-				got, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
-				if !ok || err != nil {
-					return false
-				}
-				want, ok, err := unstructured.NestedString(tc.crd2.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
-				if !ok || err != nil {
-					return false
-				}
-				return got == want
+				return crd.Spec.Conversion.Webhook.ClientConfig.Service.Name ==
+					tc.crd2.Spec.Conversion.Webhook.ClientConfig.Service.Name
 			}).
 			}).
 				WithTimeout(time.Second * 3).
 				WithTimeout(time.Second * 3).
 				WithPolling(time.Millisecond * 500).
 				WithPolling(time.Millisecond * 500).
@@ -116,21 +98,47 @@ var _ = Describe("CRD reconcile", func() {
 			mut(test)
 			mut(test)
 		}
 		}
 		ctx := context.Background()
 		ctx := context.Background()
-		k8sClient.Create(ctx, &test.secret)
-		k8sClient.Create(ctx, &test.service)
-		k8sClient.Create(ctx, &test.crd)
-		k8sClient.Create(ctx, &test.crd2)
+		err := k8sClient.Create(ctx, &test.secret)
+		Expect(err).ToNot(HaveOccurred())
+		err = k8sClient.Create(ctx, &test.service)
+		Expect(err).ToNot(HaveOccurred())
+		err = k8sClient.Create(ctx, test.crd)
+		Expect(err).ToNot(HaveOccurred())
+		err = k8sClient.Create(ctx, test.crd2)
+		Expect(err).ToNot(HaveOccurred())
 		test.assert()
 		test.assert()
 	},
 	},
-
 		Entry("[namespace] Ignore non Target CRDs", ignoreNonTargetCRDs),
 		Entry("[namespace] Ignore non Target CRDs", ignoreNonTargetCRDs),
 		Entry("[namespace] Patch target CRDs", PatchesCRD),
 		Entry("[namespace] Patch target CRDs", PatchesCRD),
 	)
 	)
 
 
 })
 })
 
 
-func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
-	crd := apiextensions.CustomResourceDefinition{
+func deleteCRD(crd *apiextensions.CustomResourceDefinition) {
+	err := k8sClient.Delete(context.Background(), crd, client.GracePeriodSeconds(0))
+	if err != nil && !apierrors.IsNotFound(err) {
+		Fail("unable to delete crd " + crd.Name)
+		return
+	}
+	Eventually(func() bool {
+		err := k8sClient.Get(context.Background(), types.NamespacedName{
+			Name: crd.Name,
+		}, crd)
+		if err == nil {
+			// force delete by removing finalizers
+			// note: we can not delete a CRD with an invalid caBundle field
+			cpy := crd.DeepCopy()
+			controllerutil.RemoveFinalizer(cpy, "customresourcecleanup.apiextensions.k8s.io")
+			p := client.MergeFrom(crd)
+			k8sClient.Patch(context.Background(), cpy, p)
+			return false
+		}
+		return apierrors.IsNotFound(err)
+	}).WithTimeout(time.Second * 5).WithPolling(time.Second).Should(BeTrue())
+}
+
+func makeCRD(plural, group string) *apiextensions.CustomResourceDefinition {
+	return &apiextensions.CustomResourceDefinition{
 		ObjectMeta: metav1.ObjectMeta{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: plural + "." + group,
 			Name: plural + "." + group,
 		},
 		},
@@ -160,7 +168,7 @@ func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
 				Webhook: &apiextensions.WebhookConversion{
 				Webhook: &apiextensions.WebhookConversion{
 					ConversionReviewVersions: []string{"v1"},
 					ConversionReviewVersions: []string{"v1"},
 					ClientConfig: &apiextensions.WebhookClientConfig{
 					ClientConfig: &apiextensions.WebhookClientConfig{
-						CABundle: []byte("foobar"),
+						CABundle: []byte(`Cg==`),
 						Service: &apiextensions.ServiceReference{
 						Service: &apiextensions.ServiceReference{
 							Name:      "webhook",
 							Name:      "webhook",
 							Namespace: "default",
 							Namespace: "default",
@@ -170,14 +178,6 @@ func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
 			},
 			},
 		},
 		},
 	}
 	}
-	marshal, _ := json.Marshal(crd)
-	unmarshal := make(map[string]interface{})
-	json.Unmarshal(marshal, &unmarshal)
-	u := unstructured.Unstructured{
-		Object: unmarshal,
-	}
-	u.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: "v1", Group: crdGroup})
-	return u
 }
 }
 
 
 func makeSecret() corev1.Secret {
 func makeSecret() corev1.Secret {
@@ -217,8 +217,8 @@ func makeDefaultTestcase() *testCase {
 		assert: func() {
 		assert: func() {
 			// this is a noop by default
 			// this is a noop by default
 		},
 		},
-		crd:     makeUnstructuredCRD("secretstores", "test.io"),
-		crd2:    makeUnstructuredCRD("some-other", "test.io"),
+		crd:     makeCRD("secretstores", "test.io"),
+		crd2:    makeCRD("some-other", "test.io"),
 		secret:  makeSecret(),
 		secret:  makeSecret(),
 		service: makeService(),
 		service: makeService(),
 	}
 	}

+ 99 - 81
pkg/controllers/crds/crds_controller.go

@@ -22,19 +22,20 @@ import (
 	"crypto/tls"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"crypto/x509/pkix"
-	"encoding/base64"
 	"encoding/pem"
 	"encoding/pem"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"math/big"
 	"math/big"
+	"net/http"
 	"os"
 	"os"
+	"path/filepath"
+	"sync"
 	"time"
 	"time"
 
 
 	"github.com/go-logr/logr"
 	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/tools/record"
 	ctrl "sigs.k8s.io/controller-runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -49,31 +50,49 @@ const (
 	caKeyName            = "ca.key"
 	caKeyName            = "ca.key"
 	certValidityDuration = 10 * 365 * 24 * time.Hour
 	certValidityDuration = 10 * 365 * 24 * time.Hour
 	LookaheadInterval    = 90 * 24 * time.Hour
 	LookaheadInterval    = 90 * 24 * time.Hour
-)
-
-type WebhookType int
 
 
-const (
-	Validating WebhookType = iota
-	Mutating
-	CRDConversion
+	errResNotReady       = "resource not ready: %s"
+	errSubsetsNotReady   = "subsets not ready"
+	errAddressesNotReady = "addresses not ready"
 )
 )
 
 
 type Reconciler struct {
 type Reconciler struct {
 	client.Client
 	client.Client
-	Log                    logr.Logger
-	Scheme                 *runtime.Scheme
-	recorder               record.EventRecorder
-	SvcName                string
-	SvcNamespace           string
-	SecretName             string
-	SecretNamespace        string
-	CrdResources           []string
-	dnsName                string
-	CAName                 string
-	CAOrganization         string
-	RestartOnSecretRefresh bool
-	RequeueInterval        time.Duration
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	recorder        record.EventRecorder
+	SvcName         string
+	SvcNamespace    string
+	SecretName      string
+	SecretNamespace string
+	CrdResources    []string
+	dnsName         string
+	CAName          string
+	CAOrganization  string
+	RequeueInterval time.Duration
+
+	// the controller is ready when all crds are injected
+	rdyMu          *sync.Mutex
+	readyStatusMap map[string]bool
+}
+
+func New(k8sClient client.Client, scheme *runtime.Scheme, logger logr.Logger,
+	interval time.Duration, svcName, svcNamespace, secretName, secretNamespace string, resources []string) *Reconciler {
+	return &Reconciler{
+		Client:          k8sClient,
+		Log:             logger,
+		Scheme:          scheme,
+		SvcName:         svcName,
+		SvcNamespace:    svcNamespace,
+		SecretName:      secretName,
+		SecretNamespace: secretNamespace,
+		RequeueInterval: interval,
+		CrdResources:    resources,
+		CAName:          "external-secrets",
+		CAOrganization:  "external-secrets",
+		rdyMu:           &sync.Mutex{},
+		readyStatusMap:  map[string]bool{},
+	}
 }
 }
 
 
 type CertInfo struct {
 type CertInfo struct {
@@ -82,10 +101,6 @@ type CertInfo struct {
 	KeyName  string
 	KeyName  string
 	CAName   string
 	CAName   string
 }
 }
-type WebhookInfo struct {
-	Name string
-	Type WebhookType
-}
 
 
 func contains(s []string, e string) bool {
 func contains(s []string, e string) bool {
 	for _, a := range s {
 	for _, a := range s {
@@ -95,44 +110,62 @@ func contains(s []string, e string) bool {
 	}
 	}
 	return false
 	return false
 }
 }
+
 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 	log := r.Log.WithValues("CustomResourceDefinition", req.NamespacedName)
 	log := r.Log.WithValues("CustomResourceDefinition", req.NamespacedName)
 	if contains(r.CrdResources, req.NamespacedName.Name) {
 	if contains(r.CrdResources, req.NamespacedName.Name) {
 		err := r.updateCRD(ctx, req)
 		err := r.updateCRD(ctx, req)
 		if err != nil {
 		if err != nil {
 			log.Error(err, "failed to inject conversion webhook")
 			log.Error(err, "failed to inject conversion webhook")
+			r.rdyMu.Lock()
+			r.readyStatusMap[req.NamespacedName.Name] = false
+			r.rdyMu.Unlock()
 			return ctrl.Result{}, err
 			return ctrl.Result{}, err
 		}
 		}
+		r.rdyMu.Lock()
+		r.readyStatusMap[req.NamespacedName.Name] = true
+		r.rdyMu.Unlock()
 	}
 	}
 	return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
 	return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
 }
 }
 
 
-func (r *Reconciler) ConvertToWebhookInfo() []WebhookInfo {
-	info := make([]WebhookInfo, len(r.CrdResources))
-	for p, v := range r.CrdResources {
-		r := WebhookInfo{
-			Name: v,
-			Type: CRDConversion,
+// ReadyCheck reviews if all webhook configs have been injected into the CRDs
+// and if the referenced webhook service is ready.
+func (r *Reconciler) ReadyCheck(_ *http.Request) error {
+	for _, res := range r.CrdResources {
+		r.rdyMu.Lock()
+		rdy := r.readyStatusMap[res]
+		r.rdyMu.Unlock()
+		if !rdy {
+			return fmt.Errorf(errResNotReady, res)
 		}
 		}
-		info[p] = r
 	}
 	}
-	return info
+	var eps corev1.Endpoints
+	err := r.Get(context.TODO(), types.NamespacedName{
+		Name:      r.SvcName,
+		Namespace: r.SvcNamespace,
+	}, &eps)
+	if err != nil {
+		return err
+	}
+	if len(eps.Subsets) == 0 {
+		return fmt.Errorf(errSubsetsNotReady)
+	}
+	if len(eps.Subsets[0].Addresses) == 0 {
+		return fmt.Errorf(errAddressesNotReady)
+	}
+	return nil
 }
 }
 
 
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
-	crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
-	res := &unstructured.Unstructured{}
-	res.SetGroupVersionKind(crdGVK)
 	r.recorder = mgr.GetEventRecorderFor("custom-resource-definition")
 	r.recorder = mgr.GetEventRecorderFor("custom-resource-definition")
 	return ctrl.NewControllerManagedBy(mgr).
 	return ctrl.NewControllerManagedBy(mgr).
 		WithOptions(opts).
 		WithOptions(opts).
-		For(res).
+		For(&apiext.CustomResourceDefinition{}).
 		Complete(r)
 		Complete(r)
 }
 }
 
 
 func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
 func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
-	crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
-
 	secret := corev1.Secret{}
 	secret := corev1.Secret{}
 	secretName := types.NamespacedName{
 	secretName := types.NamespacedName{
 		Name:      r.SecretName,
 		Name:      r.SecretName,
@@ -142,16 +175,15 @@ func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	updatedResource := &unstructured.Unstructured{}
-	updatedResource.SetGroupVersionKind(crdGVK)
-	if err := r.Get(ctx, req.NamespacedName, updatedResource); err != nil {
+	var updatedResource apiext.CustomResourceDefinition
+	if err := r.Get(ctx, req.NamespacedName, &updatedResource); err != nil {
 		return err
 		return err
 	}
 	}
 	svc := types.NamespacedName{
 	svc := types.NamespacedName{
 		Name:      r.SvcName,
 		Name:      r.SvcName,
 		Namespace: r.SvcNamespace,
 		Namespace: r.SvcNamespace,
 	}
 	}
-	if err := injectSvcToConversionWebhook(updatedResource, svc); err != nil {
+	if err := injectService(&updatedResource, svc); err != nil {
 		return err
 		return err
 	}
 	}
 	r.dnsName = fmt.Sprintf("%v.%v.svc", r.SvcName, r.SvcNamespace)
 	r.dnsName = fmt.Sprintf("%v.%v.svc", r.SvcName, r.SvcNamespace)
@@ -164,45 +196,35 @@ func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		if err := injectCertToConversionWebhook(updatedResource, artifacts.CertPEM); err != nil {
+		if err := injectCert(&updatedResource, artifacts.CertPEM); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	if err := r.Update(ctx, updatedResource); err != nil {
+	if err := r.Update(ctx, &updatedResource); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func injectSvcToConversionWebhook(crd *unstructured.Unstructured, svc types.NamespacedName) error {
-	_, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
-	if err != nil {
-		return err
-	}
-	if !found {
-		return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
-	}
-	if err := unstructured.SetNestedField(crd.Object, svc.Name, "spec", "conversion", "webhook", "clientConfig", "service", "name"); err != nil {
-		return err
-	}
-	if err := unstructured.SetNestedField(crd.Object, svc.Namespace, "spec", "conversion", "webhook", "clientConfig", "service", "namespace"); err != nil {
-		return err
+func injectService(crd *apiext.CustomResourceDefinition, svc types.NamespacedName) error {
+	if crd.Spec.Conversion == nil ||
+		crd.Spec.Conversion.Webhook == nil ||
+		crd.Spec.Conversion.Webhook.ClientConfig == nil ||
+		crd.Spec.Conversion.Webhook.ClientConfig.Service == nil {
+		return fmt.Errorf("unexpected crd conversion webhook config")
 	}
 	}
+	crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = svc.Namespace
+	crd.Spec.Conversion.Webhook.ClientConfig.Service.Name = svc.Name
 	return nil
 	return nil
 }
 }
 
 
-func injectCertToConversionWebhook(crd *unstructured.Unstructured, certPem []byte) error {
-	_, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
-	if err != nil {
-		return err
-	}
-	if !found {
-		return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
-	}
-	if err := unstructured.SetNestedField(crd.Object, base64.StdEncoding.EncodeToString(certPem), "spec", "conversion", "webhook", "clientConfig", "caBundle"); err != nil {
-		return err
+func injectCert(crd *apiext.CustomResourceDefinition, certPem []byte) error {
+	if crd.Spec.Conversion == nil ||
+		crd.Spec.Conversion.Webhook == nil ||
+		crd.Spec.Conversion.Webhook.ClientConfig == nil {
+		return fmt.Errorf("unexpected crd conversion webhook config")
 	}
 	}
-
+	crd.Spec.Conversion.Webhook.ClientConfig.CABundle = certPem
 	return nil
 	return nil
 }
 }
 
 
@@ -289,18 +311,12 @@ func (r *Reconciler) refreshCertIfNeeded(secret *corev1.Secret) (bool, error) {
 		if err := r.refreshCerts(true, secret); err != nil {
 		if err := r.refreshCerts(true, secret); err != nil {
 			return false, err
 			return false, err
 		}
 		}
-		if r.RestartOnSecretRefresh {
-			os.Exit(0)
-		}
 		return true, nil
 		return true, nil
 	}
 	}
 	if !r.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) {
 	if !r.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) {
 		if err := r.refreshCerts(false, secret); err != nil {
 		if err := r.refreshCerts(false, secret); err != nil {
 			return false, err
 			return false, err
 		}
 		}
-		if r.RestartOnSecretRefresh {
-			os.Exit(0)
-		}
 		return true, nil
 		return true, nil
 	}
 	}
 	return true, nil
 	return true, nil
@@ -450,21 +466,23 @@ func (r *Reconciler) writeSecret(cert, key []byte, caArtifacts *KeyPairArtifacts
 	return r.Update(context.Background(), secret)
 	return r.Update(context.Background(), secret)
 }
 }
 
 
+// CheckCerts verifies that certificates exist in a given fs location
+// and if they're valid.
 func CheckCerts(c CertInfo, dnsName string, at time.Time) error {
 func CheckCerts(c CertInfo, dnsName string, at time.Time) error {
-	certFile := c.CertDir + "/" + c.CertName
+	certFile := filepath.Join(c.CertDir, c.CertName)
 	_, err := os.Stat(certFile)
 	_, err := os.Stat(certFile)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	ca, err := os.ReadFile(c.CertDir + "/" + c.CAName)
+	ca, err := os.ReadFile(filepath.Join(c.CertDir, c.CAName))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	cert, err := os.ReadFile(c.CertDir + "/" + c.CertName)
+	cert, err := os.ReadFile(filepath.Join(c.CertDir, c.CertName))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	key, err := os.ReadFile(c.CertDir + "/" + c.KeyName)
+	key, err := os.ReadFile(filepath.Join(c.CertDir, c.KeyName))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 7 - 70
pkg/controllers/crds/crds_controller_test.go

@@ -18,7 +18,6 @@ import (
 	"context"
 	"context"
 	"crypto/rsa"
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509"
-	"encoding/json"
 	"os"
 	"os"
 	"testing"
 	"testing"
 	"time"
 	"time"
@@ -26,15 +25,12 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 	client "sigs.k8s.io/controller-runtime/pkg/client/fake"
 	client "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 )
 
 
 const (
 const (
-	setupError              = "Could not setup test"
-	errorSearchingField     = "Error when searching for field"
 	failedCreateCaCerts     = "could not create ca certificates:%v"
 	failedCreateCaCerts     = "could not create ca certificates:%v"
 	failedCreateServerCerts = "could not create server certificates:%v"
 	failedCreateServerCerts = "could not create server certificates:%v"
 	invalidCerts            = "generated certificates are invalid:%v,%v"
 	invalidCerts            = "generated certificates are invalid:%v,%v"
@@ -92,21 +88,6 @@ func newCRD() apiextensionsv1.CustomResourceDefinition {
 		},
 		},
 	}
 	}
 }
 }
-func TestConvertToWebhookInfo(t *testing.T) {
-	rec := newReconciler()
-	info := rec.ConvertToWebhookInfo()
-	if len(info) != 3 {
-		t.Errorf("Convert to WebhookInfo failed. Total resources:%d", len(info))
-	}
-	for _, v := range info {
-		if v.Type != CRDConversion {
-			t.Errorf("Convert to WebhookInfo failed. wrong type:%v", v.Type)
-		}
-		if v.Name != "one" && v.Name != "two" && v.Name != "three" {
-			t.Errorf("Convert to WebhookInfo failed. wrong name:%v", v.Name)
-		}
-	}
-}
 
 
 func TestUpdateCRD(t *testing.T) {
 func TestUpdateCRD(t *testing.T) {
 	rec := newReconciler()
 	rec := newReconciler()
@@ -123,51 +104,26 @@ func TestUpdateCRD(t *testing.T) {
 	}
 	}
 	err := rec.updateCRD(ctx, req)
 	err := rec.updateCRD(ctx, req)
 	if err != nil {
 	if err != nil {
-		t.Errorf("Failed updating CRD:%v", err)
+		t.Errorf("Failed updating CRD: %v", err)
 	}
 	}
 }
 }
 
 
 func TestInjectSvcToConversionWebhook(t *testing.T) {
 func TestInjectSvcToConversionWebhook(t *testing.T) {
 	svc := newService()
 	svc := newService()
 	crd := newCRD()
 	crd := newCRD()
-	crdunmarshalled := make(map[string]interface{})
-	crdJSON, err := json.Marshal(crd)
-	if err != nil {
-		t.Fatal(setupError)
-	}
-	err = json.Unmarshal(crdJSON, &crdunmarshalled)
-	if err != nil {
-		t.Fatal(setupError)
-	}
-	u := unstructured.Unstructured{
-		Object: crdunmarshalled,
-	}
 	name := types.NamespacedName{
 	name := types.NamespacedName{
 		Name:      svc.Name,
 		Name:      svc.Name,
 		Namespace: svc.Namespace,
 		Namespace: svc.Namespace,
 	}
 	}
-	err = injectSvcToConversionWebhook(&u, name)
+	err := injectService(&crd, name)
 	if err != nil {
 	if err != nil {
 		t.Errorf("Failed: error when injecting: %v", err)
 		t.Errorf("Failed: error when injecting: %v", err)
 	}
 	}
-	val, found, err := unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
-	if err != nil {
-		t.Error(errorSearchingField)
-	}
-	if !found {
-		t.Error("fieldNotFound")
-	}
+	val := crd.Spec.Conversion.Webhook.ClientConfig.Service.Name
 	if val != "foo" {
 	if val != "foo" {
 		t.Errorf("Wrong service name injected: %v", val)
 		t.Errorf("Wrong service name injected: %v", val)
 	}
 	}
-
-	val, found, err = unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "service", "namespace")
-	if err != nil {
-		t.Error(errorSearchingField)
-	}
-	if !found {
-		t.Error("fieldNotFound")
-	}
+	val = crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace
 	if val != "default" {
 	if val != "default" {
 		t.Errorf("Wrong service namespace injected: %v", val)
 		t.Errorf("Wrong service namespace injected: %v", val)
 	}
 	}
@@ -176,31 +132,12 @@ func TestInjectSvcToConversionWebhook(t *testing.T) {
 func TestInjectCertToConversionWebhook(t *testing.T) {
 func TestInjectCertToConversionWebhook(t *testing.T) {
 	certPEM := []byte("foobar")
 	certPEM := []byte("foobar")
 	crd := newCRD()
 	crd := newCRD()
-	crdunmarshalled := make(map[string]interface{})
-	crdJSON, err := json.Marshal(crd)
-	if err != nil {
-		t.Fatal(setupError)
-	}
-	err = json.Unmarshal(crdJSON, &crdunmarshalled)
-	if err != nil {
-		t.Fatal(setupError)
-	}
-	u := unstructured.Unstructured{
-		Object: crdunmarshalled,
-	}
-	err = injectCertToConversionWebhook(&u, certPEM)
+	err := injectCert(&crd, certPEM)
 	if err != nil {
 	if err != nil {
 		t.Errorf("Failed: error when injecting: %v", err)
 		t.Errorf("Failed: error when injecting: %v", err)
 	}
 	}
-	val, found, err := unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "caBundle")
-	if err != nil {
-		t.Error(errorSearchingField)
-	}
-	if !found {
-		t.Error("fieldNotFound")
-	}
-	if val != "Zm9vYmFy" {
-		t.Errorf("Wrong certificate name injected: %v", val)
+	if string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle) != "foobar" {
+		t.Errorf("Wrong certificate name injected: %v", string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle))
 	}
 	}
 }
 }
 func TestPopulateSecret(t *testing.T) {
 func TestPopulateSecret(t *testing.T) {

+ 6 - 13
pkg/controllers/crds/suite_test.go

@@ -18,6 +18,7 @@ import (
 	"context"
 	"context"
 	"path/filepath"
 	"path/filepath"
 	"testing"
 	"testing"
+	"time"
 
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	. "github.com/onsi/gomega"
@@ -73,19 +74,11 @@ var _ = BeforeSuite(func() {
 	Expect(err).ToNot(HaveOccurred())
 	Expect(err).ToNot(HaveOccurred())
 	Expect(k8sClient).ToNot(BeNil())
 	Expect(k8sClient).ToNot(BeNil())
 
 
-	err = (&Reconciler{
-		Client:                 k8sClient,
-		Scheme:                 k8sManager.GetScheme(),
-		Log:                    ctrl.Log.WithName("controllers").WithName("CustomResourceDefinition"),
-		SvcName:                "foo",
-		SvcNamespace:           "default",
-		SecretName:             "foo",
-		SecretNamespace:        "default",
-		CrdResources:           []string{"externalsecrets.test.io", "secretstores.test.io", "clustersecretstores.test.io"},
-		CAName:                 "external-secrets",
-		CAOrganization:         "external-secrets",
-		RestartOnSecretRefresh: false,
-	}).SetupWithManager(k8sManager, controller.Options{})
+	rec := New(k8sClient, k8sManager.GetScheme(), log, time.Second*1,
+		"foo", "default", "foo", "default", []string{
+			"secretstores.test.io",
+		})
+	rec.SetupWithManager(k8sManager, controller.Options{})
 	Expect(err).ToNot(HaveOccurred())
 	Expect(err).ToNot(HaveOccurred())
 
 
 	go func() {
 	go func() {

+ 6 - 8
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -36,11 +36,9 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 
 
 	// Loading registered providers.
 	// Loading registered providers.
 	_ "github.com/external-secrets/external-secrets/pkg/provider/register"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/register"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -135,7 +133,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		return ctrl.Result{}, nil
 		return ctrl.Result{}, nil
 	}
 	}
 
 
-	storeProvider, err := schema.GetProvider(store)
+	storeProvider, err := esv1beta1.GetProvider(store)
 	if err != nil {
 	if err != nil {
 		log.Error(err, errStoreProvider)
 		log.Error(err, errStoreProvider)
 		syncCallsError.With(syncCallsMetricLabels).Inc()
 		syncCallsError.With(syncCallsMetricLabels).Inc()
@@ -393,19 +391,19 @@ func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.Ext
 }
 }
 
 
 // getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
 // getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
-func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient provider.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
+func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient esv1beta1.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
 	providerData := make(map[string][]byte)
 	providerData := make(map[string][]byte)
 
 
 	for _, remoteRef := range externalSecret.Spec.DataFrom {
 	for _, remoteRef := range externalSecret.Spec.DataFrom {
 		var secretMap map[string][]byte
 		var secretMap map[string][]byte
 		var err error
 		var err error
-		if len(remoteRef.Find.Tags) > 0 || remoteRef.Find.Name != nil {
-			secretMap, err = providerClient.GetAllSecrets(ctx, remoteRef.Find)
+		if remoteRef.Find != nil {
+			secretMap, err = providerClient.GetAllSecrets(ctx, *remoteRef.Find)
 			if err != nil {
 			if err != nil {
 				return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
 				return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
 			}
 			}
-		} else if remoteRef.Extract.Key != "" {
-			secretMap, err = providerClient.GetSecretMap(ctx, remoteRef.Extract)
+		} else if remoteRef.Extract != nil {
+			secretMap, err = providerClient.GetSecretMap(ctx, *remoteRef.Extract)
 			if err != nil {
 			if err != nil {
 				return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
 				return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
 			}
 			}

+ 11 - 11
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -30,8 +30,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
 	"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
 )
 )
 
 
@@ -187,7 +185,9 @@ var _ = Describe("ExternalSecret controller", func() {
 				}
 				}
 				return true
 				return true
 			},
 			},
-			checkExternalSecret: func(es *esv1beta1.ExternalSecret) {},
+			checkExternalSecret: func(es *esv1beta1.ExternalSecret) {
+				// noop by default
+			},
 			secretStore: &esv1beta1.SecretStore{
 			secretStore: &esv1beta1.SecretStore{
 				ObjectMeta: metav1.ObjectMeta{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      ExternalSecretStore,
 					Name:      ExternalSecretStore,
@@ -540,7 +540,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		}
 		}
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Extract: esv1beta1.ExternalSecretDataRemoteRef{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 					Key: "datamap",
 					Key: "datamap",
 				},
 				},
 			},
 			},
@@ -684,7 +684,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
 		tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Extract: esv1beta1.ExternalSecretDataRemoteRef{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 					Key: remoteKey,
 					Key: remoteKey,
 				},
 				},
 			},
 			},
@@ -726,7 +726,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
 		tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Extract: esv1beta1.ExternalSecretDataRemoteRef{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 					Key: remoteKey,
 					Key: remoteKey,
 				},
 				},
 			},
 			},
@@ -791,7 +791,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		tc.externalSecret.Spec.Data = nil
 		tc.externalSecret.Spec.Data = nil
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Extract: esv1beta1.ExternalSecretDataRemoteRef{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 					Key: remoteKey,
 					Key: remoteKey,
 				},
 				},
 			},
 			},
@@ -813,7 +813,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		tc.externalSecret.Spec.Data = nil
 		tc.externalSecret.Spec.Data = nil
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Find: esv1beta1.ExternalSecretFind{
+				Find: &esv1beta1.ExternalSecretFind{
 					Name: &esv1beta1.FindName{
 					Name: &esv1beta1.FindName{
 						RegExp: "foobar",
 						RegExp: "foobar",
 					},
 					},
@@ -844,7 +844,7 @@ var _ = Describe("ExternalSecret controller", func() {
 
 
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
 			{
 			{
-				Extract: esv1beta1.ExternalSecretDataRemoteRef{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
 					Key: remoteKey,
 					Key: remoteKey,
 				},
 				},
 			},
 			},
@@ -925,7 +925,7 @@ var _ = Describe("ExternalSecret controller", func() {
 	// a SecretSyncedError status condition must be set
 	// a SecretSyncedError status condition must be set
 	storeConstructErrCondition := func(tc *testCase) {
 	storeConstructErrCondition := func(tc *testCase) {
 		fakeProvider.WithNew(func(context.Context, esv1beta1.GenericStore, client.Client,
 		fakeProvider.WithNew(func(context.Context, esv1beta1.GenericStore, client.Client,
-			string) (provider.SecretsClient, error) {
+			string) (esv1beta1.SecretsClient, error) {
 			return nil, fmt.Errorf("artificial constructor error")
 			return nil, fmt.Errorf("artificial constructor error")
 		})
 		})
 		tc.checkCondition = func(es *esv1beta1.ExternalSecret) bool {
 		tc.checkCondition = func(es *esv1beta1.ExternalSecret) bool {
@@ -1392,7 +1392,7 @@ func externalSecretConditionShouldBe(name, ns string, ct esv1beta1.ExternalSecre
 
 
 func init() {
 func init() {
 	fakeProvider = fake.New()
 	fakeProvider = fake.New()
-	schema.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
+	esv1beta1.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
 		AWS: &esv1beta1.AWSProvider{
 		AWS: &esv1beta1.AWSProvider{
 			Service: esv1beta1.AWSServiceSecretsManager,
 			Service: esv1beta1.AWSServiceSecretsManager,
 		},
 		},

+ 1 - 2
pkg/controllers/secretstore/common.go

@@ -25,7 +25,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 )
 
 
 const (
 const (
@@ -77,7 +76,7 @@ func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl
 // if it fails sets a condition and writes events.
 // if it fails sets a condition and writes events.
 func validateStore(ctx context.Context, namespace string, store esapi.GenericStore,
 func validateStore(ctx context.Context, namespace string, store esapi.GenericStore,
 	client client.Client, recorder record.EventRecorder) error {
 	client client.Client, recorder record.EventRecorder) error {
-	storeProvider, err := schema.GetProvider(store)
+	storeProvider, err := esapi.GetProvider(store)
 	if err != nil {
 	if err != nil {
 		cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionFalse, esapi.ReasonInvalidStore, errUnableGetProvider)
 		cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionFalse, esapi.ReasonInvalidStore, errUnableGetProvider)
 		SetExternalSecretCondition(store, *cond)
 		SetExternalSecretCondition(store, *cond)

+ 100 - 0
pkg/controllers/webhookconfig/suite_test.go

@@ -0,0 +1,100 @@
+/*
+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
+
+    http://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 webhookconfig
+
+import (
+	"context"
+	"path/filepath"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+var cancel context.CancelFunc
+var reconciler *Reconciler
+
+const (
+	ctrlSvcName         = "foo"
+	ctrlSvcNamespace    = "default"
+	ctrlSecretName      = "foo"
+	ctrlSecretNamespace = "default"
+)
+
+func TestAPIs(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Controller Suite")
+}
+
+var _ = BeforeSuite(func() {
+	log := zap.New(zap.WriteTo(GinkgoWriter))
+	logf.SetLogger(log)
+
+	By("bootstrapping test environment")
+	testEnv = &envtest.Environment{
+		CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "deploy", "crds")},
+	}
+
+	var ctx context.Context
+	ctx, cancel = context.WithCancel(context.Background())
+
+	var err error
+	cfg, err = testEnv.Start()
+	Expect(err).ToNot(HaveOccurred())
+	Expect(cfg).ToNot(BeNil())
+
+	err = esapi.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
+		Scheme:             scheme.Scheme,
+		MetricsBindAddress: "0", // avoid port collision when testing
+	})
+	Expect(err).ToNot(HaveOccurred())
+
+	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+	Expect(err).ToNot(HaveOccurred())
+	Expect(k8sClient).ToNot(BeNil())
+
+	reconciler = New(k8sClient, k8sManager.GetScheme(), ctrl.Log, ctrlSvcName, ctrlSvcNamespace, ctrlSecretName, ctrlSecretNamespace, time.Second)
+	reconciler.SetupWithManager(k8sManager, controller.Options{})
+	Expect(err).ToNot(HaveOccurred())
+
+	go func() {
+		defer GinkgoRecover()
+		Expect(k8sManager.Start(ctx)).ToNot(HaveOccurred())
+	}()
+})
+
+var _ = AfterSuite(func() {
+	By("tearing down the test environment")
+	cancel() // stop manager
+	err := testEnv.Stop()
+	Expect(err).ToNot(HaveOccurred())
+})

+ 186 - 0
pkg/controllers/webhookconfig/webhookconfig.go

@@ -0,0 +1,186 @@
+/*
+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
+
+    http://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 webhookconfig
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/go-logr/logr"
+	admissionregistration "k8s.io/api/admissionregistration/v1"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+)
+
+type Reconciler struct {
+	client.Client
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	recorder        record.EventRecorder
+	RequeueDuration time.Duration
+	SvcName         string
+	SvcNamespace    string
+	SecretName      string
+	SecretNamespace string
+
+	rdyMu *sync.Mutex
+	ready bool
+}
+
+func New(k8sClient client.Client, scheme *runtime.Scheme,
+	log logr.Logger, svcName, svcNamespace, secretName, secretNamespace string,
+	requeueInterval time.Duration) *Reconciler {
+	return &Reconciler{
+		Client:          k8sClient,
+		Scheme:          scheme,
+		Log:             log,
+		RequeueDuration: requeueInterval,
+		SvcName:         svcName,
+		SvcNamespace:    svcNamespace,
+		SecretName:      secretName,
+		SecretNamespace: secretNamespace,
+		rdyMu:           &sync.Mutex{},
+		ready:           false,
+	}
+}
+
+const (
+	wellKnownLabelKey   = "external-secrets.io/component"
+	wellKnownLabelValue = "webhook"
+
+	ReasonUpdateFailed   = "UpdateFailed"
+	errWebhookNotReady   = "webhook not ready"
+	errSubsetsNotReady   = "subsets not ready"
+	errAddressesNotReady = "addresses not ready"
+	errCACertNotReady    = "ca cert not yet ready"
+
+	caCertName = "ca.crt"
+)
+
+func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	log := r.Log.WithValues("Webhookconfig", req.NamespacedName)
+	var cfg admissionregistration.ValidatingWebhookConfiguration
+	err := r.Get(ctx, req.NamespacedName, &cfg)
+	if apierrors.IsNotFound(err) {
+		return ctrl.Result{}, nil
+	} else if err != nil {
+		log.Error(err, "unable to get Webhookconfig")
+		return ctrl.Result{}, err
+	}
+
+	if cfg.Labels[wellKnownLabelKey] != wellKnownLabelValue {
+		log.Info("ignoring webhook due to missing labels", wellKnownLabelKey, wellKnownLabelValue)
+		return ctrl.Result{}, nil
+	}
+
+	log.Info("updating webhook config")
+	err = r.updateConfig(ctx, &cfg)
+	if err != nil {
+		log.Error(err, "could not update webhook config")
+		r.recorder.Eventf(&cfg, v1.EventTypeWarning, ReasonUpdateFailed, err.Error())
+		return ctrl.Result{
+			RequeueAfter: time.Minute,
+		}, err
+	}
+	log.Info("updated webhook config")
+
+	// right now we only have one single
+	// webhook config we care about
+	r.rdyMu.Lock()
+	defer r.rdyMu.Unlock()
+	r.ready = true
+	return ctrl.Result{
+		RequeueAfter: r.RequeueDuration,
+	}, nil
+}
+
+func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
+	r.recorder = mgr.GetEventRecorderFor("validating-webhook-configuration")
+	return ctrl.NewControllerManagedBy(mgr).
+		WithOptions(opts).
+		For(&admissionregistration.ValidatingWebhookConfiguration{}).
+		Complete(r)
+}
+
+func (r *Reconciler) ReadyCheck(_ *http.Request) error {
+	r.rdyMu.Lock()
+	defer r.rdyMu.Unlock()
+	if !r.ready {
+		return fmt.Errorf(errWebhookNotReady)
+	}
+	var eps v1.Endpoints
+	err := r.Get(context.TODO(), types.NamespacedName{
+		Name:      r.SvcName,
+		Namespace: r.SvcNamespace,
+	}, &eps)
+	if err != nil {
+		return err
+	}
+	if len(eps.Subsets) == 0 {
+		return fmt.Errorf(errSubsetsNotReady)
+	}
+	if len(eps.Subsets[0].Addresses) == 0 {
+		return fmt.Errorf(errAddressesNotReady)
+	}
+	return nil
+}
+
+// reads the ca cert and updates the webhook config.
+func (r *Reconciler) updateConfig(ctx context.Context, cfg *admissionregistration.ValidatingWebhookConfiguration) error {
+	secret := v1.Secret{}
+	secretName := types.NamespacedName{
+		Name:      r.SecretName,
+		Namespace: r.SecretNamespace,
+	}
+	err := r.Get(context.Background(), secretName, &secret)
+	if err != nil {
+		return err
+	}
+
+	crt, ok := secret.Data[caCertName]
+	if !ok {
+		return fmt.Errorf(errCACertNotReady)
+	}
+	if err := r.inject(cfg, r.SvcName, r.SvcNamespace, crt); err != nil {
+		return err
+	}
+	return r.Update(ctx, cfg)
+}
+
+func (r *Reconciler) inject(cfg *admissionregistration.ValidatingWebhookConfiguration, svcName, svcNamespace string, certData []byte) error {
+	r.Log.Info("injecting ca certificate and service names", "cacrt", base64.StdEncoding.EncodeToString(certData), "name", cfg.Name)
+	for idx, w := range cfg.Webhooks {
+		if !strings.HasSuffix(w.Name, "external-secrets.io") {
+			r.Log.Info("skipping webhook", "name", cfg.Name, "webhook-name", w.Name)
+			continue
+		}
+		// we just patch the relevant fields
+		cfg.Webhooks[idx].ClientConfig.Service.Name = svcName
+		cfg.Webhooks[idx].ClientConfig.Service.Namespace = svcNamespace
+		cfg.Webhooks[idx].ClientConfig.CABundle = certData
+	}
+	return nil
+}

+ 324 - 0
pkg/controllers/webhookconfig/webhookconfig_test.go

@@ -0,0 +1,324 @@
+/*
+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
+
+    http://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 webhookconfig
+
+import (
+	"bytes"
+	"context"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	admissionregistration "k8s.io/api/admissionregistration/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/pointer"
+)
+
+const defaultCACert = `-----BEGIN CERTIFICATE-----
+MIIDRjCCAi6gAwIBAgIBADANBgkqhkiG9w0BAQsFADA2MRkwFwYDVQQKExBleHRl
+cm5hbC1zZWNyZXRzMRkwFwYDVQQDExBleHRlcm5hbC1zZWNyZXRzMB4XDTIyMDIx
+NzEwMDYxMFoXDTMyMDIxNTExMDYxMFowNjEZMBcGA1UEChMQZXh0ZXJuYWwtc2Vj
+cmV0czEZMBcGA1UEAxMQZXh0ZXJuYWwtc2VjcmV0czCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKSINgqU2dBdX8JpPjRHWSdpxuoltGl6xXmQHOhbTXAt
+/STDu7oi6eiFgepQ2QHuWLGwZgbbYnEhtLvw4dUwPcLyv6WIdeiUSA4pdFxL7asc
+WV4tjiRkRTJVrixJTxXpry/CsPqXBlvnu1YGESkrLOYCmA2xnDH8voEBbwYvXXB9
+3g5rOJncSh/7g+H55ZFFyWrIPyDUnfwE3CREjZXpsagFhRYpkuRlXTnU6t0OTEEh
+qLHlZ+ebUzL8NaegEgEHD32PrQPXpls1yurIrsA+I6McWkXGykykYHVK+1a1pL1g
+e+PBkegtwtX+EmB2ux7PVVeB4TTYqzCKbnObW4mJLZkCAwEAAaNfMF0wDgYDVR0P
+AQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHgSu/Im2gyu4TU0
+AWrMSFbtoVokMBsGA1UdEQQUMBKCEGV4dGVybmFsLXNlY3JldHMwDQYJKoZIhvcN
+AQELBQADggEBAJU88jCcPsAHN8DKLu+QMCoKYbeftX4gXxyoijGSde2w2O8NPtMP
+awu4Y5x3LNTwyIIxXi78UD0RI53GbUgHvS+X9v6CC2IZMS65xqKR+EsjzEh7Ldbm
+vZoF4ZDnfb2s5SK6MeYf67BE7XWpGfbHmjt6h80xsYjL6ovcik+dlu/AixMyLslS
+tDbMybAR8kR0zdQLYcZq7XEX5QsOO8qBn5rTfD6MiYik8ZrP7FqUMHyVpHiBuNio
+krnSOvynvuA9mlf2F2727dMt2Ij9uER+9QnhWBQex1h8CwALmm2k9G5Gt+RjB8oe
+lNjvmHAXUfOE/cbD7EP++X17kWt41FjmePc=
+-----END CERTIFICATE-----
+`
+
+type testCase struct {
+	vwc       *admissionregistration.ValidatingWebhookConfiguration
+	service   *corev1.Service
+	endpoints *corev1.Endpoints
+	secret    *corev1.Secret
+	assert    func()
+}
+
+var _ = Describe("ValidatingWebhookConfig reconcile", Ordered, func() {
+	var test *testCase
+
+	BeforeEach(func() {
+		test = makeDefaultTestcase()
+	})
+
+	AfterEach(func() {
+		ctx := context.Background()
+		k8sClient.Delete(ctx, test.vwc)
+		k8sClient.Delete(ctx, test.secret)
+		k8sClient.Delete(ctx, test.service)
+		k8sClient.Delete(ctx, test.endpoints)
+	})
+
+	// Should patch VWC
+	PatchAndReady := func(tc *testCase) {
+		tc.endpoints.Subsets = nil
+
+		// endpoints become ready in a moment
+		go func() {
+			<-time.After(time.Second * 4)
+			eps := makeEndpoints()
+			err := k8sClient.Update(context.Background(), eps)
+			Expect(err).ToNot(HaveOccurred())
+		}()
+		tc.assert = func() {
+			Eventually(func() bool {
+				// the controller should become ready at some point!
+				err := reconciler.ReadyCheck(nil)
+				return err == nil
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+
+			Eventually(func() bool {
+				var vwc admissionregistration.ValidatingWebhookConfiguration
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name: tc.vwc.Name,
+				}, &vwc)
+				if err != nil {
+					return false
+				}
+				for _, wc := range vwc.Webhooks {
+					if !bytes.Equal(wc.ClientConfig.CABundle, []byte(defaultCACert)) {
+						return false
+					}
+					if wc.ClientConfig.Service == nil {
+						return false
+					}
+					if wc.ClientConfig.Service.Name != ctrlSvcName {
+						return false
+					}
+					if wc.ClientConfig.Service.Namespace != ctrlSvcNamespace {
+						return false
+					}
+				}
+				return true
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+		}
+	}
+
+	IgnoreNoMatch := func(tc *testCase) {
+		delete(tc.vwc.ObjectMeta.Labels, wellKnownLabelKey)
+		tc.assert = func() {
+			Consistently(func() bool {
+				var vwc admissionregistration.ValidatingWebhookConfiguration
+
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name: tc.vwc.Name,
+				}, &vwc)
+				if err != nil {
+					return false
+				}
+				for _, wc := range vwc.Webhooks {
+					if bytes.Equal(wc.ClientConfig.CABundle, []byte(defaultCACert)) {
+						return false
+					}
+					if wc.ClientConfig.Service.Name == ctrlSvcName {
+						return false
+					}
+					if wc.ClientConfig.Service.Namespace == ctrlSvcNamespace {
+						return false
+					}
+				}
+				return true
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+		}
+	}
+
+	// Should patch and update VWC after requeue duration has passed
+	PatchAndUpdate := func(tc *testCase) {
+		foobar := "new value"
+		// ca cert will change after some time
+		go func() {
+			<-time.After(time.Second * 4)
+			sec := makeSecret()
+			sec.Data[caCertName] = []byte(foobar)
+			err := k8sClient.Update(context.Background(), sec)
+			Expect(err).ToNot(HaveOccurred())
+		}()
+		tc.assert = func() {
+
+			Eventually(func() bool {
+				var vwc admissionregistration.ValidatingWebhookConfiguration
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name: tc.vwc.Name,
+				}, &vwc)
+				if err != nil {
+					return false
+				}
+				for _, wc := range vwc.Webhooks {
+					if !bytes.Equal(wc.ClientConfig.CABundle, []byte(foobar)) {
+						return false
+					}
+					if wc.ClientConfig.Service == nil {
+						return false
+					}
+					if wc.ClientConfig.Service.Name != ctrlSvcName {
+						return false
+					}
+					if wc.ClientConfig.Service.Namespace != ctrlSvcNamespace {
+						return false
+					}
+				}
+				return true
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+		}
+	}
+
+	DescribeTable("Controller Reconcile logic", func(muts ...func(tc *testCase)) {
+		for _, mut := range muts {
+			mut(test)
+		}
+		ctx := context.Background()
+
+		err := k8sClient.Create(ctx, test.vwc)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = k8sClient.Create(ctx, test.secret)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = k8sClient.Create(ctx, test.service)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = k8sClient.Create(ctx, test.endpoints)
+		Expect(err).ToNot(HaveOccurred())
+
+		test.assert()
+	},
+
+		Entry("should patch matching webhook configs", PatchAndReady),
+		Entry("should update vwc with new ca cert after requeue duration", PatchAndUpdate),
+		Entry("should ignore when vwc labels are missing", IgnoreNoMatch),
+	)
+
+})
+
+func makeValidatingWebhookConfig() *admissionregistration.ValidatingWebhookConfiguration {
+	return &admissionregistration.ValidatingWebhookConfiguration{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "name-shouldnt-matter",
+			Labels: map[string]string{
+				wellKnownLabelKey: wellKnownLabelValue,
+			},
+		},
+		Webhooks: []admissionregistration.ValidatingWebhook{
+			{
+				Name:                    "secretstores.external-secrets.io",
+				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.StringPtr(string(admissionregistration.SideEffectClassNone))),
+				AdmissionReviewVersions: []string{"v1"},
+				ClientConfig: admissionregistration.WebhookClientConfig{
+					CABundle: []byte("Cg=="),
+					Service: &admissionregistration.ServiceReference{
+						Name:      "noop",
+						Namespace: "noop",
+						Path:      pointer.StringPtr("/validate-secretstore"),
+					},
+				},
+			},
+			{
+				Name:                    "clustersecretstores.external-secrets.io",
+				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.StringPtr(string(admissionregistration.SideEffectClassNone))),
+				AdmissionReviewVersions: []string{"v1"},
+				ClientConfig: admissionregistration.WebhookClientConfig{
+					CABundle: []byte("Cg=="),
+					Service: &admissionregistration.ServiceReference{
+						Name:      "noop",
+						Namespace: "noop",
+						Path:      pointer.StringPtr("/validate-clustersecretstore"),
+					},
+				},
+			},
+		},
+	}
+}
+
+func makeSecret() *corev1.Secret {
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      ctrlSecretName,
+			Namespace: ctrlSecretNamespace,
+		},
+		Data: map[string][]byte{
+			caCertName: []byte(defaultCACert),
+		},
+	}
+}
+
+func makeService() *corev1.Service {
+	return &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      ctrlSvcName,
+			Namespace: ctrlSvcNamespace,
+		},
+		Spec: corev1.ServiceSpec{
+			Ports: []corev1.ServicePort{
+				{
+					Name: "http",
+					Port: 80,
+				},
+			},
+		},
+	}
+}
+
+func makeEndpoints() *corev1.Endpoints {
+	return &corev1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      ctrlSvcName,
+			Namespace: ctrlSvcNamespace,
+		},
+		Subsets: []corev1.EndpointSubset{
+			{
+				Addresses: []corev1.EndpointAddress{
+					{
+						IP: "1.2.3.4",
+					},
+				},
+			},
+		},
+	}
+}
+
+func makeDefaultTestcase() *testCase {
+	return &testCase{
+		assert: func() {
+			// this is a noop by default
+		},
+		vwc:       makeValidatingWebhookConfig(),
+		secret:    makeSecret(),
+		service:   makeService(),
+		endpoints: makeEndpoints(),
+	}
+}

+ 7 - 5
pkg/provider/akeyless/akeyless.go

@@ -24,8 +24,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -56,17 +54,21 @@ type akeylessVaultInterface interface {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
 		Akeyless: &esv1beta1.AkeylessProvider{},
 		Akeyless: &esv1beta1.AkeylessProvider{},
 	})
 	})
 }
 }
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
-func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	return newClient(ctx, store, kube, namespace)
 	return newClient(ctx, store, kube, namespace)
 }
 }
 
 
-func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
+func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	akl := &akeylessBase{
 	akl := &akeylessBase{
 		kube:      kube,
 		kube:      kube,
 		store:     store,
 		store:     store,

+ 6 - 4
pkg/provider/alibaba/kms.go

@@ -26,9 +26,7 @@ import (
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -165,7 +163,7 @@ func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1beta1
 }
 }
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
-func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	alibabaSpec := storeSpec.Provider.Alibaba
 	alibabaSpec := storeSpec.Provider.Alibaba
 	iStore := &Client{
 	iStore := &Client{
@@ -196,8 +194,12 @@ func (kms *KeyManagementService) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (kms *KeyManagementService) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 func init() {
 func init() {
-	schema.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
 		Alibaba: &esv1beta1.AlibabaProvider{},
 		Alibaba: &esv1beta1.AlibabaProvider{},
 	})
 	})
 }
 }

+ 53 - 5
pkg/provider/aws/provider.go

@@ -18,15 +18,15 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 
 
+	"github.com/aws/aws-sdk-go/aws/endpoints"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 	awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
 	awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
 // Provider satisfies the provider interface.
 // Provider satisfies the provider interface.
@@ -35,14 +35,62 @@ type Provider struct{}
 const (
 const (
 	errUnableCreateSession    = "unable to create session: %w"
 	errUnableCreateSession    = "unable to create session: %w"
 	errUnknownProviderService = "unknown AWS Provider Service: %s"
 	errUnknownProviderService = "unknown AWS Provider Service: %s"
+	errRegionNotFound         = "region not found: %s"
 )
 )
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
-func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	return newClient(ctx, store, kube, namespace, awsauth.DefaultSTSProvider)
 	return newClient(ctx, store, kube, namespace, awsauth.DefaultSTSProvider)
 }
 }
 
 
-func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, assumeRoler awsauth.STSProvider) (provider.SecretsClient, error) {
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	prov, err := util.GetAWSProvider(store)
+	if err != nil {
+		return err
+	}
+	err = validateRegion(prov)
+	if err != nil {
+		return err
+	}
+
+	// case: static credentials
+	if prov.Auth.SecretRef != nil {
+		if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.AccessKeyID); err != nil {
+			return fmt.Errorf("invalid Auth.SecretRef.AccessKeyID: %w", err)
+		}
+		if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.SecretAccessKey); err != nil {
+			return fmt.Errorf("invalid Auth.SecretRef.SecretAccessKey: %w", err)
+		}
+	}
+
+	// case: jwt credentials
+	if prov.Auth.JWTAuth != nil && prov.Auth.JWTAuth.ServiceAccountRef != nil {
+		if err := utils.ValidateServiceAccountSelector(store, *prov.Auth.JWTAuth.ServiceAccountRef); err != nil {
+			return fmt.Errorf("invalid Auth.JWT.ServiceAccountRef: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func validateRegion(prov *esv1beta1.AWSProvider) error {
+	resolver := endpoints.DefaultResolver()
+	partitions := resolver.(endpoints.EnumPartitions).Partitions()
+	found := false
+	for _, p := range partitions {
+		for id := range p.Regions() {
+			if id == prov.Region {
+				found = true
+			}
+		}
+	}
+	if !found {
+		return fmt.Errorf(errRegionNotFound, prov.Region)
+	}
+	return nil
+}
+
+func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, assumeRoler awsauth.STSProvider) (esv1beta1.SecretsClient, error) {
 	prov, err := util.GetAWSProvider(store)
 	prov, err := util.GetAWSProvider(store)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -63,7 +111,7 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
 		AWS: &esv1beta1.AWSProvider{},
 		AWS: &esv1beta1.AWSProvider{},
 	})
 	})
 }
 }

+ 197 - 0
pkg/provider/aws/provider_test.go

@@ -21,6 +21,8 @@ import (
 
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -146,3 +148,198 @@ func TestProvider(t *testing.T) {
 		})
 		})
 	}
 	}
 }
 }
+
+const validRegion = "eu-central-1"
+
+func TestValidateStore(t *testing.T) {
+	type args struct {
+		store esv1beta1.GenericStore
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name:    "invalid region",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: "noop.",
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "valid region",
+			args: args{
+				store: &esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid static creds auth / AccessKeyID",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									SecretRef: &esv1beta1.AWSAuthSecretRef{
+										AccessKeyID: esmeta.SecretKeySelector{
+											Name:      "foobar",
+											Namespace: pointer.StringPtr("unacceptable"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid static creds auth / SecretAccessKey",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									SecretRef: &esv1beta1.AWSAuthSecretRef{
+										SecretAccessKey: esmeta.SecretKeySelector{
+											Name:      "foobar",
+											Namespace: pointer.StringPtr("unacceptable"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid static creds auth / SecretAccessKey missing namespace",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: v1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									SecretRef: &esv1beta1.AWSAuthSecretRef{
+										SecretAccessKey: esmeta.SecretKeySelector{
+											Name: "foobar",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid static creds auth / AccessKeyID missing namespace",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: v1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									SecretRef: &esv1beta1.AWSAuthSecretRef{
+										AccessKeyID: esmeta.SecretKeySelector{
+											Name: "foobar",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid jwt auth: missing sa selector namespace",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: v1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									JWTAuth: &esv1beta1.AWSJWTAuth{
+										ServiceAccountRef: &esmeta.ServiceAccountSelector{
+											Name: "foobar",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid jwt auth: not allowed sa selector namespace",
+			wantErr: true,
+			args: args{
+				store: &esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							AWS: &esv1beta1.AWSProvider{
+								Region: validRegion,
+								Auth: esv1beta1.AWSAuth{
+									JWTAuth: &esv1beta1.AWSJWTAuth{
+										ServiceAccountRef: &esmeta.ServiceAccountSelector{
+											Name:      "foobar",
+											Namespace: pointer.StringPtr("unacceptable"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &Provider{}
+			if err := p.ValidateStore(tt.args.store); (err != nil) != tt.wantErr {
+				t.Errorf("Provider.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 4 - 0
pkg/provider/aws/secretsmanager/fake/fake.go

@@ -42,6 +42,10 @@ func (sm *Client) GetSecretValue(in *awssm.GetSecretValueInput) (*awssm.GetSecre
 	return nil, fmt.Errorf("test case not found")
 	return nil, fmt.Errorf("test case not found")
 }
 }
 
 
+func (sm *Client) ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error) {
+	return nil, nil
+}
+
 func (sm *Client) cacheKeyForInput(in *awssm.GetSecretValueInput) string {
 func (sm *Client) cacheKeyForInput(in *awssm.GetSecretValueInput) string {
 	var secretID, versionID string
 	var secretID, versionID string
 	if in.SecretId != nil {
 	if in.SecretId != nil {

+ 41 - 5
pkg/provider/azure/keyvault/keyvault.go

@@ -30,8 +30,7 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
 const (
 const (
@@ -52,6 +51,13 @@ const (
 	errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
 	errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
 	errFindSecret            = "could not find secret %s/%s: %w"
 	errFindSecret            = "could not find secret %s/%s: %w"
 	errFindDataKey           = "no data for %q in secret '%s/%s'"
 	errFindDataKey           = "no data for %q in secret '%s/%s'"
+
+	errInvalidStore              = "invalid store"
+	errInvalidStoreSpec          = "invalid store spec"
+	errInvalidStoreProv          = "invalid store provider"
+	errInvalidAzureProv          = "invalid azure keyvault provider"
+	errInvalidSecRefClientID     = "invalid AuthSecretRef.ClientID: %w"
+	errInvalidSecRefClientSecret = "invalid AuthSecretRef.ClientSecret: %w"
 )
 )
 
 
 // interface to keyvault.BaseClient.
 // interface to keyvault.BaseClient.
@@ -71,17 +77,17 @@ type Azure struct {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
 		AzureKV: &esv1beta1.AzureKVProvider{},
 		AzureKV: &esv1beta1.AzureKVProvider{},
 	})
 	})
 }
 }
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
-func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	return newClient(ctx, store, kube, namespace)
 	return newClient(ctx, store, kube, namespace)
 }
 }
 
 
-func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	provider, err := getProvider(store)
 	provider, err := getProvider(store)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -115,6 +121,36 @@ func getProvider(store esv1beta1.GenericStore) (*esv1beta1.AzureKVProvider, erro
 	return spc.Provider.AzureKV, nil
 	return spc.Provider.AzureKV, nil
 }
 }
 
 
+func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
+	if store == nil {
+		return fmt.Errorf(errInvalidStore)
+	}
+	spc := store.GetSpec()
+	if spc == nil {
+		return fmt.Errorf(errInvalidStoreSpec)
+	}
+	if spc.Provider == nil {
+		return fmt.Errorf(errInvalidStoreProv)
+	}
+	p := spc.Provider.AzureKV
+	if p == nil {
+		return fmt.Errorf(errInvalidAzureProv)
+	}
+	if p.AuthSecretRef != nil {
+		if p.AuthSecretRef.ClientID != nil {
+			if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientID); err != nil {
+				return fmt.Errorf(errInvalidSecRefClientID, err)
+			}
+		}
+		if p.AuthSecretRef.ClientSecret != nil {
+			if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientSecret); err != nil {
+				return fmt.Errorf(errInvalidSecRefClientSecret, err)
+			}
+		}
+	}
+	return nil
+}
+
 // Empty GetAllSecrets.
 // Empty GetAllSecrets.
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
 	// TO be implemented

+ 64 - 3
pkg/provider/azure/keyvault/keyvault_test.go

@@ -30,7 +30,6 @@ import (
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	fake "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault/fake"
 	fake "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault/fake"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	utils "github.com/external-secrets/external-secrets/pkg/utils"
 	utils "github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -99,7 +98,7 @@ func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
 		}}},
 		}}},
 	}
 	}
 
 
-	provider, err := schema.GetProvider(&store)
+	provider, err := esv1beta1.GetProvider(&store)
 	tassert.Nil(t, err, "the return err should be nil")
 	tassert.Nil(t, err, "the return err should be nil")
 	k8sClient := clientfake.NewClientBuilder().Build()
 	k8sClient := clientfake.NewClientBuilder().Build()
 	secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
@@ -127,7 +126,7 @@ func TestNewClientNoCreds(t *testing.T) {
 			TenantID: &tenantID,
 			TenantID: &tenantID,
 		}}},
 		}}},
 	}
 	}
-	provider, err := schema.GetProvider(&store)
+	provider, err := esv1beta1.GetProvider(&store)
 	tassert.Nil(t, err, "the return err should be nil")
 	tassert.Nil(t, err, "the return err should be nil")
 	k8sClient := clientfake.NewClientBuilder().Build()
 	k8sClient := clientfake.NewClientBuilder().Build()
 	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
@@ -381,3 +380,65 @@ func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 		Version: "default",
 		Version: "default",
 	}
 	}
 }
 }
+
+func TestValidateStore(t *testing.T) {
+	type args struct {
+		auth esv1beta1.AzureKVAuth
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name:    "empty auth",
+			wantErr: false,
+		},
+		{
+			name:    "empty client id",
+			wantErr: false,
+			args: args{
+				auth: esv1beta1.AzureKVAuth{},
+			},
+		},
+		{
+			name:    "invalid client id",
+			wantErr: true,
+			args: args{
+				auth: esv1beta1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{
+						Namespace: pointer.StringPtr("invalid"),
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid client secret",
+			wantErr: true,
+			args: args{
+				auth: esv1beta1.AzureKVAuth{
+					ClientSecret: &v1.SecretKeySelector{
+						Namespace: pointer.StringPtr("invalid"),
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &Azure{}
+			store := &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						AzureKV: &esv1beta1.AzureKVProvider{
+							AuthSecretRef: &tt.args.auth,
+						},
+					},
+				},
+			}
+			if err := a.ValidateStore(store); (err != nil) != tt.wantErr {
+				t.Errorf("Azure.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 6 - 4
pkg/provider/fake/fake.go

@@ -21,8 +21,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 )
 
 
 var (
 var (
@@ -35,7 +33,7 @@ type Provider struct {
 	config *esv1beta1.FakeProvider
 	config *esv1beta1.FakeProvider
 }
 }
 
 
-func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	cfg, err := getProvider(store)
 	cfg, err := getProvider(store)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -99,8 +97,12 @@ func (p *Provider) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 func init() {
 func init() {
-	schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
 		Fake: &esv1beta1.FakeProvider{},
 		Fake: &esv1beta1.FakeProvider{},
 	})
 	})
 }
 }

+ 37 - 4
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -30,8 +30,6 @@ import (
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -52,6 +50,13 @@ const (
 	errUninitalizedGCPProvider                = "provider GCP is not initialized"
 	errUninitalizedGCPProvider                = "provider GCP is not initialized"
 	errClientGetSecretAccess                  = "unable to access Secret from SecretManager Client: %w"
 	errClientGetSecretAccess                  = "unable to access Secret from SecretManager Client: %w"
 	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
 	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
+
+	errInvalidStore         = "invalid store"
+	errInvalidStoreSpec     = "invalid store spec"
+	errInvalidStoreProv     = "invalid store provider"
+	errInvalidGCPProv       = "invalid gcp secrets manager provider"
+	errInvalidAuthSecretRef = "invalid auth secret ref: %w"
+	errInvalidWISARef       = "invalid workload identity service account reference: %w"
 )
 )
 
 
 type GoogleSecretManagerClient interface {
 type GoogleSecretManagerClient interface {
@@ -132,7 +137,7 @@ func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore
 }
 }
 
 
 // NewClient constructs a GCP Provider.
 // NewClient constructs a GCP Provider.
-func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
 		return nil, fmt.Errorf(errGCPSMStore)
 		return nil, fmt.Errorf(errGCPSMStore)
@@ -270,8 +275,36 @@ func (sm *ProviderGCP) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (sm *ProviderGCP) ValidateStore(store esv1beta1.GenericStore) error {
+	if store == nil {
+		return fmt.Errorf(errInvalidStore)
+	}
+	spc := store.GetSpec()
+	if spc == nil {
+		return fmt.Errorf(errInvalidStoreSpec)
+	}
+	if spc.Provider == nil {
+		return fmt.Errorf(errInvalidStoreProv)
+	}
+	p := spc.Provider.GCPSM
+	if p == nil {
+		return fmt.Errorf(errInvalidGCPProv)
+	}
+	if p.Auth.SecretRef != nil {
+		if err := utils.ValidateSecretSelector(store, p.Auth.SecretRef.SecretAccessKey); err != nil {
+			return fmt.Errorf(errInvalidAuthSecretRef, err)
+		}
+	}
+	if p.Auth.WorkloadIdentity != nil {
+		if err := utils.ValidateServiceAccountSelector(store, p.Auth.WorkloadIdentity.ServiceAccountRef); err != nil {
+			return fmt.Errorf(errInvalidWISARef, err)
+		}
+	}
+	return nil
+}
+
 func init() {
 func init() {
-	schema.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
 		GCPSM: &esv1beta1.GCPSMProvider{},
 		GCPSM: &esv1beta1.GCPSMProvider{},
 	})
 	})
 }
 }

+ 64 - 0
pkg/provider/gcp/secretmanager/secretsmanager_test.go

@@ -21,8 +21,10 @@ import (
 	"testing"
 	"testing"
 
 
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+	"k8s.io/utils/pointer"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
 )
 )
 
 
@@ -210,3 +212,65 @@ func ErrorContains(out error, want string) bool {
 	}
 	}
 	return strings.Contains(out.Error(), want)
 	return strings.Contains(out.Error(), want)
 }
 }
+
+func TestValidateStore(t *testing.T) {
+	type args struct {
+		auth esv1beta1.GCPSMAuth
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name:    "empty auth",
+			wantErr: false,
+		},
+		{
+			name:    "invalid secret ref",
+			wantErr: true,
+			args: args{
+				auth: esv1beta1.GCPSMAuth{
+					SecretRef: &esv1beta1.GCPSMAuthSecretRef{
+						SecretAccessKey: v1.SecretKeySelector{
+							Name:      "foo",
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:    "invalid wi sa ref",
+			wantErr: true,
+			args: args{
+				auth: esv1beta1.GCPSMAuth{
+					WorkloadIdentity: &esv1beta1.GCPWorkloadIdentity{
+						ServiceAccountRef: v1.ServiceAccountSelector{
+							Name:      "foo",
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sm := &ProviderGCP{}
+			store := &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						GCPSM: &esv1beta1.GCPSMProvider{
+							Auth: tt.args.auth,
+						},
+					},
+				},
+			}
+			if err := sm.ValidateStore(store); (err != nil) != tt.wantErr {
+				t.Errorf("ProviderGCP.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 6 - 4
pkg/provider/gitlab/gitlab.go

@@ -27,8 +27,6 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -63,7 +61,7 @@ type gClient struct {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&Gitlab{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Gitlab{}, &esv1beta1.SecretStoreProvider{
 		Gitlab: &esv1beta1.GitlabProvider{},
 		Gitlab: &esv1beta1.GitlabProvider{},
 	})
 	})
 }
 }
@@ -108,7 +106,7 @@ func NewGitlabProvider() *Gitlab {
 }
 }
 
 
 // Method on Gitlab Provider to set up client with credentials and populate projectID.
 // Method on Gitlab Provider to set up client with credentials and populate projectID.
-func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
 		return nil, fmt.Errorf("no store type or wrong store type")
 		return nil, fmt.Errorf("no store type or wrong store type")
@@ -220,3 +218,7 @@ func (g *Gitlab) Close(ctx context.Context) error {
 func (g *Gitlab) Validate() error {
 func (g *Gitlab) Validate() error {
 	return nil
 	return nil
 }
 }
+
+func (g *Gitlab) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}

+ 6 - 4
pkg/provider/ibm/provider.go

@@ -27,8 +27,6 @@ import (
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -323,7 +321,11 @@ func (ibm *providerIBM) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
-func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
+func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	ibmSpec := storeSpec.Provider.IBM
 	ibmSpec := storeSpec.Provider.IBM
 
 
@@ -376,7 +378,7 @@ func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericSt
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
 		IBM: &esv1beta1.IBMProvider{},
 		IBM: &esv1beta1.IBMProvider{},
 	})
 	})
 }
 }

+ 7 - 5
pkg/provider/kubernetes/kubernetes.go

@@ -27,8 +27,6 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -51,7 +49,7 @@ type ProviderKubernetes struct {
 	Client KClient
 	Client KClient
 }
 }
 
 
-var _ provider.SecretsClient = &ProviderKubernetes{}
+var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
 
 
 type BaseClient struct {
 type BaseClient struct {
 	kube        kclient.Client
 	kube        kclient.Client
@@ -65,13 +63,13 @@ type BaseClient struct {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&ProviderKubernetes{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&ProviderKubernetes{}, &esv1beta1.SecretStoreProvider{
 		Kubernetes: &esv1beta1.KubernetesProvider{},
 		Kubernetes: &esv1beta1.KubernetesProvider{},
 	})
 	})
 }
 }
 
 
 // NewClient constructs a Kubernetes Provider.
 // NewClient constructs a Kubernetes Provider.
-func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Kubernetes == nil {
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Kubernetes == nil {
 		return nil, fmt.Errorf("no store type or wrong store type")
 		return nil, fmt.Errorf("no store type or wrong store type")
@@ -233,3 +231,7 @@ func (k *BaseClient) fetchSecretKey(ctx context.Context, key esmeta.SecretKeySel
 func (k *ProviderKubernetes) Validate() error {
 func (k *ProviderKubernetes) Validate() error {
 	return nil
 	return nil
 }
 }
+
+func (k *ProviderKubernetes) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}

+ 6 - 4
pkg/provider/oracle/oracle.go

@@ -27,9 +27,7 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
@@ -124,7 +122,7 @@ func (vms *VaultManagementService) GetSecretMap(ctx context.Context, ref esv1bet
 }
 }
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
-func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	oracleSpec := storeSpec.Provider.Oracle
 	oracleSpec := storeSpec.Provider.Oracle
 
 
@@ -225,8 +223,12 @@ func (vms *VaultManagementService) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (vms *VaultManagementService) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 func init() {
 func init() {
-	schema.Register(&VaultManagementService{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&VaultManagementService{}, &esv1beta1.SecretStoreProvider{
 		Oracle: &esv1beta1.OracleProvider{},
 		Oracle: &esv1beta1.OracleProvider{},
 	})
 	})
 }
 }

+ 10 - 8
pkg/provider/testing/fake/fake.go

@@ -20,16 +20,14 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 )
 
 
-var _ provider.Provider = &Client{}
+var _ esv1beta1.Provider = &Client{}
 
 
 // Client is a fake client for testing.
 // Client is a fake client for testing.
 type Client struct {
 type Client struct {
 	NewFn func(context.Context, esv1beta1.GenericStore, client.Client,
 	NewFn func(context.Context, esv1beta1.GenericStore, client.Client,
-		string) (provider.SecretsClient, error)
+		string) (esv1beta1.SecretsClient, error)
 	GetSecretFn     func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
 	GetSecretFn     func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
 	GetSecretMapFn  func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
 	GetSecretMapFn  func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
 	GetAllSecretsFn func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error)
 	GetAllSecretsFn func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error)
@@ -49,7 +47,7 @@ func New() *Client {
 		},
 		},
 	}
 	}
 
 
-	v.NewFn = func(context.Context, esv1beta1.GenericStore, client.Client, string) (provider.SecretsClient, error) {
+	v.NewFn = func(context.Context, esv1beta1.GenericStore, client.Client, string) (esv1beta1.SecretsClient, error) {
 		return v, nil
 		return v, nil
 	}
 	}
 
 
@@ -58,7 +56,7 @@ func New() *Client {
 
 
 // RegisterAs registers the fake client in the schema.
 // RegisterAs registers the fake client in the schema.
 func (v *Client) RegisterAs(provider *esv1beta1.SecretStoreProvider) {
 func (v *Client) RegisterAs(provider *esv1beta1.SecretStoreProvider) {
-	schema.ForceRegister(v, provider)
+	esv1beta1.ForceRegister(v, provider)
 }
 }
 
 
 // GetSecret implements the provider.Provider interface.
 // GetSecret implements the provider.Provider interface.
@@ -92,6 +90,10 @@ func (v *Client) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (v *Client) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 // WithGetSecretMap wraps the secret data map returned by this fake provider.
 // WithGetSecretMap wraps the secret data map returned by this fake provider.
 func (v *Client) WithGetSecretMap(secData map[string][]byte, err error) *Client {
 func (v *Client) WithGetSecretMap(secData map[string][]byte, err error) *Client {
 	v.GetSecretMapFn = func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	v.GetSecretMapFn = func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
@@ -110,13 +112,13 @@ func (v *Client) WithGetAllSecrets(secData map[string][]byte, err error) *Client
 
 
 // WithNew wraps the fake provider factory function.
 // WithNew wraps the fake provider factory function.
 func (v *Client) WithNew(f func(context.Context, esv1beta1.GenericStore, client.Client,
 func (v *Client) WithNew(f func(context.Context, esv1beta1.GenericStore, client.Client,
-	string) (provider.SecretsClient, error)) *Client {
+	string) (esv1beta1.SecretsClient, error)) *Client {
 	v.NewFn = f
 	v.NewFn = f
 	return v
 	return v
 }
 }
 
 
 // NewClient returns a new fake provider.
 // NewClient returns a new fake provider.
-func (v *Client) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (v *Client) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	c, err := v.NewFn(ctx, store, kube, namespace)
 	c, err := v.NewFn(ctx, store, kube, namespace)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 76 - 6
pkg/provider/vault/vault.go

@@ -37,13 +37,12 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
 var (
 var (
-	_ provider.Provider      = &connector{}
-	_ provider.SecretsClient = &client{}
+	_ esv1beta1.Provider      = &connector{}
+	_ esv1beta1.SecretsClient = &client{}
 )
 )
 
 
 const (
 const (
@@ -79,6 +78,19 @@ const (
 
 
 	errUnknownCAProvider = "unknown caProvider type given"
 	errUnknownCAProvider = "unknown caProvider type given"
 	errCANamespace       = "cannot read secret for CAProvider due to missing namespace on kind ClusterSecretStore"
 	errCANamespace       = "cannot read secret for CAProvider due to missing namespace on kind ClusterSecretStore"
+
+	errInvalidStore      = "invalid store"
+	errInvalidStoreSpec  = "invalid store spec"
+	errInvalidStoreProv  = "invalid store provider"
+	errInvalidVaultProv  = "invalid vault provider"
+	errInvalidAppRoleSec = "invalid Auth.AppRole.SecretRef: %w"
+	errInvalidClientCert = "invalid Auth.Cert.ClientCert: %w"
+	errInvalidCertSec    = "invalid Auth.Cert.SecretRef: %w"
+	errInvalidJwtSec     = "invalid Auth.Jwt.SecretRef: %w"
+	errInvalidKubeSA     = "invalid Auth.Kubernetes.ServiceAccountRef: %w"
+	errInvalidKubeSec    = "invalid Auth.Kubernetes.SecretRef: %w"
+	errInvalidLdapSec    = "invalid Auth.Ldap.SecretRef: %w"
+	errInvalidTokenRef   = "invalid Auth.TokenSecretRef: %w"
 )
 )
 
 
 type Client interface {
 type Client interface {
@@ -101,7 +113,7 @@ type client struct {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&connector{
+	esv1beta1.Register(&connector{
 		newVaultClient: newVaultClient,
 		newVaultClient: newVaultClient,
 	}, &esv1beta1.SecretStoreProvider{
 	}, &esv1beta1.SecretStoreProvider{
 		Vault: &esv1beta1.VaultProvider{},
 		Vault: &esv1beta1.VaultProvider{},
@@ -116,7 +128,7 @@ type connector struct {
 	newVaultClient func(c *vault.Config) (Client, error)
 	newVaultClient func(c *vault.Config) (Client, error)
 }
 }
 
 
-func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
 		return nil, errors.New(errVaultStore)
 		return nil, errors.New(errVaultStore)
@@ -158,6 +170,64 @@ func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore,
 	return vStore, nil
 	return vStore, nil
 }
 }
 
 
+func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
+	if store == nil {
+		return fmt.Errorf(errInvalidStore)
+	}
+	spc := store.GetSpec()
+	if spc == nil {
+		return fmt.Errorf(errInvalidStoreSpec)
+	}
+	if spc.Provider == nil {
+		return fmt.Errorf(errInvalidStoreProv)
+	}
+	p := spc.Provider.Vault
+	if p == nil {
+		return fmt.Errorf(errInvalidVaultProv)
+	}
+	if p.Auth.AppRole != nil {
+		if err := utils.ValidateSecretSelector(store, p.Auth.AppRole.SecretRef); err != nil {
+			return fmt.Errorf(errInvalidAppRoleSec, err)
+		}
+	}
+	if p.Auth.Cert != nil {
+		if err := utils.ValidateSecretSelector(store, p.Auth.Cert.ClientCert); err != nil {
+			return fmt.Errorf(errInvalidClientCert, err)
+		}
+		if err := utils.ValidateSecretSelector(store, p.Auth.Cert.SecretRef); err != nil {
+			return fmt.Errorf(errInvalidCertSec, err)
+		}
+	}
+	if p.Auth.Jwt != nil {
+		if err := utils.ValidateSecretSelector(store, p.Auth.Jwt.SecretRef); err != nil {
+			return fmt.Errorf(errInvalidJwtSec, err)
+		}
+	}
+	if p.Auth.Kubernetes != nil {
+		if p.Auth.Kubernetes.ServiceAccountRef != nil {
+			if err := utils.ValidateServiceAccountSelector(store, *p.Auth.Kubernetes.ServiceAccountRef); err != nil {
+				return fmt.Errorf(errInvalidKubeSA, err)
+			}
+		}
+		if p.Auth.Kubernetes.SecretRef != nil {
+			if err := utils.ValidateSecretSelector(store, *p.Auth.Kubernetes.SecretRef); err != nil {
+				return fmt.Errorf(errInvalidKubeSec, err)
+			}
+		}
+	}
+	if p.Auth.Ldap != nil {
+		if err := utils.ValidateSecretSelector(store, p.Auth.Ldap.SecretRef); err != nil {
+			return fmt.Errorf(errInvalidLdapSec, err)
+		}
+	}
+	if p.Auth.TokenSecretRef != nil {
+		if err := utils.ValidateSecretSelector(store, *p.Auth.TokenSecretRef); err != nil {
+			return fmt.Errorf(errInvalidTokenRef, err)
+		}
+	}
+	return nil
+}
+
 // Empty GetAllSecrets.
 // Empty GetAllSecrets.
 func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
 	// TO be implemented

+ 140 - 0
pkg/provider/vault/vault_test.go

@@ -29,6 +29,7 @@ import (
 	vault "github.com/hashicorp/vault/api"
 	vault "github.com/hashicorp/vault/api"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -1070,3 +1071,142 @@ func TestGetSecretPath(t *testing.T) {
 		})
 		})
 	}
 	}
 }
 }
+
+func TestValidateStore(t *testing.T) {
+	type args struct {
+		auth esv1beta1.VaultAuth
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "empty auth",
+			args: args{},
+		},
+
+		{
+			name: "invalid approle with namespace",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					AppRole: &esv1beta1.VaultAppRole{
+						SecretRef: esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid clientcert",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Cert: &esv1beta1.VaultCertAuth{
+						ClientCert: esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid cert secret",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Cert: &esv1beta1.VaultCertAuth{
+						SecretRef: esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid jwt secret",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Jwt: &esv1beta1.VaultJwtAuth{
+						SecretRef: esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid kubernetes sa",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Kubernetes: &esv1beta1.VaultKubernetesAuth{
+						ServiceAccountRef: &esmeta.ServiceAccountSelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid kubernetes secret",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Kubernetes: &esv1beta1.VaultKubernetesAuth{
+						SecretRef: &esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid ldap secret",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					Ldap: &esv1beta1.VaultLdapAuth{
+						SecretRef: esmeta.SecretKeySelector{
+							Namespace: pointer.StringPtr("invalid"),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid token secret",
+			args: args{
+				auth: esv1beta1.VaultAuth{
+					TokenSecretRef: &esmeta.SecretKeySelector{
+						Namespace: pointer.StringPtr("invalid"),
+					},
+				},
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := &connector{
+				newVaultClient: nil,
+			}
+			store := &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Vault: &esv1beta1.VaultProvider{
+							Auth: tt.args.auth,
+						},
+					},
+				},
+			}
+			if err := c.ValidateStore(store); (err != nil) != tt.wantErr {
+				t.Errorf("connector.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 6 - 4
pkg/provider/webhook/webhook.go

@@ -34,8 +34,6 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/template/v2"
 	"github.com/external-secrets/external-secrets/pkg/template/v2"
 )
 )
 
 
@@ -51,12 +49,12 @@ type WebHook struct {
 }
 }
 
 
 func init() {
 func init() {
-	schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
 		Webhook: &esv1beta1.WebhookProvider{},
 		Webhook: &esv1beta1.WebhookProvider{},
 	})
 	})
 }
 }
 
 
-func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	whClient := &WebHook{
 	whClient := &WebHook{
 		kube:      kube,
 		kube:      kube,
 		store:     store,
 		store:     store,
@@ -74,6 +72,10 @@ func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore,
 	return whClient, nil
 	return whClient, nil
 }
 }
 
 
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 func getProvider(store esv1beta1.GenericStore) (*esv1beta1.WebhookProvider, error) {
 func getProvider(store esv1beta1.GenericStore) (*esv1beta1.WebhookProvider, error) {
 	spc := store.GetSpec()
 	spc := store.GetSpec()
 	if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
 	if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {

+ 2 - 3
pkg/provider/webhook/webhook_test.go

@@ -28,7 +28,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
 )
 )
 
 
 type testCase struct {
 type testCase struct {
@@ -268,7 +267,7 @@ func runTestCase(tc testCase, t *testing.T) {
 	}
 	}
 }
 }
 
 
-func testGetSecretMap(tc testCase, t *testing.T, client provider.SecretsClient) {
+func testGetSecretMap(tc testCase, t *testing.T, client esv1beta1.SecretsClient) {
 	testRef := esv1beta1.ExternalSecretDataRemoteRef{
 	testRef := esv1beta1.ExternalSecretDataRemoteRef{
 		Key:     tc.Args.Key,
 		Key:     tc.Args.Key,
 		Version: tc.Args.Version,
 		Version: tc.Args.Version,
@@ -293,7 +292,7 @@ func testGetSecretMap(tc testCase, t *testing.T, client provider.SecretsClient)
 	}
 	}
 }
 }
 
 
-func testGetSecret(tc testCase, t *testing.T, client provider.SecretsClient) {
+func testGetSecret(tc testCase, t *testing.T, client esv1beta1.SecretsClient) {
 	testRef := esv1beta1.ExternalSecretDataRemoteRef{
 	testRef := esv1beta1.ExternalSecretDataRemoteRef{
 		Key:     tc.Args.Key,
 		Key:     tc.Args.Key,
 		Version: tc.Args.Version,
 		Version: tc.Args.Version,

+ 6 - 4
pkg/provider/yandex/lockbox/lockbox.go

@@ -30,8 +30,6 @@ import (
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/grpc"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/grpc"
 )
 )
@@ -66,7 +64,7 @@ func newLockboxProvider(yandexCloudCreator client.YandexCloudCreator) *lockboxPr
 }
 }
 
 
 // NewClient constructs a Yandex Lockbox Provider.
 // NewClient constructs a Yandex Lockbox Provider.
-func (p *lockboxProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+func (p *lockboxProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.YandexLockbox == nil {
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.YandexLockbox == nil {
 		return nil, fmt.Errorf("received invalid Yandex Lockbox SecretStore resource")
 		return nil, fmt.Errorf("received invalid Yandex Lockbox SecretStore resource")
@@ -218,6 +216,10 @@ func (p *lockboxProvider) cleanUpIamTokenMap() {
 	}
 	}
 }
 }
 
 
+func (p *lockboxProvider) ValidateStore(store esv1beta1.GenericStore) error {
+	return nil
+}
+
 // lockboxSecretsClient is a secrets client for Yandex Lockbox.
 // lockboxSecretsClient is a secrets client for Yandex Lockbox.
 type lockboxSecretsClient struct {
 type lockboxSecretsClient struct {
 	lockboxClient client.LockboxClient
 	lockboxClient client.LockboxClient
@@ -327,7 +329,7 @@ func init() {
 		}
 		}
 	}()
 	}()
 
 
-	schema.Register(
+	esv1beta1.Register(
 		lockboxProvider,
 		lockboxProvider,
 		&esv1beta1.SecretStoreProvider{
 		&esv1beta1.SecretStoreProvider{
 			YandexLockbox: &esv1beta1.YandexLockboxProvider{},
 			YandexLockbox: &esv1beta1.YandexLockboxProvider{},

+ 1 - 2
pkg/provider/yandex/lockbox/lockbox_test.go

@@ -34,7 +34,6 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
 )
 )
 
 
@@ -58,7 +57,7 @@ func TestNewClient(t *testing.T) {
 			},
 			},
 		},
 		},
 	}
 	}
-	provider, err := schema.GetProvider(store)
+	provider, err := esv1beta1.GetProvider(store)
 	tassert.Nil(t, err)
 	tassert.Nil(t, err)
 
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	k8sClient := clientfake.NewClientBuilder().Build()

+ 31 - 0
pkg/utils/utils.go

@@ -21,6 +21,9 @@ import (
 	"fmt"
 	"fmt"
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 )
 )
 
 
 // MergeByteMap merges map of byte slices.
 // MergeByteMap merges map of byte slices.
@@ -66,3 +69,31 @@ func ErrorContains(out error, want string) bool {
 	}
 	}
 	return strings.Contains(out.Error(), want)
 	return strings.Contains(out.Error(), want)
 }
 }
+
+// ValidateSecretSelector just checks if the namespace field is present/absent
+// depending on the secret store type.
+// We MUST NOT check the name or key property here. It MAY be defaulted by the provider.
+func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySelector) error {
+	clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
+	if clusterScope && ref.Namespace == nil {
+		return fmt.Errorf("cluster scope requires namespace")
+	}
+	if !clusterScope && ref.Namespace != nil {
+		return fmt.Errorf("namespace not allowed with namespaced SecretStore")
+	}
+	return nil
+}
+
+// ValidateServiceAccountSelector just checks if the namespace field is present/absent
+// depending on the secret store type.
+// We MUST NOT check the name or key property here. It MAY be defaulted by the provider.
+func ValidateServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error {
+	clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
+	if clusterScope && ref.Namespace == nil {
+		return fmt.Errorf("cluster scope requires namespace")
+	}
+	if !clusterScope && ref.Namespace != nil {
+		return fmt.Errorf("namespace not allowed with namespaced SecretStore")
+	}
+	return nil
+}