From 3e03a67f3f527f6d56f38fef1397cd48485c6e6a Mon Sep 17 00:00:00 2001 From: Aleksandr Albekov Date: Mon, 26 Oct 2026 21:37:40 +0100 Subject: [PATCH] Verify the Postgres server certificate Stalwart connected to Postgres with allowInvalidCerts, so any certificate was accepted (infra#94). infra now signs the server certificate with an internal CA and publishes ConfigMap ca-bundle: Debian's public roots plus that CA. Stalwart's rustls verifier reads the trust store from SSL_CERT_FILE when it is set, and then ignores the system store. That verifier also checks outbound SMTP, ACME and OIDC, so the file must hold the public roots too, which is why it is ca-bundle and not the internal CA alone. Co-Authored-By: Claude Opus 5.5 --- deploy/k8s/config.json | 2 +- deploy/k8s/stalwart.yaml | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/deploy/k8s/config.json b/deploy/k8s/config.json index 112bf8a..80ac704 100644 --- a/deploy/k8s/config.json +++ b/deploy/k8s/config.json @@ -6,6 +6,6 @@ "authUsername": "stalwart", "authSecret": { "@type": "EnvironmentVariable", "variableName": "STALWART_DB_PASSWORD" }, "useTls": true, - "allowInvalidCerts": true, + "allowInvalidCerts": false, "poolMaxConnections": 10 } diff --git a/deploy/k8s/stalwart.yaml b/deploy/k8s/stalwart.yaml index f0da34a..5190a84 100644 --- a/deploy/k8s/stalwart.yaml +++ b/deploy/k8s/stalwart.yaml @@ -1,8 +1,9 @@ # Stalwart mail server. Stateless pod: settings, accounts, mail and blobs live in Postgres (config.json). # What ties it to a node is the public IP: MX, SPF and the PTR of mail.bulbashenko.com point at it, so the # pod runs on the node labelled node.bulbashenko.com/role=mail and takes the mail ports as host ports. -# The deploying repo (bulbashenko-com/infra) provides the namespace and the Secret `stalwart` -# (STALWART_DB_PASSWORD, CF_DNS_API_TOKEN). +# The deploying repo (bulbashenko-com/infra) provides the namespace, the Secret `stalwart` +# (STALWART_DB_PASSWORD, CF_DNS_API_TOKEN) and the ConfigMap `ca-bundle` (public roots + the internal CA +# that signs the Postgres server certificate). apiVersion: apps/v1 kind: Deployment metadata: @@ -33,6 +34,9 @@ spec: args: ["--config", "/etc/stalwart/config.json"] env: - { name: STALWART_PUBLIC_URL, value: "https://mail.int.bulbashenko.com" } + # rustls reads this file instead of the system store: public roots plus our internal CA, so the + # Postgres certificate verifies and outbound TLS (SMTP, ACME, OIDC) keeps working. + - { name: SSL_CERT_FILE, value: /etc/ca-bundle/ca-certificates.crt } envFrom: - secretRef: { name: stalwart } ports: @@ -57,11 +61,15 @@ spec: capabilities: { drop: [ALL], add: [NET_BIND_SERVICE] } volumeMounts: - { name: config, mountPath: /etc/stalwart } + - { name: ca-bundle, mountPath: /etc/ca-bundle, readOnly: true } # Scratch space and the file tracer (/var/lib/stalwart/logs); nothing durable lives here. - { name: scratch, mountPath: /var/lib/stalwart } volumes: - name: config configMap: { name: stalwart-config } + # trust-manager puts ConfigMap ca-bundle into every namespace (bulbashenko-com/infra). + - name: ca-bundle + configMap: { name: ca-bundle } - { name: scratch, emptyDir: {} } --- apiVersion: v1