From b507a044c17750c5e6fbed316302bc61b4844636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20Ro=C3=9Fner?= Date: Thu, 4 Dec 2025 12:04:34 +0100 Subject: [PATCH 1/3] feat(helmfile): Add templating of `smtp.spamMilterHost`; it is strongly recommended to use this feature to address spam filtering and SPF / DKIM validation of incoming mails --- docs/architecture.md | 82 +++++++++++++++++++ docs/getting-started.md | 8 ++ .../open-xchange/values-postfix.yaml.gotmpl | 2 +- .../environments/default/smtp.yaml.gotmpl | 1 + 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/docs/architecture.md b/docs/architecture.md index 653c0eac..df8164e1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -26,6 +26,9 @@ SPDX-License-Identifier: Apache-2.0 * [Filepicker](#filepicker) * [Newsfeed](#newsfeed) * [(OpenProject) File store](#openproject-file-store) +* [Mail setup](#mail-setup) + * [Overview](#overview-1) + * [The Postfixes](#the-postfixes) * [Applications vs. services](#applications-vs-services) * [Collabora (weboffice)](#collabora-weboffice) * [CryptPad Online (diagrams)](#cryptpad-online-diagrams) @@ -348,6 +351,85 @@ The file store must still be enabled per project in OpenProject's project admin - [OpenProject's documentation on Nextcloud integration](https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/) - [OpenProject Integration Nextcloud app](https://apps.nextcloud.com/apps/integration_openproject) +# Mail setup + +The mail setup depicted in the diagram below shows the design to support multiple application workloads inside openDesk while interoperating with external mail infrastructures and optional mail clients like Thunderbird. + +The system is intentionally modular: different applications (Nextcloud, OpenProject, XWiki, Synapse, Notes, etc.) may need to send emails even when no full groupware stack is deployed. In that case the following components are also not being deployed: + +* `Dovecot` +* `Postfix-OX` + +Even without these components, the platform remains operational for outbound email because the (Base) Postfix instance provides a simple SMTP submission service using static SASL credentials. This allows all applications in *openDesk* to continue sending system notifications and user emails. + +## Overview + +```mermaid +flowchart-elk + +extClient[optional Mail Clients] +extRelay[Mailrelay/MXe] +extMTA[MTAs] + +subgraph extSvc[K8s External Servies] + extSvcDC((dovecot-external)) + extSvcPF((postfix-ox-external)) +end + +subgraph openDesk + subgraph Apps + AppsOther[Nubus
Nextcloud
OpenProject
Synapse
XWiki
Notes] + AppsOXAS[OX App Suite] + end + subgraph Postfix + PostfixBase[#40;Base#41; Postfix] + PostfixOX[Postfix-OX] + end + Dovecot[Dovecot
authenticates using
SASL using LDAP & OAuth] + Dovecot -->|Sieve mails
without no auth| PostfixBase + PostfixOX -->|auth|Dovecot +end + +Postfix -->|lmtps| Dovecot +Postfix -->|smtp| extRelay + +extSvcDC --> Dovecot +extSvcPF --> PostfixOX + +AppsOther -->|auth:
static creds.| PostfixBase +AppsOXAS --> Dovecot +AppsOXAS -->|auth:
OAuth| PostfixOX + +extClient --> extSvcDC +extMTA -->|WARNING: SPF and DKIM validation required| extSvcPF +extClient -->|auth:
LDAP| extSvcPF + +classDef postfix fill:#85extMTA9C; +class PostfixBase postfix; +classDef postfix-ox fill:#F3E5Dovecot; +class PostfixOX,extSvcPF postfix-ox; +classDef dovecot fill:#BECBD6; +class Dovecot,extSvcDC dovecot; +``` + +## The Postfixes + +* Common for both Postfix + * Deliver internal mails to Dovecot using lmtps + * Deliver non-internal mails directly to a configured mail relay or to the recipients MX + +* (Base) Postfix specific + * SMTP submission from applications using static credentials + * SMTP submission without authentication for Dovecot generated mails by Sieve filters, e.g. out-of-office replys, as Dovecot does not support authentication in this flow + * Available even if OX App Suite is not installed + +* Postfix-OX specific + * External mails are relayed for internal maildomains unauthenticated + * Requires Dovecot for SASL authentication on + * mails sent from OX App Suite's Web UI using OAuth + * mails sent from mail clients using LDAP Auth + * Used exclusively when OX App Suite is deployed + # Applications vs. services openDesk consists of a variety of open-source projects, please find an overview below: diff --git a/docs/getting-started.md b/docs/getting-started.md index 056f7d5e..10c007ff 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -341,6 +341,14 @@ smtp: password: "secret" ``` +It is strongly recommended to configure a milter host for spam filtering (e.g. Rspamd) to get SPF and DKIM +validation for incoming mails in place. Otherwise external senders could spoof internal sender addresses. + +```yaml +smtp: + spamMilterHost: "rpamd.domain.internal" +``` + ### TURN configuration Some components (Jitsi, Element) use a TURN server for direct communication. You can configure your own TURN server with diff --git a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl index 54759007..87f31eb5 100644 --- a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl +++ b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl @@ -50,9 +50,9 @@ postfix: {{- if .Values.apps.dkimpy.enabled }} dkimpyHost: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}:8892" {{- end }} + rspamdHost: {{ .Values.spamMilterHost | quote }} minTLSVersion: "TLSv1.2" smtpdTLSMandatoryCiphers: "medium" - rspamdHost: "" {{- if .Values.smtp.host }} relayHost: enabled: true diff --git a/helmfile/environments/default/smtp.yaml.gotmpl b/helmfile/environments/default/smtp.yaml.gotmpl index f4d7181b..733f92cb 100644 --- a/helmfile/environments/default/smtp.yaml.gotmpl +++ b/helmfile/environments/default/smtp.yaml.gotmpl @@ -7,6 +7,7 @@ smtp: username: "" password: {{ env "SMTP_PASSWORD" | quote }} localpartNoReply: "no-reply" + spamMilterHost: "" # For the following settings to have effect `apps.dkimpy.enabled` must be `true`. dkim: From c7b6fd0d614540e34b5f4830f4dcbd868ac64113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20Ro=C3=9Fner?= Date: Fri, 5 Dec 2025 08:08:06 +0100 Subject: [PATCH 2/3] feat(helmfile): Add templating of `smtp.spamMilter.*`; it is strongly recommended to use this feature to address spam filtering and SPF / DKIM validation of incoming mails --- docs/getting-started.md | 4 +++- helmfile/apps/open-xchange/values-postfix.yaml.gotmpl | 2 +- helmfile/environments/default/smtp.yaml.gotmpl | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 10c007ff..13eea5a3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -346,7 +346,9 @@ validation for incoming mails in place. Otherwise external senders could spoof i ```yaml smtp: - spamMilterHost: "rpamd.domain.internal" + spamMilter: + host: "rspamd.domain.internal" + port: "11332" ``` ### TURN configuration diff --git a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl index 87f31eb5..64e2608a 100644 --- a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl +++ b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl @@ -50,7 +50,7 @@ postfix: {{- if .Values.apps.dkimpy.enabled }} dkimpyHost: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}:8892" {{- end }} - rspamdHost: {{ .Values.spamMilterHost | quote }} + rspamdHost: "{{ .Values.smtp.spamMilter.host }}:{{ .Values.smtp.spamMilter.port }}" minTLSVersion: "TLSv1.2" smtpdTLSMandatoryCiphers: "medium" {{- if .Values.smtp.host }} diff --git a/helmfile/environments/default/smtp.yaml.gotmpl b/helmfile/environments/default/smtp.yaml.gotmpl index 733f92cb..e48d7f99 100644 --- a/helmfile/environments/default/smtp.yaml.gotmpl +++ b/helmfile/environments/default/smtp.yaml.gotmpl @@ -7,7 +7,9 @@ smtp: username: "" password: {{ env "SMTP_PASSWORD" | quote }} localpartNoReply: "no-reply" - spamMilterHost: "" + spamMilter: + host: "" + port: "" # For the following settings to have effect `apps.dkimpy.enabled` must be `true`. dkim: From 47dc5bd9dd21df3c86a60df6a8f66bb158a34cda Mon Sep 17 00:00:00 2001 From: Thomas Kaltenbrunner Date: Fri, 5 Dec 2025 12:54:42 +0100 Subject: [PATCH 3/3] fix(open-xchange): Streamline postfix milter configuration --- .../open-xchange/values-postfix.yaml.gotmpl | 34 ++++++++++++------- .../values-postfix.yaml.gotmpl | 30 +++++++++------- .../environments/default/charts.yaml.gotmpl | 2 +- .../environments/default/smtp.yaml.gotmpl | 2 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl index 64e2608a..79ced250 100644 --- a/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl +++ b/helmfile/apps/open-xchange/values-postfix.yaml.gotmpl @@ -47,10 +47,27 @@ postfix: inetProtocols: "ipv4" messageSizeLimit: {{ mul .Values.functional.groupware.mail.maxSize 1024 1024 | int | printf "%d" | quote }} milterDefaultAction: "tempfail" - {{- if .Values.apps.dkimpy.enabled }} - dkimpyHost: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}:8892" - {{- end }} - rspamdHost: "{{ .Values.smtp.spamMilter.host }}:{{ .Values.smtp.spamMilter.port }}" + smtpdMilters: + {{- if .Values.apps.dkimpy.enabled }} + - host: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}" + port: 8892 + {{- end }} + {{- if .Values.smtp.spamMilter.host }} + - host: {{ .Values.smtp.spamMilter.host | quote }} + port: {{ .Values.smtp.spamMilter.port }} + {{- end }} + {{- if .Values.antivirus.milter.host }} + - host: {{ .Values.antivirus.milter.host | quote }} + port: {{ .Values.antivirus.milter.port }} + {{- else }} + {{- if .Values.apps.clamavDistributed.enabled }} + - host: "clamav-milter" + port:7357 + {{- else if .Values.apps.clamavSimple.enabled }} + - host: "clamav-simple" + port: 7357 + {{- end }} + {{- end }} minTLSVersion: "TLSv1.2" smtpdTLSMandatoryCiphers: "medium" {{- if .Values.smtp.host }} @@ -100,15 +117,6 @@ postfix: # -- return the following attribute from all found leaves when a recursive search is done leafResultAttribute: "mailPrimaryAddress" - {{- if .Values.antivirus.milter.host }} - smtpdMilters: "inet:{{ .Values.antivirus.milter.host }}:{{ .Values.antivirus.milter.port }}" - {{- else }} - {{- if .Values.apps.clamavDistributed.enabled }} - smtpdMilters: "inet:clamav-milter:7357" - {{- else if .Values.apps.clamavSimple.enabled }} - smtpdMilters: "inet:clamav-simple:7357" - {{- end }} - {{- end }} virtualMailboxDomains: {{ toYaml (prepend .Values.global.additionalMailDomains (.Values.global.mailDomain | default .Values.global.domain) | uniq) | nindent 4 }} virtualTransport: "lmtps:dovecot:24" diff --git a/helmfile/apps/services-external/values-postfix.yaml.gotmpl b/helmfile/apps/services-external/values-postfix.yaml.gotmpl index c22c28fb..1ebe4716 100644 --- a/helmfile/apps/services-external/values-postfix.yaml.gotmpl +++ b/helmfile/apps/services-external/values-postfix.yaml.gotmpl @@ -57,10 +57,23 @@ postfix: hostname: "postfix" inetProtocols: "ipv4" milterDefaultAction: "accept" - {{- if .Values.apps.dkimpy.enabled }} - dkimpyHost: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}:8892" - {{- end }} - rspamdHost: "" + smtpdMilters: + {{- if .Values.apps.dkimpy.enabled }} + - host: "opendesk-dkimpy-milter.{{ .Release.Namespace }}.svc.{{.Values.cluster.networking.domain }}" + port: 8892 + {{- end }} + {{- if .Values.antivirus.milter.host }} + - host: {{ .Values.antivirus.milter.host | quote }} + port: {{ .Values.antivirus.milter.port }} + {{- else }} + {{- if .Values.apps.clamavDistributed.enabled }} + - host: "clamav-milter" + port: 7357 + {{- else if .Values.apps.clamavSimple.enabled }} + - host: "clamav-simple" + port: 7357 + {{- end }} + {{- end }} {{- if .Values.smtp.host }} relayHost: enabled: true @@ -116,15 +129,6 @@ postfix: # -- return the following attribute from all found leaves when a recursive search is done leafResultAttribute: "mailPrimaryAddress" - {{- if .Values.antivirus.milter.host }} - smtpdMilters: "inet:{{ .Values.antivirus.milter.host }}:{{ .Values.antivirus.milter.port }}" - {{- else }} - {{- if .Values.apps.clamavDistributed.enabled }} - smtpdMilters: "inet:clamav-milter:7357" - {{- else if .Values.apps.clamavSimple.enabled }} - smtpdMilters: "inet:clamav-simple:7357" - {{- end }} - {{- end }} # Only deliver mail to Dovecot, if it is available {{- if .Values.apps.oxAppSuite.enabled }} virtualMailboxDomains: {{ toYaml (prepend .Values.global.additionalMailDomains (.Values.global.mailDomain | default .Values.global.domain) | uniq) | nindent 4 }} diff --git a/helmfile/environments/default/charts.yaml.gotmpl b/helmfile/environments/default/charts.yaml.gotmpl index 93608a70..e1bf01d1 100644 --- a/helmfile/environments/default/charts.yaml.gotmpl +++ b/helmfile/environments/default/charts.yaml.gotmpl @@ -437,7 +437,7 @@ charts: registry: "registry.opencode.de" repository: "bmi/opendesk/components/platform-development/charts/opendesk-postfix" name: "postfix" - version: "5.1.0" + version: "5.1.1" verify: true postgresql: # providerCategory: "Platform" diff --git a/helmfile/environments/default/smtp.yaml.gotmpl b/helmfile/environments/default/smtp.yaml.gotmpl index e48d7f99..37000b9d 100644 --- a/helmfile/environments/default/smtp.yaml.gotmpl +++ b/helmfile/environments/default/smtp.yaml.gotmpl @@ -9,7 +9,7 @@ smtp: localpartNoReply: "no-reply" spamMilter: host: "" - port: "" + port: 11332 # For the following settings to have effect `apps.dkimpy.enabled` must be `true`. dkim: