Repository: camunda/camunda-platform-helm (go)

Issue:
[ENHANCEMENT] Support existing secret for Web Modeler SMTP password
**Support issue**
* https://jira.camunda.com/browse/SUPPORT-21006

**Describe the use case:**
Currently, the password for the SMTP server used by Web Modeler (`webModeler.restapi.mail.smtpPassword`) needs to be provided as plain text (e.g. in a `yaml` configuration file) when you install the chart (it will then be stored in a new Kubernetes secret). This can be a potential security vulnerability. Therefore, it should be possible to reference an existing secret with the SMTP password which can be created by the user beforehand in a secure way.

**Describe the enhancement/feature:**
Add the possibility to use an existing secret that contains the SMTP password (similar to what the chart already offers for other types of credentials, e.g. `identity.externalDatabase.existingSecret`).

<!-- Add details about the enhancement/feature and how it should look like. -->

**Desired outcome and acceptance tests:**
* An existing secret for the SMTP password can be configured.
* The feature is optional; the current mechanism can still be used.

<!-- Add details about the expected outcome if any. -->

Pull request:
feat(web-modeler): support existing secrets for database and SMTP password
### Which problem does the PR fix?
Closes #1443 
Closes https://github.com/camunda/web-modeler/issues/8851

### What's in this PR?
The PR adds the support for referencing existing Kubernetes secrets containing the database and SMTP passwords used by Web Modeler (so that these values don't have to be provided in plain text).

### Checklist

Please make sure to follow our [Contributing Guide](../blob/main/CONTRIBUTING.md).

<!-- Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields. -->

**Before opening the PR:**

- [x] In the repo's root dir, run `make go.update-golden-only`.
- [x] There is no other open [pull request](../pulls) for the same update/change.
- [x] Tests for charts are added (if needed).
- [x] In-repo [documentation](../blob/main/CONTRIBUTING.md#documentation) are updated (if needed).

**After opening the PR:**

- [x] Did you sign our CLA (Contributor License Agreement)? It will show once you open the PR.
- [x] Did all checks/tests pass in the PR?

Patch:
diff --git a/charts/camunda-platform/README.md b/charts/camunda-platform/README.md
index d72280c5..843c7263 100644
--- a/charts/camunda-platform/README.md
+++ b/charts/camunda-platform/README.md
@@ -492,7 +492,7 @@ Please see the corresponding [release guide](../../docs/release.md) to find out
 | `global.identity.auth.identity`                     | configuration to configure Identity authentication specifics on global level, which can be accessed by other sub-charts                                  |                                                       |
 | `global.identity.auth.identity.clientId`            | defines the client id, which is used by Identity in authentication flows.                                                                                | `identity`                                            |
 | `global.identity.auth.identity.audience`            | defines the audience, which is used by Identity.                                                                                                         | `camunda-identity-resource-server`                    |
-| `global.identity.auth.identity.existingSecret`      | can be used to reference an existing secret. If not set, a random secret is generated.                                                                   | `nil`                                                 |
+| `global.identity.auth.identity.existingSecret`      | can be used to reference an existing secret. This should ONLY be used for an external OIDC provider. If not set, a random secret is generated.           | `nil`                                                 |
 | `global.identity.auth.identity.redirectUrl`         | defines the redirect URL, which is used by the auth platform to access Identity.                                                                         | `http://localhost:8085`                               |
 | `global.identity.auth.identity.initialClaimName`    | defines the initial claim name, which is used by Identity to configure initial mapping rules,                                                            | `oid`                                                 |
 | `global.identity.auth.identity.initialClaimValue`   | defines the initial claim value, which is used by Identity to configure initial mapping rules.                                                           | `nil`                                                 |
@@ -1326,16 +1326,20 @@ Please see the corresponding [release guide](../../docs/release.md) to find out
 | `webModeler.restapi.image.repository`                                  | defines which image repository to use for the restapi Docker image                                                                                                           | `web-modeler-ee/modeler-restapi` |
 | `webModeler.restapi.sidecars`                                          | can be used to attach extra containers to the restapi deployment                                                                                                             | `[]`                             |
 | `webModeler.restapi.initContainers`                                    | can be used to set up extra init containers for the application Pod                                                                                                          | `[]`                             |
-| `webModeler.restapi.externalDatabase`                                  | can be used to configure a connection to an external database. This will only be applied                                                                                     |                                  |
+| `webModeler.restapi.externalDatabase`                                  | can be used to configure a connection to an external database; will only be applied if the postgresql dependency chart is disabled (with `postgresql.enabled=false`)         |                                  |
 | `webModeler.restapi.externalDatabase.url`                              | defines the JDBC url of the database instance                                                                                                                                | `""`                             |
-| `webModeler.restapi.externalDatabase.user`                             |                                                                                                                                                                              | `""`                             |
-| `webModeler.restapi.externalDatabase.password`                         | defines the database user's password                                                                                                                                         | `""`                             |
+| `webModeler.restapi.externalDatabase.user`                             | defines the database user                                                                                                                                                    | `""`                             |
+| `webModeler.restapi.externalDatabase.password`                         | can be used to provide the database user's password; ignored if `webModeler.restapi.externalDatabase.existingSecret` is set                                                  | `""`                             |
+| `webModeler.restapi.externalDatabase.existingSecret`                   | can be used to provide the name of an existing secret resource containing the database password                                                                              | `nil`                            |
+| `webModeler.restapi.externalDatabase.existingSecretPasswordKey`        | can be used to provide the name of an existing secret key containing the database password                                                                                   | `""`                             |
 | `webModeler.restapi.mail`                                              | configuration for emails sent by WebModeler                                                                                                                                  |                                  |
 | `webModeler.restapi.mail.smtpHost`                                     | defines the host name of the SMTP server to be used by WebModeler                                                                                                            | `""`                             |
 | `webModeler.restapi.mail.smtpPort`                                     | defines the port number of the SMTP server                                                                                                                                   | `587`                            |
 | `webModeler.restapi.mail.smtpUser`                                     | can be used to provide a user for the SMTP server                                                                                                                            | `""`                             |
-| `webModeler.restapi.mail.smtpPassword`                                 | can be used to provide a password for the SMTP server                                                                                                                        | `""`                             |
+| `webModeler.restapi.mail.smtpPassword`                                 | can be used to provide a password for the SMTP server; ignored if `webModeler.restapi.mail.existingSecret` is set                                                            | `""`                             |
 | `webModeler.restapi.mail.smtpTlsEnabled`                               | if true, enforces TLS encryption for SMTP connections (using STARTTLS)                                                                                                       | `true`                           |
+| `webModeler.restapi.mail.existingSecret`                               | can be used to provide the name of an existing secret resource containing the SMTP password                                                                                  | `nil`                            |
+| `webModeler.restapi.mail.existingSecretPasswordKey`                    | can be used to provide the name of an existing secret key containing the SMTP password                                                                                       | `""`                             |
 | `webModeler.restapi.mail.fromAddress`                                  | defines the email address that will be displayed as the sender of emails sent by WebModeler                                                                                  | `""`                             |
 | `webModeler.restapi.mail.fromName`                                     | defines the name that will be displayed as the sender of emails sent by WebModeler                                                                                           | `Camunda 8`                      |
 | `webModeler.restapi.podAnnotations`                                    | can be used to define extra restapi pod annotations                                                                                                                          | `{}`                             |
@@ -1558,8 +1562,9 @@ Please see the corresponding [release guide](../../docs/release.md) to find out
 | `postgresql.image.tag`                                                 | PostgreSQL image tag                                                                                                                                                                            | `14.12.0`                      |
 | `postgresql.auth`                                                      | configuration of the database authentication                                                                                                                                                    |                                |
 | `postgresql.auth.username`                                             | defines the name of the database user to be created for WebModeler                                                                                                                              | `web-modeler`                  |
-| `postgresql.auth.password`                                             | defines the database user's password; a random password will be generated if left empty                                                                                                         | `""`                           |
+| `postgresql.auth.password`                                             | can be used to provide the database user's password; a random password will be generated if left empty / ignored if `postgresql.auth.existingSecret` is set                                     | `""`                           |
 | `postgresql.auth.database`                                             | defines the name of the database to be created for WebModeler                                                                                                                                   | `web-modeler`                  |
+| `postgresql.auth.existingSecret`                                       | can be used to provide the name of an existing secret resource containing the database password                                                                                                 | `""`                           |
 | `postgresql.primary.extraVolumes[0].name`                              |                                                                                                                                                                                                 | `tmp`                          |
 | `postgresql.primary.extraVolumes[0].emptyDir`                          |                                                                                                                                                                                                 | `{}`                           |
 | `postgresql.primary.extraVolumes[1].name`                              |                                                                                                                                                                                                 | `config`                       |
diff --git a/charts/camunda-platform/templates/camunda/constraints.tpl b/charts/camunda-platform/templates/camunda/constraints.tpl
index f4957c87..e7c3f596 100644
--- a/charts/camunda-platform/templates/camunda/constraints.tpl
+++ b/charts/camunda-platform/templates/camunda/constraints.tpl
@@ -39,6 +39,18 @@ Fail with a message if the auth type is set to non-Keycloak and its requirements
   {{- end }}
 {{- end }}
 
+{{/*
+Fail with a message if global.identity.auth.identity.existingSecret is set and global.identity.auth.type is set to KEYCLOAK
+*/}}
+
+{{- if (.Values.global.identity.auth.identity.existingSecret) }}
+  {{- if eq (upper .Values.global.identity.auth.type) "KEYCLOAK" }}
+    {{- $errorMessage := "[camunda][error] global.identity.auth.identity.existingSecret does not need to be set when using Keycloak."
+    -}}
+    {{ printf "\n%s" $errorMessage | trimSuffix "\n"| fail }}
+  {{- end }}
+{{- end }}
+
 {{/*
 Fail with a message if Identity is disabled and identityKeycloak is enabled.
 */}}
diff --git a/charts/camunda-platform/templates/web-modeler/_helpers.tpl b/charts/camunda-platform/templates/web-modeler/_helpers.tpl
index 410ae410..94fc7211 100644
--- a/charts/camunda-platform/templates/web-modeler/_helpers.tpl
+++ b/charts/camunda-platform/templates/web-modeler/_helpers.tpl
@@ -200,14 +200,60 @@ Define match labels for Web Modeler websockets to be used in matchLabels selecto
 [web-modeler] Get the name of the secret that contains the database password, depending on whether the postgresql dependency chart is enabled.
 */}}
 {{- define "webModeler.restapi.databaseSecretName" -}}
-  {{- .Values.postgresql.enabled | ternary (include "webModeler.postgresql.fullname" .) (include "webModeler.restapi.fullname" .) -}}
+  {{- if .Values.postgresql.enabled }}
+    {{- .Values.postgresql.auth.existingSecret | default (include "webModeler.postgresql.fullname" .) }}
+  {{- else }}
+    {{- if or (typeIs "string" .Values.webModeler.restapi.externalDatabase.existingSecret) .Values.webModeler.restapi.externalDatabase.password }}
+      {{- include "webModeler.restapi.fullname" . }}
+    {{- else if typeIs "map[string]interface {}" .Values.webModeler.restapi.externalDatabase.existingSecret }}
+      {{- .Values.webModeler.restapi.externalDatabase.existingSecret.name | default (include "webModeler.restapi.fullname" .) }}
+    {{- end }}
+  {{- end }}
 {{- end -}}
 
 {{/*
 [web-modeler] Get the name of the database password key in the secret, depending on whether the postgresql dependency chart is enabled.
 */}}
 {{- define "webModeler.restapi.databaseSecretKey" -}}
-  {{- .Values.postgresql.enabled | ternary "password" "database-password" -}}
+  {{- if .Values.postgresql.enabled }}
+    {{- if .Values.postgresql.auth.existingSecret }}
+      {{- .Values.postgresql.auth.secretKeys.userPasswordKey }}
+    {{- else -}}
+      password
+    {{- end }}
+  {{- else }}
+    {{- $defaultSecretKey := "database-password" -}}
+    {{- if .Values.webModeler.restapi.externalDatabase.existingSecret }}
+      {{- .Values.webModeler.restapi.externalDatabase.existingSecretPasswordKey | default $defaultSecretKey }}
+    {{- else }}
+      {{- $defaultSecretKey }}
+    {{- end }}
+  {{- end }}
+{{- end -}}
+
+{{/*
+[web-modeler] Get the name of the secret resource that contains the SMTP password.
+*/}}
+{{- define "webModeler.restapi.smtpSecretName" -}}
+  {{- if or (typeIs "string" .Values.webModeler.restapi.mail.existingSecret) .Values.webModeler.restapi.mail.smtpPassword }} 
+      {{- (include "webModeler.restapi.fullname" .) }}
+  {{- else if and (typeIs "map[string]interface {}" .Values.webModeler.restapi.mail.existingSecret) .Values.webModeler.restapi.mail.existingSecret.name }}
+      {{- .Values.webModeler.restapi.mail.existingSecret.name }}
+  {{- end }}
+{{- end -}}
+
+{{/*
+[web-modeler] Get the name of the secret key that contains the SMTP password.
+*/}}
+{{- define "webModeler.restapi.smtpSecretKey" -}}
+  {{- $defaultSecretKey := "smtp-password" -}}
+  {{- if (typeIs "string" .Values.webModeler.restapi.mail.existingSecret) }}
+    {{- $defaultSecretKey }}
+  {{- else if .Values.webModeler.restapi.mail.existingSecret }}
+    {{- .Values.webModeler.restapi.mail.existingSecretPasswordKey | default $defaultSecretKey }}
+  {{- else if .Values.webModeler.restapi.mail.smtpPassword }}
+    {{- $defaultSecretKey }}
+  {{- end }}
 {{- end -}}
 
 {{/*
diff --git a/charts/camunda-platform/templates/web-modeler/deployment-restapi.yaml b/charts/camunda-platform/templates/web-modeler/deployment-restapi.yaml
index 18eacdba..330b4999 100644
--- a/charts/camunda-platform/templates/web-modeler/deployment-restapi.yaml
+++ b/charts/camunda-platform/templates/web-modeler/deployment-restapi.yaml
@@ -39,12 +39,13 @@ spec:
                 secretKeyRef:
                   name: {{ include "webModeler.restapi.databaseSecretName" . }}
                   key: {{ include "webModeler.restapi.databaseSecretKey" . }}
-            {{- if .Values.webModeler.restapi.mail.smtpPassword }}
+            {{- $smtpSecretName := (include "webModeler.restapi.smtpSecretName" .) }}
+            {{- if $smtpSecretName }}
             - name: RESTAPI_MAIL_PASSWORD
               valueFrom:
                 secretKeyRef:
-                  name: {{ include "webModeler.restapi.fullname" . }}
-                  key: smtp-password
+                  name: {{ $smtpSecretName }}
+                  key: {{ include "webModeler.restapi.smtpSecretKey" . }}
             {{- end }}
             - name: RESTAPI_PUSHER_APP_ID
               valueFrom:
diff --git a/charts/camunda-platform/templates/web-modeler/secret-restapi.yaml b/charts/camunda-platform/templates/web-modeler/secret-restapi.yaml
index 757bb396..fec822a0 100644
--- a/charts/camunda-platform/templates/web-modeler/secret-restapi.yaml
+++ b/charts/camunda-platform/templates/web-modeler/secret-restapi.yaml
@@ -1,6 +1,7 @@
 {{- if .Values.webModeler.enabled -}}
-{{- $useExternalDatabasePassword := and (not .Values.postgresql.enabled) .Values.webModeler.restapi.externalDatabase.password }}
-{{- if or $useExternalDatabasePassword .Values.webModeler.restapi.mail.smtpPassword }}
+{{- $useExternalDatabasePassword := and (not .Values.postgresql.enabled) (or (not .Values.webModeler.restapi.externalDatabase.existingSecret ) (.Values.webModeler.restapi.externalDatabase.password) (typeIs "string" .Values.webModeler.restapi.externalDatabase.existingSecret)) }}
+{{- $useSmtpPassword := (and (not (typeIs "map[string]interface {}" .Values.webModeler.restapi.mail.existingSecret)) (or (.Values.webModeler.restapi.mail.smtpPassword) (.Values.webModeler.restapi.mail.existingSecret)) ) }}
+{{- if or $useExternalDatabasePassword $useSmtpPassword }}
 apiVersion: v1
 kind: Secret
 metadata:
@@ -10,10 +11,11 @@ metadata:
 type: Opaque
 data:
   {{- if $useExternalDatabasePassword }}
-  database-password: {{ .Values.webModeler.restapi.externalDatabase.password | b64enc }}
+  database-password: {{ .Values.webModeler.restapi.externalDatabase.existingSecret | default .Values.webModeler.restapi.externalDatabase.password | b64enc }}
   {{- end }}
-  {{- if .Values.webModeler.restapi.mail.smtpPassword }}
-  smtp-password: {{ .Values.webModeler.restapi.mail.smtpPassword | b64enc }}
+  {{- if $useSmtpPassword }}
+  smtp-password: {{ .Values.webModeler.restapi.mail.existingSecret | default .Values.webModeler.restapi.mail.smtpPassword | b64enc }}
   {{- end }}
+
 {{- end }}
 {{- end }}
diff --git a/charts/camunda-platform/values.yaml b/charts/camunda-platform/values.yaml
index 58c37c18..f2bb47da 100644
--- a/charts/camunda-platform/values.yaml
+++ b/charts/camunda-platform/values.yaml
@@ -228,7 +228,7 @@ global:
         clientId: identity
         ## @param global.identity.auth.identity.audience defines the audience, which is used by Identity.
         audience: camunda-identity-resource-server
-        ## @param global.identity.auth.identity.existingSecret can be used to reference an existing secret. If not set, a random secret is generated.
+        ## @param global.identity.auth.identity.existingSecret can be used to reference an existing secret. This should ONLY be used for an external OIDC provider. If not set, a random secret is generated.
         # The existing secret should contain an `identity-secret` field, which will be used as secret for the identity-identity communication.
         existingSecret:
         ## @param global.identity.auth.identity.redirectUrl defines the redirect URL, which is used by the auth platform to access Identity.
@@ -2374,16 +2374,19 @@ webModeler:
     ## @param webModeler.restapi.initContainers can be used to set up extra init containers for the application Pod
     initContainers: []
 
-    ## @extra webModeler.restapi.externalDatabase can be used to configure a connection to an external database. This will only be applied
-    # if the postgresql dependency chart is disabled (by setting postgresql.enabled to false).
+    ## @extra webModeler.restapi.externalDatabase can be used to configure a connection to an external database; will only be applied if the postgresql dependency chart is disabled (with `postgresql.enabled=false`)
     # Note: Currently, the only supported database system is PostgreSQL.
     externalDatabase:
-     ## @param webModeler.restapi.externalDatabase.url defines the JDBC url of the database instance
+      ## @param webModeler.restapi.externalDatabase.url defines the JDBC url of the database instance
       url: ""
-     ## @param webModeler.restapi.externalDatabase.user
+      ## @param webModeler.restapi.externalDatabase.user defines the database user
       user: ""
-      ## @param webModeler.restapi.externalDatabase.password defines the database user's password
+      ## @param webModeler.restapi.externalDatabase.password can be used to provide the database user's password; ignored if `webModeler.restapi.externalDatabase.existingSecret` is set
       password: ""
+      ## @param webModeler.restapi.externalDatabase.existingSecret can be used to provide the name of an existing secret resource containing the database password
+      existingSecret:
+      ## @param webModeler.restapi.externalDatabase.existingSecretPasswordKey can be used to provide the name of an existing secret key containing the database password
+      existingSecretPasswordKey: ""
 
     ## @extra webModeler.restapi.mail configuration for emails sent by WebModeler
     mail:
@@ -2393,10 +2396,14 @@ webModeler:
       smtpPort: 587
       ## @param webModeler.restapi.mail.smtpUser can be used to provide a user for the SMTP server
       smtpUser: ""
-      ## @param webModeler.restapi.mail.smtpPassword can be used to provide a password for the SMTP server
+      ## @param webModeler.restapi.mail.smtpPassword can be used to provide a password for the SMTP server; ignored if `webModeler.restapi.mail.existingSecret` is set
       smtpPassword: ""
       ## @param webModeler.restapi.mail.smtpTlsEnabled if true, enforces TLS encryption for SMTP connections (using STARTTLS)
       smtpTlsEnabled: true
+      ## @param webModeler.restapi.mail.existingSecret can be used to provide the name of an existing secret resource containing the SMTP password
+      existingSecret:
+      ## @param webModeler.restapi.mail.existingSecretPasswordKey can be used to provide the name of an existing secret key containing the SMTP password
+      existingSecretPasswordKey: ""
       ## @param webModeler.restapi.mail.fromAddress defines the email address that will be displayed as the sender of emails sent by WebModeler
       # NOTE: This value is mandatory.
       fromAddress: ""
@@ -2903,10 +2910,12 @@ postgresql:
   auth:
     ## @param postgresql.auth.username defines the name of the database user to be created for WebModeler
     username: web-modeler
-    ## @param postgresql.auth.password defines the database user's password; a random password will be generated if left empty
+    ## @param postgresql.auth.password can be used to provide the database user's password; a random password will be generated if left empty / ignored if `postgresql.auth.existingSecret` is set
     password: ""
     ## @param postgresql.auth.database defines the name of the database to be created for WebModeler
     database: web-modeler
+    ## @param postgresql.auth.existingSecret can be used to provide the name of an existing secret resource containing the database password
+    existingSecret: ""
 
   primary:
     ## @param postgresql.primary.extraVolumes[0].name


Test patch:
diff --git a/charts/camunda-platform/test/unit/web-modeler/deployment_restapi_test.go b/charts/camunda-platform/test/unit/web-modeler/deployment_restapi_test.go
index 9a46bbb8..5a494a11 100644
--- a/charts/camunda-platform/test/unit/web-modeler/deployment_restapi_test.go
+++ b/charts/camunda-platform/test/unit/web-modeler/deployment_restapi_test.go
@@ -51,7 +51,7 @@ func TestRestapiDeploymentTemplate(t *testing.T) {
 	})
 }
 
-func (s *restapiDeploymentTemplateTest) TestContainerShouldSetExternalDatabaseConfiguration() {
+func (s *restapiDeploymentTemplateTest) TestContainerExternalDatabasePasswordSecretRefForGivenPassword() {
 	// given
 	options := &helm.Options{
 		SetValues: map[string]string{
@@ -84,7 +84,170 @@ func (s *restapiDeploymentTemplateTest) TestContainerShouldSetExternalDatabaseCo
 		})
 }
 
-func (s *restapiDeploymentTemplateTest) TestContainerShouldSetSmtpCredentials() {
+func (s *restapiDeploymentTemplateTest) TestContainerExternalDatabasePasswordSecretRefForExistingSecretAndDefaultKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                      "true",
+			"webModeler.restapi.mail.fromAddress":                     "example@example.com",
+			"postgresql.enabled":                                      "false",
+			"webModeler.restapi.externalDatabase.url":                 "jdbc:postgresql://postgres.example.com:65432/modeler-database",
+			"webModeler.restapi.externalDatabase.user":                "modeler-user",
+			"webModeler.restapi.externalDatabase.existingSecret.name": "my-secret",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "SPRING_DATASOURCE_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "database-password",
+				},
+			},
+		})
+}
+
+func (s *restapiDeploymentTemplateTest) TestContainerExternalDatabasePasswordSecretRefForExistingSecretAndCustomKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                            "true",
+			"webModeler.restapi.mail.fromAddress":                           "example@example.com",
+			"postgresql.enabled":                                            "false",
+			"webModeler.restapi.externalDatabase.url":                       "jdbc:postgresql://postgres.example.com:65432/modeler-database",
+			"webModeler.restapi.externalDatabase.user":                      "modeler-user",
+			"webModeler.restapi.externalDatabase.existingSecret.name":       "my-secret",
+			"webModeler.restapi.externalDatabase.existingSecretPasswordKey": "my-database-password-key",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "SPRING_DATASOURCE_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "my-database-password-key",
+				},
+			},
+		})
+}
+func (s *restapiDeploymentTemplateTest) TestContainerExternalDatabasePasswordExplicitlyDefinedStillReferencesInternalSecret() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                 "true",
+			"webModeler.restapi.mail.fromAddress":                "example@example.com",
+			"postgresql.enabled":                                 "false",
+			"webModeler.restapi.externalDatabase.url":            "jdbc:postgresql://postgres.example.com:65432/modeler-database",
+			"webModeler.restapi.externalDatabase.user":           "modeler-user",
+			"webModeler.restapi.externalDatabase.existingSecret": "password1234",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "SPRING_DATASOURCE_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "camunda-platform-test-web-modeler-restapi"},
+					Key:                  "database-password",
+				},
+			},
+		})
+}
+
+func (s *restapiDeploymentTemplateTest) TestContainerInternalDatabasePasswordSecretRefForExistingSecretAndDefaultKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                  "true",
+			"webModeler.restapi.mail.fromAddress": "example@example.com",
+			"postgresql.enabled":                  "true",
+			"postgresql.auth.existingSecret":      "my-secret",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "SPRING_DATASOURCE_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "password",
+				},
+			},
+		})
+}
+
+func (s *restapiDeploymentTemplateTest) TestContainerInternalDatabasePasswordSecretRefForExistingSecretAndCustomKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                         "true",
+			"webModeler.restapi.mail.fromAddress":        "example@example.com",
+			"postgresql.enabled":                         "true",
+			"postgresql.auth.existingSecret":             "my-secret",
+			"postgresql.auth.secretKeys.userPasswordKey": "my-database-password-key",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "SPRING_DATASOURCE_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "my-database-password-key",
+				},
+			},
+		})
+}
+
+func (s *restapiDeploymentTemplateTest) TestContainerSmtpPasswordSecretRefForGivenPassword() {
 	// given
 	options := &helm.Options{
 		SetValues: map[string]string{
@@ -115,6 +278,69 @@ func (s *restapiDeploymentTemplateTest) TestContainerShouldSetSmtpCredentials()
 		})
 }
 
+func (s *restapiDeploymentTemplateTest) TestContainerSmtpPasswordSecretRefForExistingSecretAndDefaultKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                          "true",
+			"webModeler.restapi.mail.fromAddress":         "example@example.com",
+			"webModeler.restapi.mail.smtpUser":            "modeler-user",
+			"webModeler.restapi.mail.existingSecret.name": "my-secret",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "RESTAPI_MAIL_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "smtp-password",
+				},
+			},
+		})
+}
+
+func (s *restapiDeploymentTemplateTest) TestContainerSmtpPasswordSecretRefForExistingSecretAndCustomKey() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                "true",
+			"webModeler.restapi.mail.fromAddress":               "example@example.com",
+			"webModeler.restapi.mail.smtpUser":                  "modeler-user",
+			"webModeler.restapi.mail.existingSecret.name":       "my-secret",
+			"webModeler.restapi.mail.existingSecretPasswordKey": "my-smtp-password-key",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var deployment appsv1.Deployment
+	helm.UnmarshalK8SYaml(s.T(), output, &deployment)
+
+	// then
+	env := deployment.Spec.Template.Spec.Containers[0].Env
+	s.Require().Contains(env,
+		corev1.EnvVar{
+			Name: "RESTAPI_MAIL_PASSWORD",
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: &corev1.SecretKeySelector{
+					LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
+					Key:                  "my-smtp-password-key",
+				},
+			},
+		})
+}
+
 func (s *restapiDeploymentTemplateTest) TestContainerStartupProbe() {
 	// given
 	options := &helm.Options{
diff --git a/charts/camunda-platform/test/unit/web-modeler/secret_restapi_test.go b/charts/camunda-platform/test/unit/web-modeler/secret_restapi_test.go
index 842464d5..b5894d06 100644
--- a/charts/camunda-platform/test/unit/web-modeler/secret_restapi_test.go
+++ b/charts/camunda-platform/test/unit/web-modeler/secret_restapi_test.go
@@ -91,3 +91,128 @@ func (s *secretTest) TestContainerCreateSmtpPasswordSecret() {
 	s.Require().NotNil(secret.Data)
 	s.Require().Equal("secret123", string(secret.Data["smtp-password"]))
 }
+func (s *secretTest) TestDatabaseSecretAddedViaName() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                      "true",
+			"webModeler.restapi.mail.fromAddress":                     "example@example.com",
+			"webModeler.restapi.mail.smtpPassword":                    "secret123",
+			"webModeler.restapi.externalDatabase.existingSecret.name": "example-secret",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	s.Require().NotNil(secret.Data)
+	_, databaseSecretKeyExists := secret.Data["database-password"]
+	s.Require().False(databaseSecretKeyExists)
+}
+func (s *secretTest) TestDatabaseSecretAddedViaDirectPassword() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                 "true",
+			"webModeler.restapi.mail.fromAddress":                "example@example.com",
+			"webModeler.restapi.mail.smtpPassword":               "secret123",
+			"webModeler.restapi.externalDatabase.existingSecret": "password1234",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	s.Require().Equal("password1234", string(secret.Data["database-password"]))
+}
+
+func (s *secretTest) TestDatabaseSecretAddedViaDirectPasswordUsingOldSyntax() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                           "true",
+			"webModeler.restapi.mail.fromAddress":          "example@example.com",
+			"webModeler.restapi.mail.smtpPassword":         "secret123",
+			"webModeler.restapi.externalDatabase.password": "password1234",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	s.Require().Equal("password1234", string(secret.Data["database-password"]))
+}
+
+func (s *secretTest) TestSmtpSecretAddedViaDirectPassword() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                 "true",
+			"webModeler.restapi.mail.fromAddress":                "example@example.com",
+			"webModeler.restapi.mail.existingSecret":             "password1234",
+			"webModeler.restapi.externalDatabase.existingSecret": "otherPassword1234",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	s.Require().Equal("password1234", string(secret.Data["smtp-password"]))
+}
+func (s *secretTest) TestSmtpSecretNotAddedWhenSecretNameSpecified() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                 "true",
+			"webModeler.restapi.mail.fromAddress":                "example@example.com",
+			"webModeler.restapi.externalDatabase.existingSecret": "password1234",
+			"webModeler.restapi.mail.existingSecret.name":        "mail-secret",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	_, exists := secret.Data["smtp-password"]
+	s.Require().False(exists)
+}
+func (s *secretTest) TestSmtpSecretViaPasswordKeyWhenSecretNameSpecified() {
+	// given
+	options := &helm.Options{
+		SetValues: map[string]string{
+			"webModeler.enabled":                                 "true",
+			"webModeler.restapi.mail.fromAddress":                "example@example.com",
+			"webModeler.restapi.externalDatabase.existingSecret": "password1234",
+			"webModeler.restapi.mail.smtpPassword":               "password12345",
+		},
+		KubectlOptions: k8s.NewKubectlOptions("", "", s.namespace),
+	}
+
+	// when
+	output := helm.RenderTemplate(s.T(), options, s.chartPath, s.release, s.templates)
+	var secret coreV1.Secret
+	helm.UnmarshalK8SYaml(s.T(), output, &secret)
+
+	// then
+	s.Require().Equal("password12345", string(secret.Data["smtp-password"]))
+}