Reusing Existing Kubernetes Secrets in Helm Templates

Generating values in Kubernetes secrets

Helm templates can generate values that can be used in Kubernetes secrets. A Helm template for a Kubernetes secret which generates a password if a value isn’t defined in the Helm values looks something like this:

apiVersion: v1
kind: Secret
metadata:
  name: postgres-credentials
type: Opaque
data:
  postgresPassword: {{ default (randAlphaNum 64) .Values.postgresPassword.value | b64enc | quote }}

Using the lookup function

What if we only wanted Helm to generate a new value in the secret if there was no existing Kubernetes secret containing a key from which we wanted to reuse the value?

As an example, our application needs to connect to a Postgres instance. Our Helm chart creates a Kubernetes secret containing a generated password and a Kubernetes job which creates a user on our Postgres instance using our new password if a Postgres user for our application does not already exist. If we reinstalled our Helm chart, Helm would generate a new Kubernetes secret and password, resulting in our application using incorrect Postgres credentials.

This problem can be solved using Helm’s lookup function. To use the lookup function in Helm, you can include it in a template.

{{ lookup .Values "key" }}

This lookup function will retrieve the value of the key key from the Values object. If the key does not exist or the Values object is not a map or an object, the lookup function will return an empty string.

You can also use the lookup function to retrieve values from a Kubernetes resource, such as a secret, by specifying the resource API version, type, namespace, and name.

{{ lookup "v1" "Secret" .Release.Namespace "postgres-credentials" }}

We can use the lookup function’s output at a specific index in our template.

{{ $secret := (lookup "v1" "Secret" .Release.Namespace $secretName) }}

apiVersion: v1
kind: Secret
metadata:
  name: postgres-credentials
type: Opaque
data:
  postgresPassword: {{ index $secret.data "postgresPassword" }}

Putting it all together

Combining those two approaches, what if we wanted to create a template for a Kubernetes secret containing a key, the value defined in the Helm values file? If the value in the values file is empty, it reuses the value from an existing secret. If there is no existing secret, the template will use a newly generated value.

The way to achieve this is first to generate a new variable $postgresPassword, which has a value of .Values.postgresPassword.value, and defaults to generating a new random string using randAlphaNum64.

{{- $postgresPassword := default (randAlphaNum 64) .Values.postgresPassword.value | b64enc | quote }}

We use an if statement to check whether .Values.postgresPassword has a value. If .Values.postgresPassword is does not have a value; we use the lookup function to retrieve an existing Kubernetes secret, postgres-credentials.

{{- if not .Values.postgresPassword.value }}
{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "postgres-credentials") }}

We check whether the lookup function found an existing secret and, if it did, we set $postgresPassword to the value output by the lookup function at index "postgresPassword". We then close both if statements.

{{- if $existingSecret }}
{{- $postgresPassword = index $existingSecret.data "postgresPassword"}}
{{- end -}}
{{- end -}}

We ensure that this secret is persisted so we can copy its value, even if we uninstall the Helm deployment. We do this using the Helm resource policy annotation:

annotations:
  "helm.sh/resource-policy": "keep"

Finally, we set the value of the postgresPassword key in our secret to $postgresPassword:

data:
  postgresPassword: {{ $postgresPassword }}

Putting this all together gives us the following template:

{{- $postgresPassword := default (randAlphaNum 64) .Values.postgresPassword.value | b64enc | quote }}

{{- if not .Values.postgresPassword.value }}
{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "postgres-credentials") }}
{{- if $existingSecret }}
{{- $postgresPassword = index $existingSecret.data "postgresPassword"}}
{{- end -}}
{{- end -}}

apiVersion: v1
kind: Secret
metadata:
  name: postgres-credentials
  annotations:
    "helm.sh/resource-policy": "keep"
type: Opaque
data:
  postgresPassword: {{ $postgresPassword }}