diff --git a/README.md b/README.md index e8a6231..dae5361 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,28 @@ spec: If this property is omitted all secrets are synced. +## Specifying Labels to Add to Managed Secret + +You can specify labels that the operator should add to the managed Kubernetes `Secret` resource. To do this, specify them in the `managedSecret.labels` spec property. + +```yaml +apiVersion: secrets.doppler.com/v1alpha1 +kind: DopplerSecret +metadata: + name: dopplersecret-test + namespace: doppler-operator-system +spec: + tokenSecret: + name: doppler-token-secret + managedSecret: + name: doppler-test-secret + namespace: default + labels: + doppler-secret-label: test +``` + +This will result in these labels being added to the `Secret` resource as `labels` in the `metadata` field. + ## Kubernetes Secret Types and Value Encoding By default, the operator syncs secret values as they are in Doppler to an [`Opaque` Kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/) as Key / Value pairs. diff --git a/api/v1alpha1/dopplersecret_types.go b/api/v1alpha1/dopplersecret_types.go index 37a5802..a373018 100644 --- a/api/v1alpha1/dopplersecret_types.go +++ b/api/v1alpha1/dopplersecret_types.go @@ -49,6 +49,8 @@ type ManagedSecretReference struct { // +kubebuilder:default=Opaque // +optional Type string `json:"type,omitempty"` + + Labels map[string]string `json:"labels,omitempty"` } type SecretProcessor struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e71d30e..0704995 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -89,7 +89,7 @@ func (in *DopplerSecretList) DeepCopyObject() runtime.Object { func (in *DopplerSecretSpec) DeepCopyInto(out *DopplerSecretSpec) { *out = *in out.TokenSecretRef = in.TokenSecretRef - out.ManagedSecretRef = in.ManagedSecretRef + in.ManagedSecretRef.DeepCopyInto(&out.ManagedSecretRef) if in.Secrets != nil { in, out := &in.Secrets, &out.Secrets *out = make([]string, len(*in)) @@ -147,6 +147,13 @@ func (in *DopplerSecretStatus) DeepCopy() *DopplerSecretStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedSecretReference) DeepCopyInto(out *ManagedSecretReference) { *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *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 ManagedSecretReference. diff --git a/config/crd/bases/secrets.doppler.com_dopplersecrets.yaml b/config/crd/bases/secrets.doppler.com_dopplersecrets.yaml index 02552bb..1e2bc5c 100644 --- a/config/crd/bases/secrets.doppler.com_dopplersecrets.yaml +++ b/config/crd/bases/secrets.doppler.com_dopplersecrets.yaml @@ -56,6 +56,10 @@ spec: description: The Kubernetes secret where the operator will store and sync the fetched secrets properties: + labels: + additionalProperties: + type: string + type: object name: description: The name of the Secret resource type: string diff --git a/controllers/dopplersecret_controller_secrets.go b/controllers/dopplersecret_controller_secrets.go index c452074..536d90c 100644 --- a/controllers/dopplersecret_controller_secrets.go +++ b/controllers/dopplersecret_controller_secrets.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "reflect" "github.com/DopplerHQ/kubernetes-operator/pkg/models" @@ -147,6 +148,19 @@ func GetKubeSecretAnnotations(secretsResult models.SecretsResult, processorsVers return annotations } +// GetKubeSecretAnnotations generates Kube labels from a Doppler API secrets result +func GetKubeSecretLabels(additionalLabels map[string]string) map[string]string { + labels := map[string]string{} + + for k, v := range additionalLabels { + labels[k] = v + } + + labels["secrets.doppler.com/subtype"] = "dopplerSecret" + + return labels +} + // GetProcessorsVersion generates the version of given processors using a SHA256 hash func GetProcessorsVersion(processors secretsv1alpha1.SecretProcessors) (string, error) { if len(processors) == 0 { @@ -178,9 +192,7 @@ func (r *DopplerSecretReconciler) CreateManagedSecret(ctx context.Context, doppl Name: dopplerSecret.Spec.ManagedSecretRef.Name, Namespace: dopplerSecret.Spec.ManagedSecretRef.Namespace, Annotations: GetKubeSecretAnnotations(secretsResult, processorsVersion, dopplerSecret.Spec.Format), - Labels: map[string]string{ - "secrets.doppler.com/subtype": "dopplerSecret", - }, + Labels: GetKubeSecretLabels(dopplerSecret.Spec.ManagedSecretRef.Labels), }, Type: corev1.SecretType(dopplerSecret.Spec.ManagedSecretRef.Type), Data: secretData, @@ -209,6 +221,7 @@ func (r *DopplerSecretReconciler) UpdateManagedSecret(ctx context.Context, secre } secret.Data = secretData secret.ObjectMeta.Annotations = GetKubeSecretAnnotations(secretsResult, processorsVersion, dopplerSecret.Spec.Format) + secret.ObjectMeta.Labels = GetKubeSecretLabels((dopplerSecret.Spec.ManagedSecretRef.Labels)) err := r.Client.Update(ctx, &secret) if err != nil { return fmt.Errorf("Failed to update Kubernetes secret: %w", err) @@ -276,6 +289,18 @@ func (r *DopplerSecretReconciler) UpdateSecret(ctx context.Context, dopplerSecre requestedSecretVersion = "" } + if existingKubeSecret != nil { + // Compare existing managed secret labels to labels defined in the doppler secret. + // If they are not equal, we will update the managed secret with the new labels. + if reflect.DeepEqual(existingKubeSecret.Labels, GetKubeSecretLabels(dopplerSecret.Spec.ManagedSecretRef.Labels)) { + log.Info("[-] Managed secret labels not modified.") + } else { + // If labels have changed, set requestedSecretVersion to an empty secret version to reload the secrets. + log.Info("[/] Labels changed, reloading secrets.") + requestedSecretVersion = "" + } + } + secretsResult, apiErr := api.GetSecrets(GetAPIContext(dopplerSecret, dopplerToken), requestedSecretVersion, dopplerSecret.Spec.Project, dopplerSecret.Spec.Config, dopplerSecret.Spec.NameTransformer, dopplerSecret.Spec.Format, dopplerSecret.Spec.Secrets) if apiErr != nil { return apiErr diff --git a/docs/custom_types_and_processors.md b/docs/custom_types_and_processors.md index e84e03f..5100166 100644 --- a/docs/custom_types_and_processors.md +++ b/docs/custom_types_and_processors.md @@ -22,6 +22,8 @@ spec: name: doppler-test-secret namespace: default type: kubernetes.io/tls + labels: + doppler-secret-label: test # TLS secrets are required to have the secret fields `tls.crt` and `tls.key` processors: TLS_CRT: @@ -34,6 +36,8 @@ spec: First, we've added a `type` field to the managed secret reference to define the `kubernetes.io/tls` managed secret type. When the operator creates the managed secret, it will have this Kubernetes secret type. +We've also added a field called `labels`. These labels will be added verbatim to the `metadata` field in the resulting `Secret`. + We've also added a field called `processors`. Processors can make alterations to a secret's name or value before they are saved to the Kubernetes managed secret. Kubernetes TLS manged secrets require the `tls.crt` and `tls.key` fields to be present in the secret data. To accommodate this, we're using two processors to remap our Doppler secrets named `TLS_CRT` and `TLS_KEY` to the correct field names with `asName`.