Syncing Secrets

The operator uses DopplerSecret custom resources to determine which secrets should be synced from Doppler to Kubernetes. Each of these resources contain the details for a single Doppler config sync including:

  • the Doppler access token that's used to perform the secret fetch
  • the Doppler project and config the secrets are being fetched from
  • the Kubernetes secret they're being synced to

The Kubernetes secret that's being synced to will be created if it doesn't exist. The operator will then continuously monitor all DopplerSecret custom resources and ensure that any upstream changes in Doppler will be reflected in the Kubernetes secret (by default, it checks for changes every 60 seconds).

Permissions

The Doppler Kubernetes Operator will always install into the doppler-operator-system kubernetes namespace (which is automatically created when you install the helm chart). This namespace is considered privileged, which means DopplerSecret CRDs created in this namespace are able to do some things they can't do if they exist in other namespaces. Notably they can:

  • manage kubernetes secrets in other namespaces
  • reference kubernetes secrets in other namespaces (e.g., doppler token secrets)

If a DopplerSecret CRD is created in any other namespace, it's restricted to managing and referencing kubernetes secrets in that namespace. If one attempts to reference any outside of its namespace, the DopplerSecret will not get processed by the operator.

This permission setup is designed to allow an orchestration or management team to create secrets for other teams if needed by creating them in the operator namespace, while also allowing self-service by letting other teams create their own CRDs that impact their own namespaces.

Doppler Access Token

Every DopplerSecret CRD requires a tokenSecret to be specified that tells the operator where to find a Doppler access token to use to sync that CRD. In a production environment, you would likely use a Service Account token, but when testing locally it's easiest to just use your local CLI access token:

kubectl create secret generic doppler-token-secret \
  --namespace doppler-operator-system \
  --from-literal=serviceToken=$(doppler configure get token --plain)

🚧

Keep in mind that your local CLI access token is tied to your user and has all the permissions your user account does. We recommend against using these in production. Instead, consider using one or more Service Account tokens.

DopplerSecret Custom Resource

A DopplerSecret custom resource definition (CRD) looks like this:

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  project: k8s-demo
  config: dev
  managedSecret:
    name: dopplersecret-test
    namespace: default
    type: Opaque

To create the DopplerSecret CRD, you apply it using kubectl as you would any other CRD:

kubectl apply -f examples/doppler-secret.yml

Kubernetes Secret Type

Kubernetes has many different secret types. By default, the operator creates secrets using kubernetes' Opaque type. In some situations, you may want to sync to another secret type though. This can be done by specifying the type of the managedSecret. Supported types are as follows:

  • Opaque
  • kubernetes.io/tls
  • kubernetes.io/service-account-token
  • kubernetes.io/dockercfg
  • kubernetes.io/dockerconfigjson
  • kubernetes.io/basic-auth
  • kubernetes.io/ssh-auth
  • bootstrap.kubernetes.io/token

These types have additional expectations in terms of the secret keys that are added. For example, the kubernetes.io/tls type expects a tls.key and tls.crt key and no other entries. To comply with this, the usual DOPPLER_* secrets are not synced for these secret types and you need to use processors to rename the secrets:

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  managedSecret:
    name: doppler-test-secret
    namespace: default
    type: kubernetes.io/tls
  project: k8s-demo
  config: dev
  processors:
    TLS_CRT:
      type: plain
      asName: tls.crt
    TLS_KEY:
      type: plain
      asName: tls.key

In the above example, the TLS_CRT and TLS_KEY secrets are renamed to tls.crt and tls.key to comply with the expectations of the kubernetes.io/tls secret type.

Adjusting Sync Interval

You can control the polling interval that the operator uses when syncing by specifying resyncSeconds. We recommend leaving this at the default (60 seconds), but there are scenarios where adjusting this may be necessary or desired.

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  managedSecret:
    name: dopplersecret-test
    namespace: default
    type: Opaque
  project: k8s-demo
  config: dev
  resyncSeconds: 120

Name Transformers

Name transformers allow secret names to be transformed from Doppler's UPPER_SNAKE_CASE format into any of the following environment variable compatible formats:

TypeDefaultTransform
camelAPI_KEYapiKey
upper-camelAPI_KEYApiKey
lower-snakeAPI_KEYapi_key
tf-varAPI_KEYTF_VAR_api_key
dotnet-envSMTP__USER_NAMESmtp__UserName
lower-kebabAPI_KEYapi-key

To use a transformer, add the nameTransformer field with any of the above types:

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  managedSecret:
    name: dopplersecret-test
    namespace: default
    type: Opaque
  project: k8s-demo
  config: dev
  nameTransformer: dotnet-env

Processors

Type

By default, the Operator base64 encodes Doppler secret values when creating the managed Opaque Kubernetes secret as Key / Value pairs. However, instances may arise where you do not wish this base64 encoding to occur, such as when a binary .p12 key file has already been base64 encoded in Doppler and double base64 encoding it would prevent Kubernetes from mounting it in its original binary format.

Secret processors allow you to customize the default encoding behavior.

To override the default plain processor, add a processors: map which specifies the secret name and the processor to apply which in this case, is base64:

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  managedSecret:
    name: doppler-test-secret
    namespace: default
    type: Opaque
  project: k8s-demo
  config: dev
  processors:
    TLS_CRT:
      type: plain
      asName: tls.crt
    TLS_KEY:
      type: plain
      asName: tls.key

At present, only plain and base64 processors exist. Reach out to [email protected] if there is another processor you'd like us to support.

asName

You can also override the default name that's used when creating the secret in Kubernetes. By default, it will use the same uppercase secret name found in Doppler. This is useful when using the kubernetes.io/tls type that expects a tls.crt and tls.key secret entry. To accomplish this, add the asName parameter to the processor:

processors:
  TLS_CRT:
    type: plain
    asName: tls.crt
  TLS_KEY:
    type: plain
    asName: tls.key

Note that using the plain type combined with the asName parameter will let you rename any secret during the sync without affecting the value being synced.

For a complete example, check out our tutorial on using the Doppler Kubernetes Operator for Managing PKCS12 Certificates and the README in GitHub.

Secret Subsets

You can specify a subset of secrets from a config that you'd like to sync by using the secrets field. Note that the DOPPLER_* secrets will still appear in addition to any secrets you specify here.

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-secret
    namespace: doppler-operator-system
  managedSecret:
    name: doppler-test-secret
    namespace: default
    type: Opaque
  project: k8s-demo
  config: dev
  secrets:
    - TLS_CRT
    - TLS_KEY

Download Format

Instead of the standard Key / Value pairs, you can download secrets as a single file in the following formats:

  • json
  • dotnet-json
  • env
  • yaml
  • docker

When format is specified, a single DOPPLER_SECRETS_FILE key is set in the created secret with the string contents of the downloaded file.

To use this feature, add the format field:

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dotnet-webapp-appsettings
  namespace: doppler-operator-system
spec:
  tokenSecret:
    name: doppler-token-dotnet-webapp
    namespace: doppler-operator-system
  managedSecret:
    name: dotnet-webapp-appsettings
    namespace: default
  format: dotnet-json

You can then configure your deployment spec to mount the file at the desired path:

---
spec:
  containers:
    - name: dotnet-webapp
      volumeMounts:
        - name: doppler
          mountPath: /usr/src/app/secrets
          readOnly: true
  volumes:
    - name: doppler
      secret:
        secretName: dotnet-webapp-appsettings # Managed secret name
        optional: false
        items:
          - key: DOPPLER_SECRETS_FILE # Hard-coded by Operator when format specified
            path: appsettings.json # Name or path to file name appended to container mountPath

What’s Next