commit d66a4be915b8dbea9358229d23ce2e7d8d495ef3
Author: Stefan Nica <snica@suse.com>
Date:   Thu Oct 15 18:43:07 2020 +0200

    Security hardening: generate password for internal database
    
    To harden the security for the internal database service, a strong
    postgresql password is generated if one is not explicitly configured
    in values.yaml.
    
    This requires several helm chart changes to ensure that the
    password is not re-generated with every `helm upgrade`:
    * the database k8s secret is used to store not only the postgreql
    password, but also all other database connection URL variables that
    incorporate it. To keep the implementation simple, and at the same
    time to ensure that sensitive data is stored in a secret rather
    than in config-maps or as explicit environment variable values,
    the database secret is also used to store the password and URL
    values used for the external database case
    * the template logic gives preference to the database password value
    in values.yaml, if one is present. To avoid re-generating the
    password during `helm upgrade`, the previous internal password value is
    extracted from the existing secret, if present.
    * an annotation is added to all deployments and stateful-sets that
    reference the redis password or one of the connection URLs, to ensure
    they are redeployed if the password changes. The fact that the
    password is not re-generated with each `helm upgrade` reduces the
    set of impacted workload objects that need to be updated during
    an upgrade.
    * the database password and URL values used for the various consumer
    components (core, clair, notary) are removed from associated secrets
    and explicit environment variables and replaced with `env.valueFrom`
    statements pointing to the database secret data. This is needed,
    because the database password (and connection URL values that include
    it) can only be referenced *once* if auto-generated, because helm
    lacks a notion of global static variables that can be set from within
    the templates. In some cases (clair and notary) this also requires the
    database URL to be injected into the configuration files using an init
    container
    * as already mentioned, using a named template to generate the random
    password for database does not work well when the password is referenced
    from multiple places, because the template generates a different password
    each time it is invoked. For this reason, all named templates that use
    the database password value in one way or another (e.g.
    `harbor.database.notaryServer`, `harbor.database.notarySigner`)
    have been modified to expect the password to be supplied as a
    `.databasePassword` parameter instead, and these templates are called
    from a single place: the database secret template.
    
    Signed-off-by: Stefan Nica <snica@suse.com>

diff --git a/README.md b/README.md
index c97d81f..f5fcd68 100644
--- a/README.md
+++ b/README.md
@@ -295,7 +295,7 @@ The following table lists the configurable parameters of the Harbor chart and th
 | `database.type` | If external database is used, set it to `external` | `internal` |
 | `database.internal.image.repository` | Repository for database image | `goharbor/harbor-db` |
 | `database.internal.image.tag` | Tag for database image | `dev` |
-| `database.internal.password`                                                | The password for database                                                                                                                                                                                                                                                                                                                       | `changeit`                      |
+| `database.internal.password`                                                | The password for database. If a password value is not specified, Helm will generate one.                                                                                                                                                                                                                                                        |                                 |
 | `database.internal.resources`                                               | The [resources] to allocate for container                                                                                                                                                                                                                                                                                                       | undefined                       |
 | `database.internal.nodeSelector`                                            | Node labels for pod assignment                                                                                                                                                                                                                                                                                                                  | `{}`                            |
 | `database.internal.tolerations`                                             | Tolerations for pod assignment                                                                                                                                                                                                                                                                                                                  | `[]`                            |
diff --git a/conf/clair.yaml b/conf/clair.yaml
index 1cdac90..72f0f08 100644
--- a/conf/clair.yaml
+++ b/conf/clair.yaml
@@ -2,7 +2,8 @@ clair:
   database:
     type: pgsql
     options:
-      source: "{{ template "harbor.database.clair" . }}"
+      # Postgresql connection URL (filled in at runtime by entrypoint)
+      source: ""
       # Number of elements kept in the cache
       # Values unlikely to change (e.g. namespaces) are cached in order to save prevent needless roundtrips to the database.
       cachesize: 16384
diff --git a/conf/notary-server.json b/conf/notary-server.json
index b3c2624..a64acd5 100644
--- a/conf/notary-server.json
+++ b/conf/notary-server.json
@@ -14,7 +14,7 @@
   },
   "storage": {
     "backend": "postgres",
-    "db_url": "{{ template "harbor.database.notaryServer" . }}"
+    "db_url": "$DB_URL"
   },
   "auth": {
     "type": "token",
diff --git a/conf/notary-signer.json b/conf/notary-signer.json
index 75a4d68..2433867 100644
--- a/conf/notary-signer.json
+++ b/conf/notary-signer.json
@@ -9,7 +9,7 @@
   },
   "storage": {
     "backend": "postgres",
-    "db_url": "{{ template "harbor.database.notarySigner" . }}",
+    "db_url": "$DB_URL",
     "default_alias": "defaultalias"
   }
 }
\ No newline at end of file
diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl
index acecd72..0d4e8f0 100644
--- a/templates/_helpers.tpl
+++ b/templates/_helpers.tpl
@@ -78,20 +78,25 @@ app: "{{ template "harbor.name" . }}"
   {{- end -}}
 {{- end -}}
 
-{{- define "harbor.database.rawPassword" -}}
+{{- define "harbor.database.internalPassword" -}}
+  {{- $prevSecret := (lookup "v1" "Secret" .Release.Namespace (include "harbor.database" $)) }}
+  {{- if $prevSecret -}}
+    {{- $prevSecret.data.POSTGRES_PASSWORD | b64dec }}
+  {{- else -}}
+    {{- randAlphaNum 16 }}
+  {{- end -}}
+{{- end -}}
+
+{{- define "harbor.database.password" -}}
   {{- if eq .Values.database.type "internal" -}}
-    {{- .Values.database.internal.password -}}
+    {{- .Values.database.internal.password | default (include "harbor.database.internalPassword" .) -}}
   {{- else -}}
     {{- .Values.database.external.password -}}
   {{- end -}}
 {{- end -}}
 
 {{- define "harbor.database.escapedRawPassword" -}}
-  {{- include "harbor.database.rawPassword" . | urlquery | replace "+" "%20" -}}
-{{- end -}}
-
-{{- define "harbor.database.encryptedPassword" -}}
-  {{- include "harbor.database.rawPassword" . | b64enc | quote -}}
+  {{- .databasePassword | urlquery | replace "+" "%20" -}}
 {{- end -}}
 
 {{- define "harbor.database.coreDatabase" -}}
diff --git a/templates/clair/clair-dpl.yaml b/templates/clair/clair-dpl.yaml
index b95b067..44c8ea7 100644
--- a/templates/clair/clair-dpl.yaml
+++ b/templates/clair/clair-dpl.yaml
@@ -20,6 +20,7 @@ spec:
       annotations:
         checksum/secret: {{ include (print $.Template.BasePath "/clair/clair-secret.yaml") . | sha256sum }}
         checksum/secret-redis: {{ include (print $.Template.BasePath "/redis/secret.yaml") . | sha256sum }}
+        checksum/secret-database: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }}
 {{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }}
         checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }}
 {{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }}
@@ -38,6 +39,29 @@ spec:
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
       {{- end }}
+      initContainers:
+      - name: "inject-config-db-url"
+        securityContext:
+          runAsUser: 0
+        image: {{ .Values.clair.clair.image.repository }}:{{ .Values.clair.clair.image.tag }}
+        imagePullPolicy: {{ .Values.imagePullPolicy }}
+        env:
+        - name: DB_URL
+          valueFrom:
+              secretKeyRef:
+                name: {{ template "harbor.database" . }}
+                key: POSTGRES_URL_CLAIR
+        command: [
+            "sh",
+            "-ce",
+            'cp /tmp/config-in/config.yaml /tmp/config/config.yaml &&
+             sed -i \"s,\$DB_URL,$DB_URL,g\" /tmp/config/config.yaml' ]
+        volumeMounts:
+        - name: config-in
+          mountPath: /tmp/config-in/config.yaml
+          subPath: config.yaml
+        - name: config
+          mountPath: /tmp/config
       containers:
       - name: clair
         image: {{ .Values.clair.clair.image.repository }}:{{ .Values.clair.clair.image.tag }}
@@ -110,8 +134,8 @@ spec:
         - name: SCANNER_CLAIR_DATABASE_URL
           valueFrom:
             secretKeyRef:
-              name: {{ template "harbor.clair" . }}
-              key: database
+              name: {{ template "harbor.database" . }}
+              key: POSTGRES_URL_CLAIR
         {{- if .Values.internalTLS.enabled }}
         - name: INTERNAL_TLS_ENABLED
           value: "true"
@@ -141,9 +165,11 @@ spec:
         {{- end }}
         {{- end }}
       volumes:
-      - name: config
+      - name: config-in
         secret:
           secretName: "{{ template "harbor.clair" . }}"
+      - name: config
+        emptyDir: {}
       {{- if .Values.internalTLS.enabled }}
       - name: clair-internal-certs
         secret:
diff --git a/templates/clair/clair-secret.yaml b/templates/clair/clair-secret.yaml
index 6b0bda5..4bcb354 100644
--- a/templates/clair/clair-secret.yaml
+++ b/templates/clair/clair-secret.yaml
@@ -8,5 +8,4 @@ metadata:
 type: Opaque
 data:
   config.yaml: {{ tpl (.Files.Get "conf/clair.yaml") . | b64enc }}
-  database: {{ include "harbor.database.clair" . | b64enc }}
-{{- end }} 
\ No newline at end of file
+{{- end }} 
diff --git a/templates/core/core-dpl.yaml b/templates/core/core-dpl.yaml
index 13981f9..f1a9f7e 100644
--- a/templates/core/core-dpl.yaml
+++ b/templates/core/core-dpl.yaml
@@ -21,6 +21,7 @@ spec:
         checksum/secret: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }}
         checksum/secret-jobservice: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }}
         checksum/secret-redis: {{ include (print $.Template.BasePath "/redis/secret.yaml") . | sha256sum }}
+        checksum/secret-database: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }}
 {{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }}
         checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }}
 {{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }}
@@ -91,6 +92,11 @@ spec:
               secretKeyRef:
                 name: "{{ template "harbor.redis" . }}"
                 key: REDIS_URL_REGISTRY
+          - name: POSTGRESQL_PASSWORD
+            valueFrom:
+              secretKeyRef:
+                name: "{{ template "harbor.database" . }}"
+                key: POSTGRES_PASSWORD
          {{- if .Values.internalTLS.enabled }}
           - name: INTERNAL_TLS_ENABLED
             value: "true"
diff --git a/templates/core/core-secret.yaml b/templates/core/core-secret.yaml
index ba4b767..42a6112 100644
--- a/templates/core/core-secret.yaml
+++ b/templates/core/core-secret.yaml
@@ -13,6 +13,5 @@ data:
   tls.key: {{ .Files.Get "cert/tls.key" | b64enc }}
 {{- end }}
   HARBOR_ADMIN_PASSWORD: {{ .Values.harborAdminPassword | b64enc | quote }}
-  POSTGRESQL_PASSWORD: {{ template "harbor.database.encryptedPassword" . }}
   REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }}
   CSRF_KEY: {{ .Values.core.xsrfKey | default (randAlphaNum 32) | b64enc | quote }}
diff --git a/templates/database/database-secret.yaml b/templates/database/database-secret.yaml
index 864aff4..d8f78b1 100644
--- a/templates/database/database-secret.yaml
+++ b/templates/database/database-secret.yaml
@@ -1,4 +1,3 @@
-{{- if eq .Values.database.type "internal" -}}
 apiVersion: v1
 kind: Secret
 metadata:
@@ -7,5 +6,9 @@ metadata:
 {{ include "harbor.labels" . | indent 4 }}
 type: Opaque
 data:
-  POSTGRES_PASSWORD: {{ template "harbor.database.encryptedPassword" . }}
-{{- end -}}
+{{- $password := (include "harbor.database.password" .) }}
+{{- $passwordCtx := (merge (dict "databasePassword" $password) .) }}
+  POSTGRES_PASSWORD: {{ $password | b64enc | quote }}
+  POSTGRES_URL_NOTARY_SERVER: {{ include "harbor.database.notaryServer" $passwordCtx | b64enc }}
+  POSTGRES_URL_NOTARY_SIGNER: {{ include "harbor.database.notarySigner" $passwordCtx | b64enc }}
+  POSTGRES_URL_CLAIR: {{ include "harbor.database.clair" $passwordCtx | b64enc }}
diff --git a/templates/database/database-ss.yaml b/templates/database/database-ss.yaml
index fe29b31..38bfb6f 100644
--- a/templates/database/database-ss.yaml
+++ b/templates/database/database-ss.yaml
@@ -72,9 +72,12 @@ spec:
         resources:
 {{ toYaml .Values.database.internal.resources | indent 10 }}
 {{- end }}
-        envFrom:
-          - secretRef:
-              name: "{{ template "harbor.database" . }}"
+        env:
+          - name: POSTGRES_PASSWORD
+            valueFrom:
+              secretKeyRef:
+                name: "{{ template "harbor.database" . }}"
+                key: POSTGRES_PASSWORD
         volumeMounts:
         - name: database-data
           mountPath: /var/lib/postgresql/data
diff --git a/templates/notary/notary-server.yaml b/templates/notary/notary-server.yaml
index 6cb8023..e8da66c 100644
--- a/templates/notary/notary-server.yaml
+++ b/templates/notary/notary-server.yaml
@@ -20,6 +20,7 @@ spec:
       annotations:
         checksum/secret: {{ include (print $.Template.BasePath "/notary/notary-secret.yaml") . | sha256sum }}
         checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }}
+        checksum/secret-database: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }}
 {{- if .Values.notary.podAnnotations }}
 {{ toYaml .Values.notary.podAnnotations | indent 8 }}
 {{- end }}
@@ -33,6 +34,29 @@ spec:
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
       {{- end }}
+      initContainers:
+      - name: "inject-config-db-url"
+        securityContext:
+          runAsUser: 0
+        image: {{ .Values.notary.server.image.repository }}:{{ .Values.notary.server.image.tag }}
+        imagePullPolicy: {{ .Values.imagePullPolicy }}
+        env:
+        - name: DB_URL
+          valueFrom:
+            secretKeyRef:
+              name: "{{ template "harbor.database" . }}"
+              key: POSTGRES_URL_NOTARY_SERVER
+        command: [
+            "sh",
+            "-ce",
+            'cp /tmp/config-in/server.json /tmp/config/server.json &&
+             sed -i "s,\$DB_URL,$DB_URL,g" /tmp/config/server.json' ]
+        volumeMounts:
+        - name: config-in
+          mountPath: /tmp/config-in/server.json
+          subPath: server.json
+        - name: config
+          mountPath: /tmp/config
       containers:
       - name: notary-server
         image: {{ .Values.notary.server.image.repository }}:{{ .Values.notary.server.image.tag }}
@@ -45,7 +69,10 @@ spec:
         - name: MIGRATIONS_PATH
           value: migrations/server/postgresql
         - name: DB_URL
-          value: {{ template "harbor.database.notaryServer" . }}
+          valueFrom:
+            secretKeyRef:
+              name: "{{ template "harbor.database" . }}"
+              key: POSTGRES_URL_NOTARY_SERVER
         volumeMounts:
         - name: config
           mountPath: /etc/notary/server-config.postgres.json
@@ -57,9 +84,11 @@ spec:
           mountPath: /etc/ssl/notary/ca.crt
           subPath: ca.crt
       volumes:
-      - name: config
+      - name: config-in
         secret:
           secretName: "{{ template "harbor.notary-server" . }}"
+      - name: config
+        emptyDir: {}
       - name: token-service-certificate
         secret:
           {{- if .Values.core.secretName }}
diff --git a/templates/notary/notary-signer.yaml b/templates/notary/notary-signer.yaml
index f4ee98e..a3e73c5 100644
--- a/templates/notary/notary-signer.yaml
+++ b/templates/notary/notary-signer.yaml
@@ -19,6 +19,7 @@ spec:
         component: notary-signer
       annotations:
         checksum/secret: {{ include (print $.Template.BasePath "/notary/notary-secret.yaml") . | sha256sum }}
+        checksum/secret-database: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }}
     spec:
       securityContext:
         fsGroup: 10000
@@ -29,6 +30,29 @@ spec:
       imagePullSecrets:
         {{- toYaml . | nindent 8 }}
       {{- end }}
+      initContainers:
+      - name: "inject-config-db-url"
+        securityContext:
+          runAsUser: 0
+        image: {{ .Values.notary.signer.image.repository }}:{{ .Values.notary.signer.image.tag }}
+        imagePullPolicy: {{ .Values.imagePullPolicy }}
+        env:
+        - name: DB_URL
+          valueFrom:
+            secretKeyRef:
+              name: "{{ template "harbor.database" . }}"
+              key: POSTGRES_URL_NOTARY_SIGNER
+        command: [
+            "sh",
+            "-ce",
+            'cp /tmp/config-in/signer.json /tmp/config/signer.json &&
+             sed -i "s,\$DB_URL,$DB_URL,g" /tmp/config/signer.json' ]
+        volumeMounts:
+        - name: config-in
+          mountPath: /tmp/config-in/signer.json
+          subPath: signer.json
+        - name: config
+          mountPath: /tmp/config
       containers:
       - name: notary-signer
         image: {{ .Values.notary.signer.image.repository }}:{{ .Values.notary.signer.image.tag }}
@@ -41,7 +65,10 @@ spec:
         - name: MIGRATIONS_PATH
           value: migrations/signer/postgresql
         - name: DB_URL
-          value: {{ template "harbor.database.notarySigner" . }}
+          valueFrom:
+            secretKeyRef:
+              name: "{{ template "harbor.database" . }}"
+              key: POSTGRES_URL_NOTARY_SIGNER
         - name: NOTARY_SIGNER_DEFAULTALIAS
           value: defaultalias
         volumeMounts:
@@ -55,9 +82,11 @@ spec:
           mountPath: /etc/ssl/notary/tls.key
           subPath: tls.key
       volumes:
-      - name: config
+      - name: config-in
         secret:
           secretName: "{{ template "harbor.notary-server" . }}"
+      - name: config
+        emptyDir: {}
       - name: signer-certificate
         secret:
           {{- if .Values.notary.secretName }}
diff --git a/values.yaml b/values.yaml
index 6e577ac..ed0d31f 100644
--- a/values.yaml
+++ b/values.yaml
@@ -671,6 +671,7 @@ database:
       repository: goharbor/harbor-db
       tag: v2.1.1
     # The initial superuser password for internal database
+    # If a password is not specified, Helm will generate one.
     password: "changeit"
     # resources:
     #  requests:
