mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: implement a cluster-wide generator (#4140)
* feat: implement a cluster-wide generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * remove unneeded function Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * check diff run output Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * alternative implementation of the Generator approach using specs only Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * refactor the extracting code Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * slight modification to the naming of the spec from generatorSpec to simply generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * write a unit test for the generator and register it in the scheme Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * add documentation for the cluster generator Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
parent
40a698dafd
commit
fb9526f38a
24 changed files with 3220 additions and 20 deletions
|
@ -393,7 +393,7 @@ type GeneratorRef struct {
|
||||||
// Specify the apiVersion of the generator resource
|
// Specify the apiVersion of the generator resource
|
||||||
// +kubebuilder:default="generators.external-secrets.io/v1alpha1"
|
// +kubebuilder:default="generators.external-secrets.io/v1alpha1"
|
||||||
APIVersion string `json:"apiVersion,omitempty"`
|
APIVersion string `json:"apiVersion,omitempty"`
|
||||||
// Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
// Specify the Kind of the resource, e.g. Password, ACRAccessToken, ClusterGenerator etc.
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
// Specify the name of the generator resource
|
// Specify the name of the generator resource
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
|
@ -59,7 +59,7 @@ func GetGeneratorByName(kind string) (Generator, bool) {
|
||||||
return f, ok
|
return f, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGenerator returns a implementation from a generator
|
// GetGenerator returns an implementation from a generator
|
||||||
// defined as json.
|
// defined as json.
|
||||||
func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
||||||
type unknownGenerator struct {
|
type unknownGenerator struct {
|
||||||
|
@ -75,7 +75,7 @@ func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
||||||
defer buildlock.RUnlock()
|
defer buildlock.RUnlock()
|
||||||
gen, ok := builder[res.Kind]
|
gen, ok := builder[res.Kind]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to find registered generator for: %s", string(obj.Raw))
|
return nil, fmt.Errorf("failed to find registered generator for: %s with kind: %s", string(obj.Raw), res.Kind)
|
||||||
}
|
}
|
||||||
return gen, nil
|
return gen, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,65 @@ limitations under the License.
|
||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A couple of constants to define the generator's keys for accessing via Resource map values.
|
||||||
|
const (
|
||||||
|
GeneratorGeneratorKey = "generator"
|
||||||
|
GeneratorKindKey = "kind"
|
||||||
|
GeneratorSpecKey = "spec"
|
||||||
|
)
|
||||||
|
|
||||||
type ControllerClassResource struct {
|
type ControllerClassResource struct {
|
||||||
Spec struct {
|
Spec struct {
|
||||||
ControllerClass string `json:"controller"`
|
ControllerClass string `json:"controller"`
|
||||||
} `json:"spec"`
|
} `json:"spec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GeneratorSpec struct {
|
||||||
|
ACRAccessTokenSpec *ACRAccessTokenSpec `json:"acrAccessTokenSpec,omitempty"`
|
||||||
|
ECRAuthorizationTokenSpec *ECRAuthorizationTokenSpec `json:"ecrRAuthorizationTokenSpec,omitempty"`
|
||||||
|
FakeSpec *FakeSpec `json:"fakeSpec,omitempty"`
|
||||||
|
GCRAccessTokenSpec *GCRAccessTokenSpec `json:"gcrAccessTokenSpec,omitempty"`
|
||||||
|
GithubAccessTokenSpec *GithubAccessTokenSpec `json:"githubAccessTokenSpec,omitempty"`
|
||||||
|
PasswordSpec *PasswordSpec `json:"passwordSpec,omitempty"`
|
||||||
|
STSSessionTokenSpec *STSSessionTokenSpec `json:"stsSessionTokenSpec,omitempty"`
|
||||||
|
UUIDSpec *UUIDSpec `json:"uuidSpec,omitempty"`
|
||||||
|
VaultDynamicSecretSpec *VaultDynamicSecretSpec `json:"vaultDynamicSecretSpec,omitempty"`
|
||||||
|
WebhookSpec *WebhookSpec `json:"webhookSpec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterGeneratorSpec struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Generator GeneratorSpec `json:"generator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterGeneratorStatus struct{}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:storageversion
|
||||||
|
|
||||||
|
// ClusterGenerator represents a cluster-wide generator which can be referenced as part of `generatorRef` fields.
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:storageversion
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:metadata:labels="external-secrets.io/component=controller"
|
||||||
|
// +kubebuilder:resource:scope=Cluster,categories={external-secrets, external-secrets-generators},shortName=cg
|
||||||
|
type ClusterGenerator struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec ClusterGeneratorSpec `json:"spec,omitempty"`
|
||||||
|
Status ClusterGeneratorStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
|
||||||
|
// ClusterGeneratorList contains a list of ClusterGenerator resources.
|
||||||
|
type ClusterGeneratorList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []ClusterGenerator `json:"items"`
|
||||||
|
}
|
||||||
|
|
|
@ -116,6 +116,14 @@ var (
|
||||||
UUIDGroupVersionKind = SchemeGroupVersion.WithKind(UUIDKind)
|
UUIDGroupVersionKind = SchemeGroupVersion.WithKind(UUIDKind)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ClusterGenerator type metadata.
|
||||||
|
var (
|
||||||
|
ClusterGeneratorKind = reflect.TypeOf(ClusterGenerator{}).Name()
|
||||||
|
ClusterGeneratorGroupKind = schema.GroupKind{Group: Group, Kind: ClusterGeneratorKind}.String()
|
||||||
|
ClusterGeneratorKindAPIVersion = ClusterGeneratorKind + "." + SchemeGroupVersion.String()
|
||||||
|
ClusterGeneratorGroupVersionKind = SchemeGroupVersion.WithKind(ClusterGeneratorKind)
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{})
|
SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{})
|
||||||
SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{})
|
SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{})
|
||||||
|
@ -125,4 +133,5 @@ func init() {
|
||||||
SchemeBuilder.Register(&VaultDynamicSecret{}, &VaultDynamicSecretList{})
|
SchemeBuilder.Register(&VaultDynamicSecret{}, &VaultDynamicSecretList{})
|
||||||
SchemeBuilder.Register(&Password{}, &PasswordList{})
|
SchemeBuilder.Register(&Password{}, &PasswordList{})
|
||||||
SchemeBuilder.Register(&Webhook{}, &WebhookList{})
|
SchemeBuilder.Register(&Webhook{}, &WebhookList{})
|
||||||
|
SchemeBuilder.Register(&ClusterGenerator{}, &ClusterGeneratorList{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,6 +265,96 @@ func (in *AzureACRWorkloadIdentityAuth) DeepCopy() *AzureACRWorkloadIdentityAuth
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ClusterGenerator) DeepCopyInto(out *ClusterGenerator) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
out.Status = in.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGenerator.
|
||||||
|
func (in *ClusterGenerator) DeepCopy() *ClusterGenerator {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ClusterGenerator)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *ClusterGenerator) 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 *ClusterGeneratorList) DeepCopyInto(out *ClusterGeneratorList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]ClusterGenerator, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorList.
|
||||||
|
func (in *ClusterGeneratorList) DeepCopy() *ClusterGeneratorList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ClusterGeneratorList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *ClusterGeneratorList) 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 *ClusterGeneratorSpec) DeepCopyInto(out *ClusterGeneratorSpec) {
|
||||||
|
*out = *in
|
||||||
|
in.Generator.DeepCopyInto(&out.Generator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorSpec.
|
||||||
|
func (in *ClusterGeneratorSpec) DeepCopy() *ClusterGeneratorSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ClusterGeneratorSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ClusterGeneratorStatus) DeepCopyInto(out *ClusterGeneratorStatus) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorStatus.
|
||||||
|
func (in *ClusterGeneratorStatus) DeepCopy() *ClusterGeneratorStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ClusterGeneratorStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ControllerClassResource) DeepCopyInto(out *ControllerClassResource) {
|
func (in *ControllerClassResource) DeepCopyInto(out *ControllerClassResource) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -566,6 +656,71 @@ func (in *GCRAccessTokenSpec) DeepCopy() *GCRAccessTokenSpec {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *GeneratorSpec) DeepCopyInto(out *GeneratorSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.ACRAccessTokenSpec != nil {
|
||||||
|
in, out := &in.ACRAccessTokenSpec, &out.ACRAccessTokenSpec
|
||||||
|
*out = new(ACRAccessTokenSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ECRAuthorizationTokenSpec != nil {
|
||||||
|
in, out := &in.ECRAuthorizationTokenSpec, &out.ECRAuthorizationTokenSpec
|
||||||
|
*out = new(ECRAuthorizationTokenSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.FakeSpec != nil {
|
||||||
|
in, out := &in.FakeSpec, &out.FakeSpec
|
||||||
|
*out = new(FakeSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.GCRAccessTokenSpec != nil {
|
||||||
|
in, out := &in.GCRAccessTokenSpec, &out.GCRAccessTokenSpec
|
||||||
|
*out = new(GCRAccessTokenSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.GithubAccessTokenSpec != nil {
|
||||||
|
in, out := &in.GithubAccessTokenSpec, &out.GithubAccessTokenSpec
|
||||||
|
*out = new(GithubAccessTokenSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.PasswordSpec != nil {
|
||||||
|
in, out := &in.PasswordSpec, &out.PasswordSpec
|
||||||
|
*out = new(PasswordSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.STSSessionTokenSpec != nil {
|
||||||
|
in, out := &in.STSSessionTokenSpec, &out.STSSessionTokenSpec
|
||||||
|
*out = new(STSSessionTokenSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.UUIDSpec != nil {
|
||||||
|
in, out := &in.UUIDSpec, &out.UUIDSpec
|
||||||
|
*out = new(UUIDSpec)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.VaultDynamicSecretSpec != nil {
|
||||||
|
in, out := &in.VaultDynamicSecretSpec, &out.VaultDynamicSecretSpec
|
||||||
|
*out = new(VaultDynamicSecretSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.WebhookSpec != nil {
|
||||||
|
in, out := &in.WebhookSpec, &out.WebhookSpec
|
||||||
|
*out = new(WebhookSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorSpec.
|
||||||
|
func (in *GeneratorSpec) DeepCopy() *GeneratorSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(GeneratorSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *GithubAccessToken) DeepCopyInto(out *GithubAccessToken) {
|
func (in *GithubAccessToken) DeepCopyInto(out *GithubAccessToken) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -151,7 +151,7 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: Specify the Kind of the resource, e.g.
|
description: Specify the Kind of the resource, e.g.
|
||||||
Password, ACRAccessToken etc.
|
Password, ACRAccessToken, ClusterGenerator etc.
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Specify the name of the generator resource
|
description: Specify the name of the generator resource
|
||||||
|
@ -327,7 +327,7 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: Specify the Kind of the resource, e.g.
|
description: Specify the Kind of the resource, e.g.
|
||||||
Password, ACRAccessToken etc.
|
Password, ACRAccessToken, ClusterGenerator etc.
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Specify the name of the generator resource
|
description: Specify the name of the generator resource
|
||||||
|
|
|
@ -416,7 +416,7 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: Specify the Kind of the resource, e.g.
|
description: Specify the Kind of the resource, e.g.
|
||||||
Password, ACRAccessToken etc.
|
Password, ACRAccessToken, ClusterGenerator etc.
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Specify the name of the generator resource
|
description: Specify the name of the generator resource
|
||||||
|
@ -591,7 +591,7 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: Specify the Kind of the resource, e.g.
|
description: Specify the Kind of the resource, e.g.
|
||||||
Password, ACRAccessToken etc.
|
Password, ACRAccessToken, ClusterGenerator etc.
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Specify the name of the generator resource
|
description: Specify the name of the generator resource
|
||||||
|
|
|
@ -177,7 +177,7 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: Specify the Kind of the resource, e.g. Password,
|
description: Specify the Kind of the resource, e.g. Password,
|
||||||
ACRAccessToken etc.
|
ACRAccessToken, ClusterGenerator etc.
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Specify the name of the generator resource
|
description: Specify the name of the generator resource
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,7 @@ resources:
|
||||||
- external-secrets.io_pushsecrets.yaml
|
- external-secrets.io_pushsecrets.yaml
|
||||||
- external-secrets.io_secretstores.yaml
|
- external-secrets.io_secretstores.yaml
|
||||||
- generators.external-secrets.io_acraccesstokens.yaml
|
- generators.external-secrets.io_acraccesstokens.yaml
|
||||||
|
- generators.external-secrets.io_clustergenerators.yaml
|
||||||
- generators.external-secrets.io_ecrauthorizationtokens.yaml
|
- generators.external-secrets.io_ecrauthorizationtokens.yaml
|
||||||
- generators.external-secrets.io_fakes.yaml
|
- generators.external-secrets.io_fakes.yaml
|
||||||
- generators.external-secrets.io_gcraccesstokens.yaml
|
- generators.external-secrets.io_gcraccesstokens.yaml
|
||||||
|
|
|
@ -89,6 +89,7 @@ The command removes all the Kubernetes components associated with the chart and
|
||||||
| crds.annotations | object | `{}` | |
|
| crds.annotations | object | `{}` | |
|
||||||
| crds.conversion.enabled | bool | `true` | If webhook is set to false this also needs to be set to false otherwise the kubeapi will be hammered because the conversion is looking for a webhook endpoint. |
|
| crds.conversion.enabled | bool | `true` | If webhook is set to false this also needs to be set to false otherwise the kubeapi will be hammered because the conversion is looking for a webhook endpoint. |
|
||||||
| crds.createClusterExternalSecret | bool | `true` | If true, create CRDs for Cluster External Secret. |
|
| crds.createClusterExternalSecret | bool | `true` | If true, create CRDs for Cluster External Secret. |
|
||||||
|
| crds.createClusterGenerator | bool | `true` | If true, create CRDs for Cluster Generator. |
|
||||||
| crds.createClusterSecretStore | bool | `true` | If true, create CRDs for Cluster Secret Store. |
|
| crds.createClusterSecretStore | bool | `true` | If true, create CRDs for Cluster Secret Store. |
|
||||||
| crds.createPushSecret | bool | `true` | If true, create CRDs for Push Secret. |
|
| crds.createPushSecret | bool | `true` | If true, create CRDs for Push Secret. |
|
||||||
| createOperator | bool | `true` | Specifies whether an external secret operator deployment be created. |
|
| createOperator | bool | `true` | Specifies whether an external secret operator deployment be created. |
|
||||||
|
|
|
@ -51,6 +51,7 @@ rules:
|
||||||
- "generators.external-secrets.io"
|
- "generators.external-secrets.io"
|
||||||
resources:
|
resources:
|
||||||
- "acraccesstokens"
|
- "acraccesstokens"
|
||||||
|
- "clustergenerators"
|
||||||
- "ecrauthorizationtokens"
|
- "ecrauthorizationtokens"
|
||||||
- "fakes"
|
- "fakes"
|
||||||
- "gcraccesstokens"
|
- "gcraccesstokens"
|
||||||
|
@ -145,6 +146,7 @@ rules:
|
||||||
- "generators.external-secrets.io"
|
- "generators.external-secrets.io"
|
||||||
resources:
|
resources:
|
||||||
- "acraccesstokens"
|
- "acraccesstokens"
|
||||||
|
- "clustergenerators"
|
||||||
- "ecrauthorizationtokens"
|
- "ecrauthorizationtokens"
|
||||||
- "fakes"
|
- "fakes"
|
||||||
- "gcraccesstokens"
|
- "gcraccesstokens"
|
||||||
|
@ -190,6 +192,7 @@ rules:
|
||||||
- "generators.external-secrets.io"
|
- "generators.external-secrets.io"
|
||||||
resources:
|
resources:
|
||||||
- "acraccesstokens"
|
- "acraccesstokens"
|
||||||
|
- "clustergenerators"
|
||||||
- "ecrauthorizationtokens"
|
- "ecrauthorizationtokens"
|
||||||
- "fakes"
|
- "fakes"
|
||||||
- "gcraccesstokens"
|
- "gcraccesstokens"
|
||||||
|
|
|
@ -270,6 +270,9 @@
|
||||||
"createClusterExternalSecret": {
|
"createClusterExternalSecret": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"createClusterGenerator": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"createClusterSecretStore": {
|
"createClusterSecretStore": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,6 +39,8 @@ crds:
|
||||||
createClusterExternalSecret: true
|
createClusterExternalSecret: true
|
||||||
# -- If true, create CRDs for Cluster Secret Store.
|
# -- If true, create CRDs for Cluster Secret Store.
|
||||||
createClusterSecretStore: true
|
createClusterSecretStore: true
|
||||||
|
# -- If true, create CRDs for Cluster Generator.
|
||||||
|
createClusterGenerator: true
|
||||||
# -- If true, create CRDs for Push Secret.
|
# -- If true, create CRDs for Push Secret.
|
||||||
createPushSecret: true
|
createPushSecret: true
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
20
docs/api/generator/cluster.md
Normal file
20
docs/api/generator/cluster.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
`ClusterGenerator` is a generator wrapper that is available to configure a generator
|
||||||
|
cluster-wide. The purpose of this generator is that the user doesn't have to redefine
|
||||||
|
the generator in every namespace. They could define it once in the cluster and then reference that
|
||||||
|
in the consuming `ExternalSecret`.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
With this, the generator will still create objects in the namespace in which the referencing ES lives.
|
||||||
|
That has not changed as of now. It will change in future modifications.
|
||||||
|
|
||||||
|
## Example Manifest
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{% include 'generator-cluster.yaml' %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `ExternalSecret` that references the Cluster generator:
|
||||||
|
```yaml
|
||||||
|
{% include 'generator-cluster-example.yaml' %}
|
||||||
|
```
|
|
@ -4569,7 +4569,7 @@ string
|
||||||
</em>
|
</em>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<p>Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.</p>
|
<p>Specify the Kind of the resource, e.g. Password, ACRAccessToken, ClusterGenerator etc.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
Generators allow you to generate values. They are used through a ExternalSecret `spec.DataFrom`. They are referenced from a custom resource using `sourceRef.generatorRef`.
|
Generators allow you to generate values. They are used through a ExternalSecret `spec.DataFrom`. They are 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.
|
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.
|
||||||
|
@ -24,4 +23,47 @@ spec:
|
||||||
apiVersion: generators.external-secrets.io/v1alpha1
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
kind: ECRAuthorizationToken
|
kind: ECRAuthorizationToken
|
||||||
name: "my-ecr"
|
name: "my-ecr"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Cluster Generate Resource
|
||||||
|
|
||||||
|
It's possible to use a `Cluster` scoped generator. At the moment of this writing, this Generator
|
||||||
|
will only help in locating the Generator cluster-wide. It doesn't mean that the generator can create resources in all
|
||||||
|
namespaces. It will still only create a resource in the given namespace where the referencing `ExternalSecret` lives.
|
||||||
|
|
||||||
|
To define a `ClusterGenerator` use the following config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: ClusterGenerator
|
||||||
|
metadata:
|
||||||
|
name: my-generator
|
||||||
|
spec:
|
||||||
|
kind: Password
|
||||||
|
generator:
|
||||||
|
passwordSpec:
|
||||||
|
length: 42
|
||||||
|
digits: 5
|
||||||
|
symbols: 5
|
||||||
|
symbolCharacters: "-_$@"
|
||||||
|
noUpper: false
|
||||||
|
allowRepeat: true
|
||||||
|
```
|
||||||
|
|
||||||
|
All the generators are available as a ClusterGenerator spec. The `kind` field MUST match the kind of the Generator
|
||||||
|
exactly. The following Spec fields are available:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type GeneratorSpec struct {
|
||||||
|
ACRAccessTokenSpec *ACRAccessTokenSpec `json:"acrAccessTokenSpec,omitempty"`
|
||||||
|
ECRAuthorizationTokenSpec *ECRAuthorizationTokenSpec `json:"ecrRAuthorizationTokenSpec,omitempty"`
|
||||||
|
FakeSpec *FakeSpec `json:"fakeSpec,omitempty"`
|
||||||
|
GCRAccessTokenSpec *GCRAccessTokenSpec `json:"gcrAccessTokenSpec,omitempty"`
|
||||||
|
GithubAccessTokenSpec *GithubAccessTokenSpec `json:"githubAccessTokenSpec,omitempty"`
|
||||||
|
PasswordSpec *PasswordSpec `json:"passwordSpec,omitempty"`
|
||||||
|
STSSessionTokenSpec *STSSessionTokenSpec `json:"stsSessionTokenSpec,omitempty"`
|
||||||
|
UUIDSpec *UUIDSpec `json:"uuidSpec,omitempty"`
|
||||||
|
VaultDynamicSecretSpec *VaultDynamicSecretSpec `json:"vaultDynamicSecretSpec,omitempty"`
|
||||||
|
WebhookSpec *WebhookSpec `json:"webhookSpec,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
14
docs/snippets/generator-cluster-example.yaml
Normal file
14
docs/snippets/generator-cluster-example.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: "cluster-secret"
|
||||||
|
spec:
|
||||||
|
refreshInterval: "1h"
|
||||||
|
target:
|
||||||
|
name: cluster-secret
|
||||||
|
dataFrom:
|
||||||
|
- sourceRef:
|
||||||
|
generatorRef:
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: ClusterGenerator
|
||||||
|
name: "cluster-gen"
|
24
docs/snippets/generator-cluster.yaml
Normal file
24
docs/snippets/generator-cluster.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: ClusterGenerator
|
||||||
|
metadata:
|
||||||
|
name: cluster-gen
|
||||||
|
spec:
|
||||||
|
kind: Password
|
||||||
|
generator:
|
||||||
|
# Further specs are available:
|
||||||
|
# acrAccessTokenSpec:
|
||||||
|
# ecrRAuthorizationTokenSpec:
|
||||||
|
# fakeSpec:
|
||||||
|
# gcrAccessTokenSpec:
|
||||||
|
# githubAccessTokenSpec:
|
||||||
|
# stsSessionTokenSpec:
|
||||||
|
# uuidSpec:
|
||||||
|
# vaultDynamicSecretSpec:
|
||||||
|
# webhookSpec:
|
||||||
|
passwordSpec:
|
||||||
|
length: 42
|
||||||
|
digits: 5
|
||||||
|
symbols: 5
|
||||||
|
symbolCharacters: "-_$@"
|
||||||
|
noUpper: false
|
||||||
|
allowRepeat: true
|
|
@ -69,6 +69,7 @@ nav:
|
||||||
- Azure Container Registry: api/generator/acr.md
|
- Azure Container Registry: api/generator/acr.md
|
||||||
- AWS Elastic Container Registry: api/generator/ecr.md
|
- AWS Elastic Container Registry: api/generator/ecr.md
|
||||||
- AWS STS Session Token: api/generator/sts.md
|
- AWS STS Session Token: api/generator/sts.md
|
||||||
|
- Cluster Generator: api/generator/cluster.md
|
||||||
- Google Container Registry: api/generator/gcr.md
|
- Google Container Registry: api/generator/gcr.md
|
||||||
- Vault Dynamic Secret: api/generator/vault.md
|
- Vault Dynamic Secret: api/generator/vault.md
|
||||||
- Password: api/generator/password.md
|
- Password: api/generator/password.md
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
|
|
||||||
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.
|
|
||||||
"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"
|
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||||
|
@ -116,6 +115,8 @@ func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
return nil, fmt.Errorf("unable to resolve generator: %w", err)
|
||||||
}
|
}
|
||||||
|
// We still pass the namespace to the generate function because it needs to create
|
||||||
|
// namespace based objects.
|
||||||
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
secretMap, err := gen.Generate(ctx, obj, r.Client, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(errGenerate, i, err)
|
return nil, fmt.Errorf(errGenerate, i, err)
|
||||||
|
|
|
@ -650,6 +650,45 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
syncWithClusterGeneratorRef := func(tc *testCase) {
|
||||||
|
const secretKey = "somekey2"
|
||||||
|
const secretVal = "someValue2"
|
||||||
|
Expect(k8sClient.Create(context.Background(), &genv1alpha1.ClusterGenerator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "mytestfake",
|
||||||
|
},
|
||||||
|
Spec: genv1alpha1.ClusterGeneratorSpec{
|
||||||
|
Kind: "Fake",
|
||||||
|
Generator: genv1alpha1.GeneratorSpec{
|
||||||
|
FakeSpec: &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.StoreGeneratorSourceRef{
|
||||||
|
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||||
|
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||||
|
Kind: "ClusterGenerator",
|
||||||
|
Name: "mytestfake",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||||
|
// check values
|
||||||
|
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deleteOrphanedSecrets := func(tc *testCase) {
|
deleteOrphanedSecrets := func(tc *testCase) {
|
||||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||||
|
@ -2280,6 +2319,7 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
|
Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
|
||||||
Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
|
Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
|
||||||
Entry("should not delete pre-existing secret with creationPolicy=Orphan", createSecretPolicyOrphan),
|
Entry("should not delete pre-existing secret with creationPolicy=Orphan", createSecretPolicyOrphan),
|
||||||
|
Entry("should sync cluster generator ref", syncWithClusterGeneratorRef),
|
||||||
Entry("should sync with generatorRef", syncWithGeneratorRef),
|
Entry("should sync with generatorRef", syncWithGeneratorRef),
|
||||||
Entry("should not process generatorRef with mismatching controller field", ignoreMismatchControllerForGeneratorRef),
|
Entry("should not process generatorRef with mismatching controller field", ignoreMismatchControllerForGeneratorRef),
|
||||||
Entry("should sync with multiple secret stores via sourceRef", syncWithMultipleSecretStores),
|
Entry("should sync with multiple secret stores via sourceRef", syncWithMultipleSecretStores),
|
||||||
|
|
|
@ -11,6 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package resolvers
|
package resolvers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -18,8 +19,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
|
@ -70,15 +73,90 @@ func getGeneratorDefinition(ctx context.Context, restConfig *rest.Config, namesp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, err := d.Resource(mapping.Resource).
|
|
||||||
Namespace(namespace).
|
if generatorRef.Kind == "ClusterGenerator" {
|
||||||
Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
return extractGeneratorFromClusterGenerator(ctx, d, mapping, generatorRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := d.Resource(mapping.Resource).Namespace(namespace).Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonRes, err := res.MarshalJSON()
|
jsonRes, err := res.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &apiextensions.JSON{Raw: jsonRes}, nil
|
return &apiextensions.JSON{Raw: jsonRes}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractGeneratorFromClusterGenerator(
|
||||||
|
ctx context.Context,
|
||||||
|
d *dynamic.DynamicClient,
|
||||||
|
mapping *meta.RESTMapping,
|
||||||
|
generatorRef *esv1beta1.GeneratorRef,
|
||||||
|
) (*apiextensions.JSON, error) {
|
||||||
|
res, err := d.Resource(mapping.Resource).Get(ctx, generatorRef.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, err := extractValue[map[string]any](res.Object, genv1alpha1.GeneratorSpecKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
generator, err := extractValue[map[string]any](spec, genv1alpha1.GeneratorGeneratorKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kind, err := extractValue[string](spec, genv1alpha1.GeneratorKindKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the first value and that's what we are going to take
|
||||||
|
// this will be the generator that has been set by the user
|
||||||
|
var result []byte
|
||||||
|
for _, v := range generator {
|
||||||
|
vMap, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("kind was not of object type for cluster generator %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct our generator object so it can be later unmarshalled into a valid Generator Spec.
|
||||||
|
object := map[string]interface{}{}
|
||||||
|
object["kind"] = kind
|
||||||
|
object["spec"] = vMap
|
||||||
|
result, err = json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiextensions.JSON{Raw: result}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no defined generators found for cluster generator spec: %v", spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractValue fetches a specific key value that we are looking for in a map.
|
||||||
|
func extractValue[T any](m any, k string) (T, error) {
|
||||||
|
var result T
|
||||||
|
v, ok := m.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf("value was not of type map[string]any but: %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
vv, ok := v[k]
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf("key %s was not found in map", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
vvv, ok := vv.(T)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf("value was not of type T but: %T", vvv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vvv, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue