diff --git a/charts/spire/charts/spire-server/README.md b/charts/spire/charts/spire-server/README.md index 45f0241..92461b6 100644 --- a/charts/spire/charts/spire-server/README.md +++ b/charts/spire/charts/spire-server/README.md @@ -141,13 +141,13 @@ In order to run Tornjak with simple HTTP Connection only, make sure you don't cr | `dataStore.sql.host` | Only used when type != "sqlite3" | `""` | | `dataStore.sql.port` | If 0 (default), it will auto set to 5432 for postgres and 3306 for mysql. Only used by those databases. | `0` | | `dataStore.sql.username` | Only used when type != "sqlite3" | `spire` | -| `dataStore.sql.password` | Only used when type != "sqlite3" | `""` | +| `dataStore.sql.password` | Only used when type != "sqlite3". For postgres/aws_postgres, leave empty to omit the password from the connection string (e.g. TLS client-certificate or IAM authentication). | `""` | | `dataStore.sql.file` | Data source file. Only used when type == "sqlite3" and inMemory is false | `/run/spire/data/datastore.sqlite3` | | `dataStore.sql.inMemory` | Hold the sqlite3 datastore in memory instead of in a file, in which case `file` is unused. The datastore starts empty on every restart, so this only suits a single replica whose registration entries are recreated at startup, for example by the controller manager writing static entries. Required to run as a deployment on sqlite3, since a deployment has no durable per-pod storage. | `false` | | `dataStore.sql.options` | takes an array of objects of form {: } to use when building the database connection string | `[]` | -| `dataStore.sql.rootCAPath` | Path to Root CA bundle (MySQL only) | `""` | -| `dataStore.sql.clientCertPath` | Path to client certificate (MySQL only) | `""` | -| `dataStore.sql.clientKeyPath` | Path to private key for client certificate (MySQL only) | `""` | +| `dataStore.sql.rootCAPath` | Path to Root CA bundle. Supports MySQL and postgres. | `""` | +| `dataStore.sql.clientCertPath` | Path to client certificate. Supports MySQL and postgres. | `""` | +| `dataStore.sql.clientKeyPath` | Path to private key for client certificate. Supports MySQL and postgres. | `""` | | `dataStore.sql.externalSecret.enabled` | Enable external secret for datastore creds | `false` | | `dataStore.sql.externalSecret.name` | The name of the secret object | `""` | | `dataStore.sql.externalSecret.key` | The key of the secret object whose value is the dataStore.sql password | `""` | diff --git a/charts/spire/charts/spire-server/templates/_helpers.tpl b/charts/spire/charts/spire-server/templates/_helpers.tpl index 80c53f4..5dc537d 100644 --- a/charts/spire/charts/spire-server/templates/_helpers.tpl +++ b/charts/spire/charts/spire-server/templates/_helpers.tpl @@ -301,6 +301,20 @@ current-context: cluster {{- end }} {{- end }} +{{- define "spire-server.datastore-is-postgres" -}} +{{- or (eq .Values.dataStore.sql.databaseType "postgres") (eq .Values.dataStore.sql.databaseType "aws_postgres") -}} +{{- end }} + +{{- define "spire-server.datastore-postgres-passwordless" -}} +{{- $isPostgres := eq (include "spire-server.datastore-is-postgres" .) "true" -}} +{{- and $isPostgres (eq .Values.dataStore.sql.password "") (not .Values.dataStore.sql.externalSecret.enabled) -}} +{{- end }} + +{{- define "spire-server.datastore-postgres-ro-passwordless" -}} +{{- $isPostgres := eq (include "spire-server.datastore-is-postgres" .) "true" -}} +{{- and $isPostgres (eq .Values.dataStore.sql.readOnly.password "") (not .Values.dataStore.sql.readOnly.externalSecret.enabled) -}} +{{- end }} + {{- define "spire-server.datastore-config" }} {{- $config := dict }} {{- $pw := "" }} @@ -349,18 +363,32 @@ current-context: cluster {{- else if or (eq .Values.dataStore.sql.databaseType "postgres") (eq .Values.dataStore.sql.databaseType "aws_postgres") }} {{- if eq .Values.dataStore.sql.databaseType "postgres" }} {{- $_ := set $config "database_type" "postgres" }} - {{- $pw = " password=${DBPW}" }} - {{- $ropw = " password=${RODBPW}" }} {{- else }} {{- $_ := set $config "database_type" (list (dict "aws_postgres" (dict "region" .Values.dataStore.sql.region))) }} {{- end }} + {{- if ne (include "spire-server.datastore-postgres-passwordless" .) "true" }} + {{- $pw = " password=${DBPW}" }} + {{- end }} + {{- if ne (include "spire-server.datastore-postgres-ro-passwordless" .) "true" }} + {{- $ropw = " password=${RODBPW}" }} + {{- end }} + {{- $sslPaths := "" }} + {{- if ne .Values.dataStore.sql.rootCAPath "" }} + {{- $sslPaths = printf "%s sslrootcert=%s" $sslPaths .Values.dataStore.sql.rootCAPath }} + {{- end }} + {{- if ne .Values.dataStore.sql.clientCertPath "" }} + {{- $sslPaths = printf "%s sslcert=%s" $sslPaths .Values.dataStore.sql.clientCertPath }} + {{- end }} + {{- if ne .Values.dataStore.sql.clientKeyPath "" }} + {{- $sslPaths = printf "%s sslkey=%s" $sslPaths .Values.dataStore.sql.clientKeyPath }} + {{- end }} {{- $port := int .Values.dataStore.sql.port | default 5432 }} {{- $options:= include "spire-server.config-postgresql-options" .Values.dataStore.sql.options }} - {{- $_ := set $config "connection_string" (printf "dbname=%s user=%s%s host=%s port=%d%s" .Values.dataStore.sql.databaseName .Values.dataStore.sql.username $pw .Values.dataStore.sql.host $port $options) }} + {{- $_ := set $config "connection_string" (printf "dbname=%s user=%s%s host=%s port=%d%s%s" .Values.dataStore.sql.databaseName .Values.dataStore.sql.username $pw .Values.dataStore.sql.host $port $options $sslPaths) }} {{- if .Values.dataStore.sql.readOnly.enabled }} {{- $roPort := int .Values.dataStore.sql.readOnly.port | default 5432 }} {{- $roOptions:= include "spire-server.config-postgresql-options" .Values.dataStore.sql.readOnly.options }} - {{- $_ := set $config "ro_connection_string" (printf "dbname=%s user=%s%s host=%s port=%d%s" .Values.dataStore.sql.readOnly.databaseName .Values.dataStore.sql.readOnly.username $ropw .Values.dataStore.sql.readOnly.host $roPort $roOptions) }} + {{- $_ := set $config "ro_connection_string" (printf "dbname=%s user=%s%s host=%s port=%d%s%s" .Values.dataStore.sql.readOnly.databaseName .Values.dataStore.sql.readOnly.username $ropw .Values.dataStore.sql.readOnly.host $roPort $roOptions $sslPaths) }} {{- end }} {{- else }} {{- fail "Unsupported database type" }} diff --git a/charts/spire/charts/spire-server/templates/configmap.yaml b/charts/spire/charts/spire-server/templates/configmap.yaml index befedbc..2c4211b 100644 --- a/charts/spire/charts/spire-server/templates/configmap.yaml +++ b/charts/spire/charts/spire-server/templates/configmap.yaml @@ -173,6 +173,7 @@ plugins: sql: plugin_data: {{ include "spire-server.datastore-config" . | nindent 8 }} + {{- if or (eq .Values.dataStore.sql.databaseType "mysql") (eq .Values.dataStore.sql.databaseType "aws_mysql") }} {{- if ne .Values.dataStore.sql.rootCAPath "" }} root_ca_path: {{ .Values.dataStore.sql.rootCAPath }} {{- end }} @@ -180,7 +181,8 @@ plugins: client_cert_path: {{ .Values.dataStore.sql.clientCertPath }} {{- end }} {{- if ne .Values.dataStore.sql.clientKeyPath "" }} - client_key_path : {{ .Values.dataStore.sql.clientKeyPath }} + client_key_path: {{ .Values.dataStore.sql.clientKeyPath }} + {{- end }} {{- end }} max_open_conns: {{ .Values.dataStore.sql.maxOpenConns }} max_idle_conns: {{ .Values.dataStore.sql.maxIdleConns }} diff --git a/charts/spire/charts/spire-server/templates/secret.yaml b/charts/spire/charts/spire-server/templates/secret.yaml index b0baa59..ae5cfc7 100644 --- a/charts/spire/charts/spire-server/templates/secret.yaml +++ b/charts/spire/charts/spire-server/templates/secret.yaml @@ -7,7 +7,8 @@ {{- if and (.Values.dataStore.sql.externalSecret.enabled) (eq .Values.dataStore.sql.externalSecret.key "") }} {{- fail "dataStore.sql.externalSecret.key cannot be empty string when dataStore.sql.externalSecret is enabled" }} {{- end }} -{{- if and (ne .Values.dataStore.sql.databaseType "sqlite3") (not .Values.dataStore.sql.externalSecret.enabled) (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") }} +{{- $postgresPasswordless := eq (include "spire-server.datastore-postgres-passwordless" .) "true" }} +{{- if and (ne .Values.dataStore.sql.databaseType "sqlite3") (not .Values.dataStore.sql.externalSecret.enabled) (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") (not $postgresPasswordless) }} apiVersion: v1 kind: Secret metadata: diff --git a/charts/spire/charts/spire-server/templates/server-resource.yaml b/charts/spire/charts/spire-server/templates/server-resource.yaml index ea6a428..009fc84 100644 --- a/charts/spire/charts/spire-server/templates/server-resource.yaml +++ b/charts/spire/charts/spire-server/templates/server-resource.yaml @@ -59,17 +59,21 @@ {{- if hasKey .Values.dataStore.sql "plugin_data" }} {{- fail "The plugin_data setting to the sql data store is no longer supported." }} {{- end }} -{{- if and (ne .Values.dataStore.sql.databaseType "mysql") (ne .Values.dataStore.sql.databaseType "aws_mysql") }} +{{- $certPathDatabaseType := or (eq .Values.dataStore.sql.databaseType "mysql") (eq .Values.dataStore.sql.databaseType "aws_mysql") (eq .Values.dataStore.sql.databaseType "postgres") (eq .Values.dataStore.sql.databaseType "aws_postgres") }} +{{- if not $certPathDatabaseType }} {{- if ne .Values.dataStore.sql.rootCAPath "" }} -{{- fail "rootCAPath can only be set with database type mysql or aws_mysql." }} +{{- fail "rootCAPath can only be set with database type mysql, aws_mysql, postgres or aws_postgres." }} {{- end }} {{- if ne .Values.dataStore.sql.clientCertPath "" }} -{{- fail "clientCertPath can only be set with database type mysql or aws_mysql." }} +{{- fail "clientCertPath can only be set with database type mysql, aws_mysql, postgres or aws_postgres." }} {{- end }} {{- if ne .Values.dataStore.sql.clientKeyPath "" }} -{{- fail "clientKeyPath can only be set with database type mysql or aws_mysql." }} +{{- fail "clientKeyPath can only be set with database type mysql, aws_mysql, postgres or aws_postgres." }} {{- end }} {{- end }} +{{- if and (or (eq .Values.dataStore.sql.databaseType "postgres") (eq .Values.dataStore.sql.databaseType "aws_postgres")) (ne .Values.dataStore.sql.password "") (ne .Values.dataStore.sql.clientCertPath "") }} +{{- fail "dataStore.sql.password and dataStore.sql.clientCertPath are mutually exclusive for postgres; use one authentication method." }} +{{- end }} {{- $pluginsToLoad := include "spire-lib.extract_custom_plugin_images" . | fromYamlArray }} {{- $jwtExecNeeded := false }} {{- range $name, $value := .Values.kubeConfigs }} @@ -316,7 +320,9 @@ spec: {{- with .Values.extraEnv }} {{- . | toYaml | nindent 10 }} {{- end }} - {{- if and (ne .Values.dataStore.sql.databaseType "sqlite3") (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") }} + {{- $postgresPasswordless := eq (include "spire-server.datastore-postgres-passwordless" .) "true" }} + {{- $postgresRoPasswordless := eq (include "spire-server.datastore-postgres-ro-passwordless" .) "true" }} + {{- if and (ne .Values.dataStore.sql.databaseType "sqlite3") (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") (not $postgresPasswordless) }} {{- if .Values.dataStore.sql.externalSecret.enabled }} - name: DBPW valueFrom: @@ -330,7 +336,8 @@ spec: name: {{ $fullname }}-dbpw key: DBPW {{- end }} - {{- if and .Values.dataStore.sql.readOnly.enabled (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") }} + {{- end }} + {{- if and .Values.dataStore.sql.readOnly.enabled (ne .Values.dataStore.sql.databaseType "gcp_mysql_sa_iam") (not $postgresRoPasswordless) }} {{- if .Values.dataStore.sql.readOnly.externalSecret.enabled }} - name: RODBPW valueFrom: @@ -345,7 +352,6 @@ spec: key: RODBPW {{- end }} {{- end }} - {{- end }} {{- if ne .Values.keyManager.awsKMS.accessKeyID "" }} - name: AWS_KMS_ACCESS_KEY_ID valueFrom: diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index dd5e7cc..191623e 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -194,7 +194,7 @@ dataStore: port: 0 ## @param dataStore.sql.username Only used when type != "sqlite3" username: spire - ## @param dataStore.sql.password Only used when type != "sqlite3" + ## @param dataStore.sql.password Only used when type != "sqlite3". For postgres/aws_postgres, leave empty to omit the password from the connection string (e.g. TLS client-certificate or IAM authentication). password: "" ## @param dataStore.sql.file Data source file. Only used when type == "sqlite3" and inMemory is false file: "/run/spire/data/datastore.sqlite3" @@ -203,11 +203,11 @@ dataStore: ## @param dataStore.sql.options [array] takes an array of objects of form {: } to use when building the database connection string options: [] - ## @param dataStore.sql.rootCAPath Path to Root CA bundle (MySQL only) + ## @param dataStore.sql.rootCAPath Path to Root CA bundle. Supports MySQL and postgres. rootCAPath: "" - ## @param dataStore.sql.clientCertPath Path to client certificate (MySQL only) + ## @param dataStore.sql.clientCertPath Path to client certificate. Supports MySQL and postgres. clientCertPath: "" - ## @param dataStore.sql.clientKeyPath Path to private key for client certificate (MySQL only) + ## @param dataStore.sql.clientKeyPath Path to private key for client certificate. Supports MySQL and postgres. clientKeyPath: "" ## When an external source creates the secret. The secret should reside in the same namespace as the spire server diff --git a/tests/unit/spire_test.go b/tests/unit/spire_test.go index e446a24..e76c249 100644 --- a/tests/unit/spire_test.go +++ b/tests/unit/spire_test.go @@ -663,4 +663,81 @@ spire-server: Expect(err).Should(Succeed()) }) }) + Describe("spire-server.dataStore.sql.postgres passwordless", func() { + It("omits password and the -dbpw Secret for cert auth with an empty password", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + dataStore: + sql: + databaseType: postgres + host: db.example.org + username: spire + password: "" + rootCAPath: /run/spire/db-ca/ca.crt + clientCertPath: /run/spire/db-certs/tls.crt + clientKeyPath: /run/spire/db-certs/tls.key +`) + Expect(err).Should(Succeed()) + Expect(objs["spire/charts/spire-server/templates/configmap.yaml"]). + ShouldNot(ContainSubstring("password=${DBPW}")) + Expect(objs["spire/charts/spire-server/templates/configmap.yaml"]). + Should(ContainSubstring("sslrootcert=/run/spire/db-ca/ca.crt")) + Expect(objs["spire/charts/spire-server/templates/secret.yaml"]). + ShouldNot(ContainSubstring("kind: Secret")) + Expect(objs["spire/charts/spire-server/templates/server-resource.yaml"]). + ShouldNot(ContainSubstring("name: DBPW")) + }) + + It("keeps the password token and DBPW env when an external secret provides the password", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + dataStore: + sql: + databaseType: postgres + host: db.example.org + username: spire + password: "" + externalSecret: + enabled: true + name: my-db-secret + key: password +`) + Expect(err).Should(Succeed()) + Expect(objs["spire/charts/spire-server/templates/configmap.yaml"]). + Should(ContainSubstring("password=${DBPW}")) + serverResource := objs["spire/charts/spire-server/templates/server-resource.yaml"] + Expect(serverResource).Should(ContainSubstring("name: DBPW")) + Expect(serverResource).Should(ContainSubstring("name: my-db-secret")) + }) + + It("keeps the RODBPW env when a read-only external secret provides the password", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + dataStore: + sql: + databaseType: postgres + host: db.example.org + username: spire + password: "" + rootCAPath: /run/spire/db-ca/ca.crt + clientCertPath: /run/spire/db-certs/tls.crt + clientKeyPath: /run/spire/db-certs/tls.key + readOnly: + enabled: true + host: ro.example.org + username: spire + password: "" + externalSecret: + enabled: true + name: my-ro-db-secret + key: password +`) + Expect(err).Should(Succeed()) + Expect(objs["spire/charts/spire-server/templates/configmap.yaml"]). + Should(ContainSubstring("password=${RODBPW}")) + serverResource := objs["spire/charts/spire-server/templates/server-resource.yaml"] + Expect(serverResource).Should(ContainSubstring("name: RODBPW")) + Expect(serverResource).Should(ContainSubstring("name: my-ro-db-secret")) + }) + }) })