Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for labels in managed secrets #67

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/dopplersecret_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type ManagedSecretReference struct {
// +kubebuilder:default=Opaque
// +optional
Type string `json:"type,omitempty"`

Labels map[string]string `json:"labels,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a docs comment here as well as the +optional annotation?

}

type SecretProcessor struct {
Expand Down
9 changes: 8 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions config/crd/bases/secrets.doppler.com_dopplersecrets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 28 additions & 3 deletions controllers/dopplersecret_controller_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"reflect"

"github.com/DopplerHQ/kubernetes-operator/pkg/models"

Expand Down Expand Up @@ -147,6 +148,19 @@ func GetKubeSecretAnnotations(secretsResult models.SecretsResult, processorsVers
return annotations
}

// GetKubeSecretAnnotations generates Kube labels from a Doppler API secrets result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/copypasta: Can we update this comment to match the function?

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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 = ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be a good opportunity to refactor the UpdateSecret logic. Let me know what you think of the attached patch.

}
}

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
Expand Down
4 changes: 4 additions & 0 deletions docs/custom_types_and_processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`.
Expand Down