Browse Source

feat: add generator

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 3 years ago
parent
commit
0ded781c86
66 changed files with 3940 additions and 507 deletions
  1. 4 0
      TODO.md
  2. 35 0
      apis/externalsecrets/v1beta1/externalsecret_types.go
  3. 3 0
      apis/externalsecrets/v1beta1/provider_schema.go
  4. 59 1
      apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
  5. 19 0
      apis/generators/v1alpha1/doc.go
  6. 21 0
      apis/generators/v1alpha1/generator.go
  7. 120 0
      apis/generators/v1alpha1/generator_acr.go
  8. 54 0
      apis/generators/v1alpha1/generator_ecr.go
  9. 44 0
      apis/generators/v1alpha1/generator_fake.go
  10. 46 0
      apis/generators/v1alpha1/generator_gcr.go
  11. 80 0
      apis/generators/v1alpha1/generator_schema.go
  12. 76 0
      apis/generators/v1alpha1/register.go
  13. 425 0
      apis/generators/v1alpha1/zz_generated.deepcopy.go
  14. 1 2
      cmd/certcontroller.go
  15. 4 1
      cmd/root.go
  16. 1 2
      cmd/webhook.go
  17. 85 6
      config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml
  18. 85 5
      config/crds/bases/external-secrets.io_externalsecrets.yaml
  19. 172 0
      config/crds/bases/generators.external-secrets.io_acraccesstokens.yaml
  20. 133 0
      config/crds/bases/generators.external-secrets.io_ecrauthorizationtokens.yaml
  21. 49 0
      config/crds/bases/generators.external-secrets.io_fakes.yaml
  22. 111 0
      config/crds/bases/generators.external-secrets.io_gcraccesstokens.yaml
  23. 2 0
      config/crds/bases/kustomization.yaml
  24. 550 8
      deploy/crds/bundle.yaml
  25. 49 0
      docs/api/generator/acr.md
  26. 26 0
      docs/api/generator/ecr.md
  27. 8 0
      docs/api/generator/fake.md
  28. 30 0
      docs/api/generator/gcr.md
  29. 2 0
      docs/api/generator/index.md
  30. 0 0
      docs/api/spec.md
  31. 1 1
      docs/examples/gitops-using-fluxcd.md
  32. 56 0
      docs/guides/generator.md
  33. 8 2
      docs/guides/introduction.md
  34. 3 3
      docs/index.md
  35. 0 0
      docs/introduction/deprecation-policy.md
  36. 0 0
      docs/introduction/faq.md
  37. 1 1
      docs/introduction/getting-started.md
  38. 5 5
      docs/introduction/overview.md
  39. 5 0
      docs/introduction/stability-support.md
  40. 38 0
      docs/snippets/generator-acr.yaml
  41. 32 0
      docs/snippets/generator-ecr.yaml
  42. 6 0
      docs/snippets/generator-fake.yaml
  43. 27 0
      docs/snippets/generator-gcr.yaml
  44. 35 0
      gen.yaml
  45. 8 1
      go.mod
  46. 21 0
      go.sum
  47. 48 43
      hack/api-docs/mkdocs.yml
  48. 3 3
      hack/crd.generate.sh
  49. 216 116
      pkg/controllers/externalsecret/externalsecret_controller.go
  50. 159 0
      pkg/controllers/externalsecret/externalsecret_controller_test.go
  51. 5 0
      pkg/controllers/externalsecret/suite_test.go
  52. 1 1
      pkg/controllers/secretstore/common.go
  53. 345 0
      pkg/generator/acr/acr.go
  54. 98 0
      pkg/generator/ecr/ecr.go
  55. 58 0
      pkg/generator/fake/fake.go
  56. 71 0
      pkg/generator/gcr/gcr.go
  57. 24 0
      pkg/generator/register/register.go
  58. 76 29
      pkg/provider/aws/auth/auth.go
  59. 21 21
      pkg/provider/azure/keyvault/keyvault.go
  60. 2 2
      pkg/provider/azure/keyvault/keyvault_auth_test.go
  61. 78 0
      pkg/provider/gcp/secretmanager/auth.go
  62. 42 215
      pkg/provider/gcp/secretmanager/client.go
  63. 7 7
      pkg/provider/gcp/secretmanager/client_test.go
  64. 135 0
      pkg/provider/gcp/secretmanager/provider.go
  65. 9 26
      pkg/provider/gcp/secretmanager/workload_identity.go
  66. 2 6
      pkg/provider/gcp/secretmanager/workload_identity_test.go

+ 4 - 0
TODO.md

@@ -0,0 +1,4 @@
+- [ ] password generator
+    - []
+    - [] docs
+- [ ] e2e test suite: fake + password gen

+ 35 - 0
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -16,6 +16,7 @@ package v1beta1
 
 import (
 	corev1 "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -159,6 +160,8 @@ type ExternalSecretData struct {
 	SecretKey string `json:"secretKey"`
 
 	RemoteRef ExternalSecretDataRemoteRef `json:"remoteRef"`
+
+	SourceRef *SourceRef `json:"sourceRef,omitempty"`
 }
 
 // ExternalSecretDataRemoteRef defines Provider data location.
@@ -214,9 +217,11 @@ const (
 
 type ExternalSecretDataFromRemoteRef struct {
 	// Used to extract multiple key/value pairs from one secret
+	// Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.
 	// +optional
 	Extract *ExternalSecretDataRemoteRef `json:"extract,omitempty"`
 	// Used to find secrets based on tags or regular expressions
+	// Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.
 	// +optional
 	Find *ExternalSecretFind `json:"find,omitempty"`
 
@@ -224,6 +229,14 @@ type ExternalSecretDataFromRemoteRef struct {
 	// Multiple Rewrite operations can be provided. They are applied in a layered order (first to last)
 	// +optional
 	Rewrite []ExternalSecretRewrite `json:"rewrite,omitempty"`
+
+	// SourceRef points to a store or generator
+	// which contains secret values ready to use.
+	// Use this in combination with Extract or Find pull values out of
+	// a specific SecretStore.
+	// When sourceRef points to a generator Extract or Find is not supported.
+	// The generator returns a static map of values
+	SourceRef *SourceRef `json:"sourceRef,omitempty"`
 }
 
 type ExternalSecretRewrite struct {
@@ -270,6 +283,7 @@ type FindName struct {
 
 // ExternalSecretSpec defines the desired state of ExternalSecret.
 type ExternalSecretSpec struct {
+	// +optional
 	SecretStoreRef SecretStoreRef `json:"secretStoreRef"`
 	// +kubebuilder:default={creationPolicy:Owner,deletionPolicy:Retain}
 	// +optional
@@ -291,6 +305,27 @@ type ExternalSecretSpec struct {
 	DataFrom []ExternalSecretDataFromRemoteRef `json:"dataFrom,omitempty"`
 }
 
+type SourceRef struct {
+	// +optional
+	SecretStoreRef *SecretStoreRef `json:"storeRef,omitempty"`
+
+	// GeneratorRef points to a generator custom resource in
+	// +optional
+	GeneratorRef *GeneratorRef `json:"generatorRef,omitempty"`
+
+	// Generator generates secret values on demand
+	// A generator is just a embedded type, see apis/generators
+	// for available types.
+	// +optional
+	Generator *apiextensions.JSON `json:"generator,omitempty"`
+}
+
+type GeneratorRef struct {
+	APIVersion string `json:"apiVersion"`
+	Kind       string `json:"kind"`
+	Name       string `json:"name"`
+}
+
 type ExternalSecretConditionType string
 
 const (

+ 3 - 0
apis/externalsecrets/v1beta1/provider_schema.go

@@ -68,6 +68,9 @@ func GetProviderByName(name string) (Provider, bool) {
 
 // GetProvider returns the provider from the generic store.
 func GetProvider(s GenericStore) (Provider, error) {
+	if s == nil {
+		return nil, nil
+	}
 	spec := s.GetSpec()
 	if spec == nil {
 		return nil, fmt.Errorf("no spec found in %#v", s)

+ 59 - 1
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -21,6 +21,7 @@ package v1beta1
 
 import (
 	metav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
@@ -553,6 +554,11 @@ func (in *ExternalSecret) DeepCopyObject() runtime.Object {
 func (in *ExternalSecretData) DeepCopyInto(out *ExternalSecretData) {
 	*out = *in
 	out.RemoteRef = in.RemoteRef
+	if in.SourceRef != nil {
+		in, out := &in.SourceRef, &out.SourceRef
+		*out = new(SourceRef)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretData.
@@ -585,6 +591,11 @@ func (in *ExternalSecretDataFromRemoteRef) DeepCopyInto(out *ExternalSecretDataF
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.SourceRef != nil {
+		in, out := &in.SourceRef, &out.SourceRef
+		*out = new(SourceRef)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretDataFromRemoteRef.
@@ -724,7 +735,9 @@ func (in *ExternalSecretSpec) DeepCopyInto(out *ExternalSecretSpec) {
 	if in.Data != nil {
 		in, out := &in.Data, &out.Data
 		*out = make([]ExternalSecretData, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.DataFrom != nil {
 		in, out := &in.DataFrom, &out.DataFrom
@@ -1010,6 +1023,21 @@ func (in *GCPWorkloadIdentity) DeepCopy() *GCPWorkloadIdentity {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GeneratorRef) DeepCopyInto(out *GeneratorRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorRef.
+func (in *GeneratorRef) DeepCopy() *GeneratorRef {
+	if in == nil {
+		return nil
+	}
+	out := new(GeneratorRef)
+	in.DeepCopyInto(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
@@ -1634,6 +1662,36 @@ func (in *SenhaseguraProvider) DeepCopy() *SenhaseguraProvider {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SourceRef) DeepCopyInto(out *SourceRef) {
+	*out = *in
+	if in.SecretStoreRef != nil {
+		in, out := &in.SecretStoreRef, &out.SecretStoreRef
+		*out = new(SecretStoreRef)
+		**out = **in
+	}
+	if in.GeneratorRef != nil {
+		in, out := &in.GeneratorRef, &out.GeneratorRef
+		*out = new(GeneratorRef)
+		**out = **in
+	}
+	if in.Generator != nil {
+		in, out := &in.Generator, &out.Generator
+		*out = new(apiextensionsv1.JSON)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef.
+func (in *SourceRef) DeepCopy() *SourceRef {
+	if in == nil {
+		return nil
+	}
+	out := new(SourceRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TemplateFrom) DeepCopyInto(out *TemplateFrom) {
 	*out = *in

+ 19 - 0
apis/generators/v1alpha1/doc.go

@@ -0,0 +1,19 @@
+/*
+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 v1alpha1 contains resources for generators
+// +kubebuilder:object:generate=true
+// +groupName=generators.external-secrets.io
+// +versionName=v1alpha1
+package v1alpha1

+ 21 - 0
apis/generators/v1alpha1/generator.go

@@ -0,0 +1,21 @@
+package v1alpha1
+
+import (
+	"context"
+
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// +kubebuilder:object:root=false
+// +kubebuilder:object:generate:false
+// +k8s:deepcopy-gen:interfaces=nil
+// +k8s:deepcopy-gen=nil
+type Generator interface {
+	Generate(
+		ctx context.Context,
+		obj *apiextensions.JSON,
+		kube client.Client,
+		namespace string,
+	) (map[string][]byte, error)
+}

+ 120 - 0
apis/generators/v1alpha1/generator_acr.go

@@ -0,0 +1,120 @@
+/*
+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 v1alpha1
+
+import (
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// ACRAccessTokenSpec
+// see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
+type ACRAccessTokenSpec struct {
+	Auth ACRAuth `json:"auth"`
+	// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
+	TenantID string `json:"tenantId,omitempty"`
+
+	// the domain name of the ACR registry
+	// e.g. foobarexample.azurecr.io
+	ACRRegistry string `json:"registry"`
+
+	// Define the scope for the access token, e.g. pull/push access for a repository.
+	// if not provided it will return a refresh token that has full scope.
+	// Note: you need to pin it down to the repository level, there is no wildcard available.
+	//
+	// examples:
+	// repository:my-repository:pull,push
+	// repository:my-repository:pull
+	//
+	// see docs for details: https://docs.docker.com/registry/spec/auth/scope/
+	// +optional
+	Scope string `json:"scope,omitempty"`
+
+	// EnvironmentType specifies the Azure cloud environment endpoints to use for
+	// connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
+	// The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+	// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+	// +kubebuilder:default=PublicCloud
+	EnvironmentType v1beta1.AzureEnvironmentType `json:"environmentType,omitempty"`
+}
+
+type ACRAuth struct {
+	// ServicePrincipal uses Azure Service Principal credentials to authenticate with Azure.
+	// +optional
+	ServicePrincipal *AzureACRServicePrincipalAuth `json:"servicePrincipal,omitempty"`
+
+	// ManagedIdentity uses Azure Managed Identity to authenticate with Azure.
+	// +optional
+	ManagedIdentity *AzureACRManagedIdentityAuth `json:"managedIdentity,omitempty"`
+
+	// WorkloadIdentity uses Azure Workload Identity to authenticate with Azure.
+	// +optional
+	WorkloadIdentity *AzureACRWorkloadIdentityAuth `json:"workloadIdentity,omitempty"`
+}
+
+type AzureACRServicePrincipalAuth struct {
+	SecretRef AzureACRServicePrincipalAuthSecretRef `json:"secretRef"`
+}
+
+type AzureACRManagedIdentityAuth struct {
+	// If multiple Managed Identity is assigned to the pod, you can select the one to be used
+	IdentityID string `json:"identityId,omitempty"`
+}
+
+type AzureACRWorkloadIdentityAuth struct {
+	// ServiceAccountRef specified the service account
+	// that should be used when authenticating with WorkloadIdentity.
+	// +optional
+	ServiceAccountRef *smmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
+}
+
+// Configuration used to authenticate with Azure using static
+// credentials stored in a Kind=Secret
+type AzureACRServicePrincipalAuthSecretRef struct {
+	// The Azure clientId of the service principle used for authentication.
+	ClientID smmeta.SecretKeySelector `json:"clientId,omitempty"`
+	// The Azure ClientSecret of the service principle used for authentication.
+	ClientSecret smmeta.SecretKeySelector `json:"clientSecret,omitempty"`
+}
+
+// ACRAccessToken returns a Azure Container Registry token
+// that can be used for pushing/pulling images.
+// Note: by default it will return an ACR Refresh Token with full access
+// (depending on the identity).
+// This can be scoped down to the repository level using .spec.scope.
+// In case scope is defined it will return an ACR Access Token.
+//
+// See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md
+//
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={acraccesstoken},shortName=acraccesstoken
+type ACRAccessToken struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec ACRAccessTokenSpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ACRAccessTokenList contains a list of ExternalSecret resources.
+type ACRAccessTokenList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ACRAccessToken `json:"items"`
+}

+ 54 - 0
apis/generators/v1alpha1/generator_ecr.go

@@ -0,0 +1,54 @@
+/*
+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 v1alpha1
+
+import (
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an
+// authorization token.
+// The authorization token is valid for 12 hours.
+// The authorizationToken returned is a base64 encoded string that can be decoded
+// and used in a docker login command to authenticate to a registry.
+// For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
+type ECRAuthorizationTokenSpec struct {
+	// +optional
+	Auth esv1beta1.AWSAuth `json:"auth"`
+	// +optional
+	Role   string `json:"role"`
+	Region string `json:"region"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={ecrauthorizationtoken},shortName=ecrauthorizationtoken
+type ECRAuthorizationToken struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec ECRAuthorizationTokenSpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ECRAuthorizationTokenList contains a list of ExternalSecret resources.
+type ECRAuthorizationTokenList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ECRAuthorizationToken `json:"items"`
+}

+ 44 - 0
apis/generators/v1alpha1/generator_fake.go

@@ -0,0 +1,44 @@
+/*
+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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// FakeSpec
+type FakeSpec struct {
+	Data map[string]string `json:"data,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={fake},shortName=fake
+type Fake struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec FakeSpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// FakeList contains a list of ExternalSecret resources.
+type FakeList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Fake `json:"items"`
+}

+ 46 - 0
apis/generators/v1alpha1/generator_gcr.go

@@ -0,0 +1,46 @@
+/*
+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 v1alpha1
+
+import (
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// GCRAccessTokenSpec
+type GCRAccessTokenSpec struct {
+	Auth      esv1beta1.GCPSMAuth `json:"auth"`
+	ProjectID string              `json:"projectID"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={gcraccesstoken},shortName=gcraccesstoken
+type GCRAccessToken struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec GCRAccessTokenSpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// GCRAccessTokenList contains a list of ExternalSecret resources.
+type GCRAccessTokenList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []GCRAccessToken `json:"items"`
+}

+ 80 - 0
apis/generators/v1alpha1/generator_schema.go

@@ -0,0 +1,80 @@
+/*
+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 v1alpha1
+
+import (
+	"fmt"
+	"sync"
+
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/json"
+)
+
+var builder map[string]Generator
+var buildlock sync.RWMutex
+
+func init() {
+	builder = make(map[string]Generator)
+}
+
+// Register a generator type. Register panics if a
+// backend with the same store is already registered.
+func Register(kind string, g Generator) {
+	buildlock.Lock()
+	defer buildlock.Unlock()
+	_, exists := builder[kind]
+	if exists {
+		panic(fmt.Sprintf("kind %q already registered", kind))
+	}
+
+	builder[kind] = g
+}
+
+// ForceRegister adds to store schema, overwriting a store if
+// already registered. Should only be used for testing.
+func ForceRegister(kind string, g Generator) {
+	buildlock.Lock()
+	builder[kind] = g
+	buildlock.Unlock()
+}
+
+// GetGeneratorByName returns the provider implementation by name.
+func GetGeneratorByName(kind string) (Generator, bool) {
+	buildlock.RLock()
+	f, ok := builder[kind]
+	buildlock.RUnlock()
+	return f, ok
+}
+
+// GetGenerator returns the provider from the generic store.
+func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
+	type unknownGenerator struct {
+		metav1.TypeMeta   `json:",inline"`
+		metav1.ObjectMeta `json:"metadata,omitempty"`
+	}
+	var res unknownGenerator
+	err := json.Unmarshal(obj.Raw, &res)
+	if err != nil {
+		return nil, err
+	}
+	buildlock.RLock()
+	defer buildlock.RUnlock()
+	gen, ok := builder[res.Kind]
+	if !ok {
+		return nil, fmt.Errorf("failed to find registered generator for: %s", string(obj.Raw))
+	}
+	return gen, nil
+}

+ 76 - 0
apis/generators/v1alpha1/register.go

@@ -0,0 +1,76 @@
+/*
+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 v1alpha1
+
+import (
+	"reflect"
+
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+// Package type metadata.
+const (
+	Group   = "generators.external-secrets.io"
+	Version = "v1alpha1"
+)
+
+var (
+	// SchemeGroupVersion is group version used to register these objects.
+	SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version}
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
+	AddToScheme   = SchemeBuilder.AddToScheme
+)
+
+// ECRAuthorizationToken type metadata.
+var (
+	ECRAuthorizationTokenKind             = reflect.TypeOf(ECRAuthorizationToken{}).Name()
+	ECRAuthorizationTokenGroupKind        = schema.GroupKind{Group: Group, Kind: ECRAuthorizationTokenKind}.String()
+	ECRAuthorizationTokenKindAPIVersion   = ECRAuthorizationTokenKind + "." + SchemeGroupVersion.String()
+	ECRAuthorizationTokenGroupVersionKind = SchemeGroupVersion.WithKind(ECRAuthorizationTokenKind)
+)
+
+// GCRAccessToken type metadata.
+var (
+	GCRAccessTokenKind             = reflect.TypeOf(GCRAccessToken{}).Name()
+	GCRAccessTokenGroupKind        = schema.GroupKind{Group: Group, Kind: GCRAccessTokenKind}.String()
+	GCRAccessTokenKindAPIVersion   = GCRAccessTokenKind + "." + SchemeGroupVersion.String()
+	GCRAccessTokenGroupVersionKind = SchemeGroupVersion.WithKind(GCRAccessTokenKind)
+)
+
+// ACRAccessToken type metadata.
+var (
+	ACRAccessTokenKind             = reflect.TypeOf(ACRAccessToken{}).Name()
+	ACRAccessTokenGroupKind        = schema.GroupKind{Group: Group, Kind: ACRAccessTokenKind}.String()
+	ACRAccessTokenKindAPIVersion   = ACRAccessTokenKind + "." + SchemeGroupVersion.String()
+	ACRAccessTokenGroupVersionKind = SchemeGroupVersion.WithKind(ACRAccessTokenKind)
+)
+
+// Fake type metadata.
+var (
+	FakeKind             = reflect.TypeOf(Fake{}).Name()
+	FakeGroupKind        = schema.GroupKind{Group: Group, Kind: FakeKind}.String()
+	FakeKindAPIVersion   = FakeKind + "." + SchemeGroupVersion.String()
+	FakeGroupVersionKind = SchemeGroupVersion.WithKind(FakeKind)
+)
+
+func init() {
+	SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{})
+	SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{})
+	SchemeBuilder.Register(&ACRAccessToken{}, &ACRAccessTokenList{})
+	SchemeBuilder.Register(&Fake{}, &FakeList{})
+}

+ 425 - 0
apis/generators/v1alpha1/zz_generated.deepcopy.go

@@ -0,0 +1,425 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+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.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	"github.com/external-secrets/external-secrets/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ACRAccessToken) DeepCopyInto(out *ACRAccessToken) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessToken.
+func (in *ACRAccessToken) DeepCopy() *ACRAccessToken {
+	if in == nil {
+		return nil
+	}
+	out := new(ACRAccessToken)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ACRAccessToken) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ACRAccessTokenList) DeepCopyInto(out *ACRAccessTokenList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ACRAccessToken, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessTokenList.
+func (in *ACRAccessTokenList) DeepCopy() *ACRAccessTokenList {
+	if in == nil {
+		return nil
+	}
+	out := new(ACRAccessTokenList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ACRAccessTokenList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ACRAccessTokenSpec) DeepCopyInto(out *ACRAccessTokenSpec) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessTokenSpec.
+func (in *ACRAccessTokenSpec) DeepCopy() *ACRAccessTokenSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ACRAccessTokenSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ACRAuth) DeepCopyInto(out *ACRAuth) {
+	*out = *in
+	if in.ServicePrincipal != nil {
+		in, out := &in.ServicePrincipal, &out.ServicePrincipal
+		*out = new(AzureACRServicePrincipalAuth)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ManagedIdentity != nil {
+		in, out := &in.ManagedIdentity, &out.ManagedIdentity
+		*out = new(AzureACRManagedIdentityAuth)
+		**out = **in
+	}
+	if in.WorkloadIdentity != nil {
+		in, out := &in.WorkloadIdentity, &out.WorkloadIdentity
+		*out = new(AzureACRWorkloadIdentityAuth)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAuth.
+func (in *ACRAuth) DeepCopy() *ACRAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(ACRAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureACRManagedIdentityAuth) DeepCopyInto(out *AzureACRManagedIdentityAuth) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRManagedIdentityAuth.
+func (in *AzureACRManagedIdentityAuth) DeepCopy() *AzureACRManagedIdentityAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureACRManagedIdentityAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureACRServicePrincipalAuth) DeepCopyInto(out *AzureACRServicePrincipalAuth) {
+	*out = *in
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRServicePrincipalAuth.
+func (in *AzureACRServicePrincipalAuth) DeepCopy() *AzureACRServicePrincipalAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureACRServicePrincipalAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureACRServicePrincipalAuthSecretRef) DeepCopyInto(out *AzureACRServicePrincipalAuthSecretRef) {
+	*out = *in
+	in.ClientID.DeepCopyInto(&out.ClientID)
+	in.ClientSecret.DeepCopyInto(&out.ClientSecret)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRServicePrincipalAuthSecretRef.
+func (in *AzureACRServicePrincipalAuthSecretRef) DeepCopy() *AzureACRServicePrincipalAuthSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureACRServicePrincipalAuthSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureACRWorkloadIdentityAuth) DeepCopyInto(out *AzureACRWorkloadIdentityAuth) {
+	*out = *in
+	if in.ServiceAccountRef != nil {
+		in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
+		*out = new(v1.ServiceAccountSelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRWorkloadIdentityAuth.
+func (in *AzureACRWorkloadIdentityAuth) DeepCopy() *AzureACRWorkloadIdentityAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureACRWorkloadIdentityAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ECRAuthorizationToken) DeepCopyInto(out *ECRAuthorizationToken) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationToken.
+func (in *ECRAuthorizationToken) DeepCopy() *ECRAuthorizationToken {
+	if in == nil {
+		return nil
+	}
+	out := new(ECRAuthorizationToken)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ECRAuthorizationToken) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ECRAuthorizationTokenList) DeepCopyInto(out *ECRAuthorizationTokenList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ECRAuthorizationToken, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationTokenList.
+func (in *ECRAuthorizationTokenList) DeepCopy() *ECRAuthorizationTokenList {
+	if in == nil {
+		return nil
+	}
+	out := new(ECRAuthorizationTokenList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ECRAuthorizationTokenList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ECRAuthorizationTokenSpec) DeepCopyInto(out *ECRAuthorizationTokenSpec) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationTokenSpec.
+func (in *ECRAuthorizationTokenSpec) DeepCopy() *ECRAuthorizationTokenSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ECRAuthorizationTokenSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Fake) DeepCopyInto(out *Fake) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fake.
+func (in *Fake) DeepCopy() *Fake {
+	if in == nil {
+		return nil
+	}
+	out := new(Fake)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Fake) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeList) DeepCopyInto(out *FakeList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Fake, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeList.
+func (in *FakeList) DeepCopy() *FakeList {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FakeList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeSpec) DeepCopyInto(out *FakeSpec) {
+	*out = *in
+	if in.Data != nil {
+		in, out := &in.Data, &out.Data
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeSpec.
+func (in *FakeSpec) DeepCopy() *FakeSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCRAccessToken) DeepCopyInto(out *GCRAccessToken) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessToken.
+func (in *GCRAccessToken) DeepCopy() *GCRAccessToken {
+	if in == nil {
+		return nil
+	}
+	out := new(GCRAccessToken)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCRAccessToken) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCRAccessTokenList) DeepCopyInto(out *GCRAccessTokenList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCRAccessToken, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessTokenList.
+func (in *GCRAccessTokenList) DeepCopy() *GCRAccessTokenList {
+	if in == nil {
+		return nil
+	}
+	out := new(GCRAccessTokenList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCRAccessTokenList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCRAccessTokenSpec) DeepCopyInto(out *GCRAccessTokenSpec) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessTokenSpec.
+func (in *GCRAccessTokenSpec) DeepCopy() *GCRAccessTokenSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GCRAccessTokenSpec)
+	in.DeepCopyInto(out)
+	return out
+}

+ 1 - 2
cmd/certcontroller.go

@@ -48,7 +48,6 @@ var certcontrollerCmd = &cobra.Command{
 
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
 			Scheme:                 scheme,
-			MetricsBindAddress:     metricsAddr,
 			HealthProbeBindAddress: healthzAddr,
 			Port:                   9443,
 			LeaderElection:         enableLeaderElection,
@@ -114,7 +113,7 @@ var certcontrollerCmd = &cobra.Command{
 func init() {
 	rootCmd.AddCommand(certcontrollerCmd)
 
-	certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8082", "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(&serviceNamespace, "service-namespace", "default", "Webhook service namespace")

+ 4 - 1
cmd/root.go

@@ -34,6 +34,7 @@ import (
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
@@ -79,6 +80,7 @@ func init() {
 	_ = clientgoscheme.AddToScheme(scheme)
 	_ = esv1beta1.AddToScheme(scheme)
 	_ = esv1alpha1.AddToScheme(scheme)
+	_ = genv1alpha1.AddToScheme(scheme)
 	_ = apiextensionsv1.AddToScheme(scheme)
 }
 
@@ -150,6 +152,7 @@ var rootCmd = &cobra.Command{
 			Client:                    mgr.GetClient(),
 			Log:                       ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
 			Scheme:                    mgr.GetScheme(),
+			RestConfig:                mgr.GetConfig(),
 			ControllerClass:           controllerClass,
 			RequeueInterval:           time.Hour,
 			ClusterSecretStoreEnabled: enableClusterStoreReconciler,
@@ -190,7 +193,7 @@ func Execute() {
 }
 
 func init() {
-	rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":9090", "The address the metric endpoint binds to.")
 	rootCmd.Flags().StringVar(&controllerClass, "controller-class", "default", "the controller is instantiated with a specific controller name and filters ES based on this property")
 	rootCmd.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", false,
 		"Enable leader election for controller manager. "+

+ 1 - 2
cmd/webhook.go

@@ -106,7 +106,6 @@ var webhookCmd = &cobra.Command{
 		}
 		mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
 			Scheme:                 scheme,
-			MetricsBindAddress:     metricsAddr,
 			HealthProbeBindAddress: healthzAddr,
 			WebhookServer: &webhook.Server{
 				CertDir:       certDir,
@@ -207,7 +206,7 @@ func getTLSCipherSuitesIDs(cipherListString string) ([]uint16, error) {
 
 func init() {
 	rootCmd.AddCommand(webhookCmd)
-	webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8082", "The address the metric endpoint binds to.")
 	webhookCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
 	webhookCmd.Flags().IntVar(&port, "port", 10250, "The address the health endpoint binds to.")
 	webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")

+ 85 - 6
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -100,6 +100,43 @@ spec:
                           type: object
                         secretKey:
                           type: string
+                        sourceRef:
+                          properties:
+                            generator:
+                              description: Generator generates secret values on demand
+                                A generator is just a embedded type, see apis/generators
+                                for available types.
+                              x-kubernetes-preserve-unknown-fields: true
+                            generatorRef:
+                              description: GeneratorRef points to a generator custom
+                                resource in
+                              properties:
+                                apiVersion:
+                                  type: string
+                                kind:
+                                  type: string
+                                name:
+                                  type: string
+                              required:
+                              - apiVersion
+                              - kind
+                              - name
+                              type: object
+                            storeRef:
+                              description: SecretStoreRef defines which SecretStore
+                                to fetch the ExternalSecret data.
+                              properties:
+                                kind:
+                                  description: Kind of the SecretStore resource (SecretStore
+                                    or ClusterSecretStore) Defaults to `SecretStore`
+                                  type: string
+                                name:
+                                  description: Name of the SecretStore resource
+                                  type: string
+                              required:
+                              - name
+                              type: object
+                          type: object
                       required:
                       - remoteRef
                       - secretKey
@@ -112,8 +149,9 @@ spec:
                     items:
                       properties:
                         extract:
-                          description: Used to extract multiple key/value pairs from
-                            one secret
+                          description: 'Used to extract multiple key/value pairs from
+                            one secret Note: Extract does not support sourceRef.Generator
+                            or sourceRef.GeneratorRef.'
                           properties:
                             conversionStrategy:
                               default: Default
@@ -143,8 +181,9 @@ spec:
                           - key
                           type: object
                         find:
-                          description: Used to find secrets based on tags or regular
-                            expressions
+                          description: 'Used to find secrets based on tags or regular
+                            expressions Note: Find does not support sourceRef.Generator
+                            or sourceRef.GeneratorRef.'
                           properties:
                             conversionStrategy:
                               default: Default
@@ -196,6 +235,48 @@ spec:
                                 type: object
                             type: object
                           type: array
+                        sourceRef:
+                          description: SourceRef points to a store or generator which
+                            contains secret values ready to use. Use this in combination
+                            with Extract or Find pull values out of a specific SecretStore.
+                            When sourceRef points to a generator Extract or Find is
+                            not supported. The generator returns a static map of values
+                          properties:
+                            generator:
+                              description: Generator generates secret values on demand
+                                A generator is just a embedded type, see apis/generators
+                                for available types.
+                              x-kubernetes-preserve-unknown-fields: true
+                            generatorRef:
+                              description: GeneratorRef points to a generator custom
+                                resource in
+                              properties:
+                                apiVersion:
+                                  type: string
+                                kind:
+                                  type: string
+                                name:
+                                  type: string
+                              required:
+                              - apiVersion
+                              - kind
+                              - name
+                              type: object
+                            storeRef:
+                              description: SecretStoreRef defines which SecretStore
+                                to fetch the ExternalSecret data.
+                              properties:
+                                kind:
+                                  description: Kind of the SecretStore resource (SecretStore
+                                    or ClusterSecretStore) Defaults to `SecretStore`
+                                  type: string
+                                name:
+                                  description: Name of the SecretStore resource
+                                  type: string
+                              required:
+                              - name
+                              type: object
+                          type: object
                       type: object
                     type: array
                   refreshInterval:
@@ -323,8 +404,6 @@ spec:
                             type: string
                         type: object
                     type: object
-                required:
-                - secretStoreRef
                 type: object
               namespaceSelector:
                 description: The labels to select by to find the Namespaces to create

+ 85 - 5
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -335,6 +335,43 @@ spec:
                       type: object
                     secretKey:
                       type: string
+                    sourceRef:
+                      properties:
+                        generator:
+                          description: Generator generates secret values on demand
+                            A generator is just a embedded type, see apis/generators
+                            for available types.
+                          x-kubernetes-preserve-unknown-fields: true
+                        generatorRef:
+                          description: GeneratorRef points to a generator custom resource
+                            in
+                          properties:
+                            apiVersion:
+                              type: string
+                            kind:
+                              type: string
+                            name:
+                              type: string
+                          required:
+                          - apiVersion
+                          - kind
+                          - name
+                          type: object
+                        storeRef:
+                          description: SecretStoreRef defines which SecretStore to
+                            fetch the ExternalSecret data.
+                          properties:
+                            kind:
+                              description: Kind of the SecretStore resource (SecretStore
+                                or ClusterSecretStore) Defaults to `SecretStore`
+                              type: string
+                            name:
+                              description: Name of the SecretStore resource
+                              type: string
+                          required:
+                          - name
+                          type: object
+                      type: object
                   required:
                   - remoteRef
                   - secretKey
@@ -347,8 +384,9 @@ spec:
                 items:
                   properties:
                     extract:
-                      description: Used to extract multiple key/value pairs from one
-                        secret
+                      description: 'Used to extract multiple key/value pairs from
+                        one secret Note: Extract does not support sourceRef.Generator
+                        or sourceRef.GeneratorRef.'
                       properties:
                         conversionStrategy:
                           default: Default
@@ -378,7 +416,9 @@ spec:
                       - key
                       type: object
                     find:
-                      description: Used to find secrets based on tags or regular expressions
+                      description: 'Used to find secrets based on tags or regular
+                        expressions Note: Find does not support sourceRef.Generator
+                        or sourceRef.GeneratorRef.'
                       properties:
                         conversionStrategy:
                           default: Default
@@ -429,6 +469,48 @@ spec:
                             type: object
                         type: object
                       type: array
+                    sourceRef:
+                      description: SourceRef points to a store or generator which
+                        contains secret values ready to use. Use this in combination
+                        with Extract or Find pull values out of a specific SecretStore.
+                        When sourceRef points to a generator Extract or Find is not
+                        supported. The generator returns a static map of values
+                      properties:
+                        generator:
+                          description: Generator generates secret values on demand
+                            A generator is just a embedded type, see apis/generators
+                            for available types.
+                          x-kubernetes-preserve-unknown-fields: true
+                        generatorRef:
+                          description: GeneratorRef points to a generator custom resource
+                            in
+                          properties:
+                            apiVersion:
+                              type: string
+                            kind:
+                              type: string
+                            name:
+                              type: string
+                          required:
+                          - apiVersion
+                          - kind
+                          - name
+                          type: object
+                        storeRef:
+                          description: SecretStoreRef defines which SecretStore to
+                            fetch the ExternalSecret data.
+                          properties:
+                            kind:
+                              description: Kind of the SecretStore resource (SecretStore
+                                or ClusterSecretStore) Defaults to `SecretStore`
+                              type: string
+                            name:
+                              description: Name of the SecretStore resource
+                              type: string
+                          required:
+                          - name
+                          type: object
+                      type: object
                   type: object
                 type: array
               refreshInterval:
@@ -555,8 +637,6 @@ spec:
                         type: string
                     type: object
                 type: object
-            required:
-            - secretStoreRef
             type: object
           status:
             properties:

+ 172 - 0
config/crds/bases/generators.external-secrets.io_acraccesstokens.yaml

@@ -0,0 +1,172 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: acraccesstokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+    - acraccesstoken
+    kind: ACRAccessToken
+    listKind: ACRAccessTokenList
+    plural: acraccesstokens
+    shortNames:
+    - acraccesstoken
+    singular: acraccesstoken
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: "ACRAccessToken returns a Azure Container Registry token that
+          can be used for pushing/pulling images. Note: by default it will return
+          an ACR Refresh Token with full access (depending on the identity). This
+          can be scoped down to the repository level using .spec.scope. In case scope
+          is defined it will return an ACR Access Token. \n See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md"
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: 'ACRAccessTokenSpec see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview'
+            properties:
+              auth:
+                properties:
+                  managedIdentity:
+                    description: ManagedIdentity uses Azure Managed Identity to authenticate
+                      with Azure.
+                    properties:
+                      identityId:
+                        description: If multiple Managed Identity is assigned to the
+                          pod, you can select the one to be used
+                        type: string
+                    type: object
+                  servicePrincipal:
+                    description: ServicePrincipal uses Azure Service Principal credentials
+                      to authenticate with Azure.
+                    properties:
+                      secretRef:
+                        description: Configuration used to authenticate with Azure
+                          using static credentials stored in a Kind=Secret
+                        properties:
+                          clientId:
+                            description: The Azure clientId of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          clientSecret:
+                            description: The Azure ClientSecret of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                        type: object
+                    required:
+                    - secretRef
+                    type: object
+                  workloadIdentity:
+                    description: WorkloadIdentity uses Azure Workload Identity to
+                      authenticate with Azure.
+                    properties:
+                      serviceAccountRef:
+                        description: ServiceAccountRef specified the service account
+                          that should be used when authenticating with WorkloadIdentity.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
+                    type: object
+                type: object
+              environmentType:
+                default: PublicCloud
+                description: 'EnvironmentType specifies the Azure cloud environment
+                  endpoints to use for connecting and authenticating with Azure. By
+                  default it points to the public cloud AAD endpoint. The following
+                  endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+                  PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                enum:
+                - PublicCloud
+                - USGovernmentCloud
+                - ChinaCloud
+                - GermanCloud
+                type: string
+              registry:
+                description: the domain name of the ACR registry e.g. foobarexample.azurecr.io
+                type: string
+              scope:
+                description: "Define the scope for the access token, e.g. pull/push
+                  access for a repository. if not provided it will return a refresh
+                  token that has full scope. Note: you need to pin it down to the
+                  repository level, there is no wildcard available. \n examples: repository:my-repository:pull,push
+                  repository:my-repository:pull \n see docs for details: https://docs.docker.com/registry/spec/auth/scope/"
+                type: string
+              tenantId:
+                description: TenantID configures the Azure Tenant to send requests
+                  to. Required for ServicePrincipal auth type.
+                type: string
+            required:
+            - auth
+            - registry
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 133 - 0
config/crds/bases/generators.external-secrets.io_ecrauthorizationtokens.yaml

@@ -0,0 +1,133 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: ecrauthorizationtokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+    - ecrauthorizationtoken
+    kind: ECRAuthorizationToken
+    listKind: ECRAuthorizationTokenList
+    plural: ecrauthorizationtokens
+    shortNames:
+    - ecrauthorizationtoken
+    singular: ecrauthorizationtoken
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: ECRAuthorizationTokenSpec uses the GetAuthorizationToken
+              API to retrieve an authorization token. The authorization token is valid
+              for 12 hours. The authorizationToken returned is a base64 encoded string
+              that can be decoded and used in a docker login command to authenticate
+              to a registry. For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth)
+              in the Amazon Elastic Container Registry User Guide.
+            properties:
+              auth:
+                description: AWSAuth tells the controller how to do authentication
+                  with aws. Only one of secretRef or jwt can be specified. if none
+                  is specified the controller will load credentials using the aws
+                  sdk defaults.
+                properties:
+                  jwt:
+                    description: Authenticate against AWS using service account tokens.
+                    properties:
+                      serviceAccountRef:
+                        description: A reference to a ServiceAccount resource.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
+                    type: object
+                  secretRef:
+                    description: AWSAuthSecretRef holds secret references for AWS
+                      credentials both AccessKeyID and SecretAccessKey must be defined
+                      in order to properly authenticate.
+                    properties:
+                      accessKeyIDSecretRef:
+                        description: The AccessKeyID is used for authentication
+                        properties:
+                          key:
+                            description: The key of the entry in the Secret resource's
+                              `data` field to be used. Some instances of this field
+                              may be defaulted, in others it may be required.
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        type: object
+                      secretAccessKeySecretRef:
+                        description: The SecretAccessKey is used for authentication
+                        properties:
+                          key:
+                            description: The key of the entry in the Secret resource's
+                              `data` field to be used. Some instances of this field
+                              may be defaulted, in others it may be required.
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        type: object
+                    type: object
+                type: object
+              region:
+                type: string
+              role:
+                type: string
+            required:
+            - region
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 49 - 0
config/crds/bases/generators.external-secrets.io_fakes.yaml

@@ -0,0 +1,49 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: fakes.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+    - fake
+    kind: Fake
+    listKind: FakeList
+    plural: fakes
+    shortNames:
+    - fake
+    singular: fake
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: FakeSpec
+            properties:
+              data:
+                additionalProperties:
+                  type: string
+                type: object
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 111 - 0
config/crds/bases/generators.external-secrets.io_gcraccesstokens.yaml

@@ -0,0 +1,111 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: gcraccesstokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+    - gcraccesstoken
+    kind: GCRAccessToken
+    listKind: GCRAccessTokenList
+    plural: gcraccesstokens
+    shortNames:
+    - gcraccesstoken
+    singular: gcraccesstoken
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: GCRAccessTokenSpec
+            properties:
+              auth:
+                properties:
+                  secretRef:
+                    properties:
+                      secretAccessKeySecretRef:
+                        description: The SecretAccessKey is used for authentication
+                        properties:
+                          key:
+                            description: The key of the entry in the Secret resource's
+                              `data` field to be used. Some instances of this field
+                              may be defaulted, in others it may be required.
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        type: object
+                    type: object
+                  workloadIdentity:
+                    properties:
+                      clusterLocation:
+                        type: string
+                      clusterName:
+                        type: string
+                      clusterProjectID:
+                        type: string
+                      serviceAccountRef:
+                        description: A reference to a ServiceAccount resource.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
+                    required:
+                    - clusterLocation
+                    - clusterName
+                    - serviceAccountRef
+                    type: object
+                type: object
+              projectID:
+                type: string
+            required:
+            - auth
+            - projectID
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 2 - 0
config/crds/bases/kustomization.yaml

@@ -6,3 +6,5 @@ resources:
   - external-secrets.io_clustersecretstores.yaml
   - external-secrets.io_externalsecrets.yaml
   - external-secrets.io_secretstores.yaml
+  - generators.external-secrets.io_ecrauthorizationtokens.yaml
+  - generators.external-secrets.io_gcraccesstokens.yaml

+ 550 - 8
deploy/crds/bundle.yaml

@@ -86,6 +86,38 @@ spec:
                             type: object
                           secretKey:
                             type: string
+                          sourceRef:
+                            properties:
+                              generator:
+                                description: Generator generates secret values on demand A generator is just a embedded type, see apis/generators for available types.
+                                x-kubernetes-preserve-unknown-fields: true
+                              generatorRef:
+                                description: GeneratorRef points to a generator custom resource in
+                                properties:
+                                  apiVersion:
+                                    type: string
+                                  kind:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                  - apiVersion
+                                  - kind
+                                  - name
+                                type: object
+                              storeRef:
+                                description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
+                                properties:
+                                  kind:
+                                    description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
+                                    type: string
+                                  name:
+                                    description: Name of the SecretStore resource
+                                    type: string
+                                required:
+                                  - name
+                                type: object
+                            type: object
                         required:
                           - remoteRef
                           - secretKey
@@ -96,7 +128,7 @@ spec:
                       items:
                         properties:
                           extract:
-                            description: Used to extract multiple key/value pairs from one secret
+                            description: 'Used to extract multiple key/value pairs from one secret Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.'
                             properties:
                               conversionStrategy:
                                 default: Default
@@ -122,7 +154,7 @@ spec:
                               - key
                             type: object
                           find:
-                            description: Used to find secrets based on tags or regular expressions
+                            description: 'Used to find secrets based on tags or regular expressions Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.'
                             properties:
                               conversionStrategy:
                                 default: Default
@@ -167,6 +199,39 @@ spec:
                                   type: object
                               type: object
                             type: array
+                          sourceRef:
+                            description: SourceRef points to a store or generator which contains secret values ready to use. Use this in combination with Extract or Find pull values out of a specific SecretStore. When sourceRef points to a generator Extract or Find is not supported. The generator returns a static map of values
+                            properties:
+                              generator:
+                                description: Generator generates secret values on demand A generator is just a embedded type, see apis/generators for available types.
+                                x-kubernetes-preserve-unknown-fields: true
+                              generatorRef:
+                                description: GeneratorRef points to a generator custom resource in
+                                properties:
+                                  apiVersion:
+                                    type: string
+                                  kind:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                  - apiVersion
+                                  - kind
+                                  - name
+                                type: object
+                              storeRef:
+                                description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
+                                properties:
+                                  kind:
+                                    description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
+                                    type: string
+                                  name:
+                                    description: Name of the SecretStore resource
+                                    type: string
+                                required:
+                                  - name
+                                type: object
+                            type: object
                         type: object
                       type: array
                     refreshInterval:
@@ -281,8 +346,6 @@ spec:
                               type: string
                           type: object
                       type: object
-                  required:
-                    - secretStoreRef
                   type: object
                 namespaceSelector:
                   description: The labels to select by to find the Namespaces to create the ExternalSecrets in.
@@ -2921,6 +2984,38 @@ spec:
                         type: object
                       secretKey:
                         type: string
+                      sourceRef:
+                        properties:
+                          generator:
+                            description: Generator generates secret values on demand A generator is just a embedded type, see apis/generators for available types.
+                            x-kubernetes-preserve-unknown-fields: true
+                          generatorRef:
+                            description: GeneratorRef points to a generator custom resource in
+                            properties:
+                              apiVersion:
+                                type: string
+                              kind:
+                                type: string
+                              name:
+                                type: string
+                            required:
+                              - apiVersion
+                              - kind
+                              - name
+                            type: object
+                          storeRef:
+                            description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
+                            properties:
+                              kind:
+                                description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
+                                type: string
+                              name:
+                                description: Name of the SecretStore resource
+                                type: string
+                            required:
+                              - name
+                            type: object
+                        type: object
                     required:
                       - remoteRef
                       - secretKey
@@ -2931,7 +3026,7 @@ spec:
                   items:
                     properties:
                       extract:
-                        description: Used to extract multiple key/value pairs from one secret
+                        description: 'Used to extract multiple key/value pairs from one secret Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.'
                         properties:
                           conversionStrategy:
                             default: Default
@@ -2957,7 +3052,7 @@ spec:
                           - key
                         type: object
                       find:
-                        description: Used to find secrets based on tags or regular expressions
+                        description: 'Used to find secrets based on tags or regular expressions Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.'
                         properties:
                           conversionStrategy:
                             default: Default
@@ -3002,6 +3097,39 @@ spec:
                               type: object
                           type: object
                         type: array
+                      sourceRef:
+                        description: SourceRef points to a store or generator which contains secret values ready to use. Use this in combination with Extract or Find pull values out of a specific SecretStore. When sourceRef points to a generator Extract or Find is not supported. The generator returns a static map of values
+                        properties:
+                          generator:
+                            description: Generator generates secret values on demand A generator is just a embedded type, see apis/generators for available types.
+                            x-kubernetes-preserve-unknown-fields: true
+                          generatorRef:
+                            description: GeneratorRef points to a generator custom resource in
+                            properties:
+                              apiVersion:
+                                type: string
+                              kind:
+                                type: string
+                              name:
+                                type: string
+                            required:
+                              - apiVersion
+                              - kind
+                              - name
+                            type: object
+                          storeRef:
+                            description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
+                            properties:
+                              kind:
+                                description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
+                                type: string
+                              name:
+                                description: Name of the SecretStore resource
+                                type: string
+                            required:
+                              - name
+                            type: object
+                        type: object
                     type: object
                   type: array
                 refreshInterval:
@@ -3116,8 +3244,6 @@ spec:
                           type: string
                       type: object
                   type: object
-              required:
-                - secretStoreRef
               type: object
             status:
               properties:
@@ -5411,3 +5537,419 @@ spec:
           name: kubernetes
           namespace: default
           path: /convert
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: acraccesstokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+      - acraccesstoken
+    kind: ACRAccessToken
+    listKind: ACRAccessTokenList
+    plural: acraccesstokens
+    shortNames:
+      - acraccesstoken
+    singular: acraccesstoken
+  scope: Namespaced
+  versions:
+    - name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          description: "ACRAccessToken returns a Azure Container Registry token that can be used for pushing/pulling images. Note: by default it will return an ACR Refresh Token with full access (depending on the identity). This can be scoped down to the repository level using .spec.scope. In case scope is defined it will return an ACR Access Token. \n See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md"
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: 'ACRAccessTokenSpec see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview'
+              properties:
+                auth:
+                  properties:
+                    managedIdentity:
+                      description: ManagedIdentity uses Azure Managed Identity to authenticate with Azure.
+                      properties:
+                        identityId:
+                          description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
+                          type: string
+                      type: object
+                    servicePrincipal:
+                      description: ServicePrincipal uses Azure Service Principal credentials to authenticate with Azure.
+                      properties:
+                        secretRef:
+                          description: Configuration used to authenticate with Azure using static credentials stored in a Kind=Secret
+                          properties:
+                            clientId:
+                              description: The Azure clientId of the service principle used for authentication.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            clientSecret:
+                              description: The Azure ClientSecret of the service principle used for authentication.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                          type: object
+                      required:
+                        - secretRef
+                      type: object
+                    workloadIdentity:
+                      description: WorkloadIdentity uses Azure Workload Identity to authenticate with Azure.
+                      properties:
+                        serviceAccountRef:
+                          description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
+                      type: object
+                  type: object
+                environmentType:
+                  default: PublicCloud
+                  description: 'EnvironmentType specifies the Azure cloud environment endpoints to use for connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                  enum:
+                    - PublicCloud
+                    - USGovernmentCloud
+                    - ChinaCloud
+                    - GermanCloud
+                  type: string
+                registry:
+                  description: the domain name of the ACR registry e.g. foobarexample.azurecr.io
+                  type: string
+                scope:
+                  description: "Define the scope for the access token, e.g. pull/push access for a repository. if not provided it will return a refresh token that has full scope. Note: you need to pin it down to the repository level, there is no wildcard available. \n examples: repository:my-repository:pull,push repository:my-repository:pull \n see docs for details: https://docs.docker.com/registry/spec/auth/scope/"
+                  type: string
+                tenantId:
+                  description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
+                  type: string
+              required:
+                - auth
+                - registry
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+  conversion:
+    strategy: Webhook
+    webhook:
+      conversionReviewVersions:
+        - v1
+      clientConfig:
+        service:
+          name: kubernetes
+          namespace: default
+          path: /convert
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: ecrauthorizationtokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+      - ecrauthorizationtoken
+    kind: ECRAuthorizationToken
+    listKind: ECRAuthorizationTokenList
+    plural: ecrauthorizationtokens
+    shortNames:
+      - ecrauthorizationtoken
+    singular: ecrauthorizationtoken
+  scope: Namespaced
+  versions:
+    - name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an authorization token. The authorization token is valid for 12 hours. The authorizationToken returned is a base64 encoded string that can be decoded and used in a docker login command to authenticate to a registry. For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
+              properties:
+                auth:
+                  description: AWSAuth tells the controller how to do authentication with aws. Only one of secretRef or jwt can be specified. if none is specified the controller will load credentials using the aws sdk defaults.
+                  properties:
+                    jwt:
+                      description: Authenticate against AWS using service account tokens.
+                      properties:
+                        serviceAccountRef:
+                          description: A reference to a ServiceAccount resource.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
+                      type: object
+                    secretRef:
+                      description: AWSAuthSecretRef holds secret references for AWS credentials both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
+                      properties:
+                        accessKeyIDSecretRef:
+                          description: The AccessKeyID is used for authentication
+                          properties:
+                            key:
+                              description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                              type: string
+                            name:
+                              description: The name of the Secret resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          type: object
+                        secretAccessKeySecretRef:
+                          description: The SecretAccessKey is used for authentication
+                          properties:
+                            key:
+                              description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                              type: string
+                            name:
+                              description: The name of the Secret resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          type: object
+                      type: object
+                  type: object
+                region:
+                  type: string
+                role:
+                  type: string
+              required:
+                - region
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+  conversion:
+    strategy: Webhook
+    webhook:
+      conversionReviewVersions:
+        - v1
+      clientConfig:
+        service:
+          name: kubernetes
+          namespace: default
+          path: /convert
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: fakes.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+      - fake
+    kind: Fake
+    listKind: FakeList
+    plural: fakes
+    shortNames:
+      - fake
+    singular: fake
+  scope: Namespaced
+  versions:
+    - name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: FakeSpec
+              properties:
+                data:
+                  additionalProperties:
+                    type: string
+                  type: object
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+  conversion:
+    strategy: Webhook
+    webhook:
+      conversionReviewVersions:
+        - v1
+      clientConfig:
+        service:
+          name: kubernetes
+          namespace: default
+          path: /convert
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: gcraccesstokens.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+      - gcraccesstoken
+    kind: GCRAccessToken
+    listKind: GCRAccessTokenList
+    plural: gcraccesstokens
+    shortNames:
+      - gcraccesstoken
+    singular: gcraccesstoken
+  scope: Namespaced
+  versions:
+    - name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: GCRAccessTokenSpec
+              properties:
+                auth:
+                  properties:
+                    secretRef:
+                      properties:
+                        secretAccessKeySecretRef:
+                          description: The SecretAccessKey is used for authentication
+                          properties:
+                            key:
+                              description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                              type: string
+                            name:
+                              description: The name of the Secret resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          type: object
+                      type: object
+                    workloadIdentity:
+                      properties:
+                        clusterLocation:
+                          type: string
+                        clusterName:
+                          type: string
+                        clusterProjectID:
+                          type: string
+                        serviceAccountRef:
+                          description: A reference to a ServiceAccount resource.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
+                      required:
+                        - clusterLocation
+                        - clusterName
+                        - serviceAccountRef
+                      type: object
+                  type: object
+                projectID:
+                  type: string
+              required:
+                - auth
+                - projectID
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+  conversion:
+    strategy: Webhook
+    webhook:
+      conversionReviewVersions:
+        - v1
+      clientConfig:
+        service:
+          name: kubernetes
+          namespace: default
+          path: /convert

+ 49 - 0
docs/api/generator/acr.md

@@ -0,0 +1,49 @@
+
+The Azure Container Registry (ACR) generator creates a short-lived refresh or access token for accessing ACR.
+The token is generated for a particular ACR registry defined in `spec.registry`.
+
+## Output Keys and Values
+
+| Key      | Description |
+| -------- | ----------- |
+| username | username for the `docker login` command |
+| password | password for the `docker login` command |
+
+
+## Authentication
+
+You must choose one out of three authentication mechanisms:
+
+- service principal
+- managed identity
+- workload identity
+
+The generated token will inherit the permissions from the assigned policy. I.e. when you assign a read-only policy all generated tokens will be read-only.
+
+You can scope tokens to a particular repository using `spec.scope`.
+
+## Scope
+
+First, an Azure Active Directory access token is obtained with the desired authentication method.
+This AAD access token will be used to authenticate against ACR to issue a refresh token or access token.
+If `spec.scope` if it is defined it obtains an ACR access token. If  `spec.scope` is missing it obtains an ACR refresh token:
+
+- access tokens are scoped to a specific repository or action (pull,push)
+- refresh tokens can are scoped to whatever policy is attached to the identity that creates the acr refresh token
+
+The Scope grammar is defined in the [Docker Registry spec](https://docs.docker.com/registry/spec/auth/scope/).
+Note: You **can not** use a wildcards in the scope parameter, you can match exactly one repository and defined multiple actions like `pull` or `push`.
+
+Example scopes:
+
+```
+repository:my-repository:pull,push
+repository:my-repository:pull
+```
+
+## Example Manifest
+
+```yaml
+{% include 'generator-acr.yaml' %}
+```
+

+ 26 - 0
docs/api/generator/ecr.md

@@ -0,0 +1,26 @@
+ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an authorization token.
+The authorization token is valid for 12 hours. For more information, see [registry authentication](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
+
+
+## Output Keys and Values
+
+| Key            | Description                                                                       |
+| -------------- | --------------------------------------------------------------------------------- |
+| username       | username for the `docker login` command.                                          |
+| password       | password for the `docker login` command.                                          |
+| proxy_endpoint | The registry URL to use for this authorization token in a `docker login` command. |
+| expires_at     | time when token expires in UNIX time (seconds since January 1, 1970 UTC).         |
+
+## Authentication
+
+You can choose from three authentication mechanisms:
+
+* static credentials using `spec.auth.secretRef`
+* point to a IRSA Service Account with `spec.auth.jwt`
+* use credentials from the [SDK default credentials chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) from the controller environment
+
+## Example Manifest
+
+```yaml
+{% include 'generator-ecr.yaml' %}
+```

+ 8 - 0
docs/api/generator/fake.md

@@ -0,0 +1,8 @@
+The Fake generator provides hard-coded key/value pairs. The intended use is just for debugging and testing.
+The key/value pairs defined in `spec.data` is returned as-is.
+
+## Example Manifest
+
+```yaml
+{% include 'generator-fake.yaml' %}
+```

+ 30 - 0
docs/api/generator/gcr.md

@@ -0,0 +1,30 @@
+GCRAccessToken creates a GCP Access token that can be used to authenticate with GCR in order to pull OCI images.
+
+You must specify the `spec.projectID` in which GCR is located.
+
+## Output Keys and Values
+
+| Key        | Description                                                               |
+| ---------- | ------------------------------------------------------------------------- |
+| username   | username for the `docker login` command.                                  |
+| password   | password for the `docker login` command.                                  |
+| expiry     | time when token expires in UNIX time (seconds since January 1, 1970 UTC). |
+
+## Authentication
+
+### Workload Identity
+
+Use `spec.auth.workloadIdentity` to point to a Service Account that has Workload Identity enabled.
+For details see [GCP Secret Manager](../provider/google-secrets-manager.md#authentication).
+
+
+### GCP Service Account
+
+Use `spec.auth.secretRef` to point to a Secret that contains a GCP Service Account.
+For details see [GCP Secret Manager](../provider/google-secrets-manager.md#authentication).
+
+## Example Manifest
+
+```yaml
+{% include 'generator-gcr.yaml' %}
+```

+ 2 - 0
docs/api/generator/index.md

@@ -0,0 +1,2 @@
+
+Generators allow you to generate values. See [Generators Guide](../../guides/generator.md)

+ 0 - 0
docs/spec.md → docs/api/spec.md


+ 1 - 1
docs/examples/gitops-using-fluxcd.md

@@ -21,7 +21,7 @@ FluxCD is composed by several controllers dedicated to manage different custom r
 ones are **Kustomization** (to clarify, Flux one, not Kubernetes' one) and **HelmRelease** to deploy using the approaches
 of the same names.
 
-External Secrets can be deployed using Helm [as explained here](../guides/getting-started.md). The deployment includes the
+External Secrets can be deployed using Helm [as explained here](../introduction/getting-started.md). The deployment includes the
 CRDs if enabled on the `values.yaml`, but after this, you need to deploy some `SecretStore` to start
 getting credentials from your secrets manager with External Secrets.
 

+ 56 - 0
docs/guides/generator.md

@@ -0,0 +1,56 @@
+
+Generators allow you to generate values. They are used through a ExternalSecret `spec.DataFrom`. They can be defined inline using a `sourceRef.generator` or referenced from a custom resource using `sourceRef.generatorRef`.
+
+If the External Secret should be refreshed via `spec.refreshInterval` the generator produces a map of values with the `generator.spec` as input. The generator does not keep track of the produced values. Every invocation produces a new set of values.
+
+These values can be used with the other features like `rewrite` or `template`. I.e. you can modify, encode, decode, pack the values as needed.
+
+
+
+## Inline Definition
+
+Generators can be defined inline in a ExternalSecret. **Every invocation creates a new set of values**. I.e. you can not share the same value produced by a generator across different `spec.dataFrom[]` entries.
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: "ecr-token"
+spec:
+  refreshInterval: "30m"
+  target:
+    name: ecr-token
+  dataFrom:
+  - sourceRef:
+      generator:
+        apiVersion: generators.external-secrets.io/v1alpha1
+        kind: ECRAuthorizationToken
+        spec:
+          region: eu-west-1
+          auth:
+            jwt:
+              serviceAccountRef:
+                name: "oci-token-sync"
+```
+
+
+## Reference Custom Resource
+
+Generators can be defined as a custom resource and re-used across different ExternalSecrets. **Every invocation creates a new set of values**. I.e. you can not share the same value produced by a generator across different `ExternalSecrets` or `spec.dataFrom[]` entries.
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: "ecr-token"
+spec:
+  refreshInterval: "30m"
+  target:
+    name: ecr-token
+  dataFrom:
+  - sourceRef:
+      generatorRef:
+        apiVersion: generators.external-secrets.io/v1alpha1
+        kind: ECRAuthorizationToken
+        name: "my-ecr"
+```

+ 8 - 2
docs/guides/introduction.md

@@ -3,6 +3,12 @@
 The following guides demonstrate use-cases and provide examples of how to use
 the API. Please pick one of the following guides:
 
-* [Getting started](getting-started.md)
-* [Advanced Templating](templating.md)
 * [Multi-Tenancy Design Considerations](multi-tenancy.md)
+* [Find multiple secrets / Extract Secret values](getallsecrets.md)
+* [Advanced Templating](templating.md)
+* [Ownership and Deletion Policy](ownership-deletion-policy.md)
+* [DataFrom Key Rewrite](datafrom-rewrite.md)
+* [Controller Class](controller-class.md)
+* [Decoding Strategy](decoding-strategy.md)
+* [v1beta1 Migration](v1beta1.md)
+* [Deploying image from main](using-latest-image.md)

+ 3 - 3
docs/index.md

@@ -22,13 +22,13 @@ lifecycle of the secrets for you.
 
 ### Where to get started
 
-To get started, please read through [API overview](overview.md) this should
+To get started, please read through [API overview](introduction/overview.md) this should
 give you a high-level overview to understand the API and use-cases. After that
 please follow one of our [guides](guides/introduction.md) to get a jump start
-using the operator. See our [getting started guide](guides/getting-started.md) for installation instructions.
+using the operator. See our [getting started guide](introduction/getting-started.md) for installation instructions.
 
 For a complete reference of the API types please refer to our [API
-Reference](spec.md).
+Reference](api/spec.md).
 
 ### How to get involved
 

+ 0 - 0
docs/deprecation-policy.md → docs/introduction/deprecation-policy.md


+ 0 - 0
docs/faq.md → docs/introduction/faq.md


+ 1 - 1
docs/guides/getting-started.md → docs/introduction/getting-started.md

@@ -75,7 +75,7 @@ Events:                    <none>
 ```
 
 For more advanced examples, please read the other
-[guides](introduction.md).
+[guides](../guides/introduction.md).
 
 ## Installing with OLM
 

+ 5 - 5
docs/overview.md → docs/introduction/overview.md

@@ -1,7 +1,7 @@
 # API Overview
 
 ## Architecture
-![high-level](./pictures/diagrams-high-level-simple.png)
+![high-level](../pictures/diagrams-high-level-simple.png)
 
 The External Secrets Operator extends Kubernetes with [Custom
 Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/),
@@ -21,11 +21,11 @@ KeyVault or a AWS Secrets Manager in a certain AWS Account and region. Please
 take a look at the provider documentation to see what the Bucket actually maps
 to.
 
-![Resource Mapping](./pictures/diagrams-resource-mapping.png)
+![Resource Mapping](../pictures/diagrams-resource-mapping.png)
 
 ### SecretStore
 
-The idea behind the [SecretStore](api/secretstore.md) resource is to separate concerns of
+The idea behind the [SecretStore](../api/secretstore.md) resource is to separate concerns of
 authentication/access and the actual Secret and configuration needed for
 workloads. The ExternalSecret specifies what to fetch, the SecretStore specifies
 how to access. This resource is namespaced.
@@ -37,7 +37,7 @@ The `SecretStore` contains references to secrets which hold credentials to
 access the external API.
 
 ### ExternalSecret
-An [ExternalSecret](api/externalsecret.md) declares what data to fetch. It has a reference to a
+An [ExternalSecret](../api/externalsecret.md) declares what data to fetch. It has a reference to a
 `SecretStore` which knows how to access that data. The controller uses that
 `ExternalSecret` as a blueprint to create secrets.
 
@@ -47,7 +47,7 @@ An [ExternalSecret](api/externalsecret.md) declares what data to fetch. It has a
 
 ### ClusterSecretStore
 
-The [ClusterSecretStore](api/clustersecretstore.md) is a global, cluster-wide SecretStore that can be
+The [ClusterSecretStore](../api/clustersecretstore.md) is a global, cluster-wide SecretStore that can be
 referenced from all namespaces. You can use it to provide a central gateway to your secret provider.
 
 ## Behavior

+ 5 - 0
docs/stability-support.md → docs/introduction/stability-support.md

@@ -1,3 +1,8 @@
+---
+hide:
+  - toc
+---
+
 This page lists the status, timeline and policy for currently supported ESO releases and its providers. Please also see our [deprecation policy](deprecation-policy.md) that describes API versioning, deprecation and API surface.
 
 ## External Secrets Operator

+ 38 - 0
docs/snippets/generator-acr.yaml

@@ -0,0 +1,38 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: ACRAccessToken
+spec:
+  tenantId: 11111111-2222-3333-4444-111111111111
+  registry: example.azurecr.io
+
+  # optional; scope token down to a single repository/action
+  # if set, it will generate an access token instead of an refresh token.
+  scope: "repository:foo:pull,push"
+
+  # Specify Azure cloud type, defaults to PublicCloud.
+  # This is used for authenticating with Azure Active Directory.
+  # available options: PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+  environmentType: "PublicCloud"
+
+  # choose one authentication method
+  auth:
+
+    # option 1: point to a secret that contains a client-id and client-secret
+    servicePrincipal:
+      secretRef:
+        clientSecret:
+          name: az-secret
+          key: clientsecret
+        clientId:
+          name: az-secret
+          key: clientid
+
+    # option 2:
+    managedIdentity:
+      identityId: "xxxxx"
+
+    # option 3:
+    workloadIdentity:
+      # note: you can reference service accounts across namespaces.
+      serviceAccountRef:
+        name: "my-service-account"
+        audiences: []

+ 32 - 0
docs/snippets/generator-ecr.yaml

@@ -0,0 +1,32 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: ECRAuthorizationToken
+spec:
+
+  # specify aws region (mandatory)
+  region: eu-west-1
+
+  # assume role with the given authentication credentials
+  role: "my-role"
+
+  # choose an authentication strategy
+  # if no auth strategy is defined it falls back to using
+  # credentials from the environment of the controller.
+  auth:
+
+    # 1: static credentials
+    # point to a secret that contains static credentials
+    # like AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
+    secretRef:
+      accessKeyIDSecretRef:
+        name: "my-aws-creds"
+        key: "key-id"
+      secretAccessKeySecretRef:
+        name: "my-aws-creds"
+        key: "access-secret"
+
+    # option 2: IAM Roles for Service Accounts
+    # point to a service account that should be used
+    # that is configured for IAM Roles for Service Accounts (IRSA)
+    jwt:
+      serviceAccountRef:
+        name: "oci-token-sync"

+ 6 - 0
docs/snippets/generator-fake.yaml

@@ -0,0 +1,6 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Fake
+spec:
+  data:
+    foo: bar
+    baz: bang

+ 27 - 0
docs/snippets/generator-gcr.yaml

@@ -0,0 +1,27 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: GCRAccessToken
+spec:
+  # project where gcr lives in
+  projectID: ""
+
+  # choose authentication strategy
+  auth:
+    # option 1: workload identity
+    workloadIdentity:
+      # point to the workload identity
+      # service account
+      serviceAccountRef:
+        name: ""
+        audiences: []
+      # the cluster can live in a different project or location
+      # use the following fields to configure where the cluster lives
+      clusterLocation: ""
+      clusterName: ""
+      clusterProjectID: ""
+
+
+    # option 2: GCP service account
+    secretRef:
+      secretAccessKeySecretRef:
+        name: ""
+        key: ""

+ 35 - 0
gen.yaml

@@ -0,0 +1,35 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: "hello-world"
+spec:
+  refreshInterval: "5m"
+  target:
+    name: my-secret
+  dataFrom:
+  - generator:
+      apiVersion: generators.external-secrets.io/v1alpha1
+      kind: ACRAccessToken
+      spec:
+        tenantId: 4476d978-55aa-4edd-aef4-7b8899be19d4
+        registry: esoe2e.azurecr.io
+        scope: "repository:foo:pull,push"
+        auth:
+          servicePrincipal:
+            secretRef:
+              clientSecret:
+                name: az-secret
+                key: clientsecret
+              clientId:
+                name: az-secret
+                key: clientid
+  # - generator:
+  #     apiVersion: generators.external-secrets.io/v1alpha1
+  #     kind: ECRAuthorizationToken
+  #     spec:
+  #       region: "eu-west-1"
+  #       auth:
+  #         jwt:
+  #           serviceAccountRef:
+  #             name: ecr-pull-poc
+  #             namespace: default

+ 8 - 1
go.mod

@@ -95,10 +95,15 @@ require (
 
 require github.com/1Password/connect-sdk-go v1.5.0
 
-require sigs.k8s.io/yaml v1.3.0
+require (
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2
+	sigs.k8s.io/yaml v1.3.0
+)
 
 require (
 	cloud.google.com/go/compute v1.9.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
@@ -188,6 +193,7 @@ require (
 	github.com/oklog/ulid v1.3.1 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pierrec/lz4 v2.6.1+incompatible // indirect
+	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/common v0.37.0 // indirect
@@ -216,6 +222,7 @@ require (
 	gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect

+ 21 - 0
go.sum

@@ -66,6 +66,14 @@ github.com/1Password/connect-sdk-go v1.5.0 h1:F0WJcLSzGg3iXEDY49/ULdszYKsQLGTzn+
 github.com/1Password/connect-sdk-go v1.5.0/go.mod h1:TdynFeyvaRoackENbJ8RfJokH+WAowAu1MLmUbdMq6s=
 github.com/Azure/azure-sdk-for-go v66.0.0+incompatible h1:bmmC38SlE8/E81nNADlgmVGurPWMHDX2YNXVQMrBpEE=
 github.com/Azure/azure-sdk-for-go v66.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 h1:zBJcBJwte0x6PcPK7XaWDMvK2o2ZM2f1sMaqNNavQ5g=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 h1:mM/yraAumqMMIYev6zX0oxHqX6hreUs5wXf76W47r38=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2/go.mod h1:+nVKciyKD2J9TyVcEQ82Bo9b+3F92PiQfHrIE/zqLqM=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 h1:sLZ/Y+P/5RRtsXWylBjB5lkgixYfm0MQPiwrSX//JSo=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
@@ -221,6 +229,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
+github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -660,6 +670,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -707,6 +718,9 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
 github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
 github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -911,6 +925,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -997,6 +1013,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -1009,6 +1026,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1124,6 +1142,7 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1450,6 +1469,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=

+ 48 - 43
hack/api-docs/mkdocs.yml

@@ -7,6 +7,10 @@ edit_uri: ./edit/main/docs/
 remote_branch: gh-pages
 theme:
   name: material
+  features:
+    - navigation.tabs
+    - navigation.indexes
+    - navigation.expand
   custom_dir: ../../overrides
 markdown_extensions:
   - pymdownx.highlight
@@ -31,19 +35,33 @@ extra:
     provider: google
     property: G-QP38TD8K7V
 nav:
-  - Introduction: index.md
-  - Overview: overview.md
-  - API Types:
-      ExternalSecret: api/externalsecret.md
-      SecretStore: api/secretstore.md
-      ClusterSecretStore: api/clustersecretstore.md
-      ClusterExternalSecret: api/clusterexternalsecret.md
+  - Introduction:
+    - Introduction: index.md
+    - Overview: introduction/overview.md
+    - Getting started: introduction/getting-started.md
+    - FAQ: introduction/faq.md
+    - Stability and Support: introduction/stability-support.md
+    - Deprecation Policy: introduction/deprecation-policy.md
+  - API:
+    - Core Resources:
+      - ExternalSecret: api/externalsecret.md
+      - SecretStore: api/secretstore.md
+      - ClusterSecretStore: api/clustersecretstore.md
+      - ClusterExternalSecret: api/clusterexternalsecret.md
+    - Generators:
+      - "api/generator/index.md"
+      - Azure Container Registry: api/generator/acr.md
+      - AWS Elastic Container Registry: api/generator/ecr.md
+      - Google Container Registry: api/generator/gcr.md
+      - Fake: api/generator/fake.md
+    - Reference Docs:
+      - API specification: api/spec.md
   - Guides:
     - Introduction: guides/introduction.md
-    - Getting started: guides/getting-started.md
     - Advanced Templating:
         v2: guides/templating.md
         v1: guides/templating-v1.md
+    - Generators: guides/generator.md
     - All keys, One secret: guides/all-keys-one-secret.md
     - Common K8S Secret Types: guides/common-k8s-secret-types.md
     - Controller Classes: guides/controller-class.md
@@ -56,47 +74,34 @@ nav:
     - Upgrading to v1beta1: guides/v1beta1.md
     - Using Latest Image: guides/using-latest-image.md
   - Provider:
-    - AWS:
-      - Secrets Manager: provider/aws-secrets-manager.md
-      - Parameter Store: provider/aws-parameter-store.md
-    - Azure:
-      - Key Vault: provider/azure-key-vault.md
-    - Google:
-      - Secret Manager: provider/google-secrets-manager.md
-    - IBM:
-      - Secrets Manager: provider/ibm-secrets-manager.md
+    - AWS Secrets Manager: provider/aws-secrets-manager.md
+    - AWS Parameter Store: provider/aws-parameter-store.md
+    - Azure Key Vault: provider/azure-key-vault.md
+    - Google Secret Manager: provider/google-secrets-manager.md
+    - IBM Secrets Manager: provider/ibm-secrets-manager.md
     - Akeyless: provider/akeyless.md
     - HashiCorp Vault: provider/hashicorp-vault.md
-    - Yandex:
-        - Certificate Manager: provider/yandex-certificate-manager.md
-        - Lockbox: provider/yandex-lockbox.md
-    - Gitlab:
-      - Gitlab Project Variables: provider/gitlab-project-variables.md
-    - Oracle:
-      - Oracle Vault: provider/oracle-vault.md
-    - 1Password:
-      - Secrets Automation: provider/1password-automation.md
+    - Yandex Certificate Manager: provider/yandex-certificate-manager.md
+    - Yandex Lockbox: provider/yandex-lockbox.md
+    - Gitlab Project Variables: provider/gitlab-project-variables.md
+    - Oracle Vault: provider/oracle-vault.md
+    - 1Password Secrets Automation: provider/1password-automation.md
     - Webhook: provider/webhook.md
     - Fake: provider/fake.md
     - Kubernetes: provider/kubernetes.md
-    - senhasegura:
-      - DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
+    - senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
   - Examples:
     - FluxCD: examples/gitops-using-fluxcd.md
     - Anchore Engine: examples/anchore-engine-credentials.md
     - Jenkins: examples/jenkins-kubernetes-credentials.md
-  - External Resources:
-    - Talks: eso-talks.md
-    - Demos: eso-demos.md
-    - Blogs: eso-blogs.md
-  - References:
-    - API specification: spec.md
-  - Contributing:
-    - Developer guide: contributing/devguide.md
-    - Contributing Process: contributing/process.md
-    - Release Process: contributing/release.md
-    - Code of Conduct: contributing/coc.md
-    - Roadmap: contributing/roadmap.md
-  - FAQ: faq.md
-  - Stability and Support: stability-support.md
-  - Deprecation Policy: deprecation-policy.md
+  - Community:
+    - Contributing:
+      - Developer guide: contributing/devguide.md
+      - Contributing Process: contributing/process.md
+      - Release Process: contributing/release.md
+      - Code of Conduct: contributing/coc.md
+      - Roadmap: contributing/roadmap.md
+    - External Resources:
+      - Talks: eso-talks.md
+      - Demos: eso-demos.md
+      - Blogs: eso-blogs.md

+ 3 - 3
hack/crd.generate.sh

@@ -8,11 +8,11 @@ BUNDLE_YAML="${BUNDLE_DIR}/bundle.yaml"
 
 cd "${SCRIPT_DIR}"/../
 
-go run sigs.k8s.io/controller-tools/cmd/controller-gen \
+go run sigs.k8s.io/controller-tools/cmd/controller-gen object \
   object:headerFile="hack/boilerplate.go.txt" \
-  paths="./..."
+  paths="./apis/..."
 go run sigs.k8s.io/controller-tools/cmd/controller-gen crd \
-  paths="./..." \
+  paths="./apis/..." \
   output:crd:artifacts:config="${CRD_DIR}/bases"
 
 # Remove extra header lines in generated CRDs

+ 216 - 116
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -25,8 +25,15 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus"
 	v1 "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/api/equality"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/discovery"
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/restmapper"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
@@ -37,8 +44,13 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
+
+	// Loading registered generators.
+	_ "github.com/external-secrets/external-secrets/pkg/generator/register"
 	// Loading registered providers.
 	_ "github.com/external-secrets/external-secrets/pkg/provider/register"
 	"github.com/external-secrets/external-secrets/pkg/utils"
@@ -50,6 +62,7 @@ const (
 	errGetES                 = "could not get ExternalSecret"
 	errConvert               = "could not apply conversion strategy to keys: %v"
 	errDecode                = "could not apply decoding strategy to %v[%d]: %v"
+	errGenerate              = "could not generate [%d]: %w"
 	errRewrite               = "could not rewrite spec.dataFrom[%d]: %v"
 	errInvalidKeys           = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric,'-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides-datafrom-rewrite)"
 	errUpdateSecret          = "could not update Secret"
@@ -83,6 +96,7 @@ type Reconciler struct {
 	client.Client
 	Log                       logr.Logger
 	Scheme                    *runtime.Scheme
+	RestConfig                *rest.Config
 	ControllerClass           string
 	RequeueInterval           time.Duration
 	ClusterSecretStoreEnabled bool
@@ -131,42 +145,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		}
 	}()
 
-	store, err := r.getStore(ctx, &externalSecret)
-	if err != nil {
-		log.Error(err, errStoreRef)
-		r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonInvalidStoreRef, err.Error())
-		conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreRef)
-		SetExternalSecretCondition(&externalSecret, *conditionSynced)
-		syncCallsError.With(syncCallsMetricLabels).Inc()
-		return ctrl.Result{}, err
-	}
-
-	log = log.WithValues("SecretStore", store.GetNamespacedName())
-
-	// check if store should be handled by this controller instance
-	if !secretstore.ShouldProcessStore(store, r.ControllerClass) {
-		log.Info("skipping unmanaged store")
-		return ctrl.Result{}, nil
-	}
-
-	if r.EnableFloodGate {
-		if err = assertStoreIsUsable(store); err != nil {
-			log.Error(err, errStoreUsability)
-			r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUnavailableStore, err.Error())
-			conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreUsability)
-			SetExternalSecretCondition(&externalSecret, *conditionSynced)
-			syncCallsError.With(syncCallsMetricLabels).Inc()
-			return ctrl.Result{}, err
-		}
-	}
-
-	storeProvider, err := esv1beta1.GetProvider(store)
-	if err != nil {
-		log.Error(err, errStoreProvider)
-		syncCallsError.With(syncCallsMetricLabels).Inc()
-		return ctrl.Result{RequeueAfter: requeueAfter}, nil
-	}
-
 	refreshInt := r.RequeueInterval
 	if externalSecret.Spec.RefreshInterval != nil {
 		refreshInt = externalSecret.Spec.RefreshInterval.Duration
@@ -204,25 +182,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		}, nil
 	}
 
-	// secret client is created only if we are going to refresh
-	// this skip an unnecessary check/request in the case we are not going to do anything
-	secretClient, err := storeProvider.NewClient(ctx, store, r.Client, req.Namespace)
-	if err != nil {
-		log.Error(err, errStoreClient)
-		conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreClient)
-		SetExternalSecretCondition(&externalSecret, *conditionSynced)
-		r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonProviderClientConfig, err.Error())
-		syncCallsError.With(syncCallsMetricLabels).Inc()
-		return ctrl.Result{RequeueAfter: requeueAfter}, nil
-	}
-
-	defer func() {
-		err = secretClient.Close(ctx)
-		if err != nil {
-			log.Error(err, errCloseStoreClient)
-		}
-	}()
-
 	secret := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      secretName,
@@ -232,7 +191,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		Data:      make(map[string][]byte),
 	}
 
-	dataMap, err := r.getProviderSecretData(ctx, secretClient, &externalSecret)
+	dataMap, err := r.getProviderSecretData(ctx, &externalSecret, r.Client)
 	if err != nil {
 		log.Error(err, errGetSecretData)
 		r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
@@ -487,6 +446,9 @@ func isSecretValid(existingSecret v1.Secret) bool {
 
 // assertStoreIsUsable assert that the store is ready to use.
 func assertStoreIsUsable(store esv1beta1.GenericStore) error {
+	if store == nil {
+		return nil
+	}
 	condition := secretstore.GetSecretStoreCondition(store.GetStatus(), esv1beta1.SecretStoreReady)
 	if condition == nil || condition.Status != v1.ConditionTrue {
 		return fmt.Errorf(errSecretStoreNotReady, store.GetName())
@@ -494,12 +456,12 @@ func assertStoreIsUsable(store esv1beta1.GenericStore) error {
 	return nil
 }
 
-func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.ExternalSecret) (esv1beta1.GenericStore, error) {
+func (r *Reconciler) getStore(ctx context.Context, storeRef *esv1beta1.SecretStoreRef, namespace string) (esv1beta1.GenericStore, error) {
 	ref := types.NamespacedName{
-		Name: externalSecret.Spec.SecretStoreRef.Name,
+		Name: storeRef.Name,
 	}
 
-	if externalSecret.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind {
+	if storeRef.Kind == esv1beta1.ClusterSecretStoreKind {
 		var store esv1beta1.ClusterSecretStore
 		err := r.Get(ctx, ref, &store)
 		if err != nil {
@@ -508,8 +470,7 @@ func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.Ext
 		return &store, nil
 	}
 
-	ref.Namespace = externalSecret.Namespace
-
+	ref.Namespace = namespace
 	var store esv1beta1.SecretStore
 	err := r.Get(ctx, ref, &store)
 	if err != nil {
@@ -519,72 +480,41 @@ func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.Ext
 }
 
 // getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
-func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient esv1beta1.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
+func (r *Reconciler) getProviderSecretData(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, crClient client.Client) (map[string][]byte, error) {
 	providerData := make(map[string][]byte)
-
 	for i, remoteRef := range externalSecret.Spec.DataFrom {
 		var secretMap map[string][]byte
 		var err error
+
 		if remoteRef.Find != nil {
-			secretMap, err = providerClient.GetAllSecrets(ctx, *remoteRef.Find)
-			if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
-				r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
-				continue
-			}
-			if err != nil {
-				return nil, err
-			}
-			secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
-			if err != nil {
-				return nil, fmt.Errorf(errRewrite, i, err)
-			}
-			if len(remoteRef.Rewrite) == 0 {
-				// ConversionStrategy is deprecated. Use RewriteMap instead.
-				r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonDeprecated, fmt.Sprintf("dataFrom[%d].find.conversionStrategy=%v is deprecated and will be removed in further releases. Use dataFrom.rewrite instead", i, remoteRef.Find.ConversionStrategy))
-				secretMap, err = utils.ConvertKeys(remoteRef.Find.ConversionStrategy, secretMap)
-				if err != nil {
-					return nil, fmt.Errorf(errConvert, err)
-				}
-			}
-			if !utils.ValidateKeys(secretMap) {
-				return nil, fmt.Errorf(errInvalidKeys, "find", i)
-			}
-			secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
-			if err != nil {
-				return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
-			}
+			secretMap, err = r.handleFindAllSecrets(ctx, externalSecret, remoteRef, i)
 		} else if remoteRef.Extract != nil {
-			secretMap, err = providerClient.GetSecretMap(ctx, *remoteRef.Extract)
-			if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
-				r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
-				continue
-			}
-			if err != nil {
-				return nil, err
-			}
-			secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
-			if err != nil {
-				return nil, fmt.Errorf(errRewrite, i, err)
-			}
-			if len(remoteRef.Rewrite) == 0 {
-				secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
-				if err != nil {
-					return nil, fmt.Errorf(errConvert, err)
-				}
-			}
-			if !utils.ValidateKeys(secretMap) {
-				return nil, fmt.Errorf(errInvalidKeys, "extract", i)
-			}
-			secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
-			if err != nil {
-				return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
-			}
+			secretMap, err = r.handleExtractSecrets(ctx, externalSecret, remoteRef, i)
+		} else if remoteRef.SourceRef != nil && (remoteRef.SourceRef.Generator != nil || remoteRef.SourceRef.GeneratorRef != nil) {
+			secretMap, err = r.handleGenerateSecrets(ctx, externalSecret.Namespace, remoteRef, i)
+		}
+		if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
+			r.recorder.Event(
+				externalSecret,
+				v1.EventTypeNormal,
+				esv1beta1.ReasonDeleted,
+				fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i),
+			)
+			continue
+		}
+		if err != nil {
+			return nil, err
 		}
 		providerData = utils.MergeByteMap(providerData, secretMap)
 	}
 
 	for i, secretRef := range externalSecret.Spec.Data {
-		secretData, err := providerClient.GetSecret(ctx, secretRef.RemoteRef)
+		client, err := r.getClientOrDefault(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, secretRef.SourceRef)
+		if err != nil {
+			return nil, err
+		}
+		defer client.Close(ctx)
+		secretData, err := client.GetSecret(ctx, secretRef.RemoteRef)
 		if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
 			r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .data[%d] key=%s", i, secretRef.RemoteRef.Key))
 			continue
@@ -602,6 +532,176 @@ func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient e
 	return providerData, nil
 }
 
+func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
+	genDef, err := r.getGeneratorDefinition(ctx, namespace, remoteRef.SourceRef)
+	if err != nil {
+		return nil, err
+	}
+	gen, err := genv1alpha1.GetGenerator(genDef)
+	if err != nil {
+		return nil, err
+	}
+	secretMap, err := gen.Generate(ctx, genDef, r.Client, namespace)
+	if err != nil {
+		return nil, fmt.Errorf(errGenerate, i, err)
+	}
+	secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
+	if err != nil {
+		return nil, fmt.Errorf(errRewrite, i, err)
+	}
+	if !utils.ValidateKeys(secretMap) {
+		return nil, fmt.Errorf(errInvalidKeys, "generator", i)
+	}
+	return secretMap, err
+}
+
+// getGeneratorDefinition returns the generator JSON for a given sourceRef
+// when a generator is defined inline it returns sourceRef.Generator straight away
+// when it uses a generatorRef it fetches the resource and returns the JSON
+func (r *Reconciler) getGeneratorDefinition(ctx context.Context, namespace string, sourceRef *esv1beta1.SourceRef) (*apiextensions.JSON, error) {
+	if sourceRef.Generator != nil {
+		return sourceRef.Generator, nil
+	}
+	// client-go dynamic client needs a GVR to fetch the resource
+	// But we only have the GVK in our generatorRef.
+	//
+	// TODO: there is no need to discover the GroupVersionResource
+	//       this should be cached.
+	c := discovery.NewDiscoveryClientForConfigOrDie(r.RestConfig)
+	groupResources, err := restmapper.GetAPIGroupResources(c)
+	if err != nil {
+		return nil, err
+	}
+
+	gv, err := schema.ParseGroupVersion(sourceRef.GeneratorRef.APIVersion)
+	if err != nil {
+		return nil, err
+	}
+	mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
+	mapping, err := mapper.RESTMapping(schema.GroupKind{
+		Group: gv.Group,
+		Kind:  sourceRef.GeneratorRef.Kind,
+	})
+	if err != nil {
+		return nil, err
+	}
+	d, err := dynamic.NewForConfig(r.RestConfig)
+	if err != nil {
+		return nil, err
+	}
+	res, err := d.Resource(mapping.Resource).
+		Namespace(namespace).
+		Get(ctx, sourceRef.GeneratorRef.Name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	jsonRes, err := res.MarshalJSON()
+	if err != nil {
+		return nil, err
+	}
+	return &apiextensions.JSON{Raw: jsonRes}, nil
+}
+
+func (r *Reconciler) handleExtractSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
+	client, err := r.getClientOrDefault(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close(ctx)
+	secretMap, err := client.GetSecretMap(ctx, *remoteRef.Extract)
+	if err != nil {
+		return nil, err
+	}
+	secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
+	if err != nil {
+		return nil, fmt.Errorf(errRewrite, i, err)
+	}
+	if len(remoteRef.Rewrite) == 0 {
+		secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
+		if err != nil {
+			return nil, fmt.Errorf(errConvert, err)
+		}
+	}
+	if !utils.ValidateKeys(secretMap) {
+		return nil, fmt.Errorf(errInvalidKeys, "extract", i)
+	}
+	secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
+	if err != nil {
+		return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
+	}
+	return secretMap, err
+}
+
+func (r *Reconciler) handleFindAllSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
+	client, err := r.getClientOrDefault(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close(ctx)
+	secretMap, err := client.GetAllSecrets(ctx, *remoteRef.Find)
+	if err != nil {
+		return nil, err
+	}
+	secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
+	if err != nil {
+		return nil, fmt.Errorf(errRewrite, i, err)
+	}
+	if len(remoteRef.Rewrite) == 0 {
+		// ConversionStrategy is deprecated. Use RewriteMap instead.
+		r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonDeprecated, fmt.Sprintf("dataFrom[%d].find.conversionStrategy=%v is deprecated and will be removed in further releases. Use dataFrom.rewrite instead", i, remoteRef.Find.ConversionStrategy))
+		secretMap, err = utils.ConvertKeys(remoteRef.Find.ConversionStrategy, secretMap)
+		if err != nil {
+			return nil, fmt.Errorf(errConvert, err)
+		}
+	}
+	if !utils.ValidateKeys(secretMap) {
+		return nil, fmt.Errorf(errInvalidKeys, "find", i)
+	}
+	secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
+	if err != nil {
+		return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
+	}
+	return secretMap, err
+}
+
+// getClientOrDefault returns a provider client from the given storeRef or sourceRef.secretStoreRef
+// while sourceRef.SecretStoreRef takes precedence over storeRef.
+// it returns nil if both storeRef and sourceRef.secretStoreRef is empty
+func (r *Reconciler) getClientOrDefault(ctx context.Context, storeRef esv1beta1.SecretStoreRef, namespace string, sourceRef *esv1beta1.SourceRef) (v1beta1.SecretsClient, error) {
+	if sourceRef != nil && sourceRef.SecretStoreRef != nil {
+		storeRef = *sourceRef.SecretStoreRef
+	}
+
+	store, err := r.getStore(ctx, &storeRef, namespace)
+	if err != nil {
+		return nil, err
+	}
+
+	// check if store should be handled by this controller instance
+	if !secretstore.ShouldProcessStore(store, r.ControllerClass) {
+		return nil, fmt.Errorf("can not reference unmanaged store")
+	}
+
+	if r.EnableFloodGate {
+		if err = assertStoreIsUsable(store); err != nil {
+			return nil, err
+		}
+	}
+
+	storeProvider, err := esv1beta1.GetProvider(store)
+	if err != nil {
+		return nil, err
+	}
+
+	// secret client is created only if we are going to refresh
+	// this skip an unnecessary check/request in the case we are not going to do anything
+	providerClient, err := storeProvider.NewClient(ctx, store, r.Client, namespace)
+	if err != nil {
+		return nil, err
+	}
+	return providerClient, nil
+}
+
 // SetupWithManager returns a new controller builder that will be started by the provided Manager.
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
 	r.recorder = mgr.GetEventRecorderFor("external-secrets")

+ 159 - 0
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -21,10 +21,12 @@ import (
 	"strconv"
 	"time"
 
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	dto "github.com/prometheus/client_model/go"
 	v1 "k8s.io/api/core/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"
 	"k8s.io/apimachinery/pkg/types"
@@ -415,6 +417,160 @@ var _ = Describe("ExternalSecret controller", func() {
 		}
 	}
 
+	syncWithInlineGenerator := func(tc *testCase) {
+		const secretKey = "somekey"
+		const secretVal = "someValue"
+
+		// generator is defined in yaml, however it is transformed as json
+		// internally
+		fakeGen := `
+{
+  "apiVersion": "generators.external-secrets.io/v1alpha1",
+  "kind": "Fake",
+  "spec": {
+    "data": {
+      "%s": "%s"
+    }
+  }
+}`
+
+		// reset secretStoreRef
+		tc.externalSecret.Spec.SecretStoreRef = esv1beta1.SecretStoreRef{}
+		tc.externalSecret.Spec.Data = nil
+		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
+			{
+				SourceRef: &esv1beta1.SourceRef{
+					Generator: &apiextensions.JSON{
+						Raw: []byte(fmt.Sprintf(fakeGen, secretKey, secretVal)),
+					},
+				},
+			},
+		}
+
+		tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
+			// check values
+			Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
+		}
+	}
+
+	syncWithGeneratorRef := func(tc *testCase) {
+		const secretKey = "somekey"
+		const secretVal = "someValue"
+
+		Expect(k8sClient.Create(context.Background(), &genv1alpha1.Fake{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "mytestfake",
+				Namespace: ExternalSecretNamespace,
+			},
+			Spec: genv1alpha1.FakeSpec{
+				Data: map[string]string{
+					secretKey: secretVal,
+				},
+			},
+		})).To(Succeed())
+
+		// reset secretStoreRef
+		tc.externalSecret.Spec.SecretStoreRef = esv1beta1.SecretStoreRef{}
+		tc.externalSecret.Spec.Data = nil
+		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
+			{
+				SourceRef: &esv1beta1.SourceRef{
+					GeneratorRef: &esv1beta1.GeneratorRef{
+						APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
+						Kind:       "Fake",
+						Name:       "mytestfake",
+					},
+				},
+			},
+		}
+
+		tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
+			// check values
+			Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
+		}
+	}
+
+	syncWithMultipleSecretStores := func(tc *testCase) {
+		Expect(k8sClient.Create(context.Background(), &esv1beta1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: ExternalSecretNamespace,
+			},
+			Spec: esv1beta1.SecretStoreSpec{
+				Provider: &esv1beta1.SecretStoreProvider{
+					Fake: &esv1beta1.FakeProvider{
+						Data: []esv1beta1.FakeProviderData{
+							{
+								Key:     "foo",
+								Version: "",
+								ValueMap: map[string]string{
+									"foo":  "bar",
+									"foo2": "bar2",
+								},
+							},
+						},
+					},
+				},
+			},
+		})).To(Succeed())
+
+		Expect(k8sClient.Create(context.Background(), &esv1beta1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "baz",
+				Namespace: ExternalSecretNamespace,
+			},
+			Spec: esv1beta1.SecretStoreSpec{
+				Provider: &esv1beta1.SecretStoreProvider{
+					Fake: &esv1beta1.FakeProvider{
+						Data: []esv1beta1.FakeProviderData{
+							{
+								Key:     "baz",
+								Version: "",
+								ValueMap: map[string]string{
+									"baz":  "bang",
+									"baz2": "bang2",
+								},
+							},
+						},
+					},
+				},
+			},
+		})).To(Succeed())
+
+		tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
+			{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
+					Key: "foo",
+				},
+				SourceRef: &esv1beta1.SourceRef{
+					SecretStoreRef: &esv1beta1.SecretStoreRef{
+						Name: "foo",
+						Kind: esv1beta1.SecretStoreKind,
+					},
+				},
+			},
+			{
+				Extract: &esv1beta1.ExternalSecretDataRemoteRef{
+					Key: "baz",
+				},
+				SourceRef: &esv1beta1.SourceRef{
+					SecretStoreRef: &esv1beta1.SecretStoreRef{
+						Name: "baz",
+						Kind: esv1beta1.SecretStoreKind,
+					},
+				},
+			},
+		}
+
+		tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
+			// check values
+			Expect(string(secret.Data["foo"])).To(Equal("bar"))
+			Expect(string(secret.Data["foo2"])).To(Equal("bar2"))
+			Expect(string(secret.Data["baz"])).To(Equal("bang"))
+			Expect(string(secret.Data["baz2"])).To(Equal("bang2"))
+		}
+	}
+
 	// when using a template it should be used as a blueprint
 	// to construct a new secret: labels, annotations and type
 	syncWithTemplate := func(tc *testCase) {
@@ -1438,6 +1594,9 @@ var _ = Describe("ExternalSecret controller", func() {
 		Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
 		Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
 		Entry("should not delete pre-existing secret with creationPolicy=Orphan", createSecretPolicyOrphan),
+		Entry("should sync with inline generator", syncWithInlineGenerator),
+		Entry("should sync with generatorRef", syncWithGeneratorRef),
+		Entry("should sync with multiple secret stores via sourceRef", syncWithMultipleSecretStores),
 		Entry("should sync with template", syncWithTemplate),
 		Entry("should sync with template engine v2", syncWithTemplateV2),
 		Entry("should sync template with correct value precedence", syncWithTemplatePrecedence),

+ 5 - 0
pkg/controllers/externalsecret/suite_test.go

@@ -33,6 +33,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
 )
 
 // These tests use Ginkgo (BDD-style Go testing framework). Refer to
@@ -69,6 +70,9 @@ var _ = BeforeSuite(func() {
 	err = esv1beta1.AddToScheme(scheme.Scheme)
 	Expect(err).NotTo(HaveOccurred())
 
+	err = genv1alpha1.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+
 	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
 		Scheme:             scheme.Scheme,
 		MetricsBindAddress: "0", // avoid port collision when testing
@@ -83,6 +87,7 @@ var _ = BeforeSuite(func() {
 
 	err = (&Reconciler{
 		Client:          k8sClient,
+		RestConfig:      cfg,
 		Scheme:          k8sManager.GetScheme(),
 		Log:             ctrl.Log.WithName("controllers").WithName("ExternalSecrets"),
 		RequeueInterval: time.Second,

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

@@ -111,7 +111,7 @@ func validateStore(ctx context.Context, namespace string, store esapi.GenericSto
 
 // ShouldProcessStore returns true if the store should be processed.
 func ShouldProcessStore(store esapi.GenericStore, class string) bool {
-	if store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
+	if store == nil || store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
 		return true
 	}
 

+ 345 - 0
pkg/generator/acr/acr.go

@@ -0,0 +1,345 @@
+/*
+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 acr
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"strings"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/go-autorest/autorest/azure"
+	"k8s.io/client-go/kubernetes"
+	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
+
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
+	corev1 "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/json"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Generator struct{}
+
+type TokenGetter interface {
+	GetToken(ctx context.Context, opts policy.TokenRequestOptions) (*azcore.AccessToken, error)
+}
+
+const (
+	defaultLoginUsername = "00000000-0000-0000-0000-000000000000"
+
+	errNoSpec     = "no config spec provided"
+	errParseSpec  = "unable to parse spec: %w"
+	errCreateSess = "unable to create aws session: %w"
+	errGetToken   = "unable to get authorization token: %w"
+)
+
+// Generate generates a token that can be used to authenticate against Azure Container Registry.
+// First, an Azure Active Directory access token is obtained with the desired authentication method.
+// This AAD access token will be used to authenticate against ACR.
+// Depending on the generator spec it generates an ACR access token or an ACR refresh token.
+// * access tokens are scoped to a specific repository or action (pull,push)
+// * refresh tokens can are scoped to whatever policy is attached to the identity that creates the acr refresh token
+// details can be found here: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
+func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, crClient client.Client, namespace string) (map[string][]byte, error) {
+	if jsonSpec == nil {
+		return nil, fmt.Errorf(errNoSpec)
+	}
+	res, err := parseSpec(jsonSpec.Raw)
+	if err != nil {
+		return nil, fmt.Errorf(errParseSpec, err)
+	}
+
+	cfg, err := ctrlcfg.GetConfig()
+	if err != nil {
+		return nil, err
+	}
+	kubeClient, err := kubernetes.NewForConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+	var accessToken string
+	// pick authentication strategy to create an AAD access token
+	if res.Spec.Auth.ServicePrincipal != nil {
+		accessToken, err = accessTokenForServicePrincipal(
+			ctx,
+			crClient,
+			namespace,
+			res.Spec.EnvironmentType,
+			res.Spec.TenantID,
+			res.Spec.Auth.ServicePrincipal.SecretRef.ClientID,
+			res.Spec.Auth.ServicePrincipal.SecretRef.ClientSecret,
+		)
+	} else if res.Spec.Auth.ManagedIdentity != nil {
+		accessToken, err = accessTokenForManagedIdentity(
+			ctx,
+			res.Spec.EnvironmentType,
+			res.Spec.Auth.ManagedIdentity.IdentityID,
+		)
+	} else if res.Spec.Auth.WorkloadIdentity != nil {
+		accessToken, err = accessTokenForWorkloadIdentity(
+			ctx,
+			crClient,
+			kubeClient.CoreV1(),
+			res.Spec.ACRRegistry,
+			res.Spec.EnvironmentType,
+			res.Spec.Auth.WorkloadIdentity.ServiceAccountRef,
+			namespace,
+		)
+	}
+	if err != nil {
+		return nil, err
+	}
+	var acrToken string
+	acrToken, err = fetchACRRefreshToken(accessToken, res.Spec.TenantID, res.Spec.ACRRegistry)
+	if err != nil {
+		return nil, err
+	}
+	if res.Spec.Scope != "" {
+		acrToken, err = fetchACRAccessToken(acrToken, res.Spec.TenantID, res.Spec.ACRRegistry, res.Spec.Scope)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return map[string][]byte{
+		"username": []byte(defaultLoginUsername),
+		"password": []byte(acrToken),
+	}, nil
+}
+
+func fetchACRAccessToken(acrRefreshToken, tenantID, registryURL, scope string) (string, error) {
+	formData := url.Values{
+		"grant_type":    {"refresh_token"},
+		"service":       {registryURL},
+		"scope":         {scope},
+		"refresh_token": {acrRefreshToken},
+	}
+	res, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/token", registryURL), formData)
+	if err != nil {
+		return "", err
+	}
+	if res.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("unexpected status code: %d", res.StatusCode)
+	}
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return "", err
+	}
+	var payload map[string]string
+	err = json.Unmarshal(body, &payload)
+	if err != nil {
+		return "", err
+	}
+	accessToken, ok := payload["access_token"]
+	if !ok {
+		return "", fmt.Errorf("unable to get token")
+	}
+	return accessToken, nil
+}
+
+func fetchACRRefreshToken(aadAccessToken, tenantID, registryURL string) (string, error) {
+	// https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
+	// https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli
+	formData := url.Values{
+		"grant_type":   {"access_token"},
+		"service":      {registryURL},
+		"tenant":       {tenantID},
+		"access_token": {aadAccessToken},
+	}
+	res, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", registryURL), formData)
+	if err != nil {
+		return "", err
+	}
+	if res.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("unexpected status code %d, expected %d", res.StatusCode, http.StatusOK)
+	}
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return "", err
+	}
+	var payload map[string]string
+	err = json.Unmarshal(body, &payload)
+	if err != nil {
+		return "", err
+	}
+	refreshToken, ok := payload["refresh_token"]
+	if !ok {
+		return "", fmt.Errorf("unable to get token")
+	}
+	return refreshToken, nil
+}
+
+func accessTokenForWorkloadIdentity(ctx context.Context, crClient client.Client, kubeClient kcorev1.CoreV1Interface, acrRegistry string, envType v1beta1.AzureEnvironmentType, serviceAccountRef *smmeta.ServiceAccountSelector, namespace string) (string, error) {
+	aadEndpoint := keyvault.AadEndpointForType(envType)
+	if !strings.HasSuffix(acrRegistry, "/") {
+		acrRegistry += "/"
+	}
+	acrResource := fmt.Sprintf("https://%s/.default", acrRegistry)
+	// if no serviceAccountRef was provided
+	// we expect certain env vars to be present.
+	// They are set by the azure workload identity webhook.
+	if serviceAccountRef == nil {
+		clientID := os.Getenv("AZURE_CLIENT_ID")
+		tenantID := os.Getenv("AZURE_TENANT_ID")
+		tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
+		if clientID == "" || tenantID == "" || tokenFilePath == "" {
+			return "", errors.New("missing environment variables")
+		}
+		token, err := os.ReadFile(tokenFilePath)
+		if err != nil {
+			return "", fmt.Errorf("unable to read token file %s: %w", tokenFilePath, err)
+		}
+		tp, err := keyvault.NewTokenProvider(ctx, string(token), clientID, tenantID, aadEndpoint, acrResource)
+		if err != nil {
+			return "", err
+		}
+		return tp.OAuthToken(), nil
+	}
+	var sa corev1.ServiceAccount
+	err := crClient.Get(ctx, types.NamespacedName{
+		Name:      serviceAccountRef.Name,
+		Namespace: namespace,
+	}, &sa)
+	if err != nil {
+		return "", err
+	}
+	clientID, ok := sa.ObjectMeta.Annotations[keyvault.AnnotationClientID]
+	if !ok {
+		return "", fmt.Errorf("service account is missing annoation: %s", keyvault.AnnotationClientID)
+	}
+	tenantID, ok := sa.ObjectMeta.Annotations[keyvault.AnnotationTenantID]
+	if !ok {
+		return "", fmt.Errorf("service account is missing annotation: %s", keyvault.AnnotationTenantID)
+	}
+	audiences := []string{keyvault.AzureDefaultAudience}
+	if len(serviceAccountRef.Audiences) > 0 {
+		audiences = append(audiences, serviceAccountRef.Audiences...)
+	}
+	token, err := keyvault.FetchSAToken(ctx, namespace, serviceAccountRef.Name, audiences, kubeClient)
+	if err != nil {
+		return "", err
+	}
+	tp, err := keyvault.NewTokenProvider(ctx, token, clientID, tenantID, aadEndpoint, acrResource)
+	if err != nil {
+		return "", err
+	}
+	return tp.OAuthToken(), nil
+}
+
+func accessTokenForManagedIdentity(ctx context.Context, envType v1beta1.AzureEnvironmentType, identityID string) (string, error) {
+	// handle workload identity
+	creds, err := azidentity.NewManagedIdentityCredential(
+		&azidentity.ManagedIdentityCredentialOptions{
+			ID: azidentity.ResourceID(identityID),
+		},
+	)
+	if err != nil {
+		return "", err
+	}
+	aud := audienceForType(envType)
+	accessToken, err := creds.GetToken(ctx, policy.TokenRequestOptions{
+		Scopes: []string{aud},
+	})
+	if err != nil {
+		return "", err
+	}
+	return accessToken.Token, nil
+}
+
+func accessTokenForServicePrincipal(ctx context.Context, crClient client.Client, namespace string, envType v1beta1.AzureEnvironmentType, tenantID string, idRef, secretRef smmeta.SecretKeySelector) (string, error) {
+	cid, err := secretKeyRef(ctx, crClient, namespace, idRef)
+	if err != nil {
+		return "", err
+	}
+	csec, err := secretKeyRef(ctx, crClient, namespace, secretRef)
+	if err != nil {
+		return "", err
+	}
+	aadEndpoint := keyvault.AadEndpointForType(envType)
+	creds, err := azidentity.NewClientSecretCredential(
+		tenantID,
+		cid,
+		csec,
+		&azidentity.ClientSecretCredentialOptions{
+			AuthorityHost: azidentity.AuthorityHost(aadEndpoint),
+		})
+	if err != nil {
+		return "", err
+	}
+	aud := audienceForType(envType)
+	accessToken, err := creds.GetToken(ctx, policy.TokenRequestOptions{
+		Scopes: []string{aud},
+	})
+	if err != nil {
+		return "", err
+	}
+	return accessToken.Token, nil
+}
+
+// secretKeyRef fetches a secret key.
+func secretKeyRef(ctx context.Context, crClient client.Client, namespace string, secretRef smmeta.SecretKeySelector) (string, error) {
+	var secret corev1.Secret
+	ref := types.NamespacedName{
+		Namespace: namespace,
+		Name:      secretRef.Name,
+	}
+	err := crClient.Get(ctx, ref, &secret)
+	if err != nil {
+		return "", fmt.Errorf("unable to find namespace=%q secret=%q %w", ref.Namespace, ref.Name, err)
+	}
+	keyBytes, ok := secret.Data[secretRef.Key]
+	if !ok {
+		return "", fmt.Errorf("unable to find key=%q secret=%q namespace=%q", secretRef.Key, secretRef.Name, namespace)
+	}
+	value := strings.TrimSpace(string(keyBytes))
+	return value, nil
+}
+
+func audienceForType(t v1beta1.AzureEnvironmentType) string {
+	suffix := ".default"
+	switch t {
+	case v1beta1.AzureEnvironmentChinaCloud:
+		return azure.ChinaCloud.TokenAudience + suffix
+	case v1beta1.AzureEnvironmentGermanCloud:
+		return azure.GermanCloud.TokenAudience + suffix
+	case v1beta1.AzureEnvironmentUSGovernmentCloud:
+		return azure.USGovernmentCloud.TokenAudience + suffix
+	}
+	return azure.PublicCloud.TokenAudience + suffix
+}
+
+func parseSpec(data []byte) (*genv1alpha1.ACRAccessToken, error) {
+	var spec genv1alpha1.ACRAccessToken
+	err := json.Unmarshal(data, &spec)
+	return &spec, err
+}
+
+func init() {
+	genv1alpha1.Register(genv1alpha1.ACRAccessTokenKind, &Generator{})
+}

+ 98 - 0
pkg/generator/ecr/ecr.go

@@ -0,0 +1,98 @@
+/*
+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 ecr
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"encoding/base64"
+
+	"github.com/aws/aws-sdk-go/service/ecr"
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+	awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/util/json"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Generator struct{}
+
+const (
+	errNoSpec     = "no config spec provided"
+	errParseSpec  = "unable to parse spec: %w"
+	errCreateSess = "unable to create aws session: %w"
+	errGetToken   = "unable to get authorization token: %w"
+)
+
+func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, error) {
+	if jsonSpec == nil {
+		return nil, fmt.Errorf(errNoSpec)
+	}
+	res, err := parseSpec(jsonSpec.Raw)
+	if err != nil {
+		return nil, fmt.Errorf(errParseSpec, err)
+	}
+	sess, err := awsauth.NewGeneratorSession(
+		ctx,
+		res.Spec.Auth,
+		res.Spec.Role,
+		res.Spec.Region,
+		kube,
+		namespace,
+		awsauth.DefaultSTSProvider,
+		awsauth.DefaultJWTProvider)
+	if err != nil {
+		return nil, fmt.Errorf(errCreateSess, err)
+	}
+	client := ecr.New(sess)
+	out, err := client.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
+	if err != nil {
+		return nil, fmt.Errorf(errGetToken, err)
+	}
+	if len(out.AuthorizationData) != 1 {
+		return nil, fmt.Errorf("unexpected number of authorization tokens. expected 1, found %d", len(out.AuthorizationData))
+	}
+
+	// AuthorizationToken is base64 encoded {username}:{password} string
+	decodedToken, err := base64.StdEncoding.DecodeString(*out.AuthorizationData[0].AuthorizationToken)
+	if err != nil {
+		return nil, err
+	}
+	parts := strings.Split(string(decodedToken), ":")
+	if len(parts) != 2 {
+		return nil, fmt.Errorf("unexpected token format")
+	}
+
+	exp := out.AuthorizationData[0].ExpiresAt.UTC().Unix()
+	return map[string][]byte{
+		"username":       []byte(parts[0]),
+		"password":       []byte(parts[1]),
+		"proxy_endpoint": []byte(*out.AuthorizationData[0].ProxyEndpoint),
+		"expires_at":     []byte(strconv.FormatInt(exp, 10)),
+	}, nil
+}
+
+func parseSpec(data []byte) (*genv1alpha1.ECRAuthorizationToken, error) {
+	var spec genv1alpha1.ECRAuthorizationToken
+	err := json.Unmarshal(data, &spec)
+	return &spec, err
+}
+
+func init() {
+	genv1alpha1.Register(genv1alpha1.ECRAuthorizationTokenKind, &Generator{})
+}

+ 58 - 0
pkg/generator/fake/fake.go

@@ -0,0 +1,58 @@
+/*
+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 fake
+
+import (
+	"context"
+	"fmt"
+
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/util/json"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Generator struct{}
+
+const (
+	errNoSpec    = "no config spec provided"
+	errParseSpec = "unable to parse spec: %w"
+	errGetToken  = "unable to get authorization token: %w"
+)
+
+func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, error) {
+	if jsonSpec == nil {
+		return nil, fmt.Errorf(errNoSpec)
+	}
+	res, err := parseSpec(jsonSpec.Raw)
+	if err != nil {
+		return nil, fmt.Errorf(errParseSpec, err)
+	}
+	out := make(map[string][]byte)
+	for k, v := range res.Spec.Data {
+		out[k] = []byte(v)
+	}
+	return out, nil
+}
+
+func parseSpec(data []byte) (*genv1alpha1.Fake, error) {
+	var spec genv1alpha1.Fake
+	err := json.Unmarshal(data, &spec)
+	return &spec, err
+}
+
+func init() {
+	genv1alpha1.Register(genv1alpha1.FakeKind, &Generator{})
+}

+ 71 - 0
pkg/generator/gcr/gcr.go

@@ -0,0 +1,71 @@
+/*
+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 gcr
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+	"github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/util/json"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Generator struct{}
+
+const (
+	defaultLoginUsername = `oauth2accesstoken`
+
+	errNoSpec    = "no config spec provided"
+	errParseSpec = "unable to parse spec: %w"
+	errGetToken  = "unable to get authorization token: %w"
+)
+
+func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, error) {
+	if jsonSpec == nil {
+		return nil, fmt.Errorf(errNoSpec)
+	}
+	res, err := parseSpec(jsonSpec.Raw)
+	if err != nil {
+		return nil, fmt.Errorf(errParseSpec, err)
+	}
+	ts, err := secretmanager.NewTokenSource(ctx, res.Spec.Auth, res.Spec.ProjectID, false, kube, namespace)
+	if err != nil {
+		return nil, err
+	}
+	token, err := ts.Token()
+	if err != nil {
+		return nil, err
+	}
+	exp := strconv.FormatInt(token.Expiry.UTC().Unix(), 10)
+	return map[string][]byte{
+		"username": []byte(defaultLoginUsername),
+		"password": []byte(token.AccessToken),
+		"expiry":   []byte(exp),
+	}, nil
+}
+
+func parseSpec(data []byte) (*genv1alpha1.GCRAccessToken, error) {
+	var spec genv1alpha1.GCRAccessToken
+	err := json.Unmarshal(data, &spec)
+	return &spec, err
+}
+
+func init() {
+	genv1alpha1.Register(genv1alpha1.GCRAccessTokenKind, &Generator{})
+}

+ 24 - 0
pkg/generator/register/register.go

@@ -0,0 +1,24 @@
+/*
+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 register
+
+// packages imported here are registered to the controller schema.
+// nolint:revive
+import (
+	_ "github.com/external-secrets/external-secrets/pkg/generator/acr"
+	_ "github.com/external-secrets/external-secrets/pkg/generator/ecr"
+	_ "github.com/external-secrets/external-secrets/pkg/generator/fake"
+	_ "github.com/external-secrets/external-secrets/pkg/generator/gcr"
+)

+ 76 - 29
pkg/provider/aws/auth/auth.go

@@ -81,11 +81,12 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 		return nil, err
 	}
 	var creds *credentials.Credentials
+	isClusterKind := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
 
 	// use credentials via service account token
 	jwtAuth := prov.Auth.JWTAuth
 	if jwtAuth != nil {
-		creds, err = sessionFromServiceAccount(ctx, prov, store, kube, namespace, jwtProvider)
+		creds, err = sessionFromServiceAccount(ctx, prov.Auth, prov.Region, isClusterKind, kube, namespace, jwtProvider)
 		if err != nil {
 			return nil, err
 		}
@@ -95,7 +96,7 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 	secretRef := prov.Auth.SecretRef
 	if secretRef != nil {
 		log.V(1).Info("using credentials from secretRef")
-		creds, err = sessionFromSecretRef(ctx, prov, store, kube, namespace)
+		creds, err = sessionFromSecretRef(ctx, prov.Auth, isClusterKind, kube, namespace)
 		if err != nil {
 			return nil, err
 		}
@@ -109,7 +110,7 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 		config.WithRegion(prov.Region)
 	}
 
-	sess, err := getAWSSession(config, store, namespace)
+	sess, err := getAWSSession(config, EnableCache, store.GetName(), store.GetTypeMeta().Kind, namespace, store.GetObjectMeta().ResourceVersion)
 	if err != nil {
 		return nil, err
 	}
@@ -122,17 +123,66 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 	return sess, nil
 }
 
-func sessionFromSecretRef(ctx context.Context, prov *esv1beta1.AWSProvider, store esv1beta1.GenericStore, kube client.Client, namespace string) (*credentials.Credentials, error) {
+// NewSession creates a new aws session based on the provided store
+// it uses the following authentication mechanisms in order:
+// * service-account token authentication via AssumeRoleWithWebIdentity
+// * static credentials from a Kind=Secret, optionally with doing a AssumeRole.
+// * sdk default provider chain, see: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default
+func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, region string, kube client.Client, namespace string, assumeRoler STSProvider, jwtProvider jwtProviderFactory) (*session.Session, error) {
+	var creds *credentials.Credentials
+	var err error
+
+	// use credentials via service account token
+	jwtAuth := auth.JWTAuth
+	if jwtAuth != nil {
+		creds, err = sessionFromServiceAccount(ctx, auth, region, false, kube, namespace, jwtProvider)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// use credentials from sercretRef
+	secretRef := auth.SecretRef
+	if secretRef != nil {
+		log.V(1).Info("using credentials from secretRef")
+		creds, err = sessionFromSecretRef(ctx, auth, false, kube, namespace)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	config := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
+	if creds != nil {
+		config.WithCredentials(creds)
+	}
+	if region != "" {
+		config.WithRegion(region)
+	}
+
+	sess, err := getAWSSession(config, false, "", "", "", "")
+	if err != nil {
+		return nil, err
+	}
+
+	if role != "" {
+		stsclient := assumeRoler(sess)
+		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, role))
+	}
+	log.Info("using aws session", "region", *sess.Config.Region, "credentials", creds)
+	return sess, nil
+}
+
+func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isClusterKind bool, kube client.Client, namespace string) (*credentials.Credentials, error) {
 	ke := client.ObjectKey{
-		Name:      prov.Auth.SecretRef.AccessKeyID.Name,
+		Name:      auth.SecretRef.AccessKeyID.Name,
 		Namespace: namespace, // default to ExternalSecret namespace
 	}
 	// only ClusterStore is allowed to set namespace (and then it's required)
-	if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
-		if prov.Auth.SecretRef.AccessKeyID.Namespace == nil {
+	if isClusterKind {
+		if auth.SecretRef.AccessKeyID.Namespace == nil {
 			return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
 		}
-		ke.Namespace = *prov.Auth.SecretRef.AccessKeyID.Namespace
+		ke.Namespace = *auth.SecretRef.AccessKeyID.Namespace
 	}
 	akSecret := v1.Secret{}
 	err := kube.Get(ctx, ke, &akSecret)
@@ -140,23 +190,23 @@ func sessionFromSecretRef(ctx context.Context, prov *esv1beta1.AWSProvider, stor
 		return nil, fmt.Errorf(errFetchAKIDSecret, err)
 	}
 	ke = client.ObjectKey{
-		Name:      prov.Auth.SecretRef.SecretAccessKey.Name,
+		Name:      auth.SecretRef.SecretAccessKey.Name,
 		Namespace: namespace, // default to ExternalSecret namespace
 	}
 	// only ClusterStore is allowed to set namespace (and then it's required)
-	if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
-		if prov.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+	if isClusterKind {
+		if auth.SecretRef.SecretAccessKey.Namespace == nil {
 			return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
 		}
-		ke.Namespace = *prov.Auth.SecretRef.SecretAccessKey.Namespace
+		ke.Namespace = *auth.SecretRef.SecretAccessKey.Namespace
 	}
 	sakSecret := v1.Secret{}
 	err = kube.Get(ctx, ke, &sakSecret)
 	if err != nil {
 		return nil, fmt.Errorf(errFetchSAKSecret, err)
 	}
-	sak := string(sakSecret.Data[prov.Auth.SecretRef.SecretAccessKey.Key])
-	aks := string(akSecret.Data[prov.Auth.SecretRef.AccessKeyID.Key])
+	sak := string(sakSecret.Data[auth.SecretRef.SecretAccessKey.Key])
+	aks := string(akSecret.Data[auth.SecretRef.AccessKeyID.Key])
 	if sak == "" {
 		return nil, fmt.Errorf(errMissingSAK)
 	}
@@ -167,14 +217,11 @@ func sessionFromSecretRef(ctx context.Context, prov *esv1beta1.AWSProvider, stor
 	return credentials.NewStaticCredentials(aks, sak, ""), err
 }
 
-func sessionFromServiceAccount(ctx context.Context, prov *esv1beta1.AWSProvider, store esv1beta1.GenericStore, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
-	if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
-		if prov.Auth.JWTAuth.ServiceAccountRef.Namespace == nil {
-			return nil, fmt.Errorf("serviceAccountRef has no Namespace field (mandatory for ClusterSecretStore specs)")
-		}
-		namespace = *prov.Auth.JWTAuth.ServiceAccountRef.Namespace
+func sessionFromServiceAccount(ctx context.Context, auth esv1beta1.AWSAuth, region string, isClusterKind bool, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
+	name := auth.JWTAuth.ServiceAccountRef.Name
+	if isClusterKind {
+		namespace = *auth.JWTAuth.ServiceAccountRef.Namespace
 	}
-	name := prov.Auth.JWTAuth.ServiceAccountRef.Name
 	sa := v1.ServiceAccount{}
 	err := kube.Get(ctx, types.NamespacedName{
 		Name:      name,
@@ -195,16 +242,16 @@ func sessionFromServiceAccount(ctx context.Context, prov *esv1beta1.AWSProvider,
 		tokenAud = defaultTokenAudience
 	}
 	audiences := []string{tokenAud}
-	if len(prov.Auth.JWTAuth.ServiceAccountRef.Audiences) > 0 {
-		audiences = append(audiences, prov.Auth.JWTAuth.ServiceAccountRef.Audiences...)
+	if len(auth.JWTAuth.ServiceAccountRef.Audiences) > 0 {
+		audiences = append(audiences, auth.JWTAuth.ServiceAccountRef.Audiences...)
 	}
 
-	jwtProv, err := jwtProvider(name, namespace, roleArn, audiences, prov.Region)
+	jwtProv, err := jwtProvider(name, namespace, roleArn, audiences, region)
 	if err != nil {
 		return nil, err
 	}
 
-	log.V(1).Info("using credentials via service account", "role", roleArn, "region", prov.Region)
+	log.V(1).Info("using credentials via service account", "role", roleArn, "region", region)
 	return credentials.NewCredentials(jwtProv), nil
 }
 
@@ -255,12 +302,12 @@ func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
 
 // getAWSSession check if an AWS session should be reused
 // it returns the aws session or an error.
-func getAWSSession(config *aws.Config, store esv1beta1.GenericStore, namespace string) (*session.Session, error) {
+func getAWSSession(config *aws.Config, enableCache bool, name, kind, namespace, resourceVersion string) (*session.Session, error) {
 	tmpSession := SessionCache{
-		Name:            store.GetObjectMeta().Name,
+		Name:            name,
 		Namespace:       namespace,
-		Kind:            store.GetTypeMeta().Kind,
-		ResourceVersion: store.GetObjectMeta().ResourceVersion,
+		Kind:            kind,
+		ResourceVersion: resourceVersion,
 	}
 
 	if EnableCache {

+ 21 - 21
pkg/provider/azure/keyvault/keyvault.go

@@ -49,9 +49,9 @@ const (
 	defaultObjType       = "secret"
 	objectTypeCert       = "cert"
 	objectTypeKey        = "key"
-	azureDefaultAudience = "api://AzureADTokenExchange"
-	annotationClientID   = "azure.workload.identity/client-id"
-	annotationTenantID   = "azure.workload.identity/tenant-id"
+	AzureDefaultAudience = "api://AzureADTokenExchange"
+	AnnotationClientID   = "azure.workload.identity/client-id"
+	AnnotationTenantID   = "azure.workload.identity/tenant-id"
 
 	errUnexpectedStoreSpec   = "unexpected store spec"
 	errMissingAuthType       = "cannot initialize Azure Client: no valid authType was specified"
@@ -140,7 +140,7 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
 	case esv1beta1.AzureServicePrincipal:
 		authorizer, err = az.authorizerForServicePrincipal(ctx)
 	case esv1beta1.AzureWorkloadIdentity:
-		authorizer, err = az.authorizerForWorkloadIdentity(ctx, newTokenProvider)
+		authorizer, err = az.authorizerForWorkloadIdentity(ctx, NewTokenProvider)
 	default:
 		err = fmt.Errorf(errMissingAuthType)
 	}
@@ -423,8 +423,8 @@ func getSecretMapProperties(tags map[string]*string, key, property string) map[s
 }
 
 func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
-	aadEndpoint := aadEndpointForProviderConfig(a.provider)
-	kvResource := kvResourceForProviderConfig(a.provider)
+	aadEndpoint := AadEndpointForType(a.provider.EnvironmentType)
+	kvResource := kvResourceForProviderConfig(a.provider.EnvironmentType)
 	// if no serviceAccountRef was provided
 	// we expect certain env vars to be present.
 	// They are set by the azure workload identity webhook.
@@ -457,19 +457,19 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 	if err != nil {
 		return nil, err
 	}
-	clientID, ok := sa.ObjectMeta.Annotations[annotationClientID]
+	clientID, ok := sa.ObjectMeta.Annotations[AnnotationClientID]
 	if !ok {
-		return nil, fmt.Errorf(errMissingSAAnnotation, annotationClientID)
+		return nil, fmt.Errorf(errMissingSAAnnotation, AnnotationClientID)
 	}
-	tenantID, ok := sa.ObjectMeta.Annotations[annotationTenantID]
+	tenantID, ok := sa.ObjectMeta.Annotations[AnnotationTenantID]
 	if !ok {
-		return nil, fmt.Errorf(errMissingSAAnnotation, annotationTenantID)
+		return nil, fmt.Errorf(errMissingSAAnnotation, AnnotationTenantID)
 	}
-	audiences := []string{azureDefaultAudience}
+	audiences := []string{AzureDefaultAudience}
 	if len(a.provider.ServiceAccountRef.Audiences) > 0 {
 		audiences = append(audiences, a.provider.ServiceAccountRef.Audiences...)
 	}
-	token, err := fetchSAToken(ctx, ns, a.provider.ServiceAccountRef.Name, audiences, a.kubeClient)
+	token, err := FetchSAToken(ctx, ns, a.provider.ServiceAccountRef.Name, audiences, a.kubeClient)
 	if err != nil {
 		return nil, err
 	}
@@ -480,7 +480,7 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 	return autorest.NewBearerAuthorizer(tp), nil
 }
 
-func fetchSAToken(ctx context.Context, ns, name string, audiences []string, kubeClient kcorev1.CoreV1Interface) (string, error) {
+func FetchSAToken(ctx context.Context, ns, name string, audiences []string, kubeClient kcorev1.CoreV1Interface) (string, error) {
 	token, err := kubeClient.ServiceAccounts(ns).CreateToken(ctx, name, &authv1.TokenRequest{
 		Spec: authv1.TokenRequestSpec{
 			Audiences: audiences,
@@ -499,7 +499,7 @@ type tokenProvider struct {
 
 type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error)
 
-func newTokenProvider(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
+func NewTokenProvider(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
 	// exchange token with Azure AccessToken
 	cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, aro confidential.AssertionRequestOptions) (string, error) {
 		return token, nil
@@ -532,7 +532,7 @@ func (t *tokenProvider) OAuthToken() string {
 
 func (a *Azure) authorizerForManagedIdentity() (autorest.Authorizer, error) {
 	msiConfig := kvauth.NewMSIConfig()
-	msiConfig.Resource = kvResourceForProviderConfig(a.provider)
+	msiConfig.Resource = kvResourceForProviderConfig(a.provider.EnvironmentType)
 	if a.provider.IdentityID != nil {
 		msiConfig.ClientID = *a.provider.IdentityID
 	}
@@ -562,8 +562,8 @@ func (a *Azure) authorizerForServicePrincipal(ctx context.Context) (autorest.Aut
 		return nil, err
 	}
 	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
-	clientCredentialsConfig.Resource = kvResourceForProviderConfig(a.provider)
-	clientCredentialsConfig.AADEndpoint = aadEndpointForProviderConfig(a.provider)
+	clientCredentialsConfig.Resource = kvResourceForProviderConfig(a.provider.EnvironmentType)
+	clientCredentialsConfig.AADEndpoint = AadEndpointForType(a.provider.EnvironmentType)
 	return clientCredentialsConfig.Authorizer()
 }
 
@@ -597,8 +597,8 @@ func (a *Azure) Validate() (esv1beta1.ValidationResult, error) {
 	return esv1beta1.ValidationResultReady, nil
 }
 
-func aadEndpointForProviderConfig(prov *esv1beta1.AzureKVProvider) string {
-	switch prov.EnvironmentType {
+func AadEndpointForType(t esv1beta1.AzureEnvironmentType) string {
+	switch t {
 	case esv1beta1.AzureEnvironmentPublicCloud:
 		return azure.PublicCloud.ActiveDirectoryEndpoint
 	case esv1beta1.AzureEnvironmentChinaCloud:
@@ -612,9 +612,9 @@ func aadEndpointForProviderConfig(prov *esv1beta1.AzureKVProvider) string {
 	}
 }
 
-func kvResourceForProviderConfig(prov *esv1beta1.AzureKVProvider) string {
+func kvResourceForProviderConfig(t esv1beta1.AzureEnvironmentType) string {
 	var res string
-	switch prov.EnvironmentType {
+	switch t {
 	case esv1beta1.AzureEnvironmentPublicCloud:
 		res = azure.PublicCloud.KeyVaultEndpoint
 	case esv1beta1.AzureEnvironmentChinaCloud:

+ 2 - 2
pkg/provider/azure/keyvault/keyvault_auth_test.go

@@ -169,8 +169,8 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 						Name:      saName,
 						Namespace: namespace,
 						Annotations: map[string]string{
-							annotationClientID: clientID,
-							annotationTenantID: tenantID,
+							AnnotationClientID: clientID,
+							AnnotationTenantID: tenantID,
 						},
 					},
 				},

+ 78 - 0
pkg/provider/gcp/secretmanager/auth.go

@@ -0,0 +1,78 @@
+/*
+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 secretmanager
+
+import (
+	"context"
+	"fmt"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func NewTokenSource(ctx context.Context, auth esv1beta1.GCPSMAuth, projectID string, isClusterKind bool, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
+	ts, err := serviceAccountTokenSource(ctx, auth, isClusterKind, kube, namespace)
+	if ts != nil || err != nil {
+		return ts, err
+	}
+	wi, err := newWorkloadIdentity(ctx, projectID)
+	if err != nil {
+		useMu.Unlock()
+		return nil, fmt.Errorf("unable to initialize workload identity")
+	}
+	ts, err = wi.TokenSource(ctx, auth, isClusterKind, kube, namespace)
+	if ts != nil || err != nil {
+		return ts, err
+	}
+	return google.DefaultTokenSource(ctx, CloudPlatformRole)
+}
+
+func serviceAccountTokenSource(ctx context.Context, auth esv1beta1.GCPSMAuth, isClusterKind bool, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
+	sr := auth.SecretRef
+	if sr == nil {
+		return nil, nil
+	}
+	credentialsSecret := &v1.Secret{}
+	credentialsSecretName := sr.SecretAccessKey.Name
+	objectKey := types.NamespacedName{
+		Name:      credentialsSecretName,
+		Namespace: namespace,
+	}
+
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if isClusterKind {
+		if credentialsSecretName != "" && sr.SecretAccessKey.Namespace == nil {
+			return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
+		} else if credentialsSecretName != "" {
+			objectKey.Namespace = *sr.SecretAccessKey.Namespace
+		}
+	}
+	err := kube.Get(ctx, objectKey, credentialsSecret)
+	if err != nil {
+		return nil, fmt.Errorf(errFetchSAKSecret, err)
+	}
+	credentials := credentialsSecret.Data[sr.SecretAccessKey.Key]
+	if (credentials == nil) || (len(credentials) == 0) {
+		return nil, fmt.Errorf(errMissingSAK)
+	}
+	config, err := google.JWTConfigFromJSON(credentials, CloudPlatformRole)
+	if err != nil {
+		return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
+	}
+	return config.TokenSource(ctx), nil
+}

+ 42 - 215
pkg/provider/gcp/secretmanager/secretsmanager.go → pkg/provider/gcp/secretmanager/client.go

@@ -20,24 +20,17 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
-	"sync"
 
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/find"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/googleapis/gax-go/v2"
 	"github.com/tidwall/gjson"
-	"golang.org/x/oauth2"
-	"golang.org/x/oauth2/google"
 	"google.golang.org/api/iterator"
-	"google.golang.org/api/option"
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
-	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
-	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	"github.com/external-secrets/external-secrets/pkg/find"
-	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
 const (
@@ -66,7 +59,15 @@ const (
 	errUnexpectedFindOperator = "unexpected find operator"
 )
 
-var log = ctrl.Log.WithName("provider").WithName("gcp").WithName("secretsmanager")
+type Client struct {
+	smClient GoogleSecretManagerClient
+	kube     kclient.Client
+	store    *esv1beta1.GCPSMProvider
+
+	// namespace of the external secret
+	namespace        string
+	workloadIdentity *workloadIdentity
+}
 
 type GoogleSecretManagerClient interface {
 	AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
@@ -74,171 +75,33 @@ type GoogleSecretManagerClient interface {
 	Close() error
 }
 
-/*
-Currently, GCPSM client has a limitation around how concurrent connections work
-This limitation causes memory leaks due to random disconnects from living clients
-and also payload switches when sending a call (such as using a credential from one
-thread to ask secrets from another thread).
-A Mutex was implemented to make sure only one connection can be in place at a time.
-*/
-var useMu = sync.Mutex{}
-
-// https://github.com/external-secrets/external-secrets/issues/644
-var _ esv1beta1.SecretsClient = &ProviderGCP{}
-var _ esv1beta1.Provider = &ProviderGCP{}
-
-// ProviderGCP is a provider for GCP Secret Manager.
-type ProviderGCP struct {
-	projectID           string
-	SecretManagerClient GoogleSecretManagerClient
-	gClient             *gClient
-}
-
-type gClient struct {
-	kube      kclient.Client
-	store     *esv1beta1.GCPSMProvider
-	namespace string
-	storeKind string
-
-	workloadIdentity *workloadIdentity
-}
-
-func (c *gClient) getTokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
-	ts, err := serviceAccountTokenSource(ctx, store, kube, namespace)
-	if ts != nil || err != nil {
-		return ts, err
-	}
-	ts, err = c.workloadIdentity.TokenSource(ctx, store, kube, namespace)
-	if ts != nil || err != nil {
-		return ts, err
-	}
-
-	return google.DefaultTokenSource(ctx, CloudPlatformRole)
-}
-
-func (c *gClient) Close() error {
-	return c.workloadIdentity.Close()
-}
-
-func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
-	spec := store.GetSpec()
-	if spec == nil || spec.Provider.GCPSM == nil {
-		return nil, fmt.Errorf(errMissingStoreSpec)
-	}
-	sr := spec.Provider.GCPSM.Auth.SecretRef
-	if sr == nil {
-		return nil, nil
-	}
-	storeKind := store.GetObjectKind().GroupVersionKind().Kind
-	credentialsSecret := &v1.Secret{}
-	credentialsSecretName := sr.SecretAccessKey.Name
-	objectKey := types.NamespacedName{
-		Name:      credentialsSecretName,
-		Namespace: namespace,
-	}
-
-	// only ClusterStore is allowed to set namespace (and then it's required)
-	if storeKind == esv1beta1.ClusterSecretStoreKind {
-		if credentialsSecretName != "" && sr.SecretAccessKey.Namespace == nil {
-			return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
-		} else if credentialsSecretName != "" {
-			objectKey.Namespace = *sr.SecretAccessKey.Namespace
-		}
-	}
-	err := kube.Get(ctx, objectKey, credentialsSecret)
-	if err != nil {
-		return nil, fmt.Errorf(errFetchSAKSecret, err)
-	}
-	credentials := credentialsSecret.Data[sr.SecretAccessKey.Key]
-	if (credentials == nil) || (len(credentials) == 0) {
-		return nil, fmt.Errorf(errMissingSAK)
-	}
-	config, err := google.JWTConfigFromJSON(credentials, CloudPlatformRole)
-	if err != nil {
-		return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
-	}
-	return config.TokenSource(ctx), nil
-}
-
-// NewClient constructs a GCP Provider.
-func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
-	storeSpec := store.GetSpec()
-	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
-		return nil, fmt.Errorf(errGCPSMStore)
-	}
-	storeSpecGCPSM := storeSpec.Provider.GCPSM
-
-	useMu.Lock()
-	wi, err := newWorkloadIdentity(ctx)
-	if err != nil {
-		useMu.Unlock()
-		return nil, fmt.Errorf("unable to initialize workload identity")
-	}
-
-	cliStore := gClient{
-		kube:             kube,
-		store:            storeSpecGCPSM,
-		namespace:        namespace,
-		storeKind:        store.GetObjectKind().GroupVersionKind().Kind,
-		workloadIdentity: wi,
-	}
-	sm.gClient = &cliStore
-	defer func() {
-		// closes IAMClient to prevent gRPC connection leak in case of an error.
-		if sm.SecretManagerClient == nil {
-			_ = sm.gClient.Close()
-		}
-	}()
-
-	sm.projectID = cliStore.store.ProjectID
-
-	ts, err := cliStore.getTokenSource(ctx, store, kube, namespace)
-	if err != nil {
-		useMu.Unlock()
-		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
-	}
-
-	// check if we can get credentials
-	_, err = ts.Token()
-	if err != nil {
-		useMu.Unlock()
-		return nil, fmt.Errorf(errUnableGetCredentials, err)
-	}
-
-	clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
-	if err != nil {
-		useMu.Unlock()
-		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
-	}
-	sm.SecretManagerClient = clientGCPSM
-	return sm, nil
-}
+var log = ctrl.Log.WithName("provider").WithName("gcp").WithName("secretsmanager")
 
 // GetAllSecrets syncs multiple secrets from gcp provider into a single Kubernetes Secret.
-func (sm *ProviderGCP) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	if ref.Name != nil {
-		return sm.findByName(ctx, ref)
+		return c.findByName(ctx, ref)
 	}
 	if len(ref.Tags) > 0 {
-		return sm.findByTags(ctx, ref)
+		return c.findByTags(ctx, ref)
 	}
 	return nil, errors.New(errUnexpectedFindOperator)
 }
 
-func (sm *ProviderGCP) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (c *Client) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// regex matcher
 	matcher, err := find.New(*ref.Name)
 	if err != nil {
 		return nil, err
 	}
 	req := &secretmanagerpb.ListSecretsRequest{
-		Parent: fmt.Sprintf("projects/%s", sm.projectID),
+		Parent: fmt.Sprintf("projects/%s", c.store.ProjectID),
 	}
 	if ref.Path != nil {
 		req.Filter = fmt.Sprintf("name:%s", *ref.Path)
 	}
 	// Call the API.
-	it := sm.SecretManagerClient.ListSecrets(ctx, req)
+	it := c.smClient.ListSecrets(ctx, req)
 	secretMap := make(map[string][]byte)
 	for {
 		resp, err := it.Next()
@@ -249,7 +112,7 @@ func (sm *ProviderGCP) findByName(ctx context.Context, ref esv1beta1.ExternalSec
 			return nil, fmt.Errorf("failed to list secrets: %w", err)
 		}
 		log.V(1).Info("gcp sm findByName found", "secrets", strconv.Itoa(it.PageInfo().Remaining()))
-		key := sm.trimName(resp.Name)
+		key := c.trimName(resp.Name)
 		// If we don't match we skip.
 		// Also, if we have path, and it is not at the beguining we skip.
 		// We have to check if path is at the beguining of the key because
@@ -260,7 +123,7 @@ func (sm *ProviderGCP) findByName(ctx context.Context, ref esv1beta1.ExternalSec
 			continue
 		}
 		log.V(1).Info("gcp sm findByName matches", "name", resp.Name)
-		secretMap[key], err = sm.getData(ctx, key)
+		secretMap[key], err = c.getData(ctx, key)
 		if err != nil {
 			return nil, err
 		}
@@ -269,18 +132,18 @@ func (sm *ProviderGCP) findByName(ctx context.Context, ref esv1beta1.ExternalSec
 	return utils.ConvertKeys(ref.ConversionStrategy, secretMap)
 }
 
-func (sm *ProviderGCP) getData(ctx context.Context, key string) ([]byte, error) {
+func (c *Client) getData(ctx context.Context, key string) ([]byte, error) {
 	dataRef := esv1beta1.ExternalSecretDataRemoteRef{
 		Key: key,
 	}
-	data, err := sm.GetSecret(ctx, dataRef)
+	data, err := c.GetSecret(ctx, dataRef)
 	if err != nil {
 		return []byte(""), err
 	}
 	return data, nil
 }
 
-func (sm *ProviderGCP) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (c *Client) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	var tagFilter string
 	for k, v := range ref.Tags {
 		tagFilter = fmt.Sprintf("%slabels.%s=%s ", tagFilter, k, v)
@@ -290,12 +153,12 @@ func (sm *ProviderGCP) findByTags(ctx context.Context, ref esv1beta1.ExternalSec
 		tagFilter = fmt.Sprintf("%s name:%s", tagFilter, *ref.Path)
 	}
 	req := &secretmanagerpb.ListSecretsRequest{
-		Parent: fmt.Sprintf("projects/%s", sm.projectID),
+		Parent: fmt.Sprintf("projects/%s", c.store.ProjectID),
 	}
 	log.V(1).Info("gcp sm findByTags", "tagFilter", tagFilter)
 	req.Filter = tagFilter
 	// Call the API.
-	it := sm.SecretManagerClient.ListSecrets(ctx, req)
+	it := c.smClient.ListSecrets(ctx, req)
 	secretMap := make(map[string][]byte)
 	for {
 		resp, err := it.Next()
@@ -305,12 +168,12 @@ func (sm *ProviderGCP) findByTags(ctx context.Context, ref esv1beta1.ExternalSec
 		if err != nil {
 			return nil, fmt.Errorf("failed to list secrets: %w", err)
 		}
-		key := sm.trimName(resp.Name)
+		key := c.trimName(resp.Name)
 		if ref.Path != nil && !strings.HasPrefix(key, *ref.Path) {
 			continue
 		}
 		log.V(1).Info("gcp sm findByTags matches tags", "name", resp.Name)
-		secretMap[key], err = sm.getData(ctx, key)
+		secretMap[key], err = c.getData(ctx, key)
 		if err != nil {
 			return nil, err
 		}
@@ -319,8 +182,8 @@ func (sm *ProviderGCP) findByTags(ctx context.Context, ref esv1beta1.ExternalSec
 	return utils.ConvertKeys(ref.ConversionStrategy, secretMap)
 }
 
-func (sm *ProviderGCP) trimName(name string) string {
-	projectIDNumuber := sm.extractProjectIDNumber(name)
+func (c *Client) trimName(name string) string {
+	projectIDNumuber := c.extractProjectIDNumber(name)
 	key := strings.TrimPrefix(name, fmt.Sprintf("projects/%s/secrets/", projectIDNumuber))
 	return key
 }
@@ -328,15 +191,15 @@ func (sm *ProviderGCP) trimName(name string) string {
 // extractProjectIDNumber grabs the project id from the full name returned by gcp api
 // gcp api seems to always return the number and not the project name
 // (and users would always use the name, while requests accept both).
-func (sm *ProviderGCP) extractProjectIDNumber(secretFullName string) string {
+func (c *Client) extractProjectIDNumber(secretFullName string) string {
 	s := strings.Split(secretFullName, "/")
 	projectIDNumuber := s[1]
 	return projectIDNumuber
 }
 
 // GetSecret returns a single secret from the provider.
-func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	if utils.IsNil(sm.SecretManagerClient) || sm.projectID == "" {
+func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	if utils.IsNil(c.smClient) || c.store.ProjectID == "" {
 		return nil, fmt.Errorf(errUninitalizedGCPProvider)
 	}
 
@@ -346,9 +209,9 @@ func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecr
 	}
 
 	req := &secretmanagerpb.AccessSecretVersionRequest{
-		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version),
+		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", c.store.ProjectID, ref.Key, version),
 	}
-	result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
+	result, err := c.smClient.AccessSecretVersion(ctx, req)
 	if err != nil {
 		return nil, fmt.Errorf(errClientGetSecretAccess, err)
 	}
@@ -381,12 +244,12 @@ func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecr
 }
 
 // GetSecretMap returns multiple k/v pairs from the provider.
-func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	if sm.SecretManagerClient == nil || sm.projectID == "" {
+func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	if c.smClient == nil || c.store.ProjectID == "" {
 		return nil, fmt.Errorf(errUninitalizedGCPProvider)
 	}
 
-	data, err := sm.GetSecret(ctx, ref)
+	data, err := c.GetSecret(ctx, ref)
 	if err != nil {
 		return nil, err
 	}
@@ -411,11 +274,9 @@ func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalS
 	return secretData, nil
 }
 
-func (sm *ProviderGCP) Close(ctx context.Context) error {
-	err := sm.SecretManagerClient.Close()
-	if sm.gClient != nil {
-		err = sm.gClient.Close()
-	}
+func (c *Client) Close(ctx context.Context) error {
+	err := c.smClient.Close()
+	c.workloadIdentity.Close()
 	useMu.Unlock()
 	if err != nil {
 		return fmt.Errorf(errClientClose, err)
@@ -423,40 +284,6 @@ func (sm *ProviderGCP) Close(ctx context.Context) error {
 	return nil
 }
 
-func (sm *ProviderGCP) Validate() (esv1beta1.ValidationResult, error) {
+func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
 	return esv1beta1.ValidationResultReady, 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() {
-	esv1beta1.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
-		GCPSM: &esv1beta1.GCPSMProvider{},
-	})
-}

+ 7 - 7
pkg/provider/gcp/secretmanager/secretsmanager_test.go → pkg/provider/gcp/secretmanager/client_test.go

@@ -167,10 +167,10 @@ func TestSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setNilMockClient),
 	}
 
-	sm := ProviderGCP{}
+	sm := Client{}
 	for k, v := range successCases {
-		sm.projectID = v.projectID
-		sm.SecretManagerClient = v.mockClient
+		sm.store = &esv1beta1.GCPSMProvider{ProjectID: v.projectID}
+		sm.smClient = v.mockClient
 		out, err := sm.GetSecret(context.Background(), *v.ref)
 		if !ErrorContains(err, v.expectError) {
 			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
@@ -209,10 +209,10 @@ func TestGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setNestedJSON),
 	}
 
-	sm := ProviderGCP{}
+	sm := Client{}
 	for k, v := range successCases {
-		sm.projectID = v.projectID
-		sm.SecretManagerClient = v.mockClient
+		sm.store = &esv1beta1.GCPSMProvider{ProjectID: v.projectID}
+		sm.smClient = v.mockClient
 		out, err := sm.GetSecretMap(context.Background(), *v.ref)
 		if !ErrorContains(err, v.expectError) {
 			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
@@ -278,7 +278,7 @@ func TestValidateStore(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			sm := &ProviderGCP{}
+			sm := &Provider{}
 			store := &esv1beta1.SecretStore{
 				Spec: esv1beta1.SecretStoreSpec{
 					Provider: &esv1beta1.SecretStoreProvider{

+ 135 - 0
pkg/provider/gcp/secretmanager/provider.go

@@ -0,0 +1,135 @@
+/*
+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 secretmanager
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	secretmanager "cloud.google.com/go/secretmanager/apiv1"
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+	"google.golang.org/api/option"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Provider is a secrets provider for GCP Secret Manager.
+// It implements the necessary NewClient() and ValidateStore() funcs.
+type Provider struct{}
+
+// https://github.com/external-secrets/external-secrets/issues/644
+var _ esv1beta1.SecretsClient = &Client{}
+var _ esv1beta1.Provider = &Provider{}
+
+func init() {
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+		GCPSM: &esv1beta1.GCPSMProvider{},
+	})
+}
+
+/*
+ Currently, GCPSM client has a limitation around how concurrent connections work
+ This limitation causes memory leaks due to random disconnects from living clients
+ and also payload switches when sending a call (such as using a credential from one
+ thread to ask secrets from another thread).
+ A Mutex was implemented to make sure only one connection can be in place at a time.
+*/
+var useMu = sync.Mutex{}
+
+// NewClient constructs a GCP Provider.
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
+		return nil, fmt.Errorf(errGCPSMStore)
+	}
+	gcpStore := storeSpec.Provider.GCPSM
+
+	useMu.Lock()
+
+	client := &Client{
+		kube:      kube,
+		store:     gcpStore,
+		namespace: namespace,
+	}
+	defer func() {
+		_ = client.Close(ctx)
+	}()
+
+	// this project ID is used for authentication (currently only relevant for workload identity)
+	clusterProjectID, err := clusterProjectID(storeSpec)
+	if err != nil {
+		useMu.Unlock()
+		return nil, err
+	}
+	isClusterKind := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
+	ts, err := NewTokenSource(ctx, gcpStore.Auth, clusterProjectID, isClusterKind, kube, namespace)
+	if err != nil {
+		useMu.Unlock()
+		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
+	}
+
+	// check if we can get credentials
+	_, err = ts.Token()
+	if err != nil {
+		useMu.Unlock()
+		return nil, fmt.Errorf(errUnableGetCredentials, err)
+	}
+
+	clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
+	if err != nil {
+		useMu.Unlock()
+		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
+	}
+	client.smClient = clientGCPSM
+	return client, nil
+}
+
+func (sm *Provider) 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 clusterProjectID(spec *esv1beta1.SecretStoreSpec) (string, error) {
+	if spec.Provider.GCPSM.Auth.WorkloadIdentity.ClusterProjectID != "" {
+		return spec.Provider.GCPSM.Auth.WorkloadIdentity.ClusterProjectID, nil
+	} else if spec.Provider.GCPSM.ProjectID != "" {
+		return spec.Provider.GCPSM.ProjectID, nil
+	} else {
+		return "", fmt.Errorf(errNoProjectID)
+	}
+}

+ 9 - 26
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity.go → pkg/provider/gcp/secretmanager/workload_identity.go

@@ -58,6 +58,7 @@ type workloadIdentity struct {
 	iamClient            IamClient
 	idBindTokenGenerator idBindTokenGenerator
 	saTokenGenerator     saTokenGenerator
+	clusterProjectID     string
 }
 
 // interface to GCP IAM API.
@@ -76,7 +77,7 @@ type saTokenGenerator interface {
 	Generate(context.Context, []string, string, string) (*authenticationv1.TokenRequest, error)
 }
 
-func newWorkloadIdentity(ctx context.Context) (*workloadIdentity, error) {
+func newWorkloadIdentity(ctx context.Context, projectID string) (*workloadIdentity, error) {
 	iamc, err := newIAMClient(ctx)
 	if err != nil {
 		return nil, err
@@ -89,47 +90,39 @@ func newWorkloadIdentity(ctx context.Context) (*workloadIdentity, error) {
 		iamClient:            iamc,
 		idBindTokenGenerator: newIDBindTokenGenerator(),
 		saTokenGenerator:     satg,
+		clusterProjectID:     projectID,
 	}, nil
 }
 
-func (w *workloadIdentity) TokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
-	spec := store.GetSpec()
-	if spec == nil || spec.Provider == nil || spec.Provider.GCPSM == nil {
-		return nil, fmt.Errorf(errMissingStoreSpec)
-	}
-	wi := spec.Provider.GCPSM.Auth.WorkloadIdentity
+func (w *workloadIdentity) TokenSource(ctx context.Context, auth esv1beta1.GCPSMAuth, isClusterKind bool, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
+	wi := auth.WorkloadIdentity
 	if wi == nil {
 		return nil, nil
 	}
-	storeKind := store.GetObjectKind().GroupVersionKind().Kind
 	saKey := types.NamespacedName{
 		Name:      wi.ServiceAccountRef.Name,
 		Namespace: namespace,
 	}
 
 	// only ClusterStore is allowed to set namespace (and then it's required)
-	if storeKind == esv1beta1.ClusterSecretStoreKind {
+	if isClusterKind {
 		if wi.ServiceAccountRef.Namespace == nil {
 			return nil, fmt.Errorf(errInvalidClusterStoreMissingSANamespace)
 		}
 		saKey.Namespace = *wi.ServiceAccountRef.Namespace
 	}
 
-	clusterProjectID, err := clusterProjectID(spec)
-	if err != nil {
-		return nil, err
-	}
 	sa := &v1.ServiceAccount{}
-	err = kube.Get(ctx, saKey, sa)
+	err := kube.Get(ctx, saKey, sa)
 	if err != nil {
 		return nil, err
 	}
 
 	idProvider := fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/locations/%s/clusters/%s",
-		clusterProjectID,
+		w.clusterProjectID,
 		wi.ClusterLocation,
 		wi.ClusterName)
-	idPool := fmt.Sprintf("%s.svc.id.goog", clusterProjectID)
+	idPool := fmt.Sprintf("%s.svc.id.goog", w.clusterProjectID)
 	audiences := []string{idPool}
 	if len(wi.ServiceAccountRef.Audiences) > 0 {
 		audiences = append(audiences, wi.ServiceAccountRef.Audiences...)
@@ -266,13 +259,3 @@ func (g *gcpIDBindTokenGenerator) Generate(ctx context.Context, client *http.Cli
 	}
 	return idBindToken, nil
 }
-
-func clusterProjectID(spec *esv1beta1.SecretStoreSpec) (string, error) {
-	if spec.Provider.GCPSM.Auth.WorkloadIdentity.ClusterProjectID != "" {
-		return spec.Provider.GCPSM.Auth.WorkloadIdentity.ClusterProjectID, nil
-	} else if spec.Provider.GCPSM.ProjectID != "" {
-		return spec.Provider.GCPSM.ProjectID, nil
-	} else {
-		return "", fmt.Errorf(errNoProjectID)
-	}
-}

+ 2 - 6
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity_test.go → pkg/provider/gcp/secretmanager/workload_identity_test.go

@@ -51,11 +51,6 @@ type workloadIdentityTest struct {
 func TestWorkloadIdentity(t *testing.T) {
 	clusterSANamespace := "foobar"
 	tbl := []*workloadIdentityTest{
-		composeTestcase(
-			defaultTestCase("missing store spec should result in error"),
-			withErr("invalid: missing store spec"),
-			withStore(&esv1beta1.SecretStore{}),
-		),
 		composeTestcase(
 			defaultTestCase("should skip when no workload identity is configured: TokenSource and error must be nil"),
 			withStore(&esv1beta1.SecretStore{
@@ -137,7 +132,8 @@ func TestWorkloadIdentity(t *testing.T) {
 			cb := clientfake.NewClientBuilder()
 			cb.WithObjects(row.kubeObjects...)
 			client := cb.Build()
-			ts, err := w.TokenSource(context.Background(), row.store, client, "default")
+			isCluster := row.store.GetTypeMeta().Kind == esv1beta1.ClusterSecretStoreKind
+			ts, err := w.TokenSource(context.Background(), row.store.GetSpec().Provider.GCPSM.Auth, isCluster, client, "default")
 			// assert err
 			if row.expErr == "" {
 				assert.NoError(t, err)