commit eff126d4e1961013cdf78d27c27ce43de8aa35e4
Author: Stefan Nica <snica@suse.com>
Date:   Fri Nov 6 10:08:13 2020 +0100

    Add values.yaml options to control TLS certificate rotation
    
    When auto-generated TLS certificates are used for external and internal
    TLS, these certificates will be re-generated with every helm run, which
    can have undesired effects, such as:
    * external TLS certificates being re-generated during upgrades, which
    requires users to re-configure their clients to accept the new CA
    certificate. This can currently be alleviated by making a copy of the
    existing secret and providing that as a secret certSource, but it's
    an unnecessary complication that can be avoided by extending the
    helm chart templates to cover this case.
    * "helm upgrade" is also used for Day 2 operations other than upgrades,
    such as making targeted configuration changes (e.g. password changes
    for the external database or redis services, resizing the persistent
    storage volume for a particular component). It is expected that such
    operations only restart the pods of the affected services, to reduce
    downtime. However, when internal TLS is enabled with auto-generated
    certificates, almost all internal services are restarted, leading
    to unnecessary load and downtime.
    
    This change introduces a `rotateCert` configuration option to all places
    that involve auto-generating certificates:
    * external TLS
    * internal TLS
    * notary communications (also internal TLS, but one that cannot be
    disabled for technical reasons)
    * core token encryption/decryption - currently using a TLS certificate
    and private key that are hard-coded in the helm chart (which can be
    labeled as a security risk)
    
    The `rotateCert` configuration option may be set to `false` in the
    helm chart configuration (or supplied as a helm `--set` command line
    argument) to explicitly disable re-generating TLS certificates during
    a "helm upgrade" run where such a change isn't required. When set to
    `false', the existing TLS certificates are loaded from their respective
    secrets and reused.
    
    Signed-off-by: Stefan Nica <snica@suse.com>

diff --git a/README.md b/README.md
index f5fcd68..509194e 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ The following table lists the configurable parameters of the Harbor chart and th
 | `expose.tls.enabled` | Enable the tls or not | `true` |
 | `expose.tls.certSource` | The source of the tls certificate. Set it as `auto`, `secret` or `none` and fill the information in the corresponding section: 1) auto: generate the tls certificate automatically 2) secret: read the tls certificate from the specified secret. The tls certificate can be generated manually or by cert manager 3) none: configure no tls certificate for the ingress. If the default tls certificate is configured in the ingress controller, choose this option | `auto` |
 | `expose.tls.auto.commonName` | The common name used to generate the certificate, it's necessary when the type isn't `ingress` | |
+| `expose.tls.auto.rotateCert` | Set to `true` to generate new TLS certificates on the next helm run, or `false` to keep existing TLS certificates. Only available when `certSource` is `auto` | true |
 | `expose.tls.secret.secretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key | |
 | `expose.tls.secret.notarySecretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key. Only needed when the `expose.type` is `ingress` | |
 | `expose.ingress.hosts.core` | The host of Harbor core service in ingress rule | `core.harbor.domain` |
@@ -110,6 +111,7 @@ The following table lists the configurable parameters of the Harbor chart and th
 | **Internal TLS** |
 | `internalTLS.enabled` | Enable the tls for the components (chartmuseum, clair, core, jobservice, portal, registry, trivy) | `false` |
 | `internalTLS.certSource` | Method to provide tls for the components, options is `auto`, `manual`, `secret`. | `auto` |
+| `internalTLS.rotateCert` | Set to `true` to generate new certificates for internal TLS on the next helm run, or `false` to keep existing TLS certificates. Only available when `certSource` is `auto` | true |
 | `internalTLS.trustCa` | The content of trust ca, only available when `certSrouce` is `manual`. **Note**: all the internal certificates of the components must be issued by this ca |  |
 | `internalTLS.core.secretName` | The secret name for core component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the certificate of CA which is used to issue internal key and crt pair for components and all Harbor components must issued by the same CA , `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | |
 | `internalTLS.core.crt` | Content of core's TLS cert file, only available when `certSource` is `manual` | |
@@ -208,6 +210,7 @@ The following table lists the configurable parameters of the Harbor chart and th
 | `core.podAnnotations` | Annotations to add to the core pod | `{}` |
 | `core.secret` | Secret is used when core server communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | |
 | `core.secretName` | Fill the name of a kubernetes secret if you want to use your own TLS certificate and private key for token encryption/decryption. The secret must contain keys named: `tls.crt` - the certificate and `tls.key` - the private key. The default key pair will be used if it isn't set | |
+| `core.rotateCert` | Set to `true` to generate a new TLS certificate and private key for token encryption/decryption on the next helm run, or `false` to keep the existing TLS certificate and private key. Only available when `secretName` is unset. | false |
 | `core.xsrfKey` | The XSRF key. Will be generated automatically if it isn't specified | |
 | **Jobservice**                                                              |
 | `jobservice.image.repository`                                               | Repository for jobservice image                                                                                                                                                                                                                                                                                                                 | `goharbor/harbor-jobservice`    |
@@ -291,6 +294,7 @@ The following table lists the configurable parameters of the Harbor chart and th
 | `notary.affinity`                                                           | Node/Pod affinities                                                                                                                                                                                                                                                                                                                             | `{}`                            |
 | `notary.podAnnotations`                                                     | Annotations to add to the notary pod                                                                                                                                                                                                                                                                                                            | `{}`                            |
 | `notary.secretName`                                                         | Fill the name of a kubernetes secret if you want to use your own TLS certificate authority, certificate and private key for notary communications. The secret must contain keys named `tls.ca`, `tls.crt` and `tls.key` that contain the CA, certificate and private key. They will be generated if not set.                                    |                                 |
+| `notary.rotateCert` | Set to `true` to generate new TLS certificates for notary communications on the next helm run, or `false` to keep the existing TLS certificates. Only available when `secretName` is unset. | true |
 | **Database** |
 | `database.type` | If external database is used, set it to `external` | `internal` |
 | `database.internal.image.repository` | Repository for database image | `goharbor/harbor-db` |
diff --git a/cert/tls.crt b/cert/tls.crt
deleted file mode 100644
index de4aa3e..0000000
--- a/cert/tls.crt
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIE0zCCArugAwIBAgIJAPY/OzLMeVq2MA0GCSqGSIb3DQEBCwUAMAAwHhcNMTkw
-NDE4MDIyNzM3WhcNMjkwNDE1MDIyNzM3WjAAMIICIjANBgkqhkiG9w0BAQEFAAOC
-Ag8AMIICCgKCAgEA3xlUJs2b/aI2NLoy4OIQ+dn/yMb/O99iKDRyZKpH8rSOmS+o
-F9unmSAzL65XA/v6nY0OLI/dASDjkqkBpIdTGzogR5f8UiB6osuEY7V71XZdzWLr
-PjnJq6ZLAaoKmwG80W5+Wd6V8PygOx52mkr1w7IWKz+1ZLI5izbppon7XVGVRaAT
-RvNZDiJ6CeJpcJ5H723lkf5RvJWatZLCYIYDbRfTiKsyQ/SlRcv5BVfHg/LJSH9Q
-LGRhPMARldl9wyZCwZZDHxheI4a+26aa8MY3u9st/l0/Oo6VCTGpMiEhiGF2LVjp
-UWq/+BP4SFEvJfq/DuinI139W/5aZZ7/HwRPlmYU6pXTRLyIg7jd+19fJwR7X37q
-w0o8t06FhjmrCzaYCUjoReqDmHaNmZN/ddvG7jZWBu+jNh0YavsyQyCIVmv6yqSc
-jPiD9uivxqTwjJidIBRfuUrz3aERQ7cQgf0qhqjIzflzHbFKhILocBWq7zyNl9hr
-vUGT/WZcw0t/OtM72SPaplmTgVbbQRxf2VHzyptGIvtydlXK8thxOMpXo4e+Sl8d
-1gdQcC4oisN9F29oNs8P5yFQP//xYuv8C607nCj1DzrId5avG/NVfKB/fbDKEFgN
-2WhHInTzPLEcjF4fErcUAEuWW0buX/6FHCG3iTtrqyD92KTVDfN1J56rrcsCAwEA
-AaNQME4wHQYDVR0OBBYEFFhNhTo4UAC2PUsf8jYaWj160vGEMB8GA1UdIwQYMBaA
-FFhNhTo4UAC2PUsf8jYaWj160vGEMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggIBAMAsEtVlELMwdtcifHeOT0kOmf5wo9In/eFSgscCzBMDaRx2B3q36AoS
-Il7XWAZpevaR7W7yeARKaAshBLhygUqLD0zWbKlSN9Hprd1wdpM0ffyPpN5dxOYA
-er04y12GRnCbMYqi4cvztP4TinXqq2yHSYhLbO9qkI5gbWVxkRuIcMKvixddllNY
-Q3obJaDDHmovM3+g/G+1YFgt4qES38XnJ7BrSshHnn5EIQh286xfJriyrK2hHbLJ
-qz0YuF6G3DXPeWGgXvj0Hipc0f8UDZkKkk/eGEI6vEkytyvoepoZI2XbAf/ZMy5n
-KwuhEn4hhkFMwWaSWp/h0QdMCaxk4BVSOqmNVaLSB7+FjsIj4CasFotYiyJ2gpRB
-Nf8QaS4bz0Tn1eBbC8ksj+e3ZWeX2b5wVMjql9jTt2X1ICs8KKe3vEBkjqT2AUi2
-52TtKzm73aWrz/GPy/Q2LCor3Fh9FGVSBOBBDXGy6MJpNHJnYVH9EENFGOh85ol1
-2pADOBB5vAU/kLB5LHPj2kue/FMiHaNnrSYIGrMlBSX2jj9EYa1uuUH+pd4MBj1F
-5uH8ORiaQ6ht2+WHklxic1Rj5yTYQwVlH70CBOn+qVEdo63yQwzAMJKFIwlGUQEX
-jiljgc86q4cZtUTFrcwMidbk+8Q6+JbDVg7HV/+pnC+wnv197kwe
------END CERTIFICATE-----
diff --git a/cert/tls.key b/cert/tls.key
deleted file mode 100644
index 014e2fa..0000000
--- a/cert/tls.key
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----
diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl
index 0d4e8f0..ae9987e 100644
--- a/templates/_helpers.tpl
+++ b/templates/_helpers.tpl
@@ -54,6 +54,28 @@ app: "{{ template "harbor.name" . }}"
   {{- end -}}
 {{- end -}}
 
+{{- define "harbor.getGenCertOrLoadFromSecret" -}}
+  {{- $certData := dict "tls_crt" (.cert.Cert | b64enc) "tls_key" (.cert.Key | b64enc) -}}
+  {{- if .ca -}}
+    {{- $_ := set $certData "ca_crt" (.ca.Cert | b64enc) -}}
+  {{- end -}}
+  {{- if not .rotateCert -}}
+    {{- $prevSecret := (lookup "v1" "Secret" .Release.Namespace .secret) -}}
+    {{- if $prevSecret -}}
+      {{- $_ := set $certData "tls_crt" (index $prevSecret.data "tls.crt") -}}
+      {{- $_ := set $certData "tls_key" (index $prevSecret.data "tls.key") -}}
+      {{- if .ca -}}
+        {{- $_ := set $certData "ca_crt" (index $prevSecret.data "ca.crt") -}}
+      {{- end -}}
+    {{- end -}}
+  {{- end -}}
+  {{- if .ca -}}
+ca.crt: {{ get $certData "ca_crt" | quote }}
+  {{- end }}
+tls.crt: {{ get $certData "tls_crt" | quote }}
+tls.key: {{ get $certData "tls_key" | quote }}
+{{- end -}}
+
 {{- define "harbor.database.host" -}}
   {{- if eq .Values.database.type "internal" -}}
     {{- template "harbor.database" . }}
diff --git a/templates/core/core-secret.yaml b/templates/core/core-secret.yaml
index c38430a..bc3ac65 100644
--- a/templates/core/core-secret.yaml
+++ b/templates/core/core-secret.yaml
@@ -44,7 +44,8 @@ data:
   CSRF_KEY: {{ .Values.core.xsrfKey | default (randAlphaNum 32) | b64enc | quote }}
 {{- end }}
 {{- if not .Values.core.secretName }}
-  tls.crt: {{ .Files.Get "cert/tls.crt" | b64enc }}
-  tls.key: {{ .Files.Get "cert/tls.key" | b64enc }}
+  {{- $cert := genSelfSignedCert "harbor" nil nil 365 }}
+  {{- $certCtx := (merge (dict "ca" nil "cert" $cert "secret" $secret "rotateCert" .Values.core.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 {{- end }}
   REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }}
diff --git a/templates/ingress/secret.yaml b/templates/ingress/secret.yaml
index 0d89af9..7ff7c0d 100644
--- a/templates/ingress/secret.yaml
+++ b/templates/ingress/secret.yaml
@@ -1,15 +1,15 @@
 {{- if eq (include "harbor.autoGenCertForIngress" .) "true" }}
-{{- $ca := genCA "harbor-ca" 365 }}
-{{- $cert := genSignedCert .Values.expose.ingress.hosts.core nil (list .Values.expose.ingress.hosts.core .Values.expose.ingress.hosts.notary) 365 $ca }}
+  {{- $secret := include "harbor.ingress" . }}
+  {{- $ca := genCA "harbor-ca" 365 }}
+  {{- $cert := genSignedCert .Values.expose.ingress.hosts.core nil (list .Values.expose.ingress.hosts.core .Values.expose.ingress.hosts.notary) 365 $ca }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $cert "secret" $secret "rotateCert" .Values.expose.tls.auto.rotateCert ) .) }}
 apiVersion: v1
 kind: Secret
 metadata:
-  name: "{{ template "harbor.ingress" . }}"
+  name: "{{ $secret }}"
   labels:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  tls.crt: {{ $cert.Cert | b64enc | quote }}
-  tls.key: {{ $cert.Key | b64enc | quote }}
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 {{- end }}
\ No newline at end of file
diff --git a/templates/internal/auto-tls.yaml b/templates/internal/auto-tls.yaml
index cbeeece..7331e8e 100644
--- a/templates/internal/auto-tls.yaml
+++ b/templates/internal/auto-tls.yaml
@@ -18,9 +18,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $coreCrt.Cert | b64enc | quote }}
-  tls.key: {{ $coreCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $coreCrt "secret" (include "harbor.internalTLS.core.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 
 ---
 apiVersion: v1
@@ -31,9 +30,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $jsCrt.Cert | b64enc | quote }}
-  tls.key: {{ $jsCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $jsCrt "secret" (include "harbor.internalTLS.jobservice.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 
 ---
 apiVersion: v1
@@ -44,9 +42,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $regCrt.Cert | b64enc | quote }}
-  tls.key: {{ $regCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $regCrt "secret" (include "harbor.internalTLS.registry.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 
 ---
 apiVersion: v1
@@ -57,9 +54,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $portalCrt.Cert | b64enc | quote }}
-  tls.key: {{ $portalCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $portalCrt "secret" (include "harbor.internalTLS.portal.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 
 {{- if .Values.chartmuseum.enabled }}
 ---
@@ -73,9 +69,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $chartCrt.Cert | b64enc | quote }}
-  tls.key: {{ $chartCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $chartCrt "secret" (include "harbor.internalTLS.chartmuseum.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 {{- end }}
 
 {{- if .Values.clair.enabled }}
@@ -90,9 +85,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $clairCrt.Cert | b64enc | quote }}
-  tls.key: {{ $clairCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $clairCrt "secret" (include "harbor.internalTLS.clair.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 {{- end }}
 
 {{- if and .Values.trivy.enabled}}
@@ -107,9 +101,8 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: kubernetes.io/tls
 data:
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $trivyCrt.Cert | b64enc | quote }}
-  tls.key: {{ $trivyCrt.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $trivyCrt "secret" (include "harbor.internalTLS.trivy.secretName" .) "rotateCert" .Values.internalTLS.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
 {{- end }}
 
 {{- end }}
\ No newline at end of file
diff --git a/templates/nginx/secret.yaml b/templates/nginx/secret.yaml
index c819c55..21b0762 100644
--- a/templates/nginx/secret.yaml
+++ b/templates/nginx/secret.yaml
@@ -1,23 +1,23 @@
 {{- if eq (include "harbor.autoGenCertForNginx" .) "true" }}
-{{- $ca := genCA "harbor-ca" 365 }}
-{{- $cn := (required "The \"expose.tls.auto.commonName\" is required!" .Values.expose.tls.auto.commonName) }}
+  {{- $secret := include "harbor.nginx" . }}
+  {{- $ca := genCA "harbor-ca" 365 }}
+  {{- $cn := (required "The \"expose.tls.auto.commonName\" is required!" .Values.expose.tls.auto.commonName) }}
+  {{- $certData := dict -}}
 apiVersion: v1
 kind: Secret
 metadata:
-  name: {{ template "harbor.nginx" . }}
+  name: {{ $secret }}
   labels:
 {{ include "harbor.labels" . | indent 4 }}
 type: Opaque
 data:
   {{- if regexMatch `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` $cn }}
-  {{- $cert := genSignedCert $cn (list $cn) nil 365 $ca }}
-  tls.crt: {{ $cert.Cert | b64enc | quote }}
-  tls.key: {{ $cert.Key | b64enc | quote }}
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
+    {{- $cert := genSignedCert $cn (list $cn) nil 365 $ca }}
+    {{- $certCtx := (merge (dict "ca" $ca "cert" $cert "secret" $secret "rotateCert" .Values.expose.tls.auto.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
   {{- else }}
-  {{- $cert := genSignedCert $cn nil (list $cn) 365 $ca }}
-  tls.crt: {{ $cert.Cert | b64enc | quote }}
-  tls.key: {{ $cert.Key | b64enc | quote }}
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
+    {{- $cert := genSignedCert $cn nil (list $cn) 365 $ca }}
+    {{- $certCtx := (merge (dict "ca" $ca "cert" $cert "secret" $secret "rotateCert" .Values.expose.tls.auto.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
   {{- end }}
 {{- end }}
\ No newline at end of file
diff --git a/templates/notary/notary-secret.yaml b/templates/notary/notary-secret.yaml
index 65b4719..5082041 100644
--- a/templates/notary/notary-secret.yaml
+++ b/templates/notary/notary-secret.yaml
@@ -1,8 +1,9 @@
 {{- if and .Values.notary.enabled }}
+{{- $secret := include "harbor.notary-server" . }}
 apiVersion: v1
 kind: Secret
 metadata:
-  name: {{ template "harbor.notary-server" . }}
+  name: {{ $secret }}
   labels:
 {{ include "harbor.labels" . | indent 4 }}
     component: notary
@@ -11,9 +12,8 @@ data:
   {{- if not .Values.notary.secretName }}
   {{- $ca := genCA "harbor-notary-ca" 365 }}
   {{- $cert := genSignedCert (include "harbor.notary-signer" .) nil nil 365 $ca }}
-  ca.crt: {{ $ca.Cert | b64enc | quote }}
-  tls.crt: {{ $cert.Cert | b64enc | quote }}
-  tls.key: {{ $cert.Key | b64enc | quote }}
+  {{- $certCtx := (merge (dict "ca" $ca "cert" $cert "secret" $secret "rotateCert" .Values.notary.rotateCert ) .) }}
+{{ include "harbor.getGenCertOrLoadFromSecret" $certCtx | indent 2 }}
   {{- end }}
   server.json: {{ tpl (.Files.Get "conf/notary-server.json") . | b64enc }}
   signer.json: {{ tpl (.Files.Get "conf/notary-signer.json") . | b64enc }}
diff --git a/values.yaml b/values.yaml
index 3032f82..39afc37 100644
--- a/values.yaml
+++ b/values.yaml
@@ -21,6 +21,10 @@ expose:
       # The common name used to generate the certificate, it's necessary
       # when the type isn't "ingress"
       commonName: ""
+      # Set to "true" to generate new TLS certificates on the next helm run, or "false" to
+      # keep the existing TLS certificates.
+      # Only available if "certSource" is set to "auto". Has no effect on new installations.
+      rotateCert: true
     secret:
       # The name of secret which contains keys named:
       # "tls.crt" - the certificate
@@ -117,6 +121,10 @@ internalTLS:
   # 2) "manual" need provide cert file manually in following value
   # 3) "secret" internal certificates from secret
   certSource: "auto"
+  # Set to "true" to generate new certificates for internal TLS on the next
+  # helm run, or "false" to keep the existing TLS certificates.
+  # Only available if "certSource" is set to "auto". Has no effect on new installations.
+  rotateCert: true
   # The content of trust ca, only available when `certSource` is "manual"
   trustCa: ""
   # core related cert configuration
@@ -435,8 +443,12 @@ core:
   # The secret must contain keys named:
   # "tls.crt" - the certificate
   # "tls.key" - the private key
-  # The default key pair will be used if it isn't set
+  # They will be automatically generated if not set
   secretName: ""
+  # Set to "true" to generate a new TLS certificate and private key for token encryption/decription
+  # on the next helm run, or "false" to keep the existing TLS certificate and private key.
+  # Only available if "secretName" is unset. Has no effect on new installations.
+  rotateCert: false
   # The XSRF key. Will be generated automatically if it isn't specified
   xsrfKey: ""
 
@@ -660,6 +672,10 @@ notary:
   # contain the CA, certificate and private key.
   # They will be generated if not set.
   secretName: ""
+  # Set to "true" to generate new TLS certificates for notary communications
+  # on the next helm run, or "false" to keep the existing TLS certificates.
+  # Only available if "secretName" is unset. Has no effect on new installations.
+  rotateCert: true
 
 database:
   # if external database is used, set "type" to "external"
