mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: allow generators to be referenced from a PushSecret (#3965)
This removes the need for an intermediary Kind=ExternalSecret and Kind=Secret when using a generator. Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
parent
f67b935116
commit
76cf8ad263
15 changed files with 289 additions and 83 deletions
|
@ -92,9 +92,16 @@ type PushSecretSecret struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:MinProperties=1
|
||||||
|
// +kubebuilder:validation:MaxProperties=1
|
||||||
type PushSecretSelector struct {
|
type PushSecretSelector struct {
|
||||||
// Select a Secret to Push.
|
// Select a Secret to Push.
|
||||||
Secret PushSecretSecret `json:"secret"`
|
// +optional
|
||||||
|
Secret *PushSecretSecret `json:"secret,omitempty"`
|
||||||
|
|
||||||
|
// Point to a generator to create a Secret.
|
||||||
|
// +optional
|
||||||
|
GeneratorRef *esv1beta1.GeneratorRef `json:"generatorRef,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushSecretRemoteRef struct {
|
type PushSecretRemoteRef struct {
|
||||||
|
|
|
@ -1208,7 +1208,16 @@ func (in *PushSecretSecret) DeepCopy() *PushSecretSecret {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *PushSecretSelector) DeepCopyInto(out *PushSecretSelector) {
|
func (in *PushSecretSelector) DeepCopyInto(out *PushSecretSelector) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.Secret = in.Secret
|
if in.Secret != nil {
|
||||||
|
in, out := &in.Secret, &out.Secret
|
||||||
|
*out = new(PushSecretSecret)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.GeneratorRef != nil {
|
||||||
|
in, out := &in.GeneratorRef, &out.GeneratorRef
|
||||||
|
*out = new(v1beta1.GeneratorRef)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSelector.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSelector.
|
||||||
|
@ -1236,7 +1245,7 @@ func (in *PushSecretSpec) DeepCopyInto(out *PushSecretSpec) {
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.Selector = in.Selector
|
in.Selector.DeepCopyInto(&out.Selector)
|
||||||
if in.Data != nil {
|
if in.Data != nil {
|
||||||
in, out := &in.Data, &out.Data
|
in, out := &in.Data, &out.Data
|
||||||
*out = make([]PushSecretData, len(*in))
|
*out = make([]PushSecretData, len(*in))
|
||||||
|
|
|
@ -216,6 +216,7 @@ var rootCmd = &cobra.Command{
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("PushSecret"),
|
Log: ctrl.Log.WithName("controllers").WithName("PushSecret"),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ControllerClass: controllerClass,
|
ControllerClass: controllerClass,
|
||||||
|
RestConfig: mgr.GetConfig(),
|
||||||
RequeueInterval: time.Hour,
|
RequeueInterval: time.Hour,
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
setupLog.Error(err, errCreateController, "controller", "PushSecret")
|
setupLog.Error(err, errCreateController, "controller", "PushSecret")
|
||||||
|
|
|
@ -165,7 +165,27 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
selector:
|
selector:
|
||||||
description: The Secret Selector (k8s source) for the Push Secret
|
description: The Secret Selector (k8s source) for the Push Secret
|
||||||
|
maxProperties: 1
|
||||||
|
minProperties: 1
|
||||||
properties:
|
properties:
|
||||||
|
generatorRef:
|
||||||
|
description: Point to a generator to create a Secret.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
default: generators.external-secrets.io/v1alpha1
|
||||||
|
description: Specify the apiVersion of the generator resource
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: Specify the Kind of the resource, e.g. Password,
|
||||||
|
ACRAccessToken etc.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: Specify the name of the generator resource
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
secret:
|
secret:
|
||||||
description: Select a Secret to Push.
|
description: Select a Secret to Push.
|
||||||
properties:
|
properties:
|
||||||
|
@ -176,8 +196,6 @@ spec:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- secret
|
|
||||||
type: object
|
type: object
|
||||||
template:
|
template:
|
||||||
description: Template defines a blueprint for the created Secret resource.
|
description: Template defines a blueprint for the created Secret resource.
|
||||||
|
|
|
@ -6258,7 +6258,26 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
selector:
|
selector:
|
||||||
description: The Secret Selector (k8s source) for the Push Secret
|
description: The Secret Selector (k8s source) for the Push Secret
|
||||||
|
maxProperties: 1
|
||||||
|
minProperties: 1
|
||||||
properties:
|
properties:
|
||||||
|
generatorRef:
|
||||||
|
description: Point to a generator to create a Secret.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
default: generators.external-secrets.io/v1alpha1
|
||||||
|
description: Specify the apiVersion of the generator resource
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: Specify the name of the generator resource
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
secret:
|
secret:
|
||||||
description: Select a Secret to Push.
|
description: Select a Secret to Push.
|
||||||
properties:
|
properties:
|
||||||
|
@ -6268,8 +6287,6 @@ spec:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- secret
|
|
||||||
type: object
|
type: object
|
||||||
template:
|
template:
|
||||||
description: Template defines a blueprint for the created Secret resource.
|
description: Template defines a blueprint for the created Secret resource.
|
||||||
|
|
|
@ -43,3 +43,12 @@ This will _marshal_ the entire secret data and push it into this single property
|
||||||
|
|
||||||
### Key conversion strategy
|
### Key conversion strategy
|
||||||
You can also set `data[*].conversionStrategy: ReverseUnicode` to reverse the invalid character replaced by the `conversionStrategy: Unicode` configuration in the `ExternalSecret` object as [documented here](../guides/getallsecrets.md#avoiding-name-conflicts).
|
You can also set `data[*].conversionStrategy: ReverseUnicode` to reverse the invalid character replaced by the `conversionStrategy: Unicode` configuration in the `ExternalSecret` object as [documented here](../guides/getallsecrets.md#avoiding-name-conflicts).
|
||||||
|
|
||||||
|
## Rotate Secrets
|
||||||
|
|
||||||
|
You can use ESO to rotate secrets by using the PushSecret and Generator resources. ESO will consult the `Kind=Generator` to generate a new secret and then ESO will store it.
|
||||||
|
Every `spec.refreshInterval` the secret will be rotated and the value will be replaced in the store unless `spec.updatePolicy=IfNotExist` is set. Then ESO will generate the secret once and won't rotate it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{% include 'pushsecret-generator-rotation-example.yaml' %}
|
||||||
|
```
|
||||||
|
|
|
@ -14,6 +14,11 @@ spec:
|
||||||
selector:
|
selector:
|
||||||
secret:
|
secret:
|
||||||
name: pokedex-credentials # Source Kubernetes secret to be pushed
|
name: pokedex-credentials # Source Kubernetes secret to be pushed
|
||||||
|
# Alternatively, you can point to a generator that produces values to be pushed
|
||||||
|
generatorRef:
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ECRAuthorizationToken
|
||||||
|
name: prod-registry-credentials
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
annotations: { }
|
annotations: { }
|
||||||
|
|
33
docs/snippets/pushsecret-generator-rotation-example.yaml
Normal file
33
docs/snippets/pushsecret-generator-rotation-example.yaml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{% raw %}
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: Password
|
||||||
|
metadata:
|
||||||
|
name: strong-password
|
||||||
|
spec:
|
||||||
|
length: 128
|
||||||
|
digits: 5
|
||||||
|
symbols: 5
|
||||||
|
symbolCharacters: "-_$@"
|
||||||
|
noUpper: false
|
||||||
|
allowRepeat: true
|
||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: PushSecret
|
||||||
|
metadata:
|
||||||
|
name: pushsecret-example
|
||||||
|
spec:
|
||||||
|
refreshInterval: 6h
|
||||||
|
secretStoreRefs:
|
||||||
|
- name: aws-parameter-store
|
||||||
|
kind: SecretStore
|
||||||
|
selector:
|
||||||
|
generatorRef:
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: Password
|
||||||
|
name: strong-password
|
||||||
|
data:
|
||||||
|
- match:
|
||||||
|
secretKey: password # property in the generator output
|
||||||
|
remoteRef:
|
||||||
|
remoteKey: prod/myql/password
|
||||||
|
{% endraw %}
|
|
@ -133,7 +133,7 @@ func genericPushSecretTemplate(f *framework.Framework) (string, func(*framework.
|
||||||
Type: v1.SecretTypeOpaque,
|
Type: v1.SecretTypeOpaque,
|
||||||
}
|
}
|
||||||
tc.PushSecret.Spec.Selector = esv1alpha1.PushSecretSelector{
|
tc.PushSecret.Spec.Selector = esv1alpha1.PushSecretSelector{
|
||||||
Secret: esv1alpha1.PushSecretSecret{
|
Secret: &esv1alpha1.PushSecretSecret{
|
||||||
Name: secretKey1,
|
Name: secretKey1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import (
|
||||||
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
|
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
|
||||||
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
||||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||||
|
|
||||||
// Loading registered generators.
|
// Loading registered generators.
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||||
|
@ -549,11 +550,11 @@ func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconcil
|
||||||
|
|
||||||
// verify that generator's controllerClass matches
|
// verify that generator's controllerClass matches
|
||||||
if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
|
if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
|
||||||
genDef, err := r.getGeneratorDefinition(ctx, namespace, ref.SourceRef.GeneratorRef)
|
_, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, ref.SourceRef.GeneratorRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
skipGenerator, err := shouldSkipGenerator(r, genDef)
|
skipGenerator, err := shouldSkipGenerator(r, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,13 @@ import (
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/client-go/discovery"
|
|
||||||
"k8s.io/client-go/dynamic"
|
|
||||||
"k8s.io/client-go/restmapper"
|
|
||||||
|
|
||||||
esv1beta1 "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"
|
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||||
// Loading registered providers.
|
// Loading registered providers.
|
||||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||||
|
|
||||||
// Loading registered generators.
|
// Loading registered generators.
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||||
|
@ -116,15 +112,11 @@ func toStoreGenSourceRef(ref *esv1beta1.StoreSourceRef) *esv1beta1.StoreGenerato
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
|
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.GeneratorRef)
|
gen, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, remoteRef.SourceRef.GeneratorRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||||
}
|
}
|
||||||
gen, err := genv1alpha1.GetGenerator(genDef)
|
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
secretMap, err := gen.Generate(ctx, genDef, r.Client, namespace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(errGenerate, i, err)
|
return nil, fmt.Errorf(errGenerate, i, err)
|
||||||
}
|
}
|
||||||
|
@ -138,49 +130,6 @@ func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string
|
||||||
return secretMap, err
|
return secretMap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGeneratorDefinition returns the generator JSON for a given sourceRef
|
|
||||||
// when it uses a generatorRef it fetches the resource and returns the JSON.
|
|
||||||
func (r *Reconciler) getGeneratorDefinition(ctx context.Context, namespace string, generatorRef *esv1beta1.GeneratorRef) (*apiextensions.JSON, error) {
|
|
||||||
// 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(generatorRef.APIVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
|
||||||
mapping, err := mapper.RESTMapping(schema.GroupKind{
|
|
||||||
Group: gv.Group,
|
|
||||||
Kind: 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, 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, cmgr *secretstore.Manager, i int) (map[string][]byte, error) {
|
func (r *Reconciler) handleExtractSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, cmgr *secretstore.Manager, i int) (map[string][]byte, error) {
|
||||||
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
|
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
@ -40,6 +41,10 @@ import (
|
||||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||||
"github.com/external-secrets/external-secrets/pkg/provider/util/locks"
|
"github.com/external-secrets/external-secrets/pkg/provider/util/locks"
|
||||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||||
|
|
||||||
|
// load generators.
|
||||||
|
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -59,6 +64,7 @@ type Reconciler struct {
|
||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
|
RestConfig *rest.Config
|
||||||
RequeueInterval time.Duration
|
RequeueInterval time.Duration
|
||||||
ControllerClass string
|
ControllerClass string
|
||||||
}
|
}
|
||||||
|
@ -148,7 +154,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := r.GetSecret(ctx, ps)
|
secret, err := r.resolveSecret(ctx, ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.markAsFailed(errFailedGetSecret, &ps, nil)
|
r.markAsFailed(errFailedGetSecret, &ps, nil)
|
||||||
|
|
||||||
|
@ -347,14 +353,38 @@ func secretKeyExists(key string, secret *v1.Secret) bool {
|
||||||
return key == "" || ok
|
return key == "" || ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
|
func (r *Reconciler) resolveSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
|
||||||
secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
|
if ps.Spec.Selector.Secret != nil {
|
||||||
secret := &v1.Secret{}
|
secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
|
||||||
err := r.Client.Get(ctx, secretName, secret)
|
secret := &v1.Secret{}
|
||||||
if err != nil {
|
err := r.Client.Get(ctx, secretName, secret)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return secret, nil
|
||||||
}
|
}
|
||||||
return secret, nil
|
if ps.Spec.Selector.GeneratorRef != nil {
|
||||||
|
return r.resolveSecretFromGenerator(ctx, ps.Namespace, ps.Spec.Selector.GeneratorRef)
|
||||||
|
}
|
||||||
|
return nil, errors.New("no secret selector provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reconciler) resolveSecretFromGenerator(ctx context.Context, namespace string, generatorRef *v1beta1.GeneratorRef) (*v1.Secret, error) {
|
||||||
|
gen, obj, err := resolvers.GeneratorRef(ctx, r.RestConfig, namespace, generatorRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||||
|
}
|
||||||
|
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to generate: %w", err)
|
||||||
|
}
|
||||||
|
return &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "___generated-secret",
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Data: secretMap,
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (map[esapi.PushSecretStoreRef]v1beta1.GenericStore, error) {
|
func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (map[esapi.PushSecretStoreRef]v1beta1.GenericStore, error) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
|
|
||||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||||
ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
||||||
"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
|
"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
|
||||||
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||||
|
@ -99,6 +100,21 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
|
PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
fakeProvider.Reset()
|
fakeProvider.Reset()
|
||||||
|
|
||||||
|
Expect(k8sClient.Create(context.Background(), &genv1alpha1.Fake{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Fake",
|
||||||
|
APIVersion: "generators.external-secrets.io/v1alpha1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: PushSecretNamespace,
|
||||||
|
},
|
||||||
|
Spec: genv1alpha1.FakeSpec{
|
||||||
|
Data: map[string]string{
|
||||||
|
"key": "foo-bar-from-generator",
|
||||||
|
},
|
||||||
|
}})).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
|
@ -162,7 +178,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -395,7 +411,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -459,7 +475,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -515,7 +531,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -570,7 +586,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -716,7 +732,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -782,7 +798,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -861,6 +877,28 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncWithGenerator := func(tc *testCase) {
|
||||||
|
fakeProvider.SetSecretFn = func() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tc.pushsecret.Spec.Selector.Secret = nil
|
||||||
|
tc.pushsecret.Spec.Selector.GeneratorRef = &v1beta1.GeneratorRef{
|
||||||
|
APIVersion: "generators.external-secrets.io/v1alpha1",
|
||||||
|
Kind: "Fake",
|
||||||
|
Name: "test",
|
||||||
|
}
|
||||||
|
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
||||||
|
providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
|
||||||
|
expected := v1alpha1.PushSecretStatusCondition{
|
||||||
|
Type: v1alpha1.PushSecretReady,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
Reason: v1alpha1.ReasonSynced,
|
||||||
|
Message: "PushSecret synced successfully",
|
||||||
|
}
|
||||||
|
return bytes.Equal([]byte("foo-bar-from-generator"), providerValue) && checkCondition(ps.Status, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
// if target Secret name is not specified it should use the ExternalSecret name.
|
// if target Secret name is not specified it should use the ExternalSecret name.
|
||||||
syncWithClusterStoreMatchingLabels := func(tc *testCase) {
|
syncWithClusterStoreMatchingLabels := func(tc *testCase) {
|
||||||
fakeProvider.SetSecretFn = func() error {
|
fakeProvider.SetSecretFn = func() error {
|
||||||
|
@ -884,7 +922,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1069,6 +1107,7 @@ var _ = Describe("PushSecret controller", func() {
|
||||||
Entry("should sync to stores matching labels", syncMatchingLabels),
|
Entry("should sync to stores matching labels", syncMatchingLabels),
|
||||||
Entry("should sync with ClusterStore", syncWithClusterStore),
|
Entry("should sync with ClusterStore", syncWithClusterStore),
|
||||||
Entry("should sync with ClusterStore matching labels", syncWithClusterStoreMatchingLabels),
|
Entry("should sync with ClusterStore matching labels", syncWithClusterStoreMatchingLabels),
|
||||||
|
Entry("should sync with Generator", syncWithGenerator),
|
||||||
Entry("should fail if Secret is not created", failNoSecret),
|
Entry("should fail if Secret is not created", failNoSecret),
|
||||||
Entry("should fail if Secret Key does not exist", failNoSecretKey),
|
Entry("should fail if Secret Key does not exist", failNoSecretKey),
|
||||||
Entry("should fail if SetSecret fails", setSecretFail),
|
Entry("should fail if SetSecret fails", setSecretFail),
|
||||||
|
@ -1168,7 +1207,7 @@ var _ = Describe("PushSecret Controller Un/Managed Stores", func() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: v1alpha1.PushSecretSelector{
|
Selector: v1alpha1.PushSecretSelector{
|
||||||
Secret: v1alpha1.PushSecretSecret{
|
Secret: &v1alpha1.PushSecretSecret{
|
||||||
Name: SecretName,
|
Name: SecretName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
|
|
||||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||||
esv1beta1 "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/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
@ -72,6 +73,8 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
err = esv1alpha1.AddToScheme(scheme.Scheme)
|
err = esv1alpha1.AddToScheme(scheme.Scheme)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = genv1alpha1.AddToScheme(scheme.Scheme)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||||
Scheme: scheme.Scheme,
|
Scheme: scheme.Scheme,
|
||||||
|
@ -90,7 +93,8 @@ var _ = BeforeSuite(func() {
|
||||||
err = (&Reconciler{
|
err = (&Reconciler{
|
||||||
Client: k8sClient,
|
Client: k8sClient,
|
||||||
Scheme: k8sManager.GetScheme(),
|
Scheme: k8sManager.GetScheme(),
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("ExternalSecrets"),
|
Log: ctrl.Log.WithName("controllers").WithName("PushSecret"),
|
||||||
|
RestConfig: cfg,
|
||||||
RequeueInterval: time.Second,
|
RequeueInterval: time.Second,
|
||||||
}).SetupWithManager(k8sManager)
|
}).SetupWithManager(k8sManager)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
84
pkg/utils/resolvers/generator.go
Normal file
84
pkg/utils/resolvers/generator.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
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 resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GeneratorRef resolves a generator reference to a generator implementation.
|
||||||
|
func GeneratorRef(ctx context.Context, restConfig *rest.Config, namespace string, generatorRef *esv1beta1.GeneratorRef) (genv1alpha1.Generator, *apiextensions.JSON, error) {
|
||||||
|
obj, err := getGeneratorDefinition(ctx, restConfig, namespace, generatorRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to get generator definition: %w", err)
|
||||||
|
}
|
||||||
|
generator, err := genv1alpha1.GetGenerator(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to get generator: %w", err)
|
||||||
|
}
|
||||||
|
return generator, obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGeneratorDefinition(ctx context.Context, restConfig *rest.Config, namespace string, generatorRef *esv1beta1.GeneratorRef) (*apiextensions.JSON, error) {
|
||||||
|
// 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(restConfig)
|
||||||
|
groupResources, err := restmapper.GetAPIGroupResources(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gv, err := schema.ParseGroupVersion(generatorRef.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||||
|
mapping, err := mapper.RESTMapping(schema.GroupKind{
|
||||||
|
Group: gv.Group,
|
||||||
|
Kind: generatorRef.Kind,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d, err := dynamic.NewForConfig(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := d.Resource(mapping.Resource).
|
||||||
|
Namespace(namespace).
|
||||||
|
Get(ctx, 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
|
||||||
|
}
|
Loading…
Reference in a new issue