From f2314259829d170984eacd5790406a6f745ae032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:05:07 +0200 Subject: [PATCH 1/5] chore(deps): Bump peaceiris/actions-gh-pages from 3.9.3 to 4.0.0 (#3978) --- .github/workflows/docs-production-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-production-deploy.yml b/.github/workflows/docs-production-deploy.yml index cb8bdbed..2c6c1e2c 100644 --- a/.github/workflows/docs-production-deploy.yml +++ b/.github/workflows/docs-production-deploy.yml @@ -59,7 +59,7 @@ jobs: {} + - name: 'Deploy to Github Pages' - uses: peaceiris/actions-gh-pages@v3.9.3 + uses: peaceiris/actions-gh-pages@v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} # Build directory contents to publish to the `gh-pages` branch: From dc5185003058dbdb1d18947a870a09c43a275d85 Mon Sep 17 00:00:00 2001 From: fanqiaojun <166898955+fanqiaojun@users.noreply.github.com> Date: Tue, 16 Apr 2024 03:48:55 +0800 Subject: [PATCH 2/5] chore: remove repetitive words (#3977) --- docs/content/config/advanced/auth-ldap.md | 2 +- docs/content/examples/use-cases/auth-lua.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/config/advanced/auth-ldap.md b/docs/content/config/advanced/auth-ldap.md index ea24526d..397e42eb 100644 --- a/docs/content/config/advanced/auth-ldap.md +++ b/docs/content/config/advanced/auth-ldap.md @@ -26,7 +26,7 @@ Those variables contain the LDAP lookup filters for postfix, using `%s` as the p - Technically, there is no difference between `ALIAS` and `GROUP`, but ideally you should use `ALIAS` for personal aliases for a singular person (like `ceo@example.org`) and `GROUP` for multiple people (like `hr@example.org`). - ...for outgoing email, the sender address is put through the `SENDERS` filter, and only if the authenticated user is one of the returned entries, the email can be sent. - This only applies if `SPOOF_PROTECTION=1`. - - If the `SENDERS` filter is missing, the `USER`, `ALIAS` and `GROUP` filters will be used in in a disjunction (OR). + - If the `SENDERS` filter is missing, the `USER`, `ALIAS` and `GROUP` filters will be used in a disjunction (OR). - To for example allow users from the `admin` group to spoof any sender email address, and to force everyone else to only use their personal mailbox address for outgoing email, you can use something like this: `(|(memberOf=cn=admin,*)(mail=%s))` ???+ example diff --git a/docs/content/examples/use-cases/auth-lua.md b/docs/content/examples/use-cases/auth-lua.md index 82586885..e34ed3b0 100644 --- a/docs/content/examples/use-cases/auth-lua.md +++ b/docs/content/examples/use-cases/auth-lua.md @@ -34,7 +34,7 @@ A drawback of this method is that any (compromised) Nextcloud application passwo To answer the questions asked earlier for this specific scenario: -1. Do I want to use Lua to identify mailboxes and verify that users are are authorized to use mail services? **No. Provisioning is done through LDAP.** +1. Do I want to use Lua to identify mailboxes and verify that users are authorized to use mail services? **No. Provisioning is done through LDAP.** 1. Do I want to use Lua to verify passwords that users authenticate with for IMAP/POP3/SMTP in their mail clients? **Yes. Password authentication is done through Lua against Nextcloud.** 1. If the answer is 'yes' to question 1 or 2: are there other methods that better facilitate my use case instead of custom scripts which rely on me being a developer and not just a user? **No. Only HTTP can be used to authenticate against Nextcloud, which is not supported natively by Dovecot or DMS.** From d87e4d3bfd7780def3c187b0f31c9034954cdfe7 Mon Sep 17 00:00:00 2001 From: Iztok Fister Jr Date: Tue, 16 Apr 2024 22:25:45 +0200 Subject: [PATCH 3/5] docs: Fix typos (#3979) --- docs/content/config/environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/config/environment.md b/docs/content/config/environment.md index 9adfd5d9..867c7459 100644 --- a/docs/content/config/environment.md +++ b/docs/content/config/environment.md @@ -456,8 +456,8 @@ Default: 6 (which corresponds to the `add_header` action) ##### RSPAMD_NEURAL -Can be used to enable or disable the [Neural network module][rspamd-docs-neural-network]. This is an experimental anti-spam weigh method using three neuaral networks in the configuration added here. As far as we can tell it trains itsself by using other modules to find out what spam is. It will take a while (a week or more) to train its first neural network. The config trains new networks all the time and discards of old networks. -Since it is experimental it is switched of by default. +Can be used to enable or disable the [Neural network module][rspamd-docs-neural-network]. This is an experimental anti-spam weigh method using three neural networks in the configuration added here. As far as we can tell it trains itself by using other modules to find out what spam is. It will take a while (a week or more) to train its first neural network. The config trains new networks all the time and discards old networks. +Since it is experimental, it is switched off by default. - **0** => Disabled - 1 => Enabled From 942920615c0ab3d9946d6ff1db6a83f9034351cc Mon Sep 17 00:00:00 2001 From: Tobia Bocchi <29007647+tobiabocchi@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:08:26 +0200 Subject: [PATCH 4/5] docs: Fix typo on usage page (#3980) --- docs/content/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/usage.md b/docs/content/usage.md index 76011fe7..087547ea 100644 --- a/docs/content/usage.md +++ b/docs/content/usage.md @@ -11,7 +11,7 @@ This page explains how to get started with DMS. The guide uses Docker Compose as Before you can get started with deploying your own mail server, there are some requirements to be met: 1. You need to have a host that you can manage. -2. You need to own a domain, and you need to able to manage DNS for this domain. +2. You need to own a domain, and you need to be able to manage DNS for this domain. ### Host Setup From ac22caf74eff031ad8086ac147949c0b15c24398 Mon Sep 17 00:00:00 2001 From: Brennan Kinney <5098581+polarathene@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:25:02 +1200 Subject: [PATCH 5/5] docs: Updates to TLS page (Caddy, testing, etc) (#3981) --- docs/content/config/security/ssl.md | 227 +++++++++++++++------------- 1 file changed, 124 insertions(+), 103 deletions(-) diff --git a/docs/content/config/security/ssl.md b/docs/content/config/security/ssl.md index a9174483..a5ba005a 100644 --- a/docs/content/config/security/ssl.md +++ b/docs/content/config/security/ssl.md @@ -481,113 +481,108 @@ DSM-generated letsencrypt certificates get auto-renewed every three months. ### Caddy -For Caddy v2 you can specify the `key_type` in your server's global settings, which would end up looking something like this if you're using a `Caddyfile`: +[Caddy][web::caddy] is an open-source web server with built-in TLS certificate generation. You can use the [official Docker image][dockerhub::caddy] and write your own `Caddyfile`. -```caddyfile -{ - debug - admin localhost:2019 - http_port 80 - https_port 443 - default_sni example.com - key_type rsa2048 -} -``` +!!! example -If you are instead using a json config for Caddy v2, you can set it in your site's TLS automation policies: + ```yaml title="compose.yaml" + services: + # Basic Caddy service to provision certs: + reverse-proxy: + image: caddy:2.7 + ports: + - 80:80 + - 443:443 + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ${CADDY_DATA_DIR}:/data -??? example "Caddy v2 JSON example snippet" + # Share the Caddy data volume for certs and configure SSL_TYPE to `letsencrypt` + mailserver: + image: ghcr.io/docker-mailserver/docker-mailserver:latest + hostname: mail.example.com + environment: + SSL_TYPE: letsencrypt + # While you could use a named data volume instead of a bind mount volume, it would require the long-syntax to rename cert files: + # https://docs.docker.com/compose/compose-file/05-services/#volumes + volumes: + - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.crt:/etc/letsencrypt/live/mail.example.com/fullchain.pem + - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.key:/etc/letsencrypt/live/mail.example.com/privkey.pem + ``` - ```json - { - "apps": { - "http": { - "servers": { - "srv0": { - "listen": [ - ":443" - ], - "routes": [ - { - "match": [ - { - "host": [ - "mail.example.com", - ] - } - ], - "handle": [ - { - "handler": "subroute", - "routes": [ - { - "handle": [ - { - "body": "", - "handler": "static_response" - } - ] - } - ] - } - ], - "terminal": true - }, - ] - } - } - }, - "tls": { - "automation": { - "policies": [ - { - "subjects": [ - "mail.example.com", - ], - "key_type": "rsa2048", - "issuer": { - "email": "admin@example.com", - "module": "acme" - } - }, - { - "issuer": { - "email": "admin@example.com", - "module": "acme" - } - } - ] - } - } + ```caddyfile title="Caddyfile" + mail.example.com { + tls internal { + key_type rsa2048 } + + # Optional, can be useful for troubleshooting + # connection to Caddy with correct certificate: + respond "Hello DMS" } ``` -The generated certificates can then be mounted: + While DMS does not need a webserver to work, this workaround will provision a TLS certificate for DMS to use. -```yaml -volumes: - - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.crt:/etc/letsencrypt/live/mail.example.com/fullchain.pem - - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.key:/etc/letsencrypt/live/mail.example.com/privkey.pem -``` + - [`tls internal`][caddy-docs::tls-internal] will create a local self-signed cert for testing. This targets only the site-address, unlike the global `local_certs` option. + - [`key_type`][caddy-docs::key-type] can be used in the `tls` block if you need to enforce RSA as the key type for certificates provisioned. The default is currently ECDSA (P-256). -### Traefik v2 +??? example "With `caddy-docker-proxy`" -[Traefik][traefik::github] is an open-source application proxy using the [ACME protocol][ietf::rfc::acme]. [Traefik][traefik::github] can request certificates for domains and subdomains, and it will take care of renewals, challenge negotiations, etc. We strongly recommend to use [Traefik][traefik::github]'s major version 2. + Using [`lucaslorentz/caddy-docker-proxy`][github::caddy-docker-proxy] allows you to generate a `Caddyfile` by adding labels to your services in `compose.yaml`: -[Traefik][traefik::github]'s storage format is natively supported if the `acme.json` store is mounted into the container at `/etc/letsencrypt/acme.json`. The file is also monitored for changes and will trigger a reload of the mail services (Postfix and Dovecot). + ```yaml title="compose.yaml" + services: + reverse-proxy: + image: lucaslorentz/caddy-docker-proxy:2.8 + ports: + - 80:80 + - 443:443 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${CADDY_DATA_DIR}:/data + labels: + # Set global config here, this option has an empty value to enable self-signed certs for local testing: + # NOTE: Remove this label when going to production. + caddy.local_certs: "" -Wildcard certificates are supported. If your FQDN is `mail.example.com` and your wildcard certificate is `*.example.com`, add the ENV: `#!bash SSL_DOMAIN=example.com`. + # Use labels to configure Caddy to provision DMS certs + mailserver: + image: ghcr.io/docker-mailserver/docker-mailserver:latest + hostname: mail.example.com + environment: + SSL_TYPE: letsencrypt + volumes: + - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.crt:/etc/letsencrypt/live/mail.example.com/fullchain.pem + - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.key:/etc/letsencrypt/live/mail.example.com/privkey.pem + labels: + # Set your DMS FQDN here to add the site-address into the generated Caddyfile: + caddy_0: mail.example.com + # Add a dummy directive is required: + caddy_0.respond: "Hello DMS" + # Uncomment to make a proxy for Rspamd + # caddy_1: rspamd.example.com + # caddy_1.reverse_proxy: "{{upstreams 11334}}" + ``` -DMS will select it's certificate from `acme.json` checking these ENV for a matching FQDN (_in order of priority_): +!!! warning "Caddy certificate location varies" -1. `#!bash ${SSL_DOMAIN}` -2. `#!bash ${HOSTNAME}` -3. `#!bash ${DOMAINNAME}` + The path contains the certificate provisioner used. This path may be different from the example above for you and may change over time when multiple provisioner services are used][dms-pr-feedback::caddy-provisioning-gotcha]. -This setup only comes with one caveat: The domain has to be configured on another service for [Traefik][traefik::github] to actually request it from _Let's Encrypt_, i.e. [Traefik][traefik::github] will not issue a certificate without a service / router demanding it. + This can make the volume mounting for DMS to find the certificates non-deterministic, but you can [restrict provisioning to single service via the `acme_ca` setting][caddy::restrict-acme-provisioner]. + +### Traefik + +[Traefik][traefik::github] is an open-source application proxy using the [ACME protocol][ietf::rfc::acme]. Traefik can request certificates for domains and subdomains, and it will take care of renewals, challenge negotiations, etc. + +Traefik's storage format is natively supported if the `acme.json` store is mounted into the container at `/etc/letsencrypt/acme.json`. The file is also monitored for changes and will trigger a reload of the mail services (Postfix and Dovecot). + +DMS will select it's certificate from `acme.json` prioritizing a match for the DMS FQDN (hostname), while also checking one DNS level up (_eg: `mail.example.com` => `example.com`_). Wildcard certificates are supported. + +This setup only comes with one caveat - The domain has to be configured on another service for Traefik to actually request it from _Let's Encrypt_ (_i.e. Traefik will not issue a certificate without a service / router demanding it_). ???+ example "Example Code" + Here is an example setup for [`docker-compose`](https://docs.docker.com/compose/): ```yaml @@ -716,7 +711,7 @@ The local and internal paths may be whatever you prefer, so long as both `SSL_CE ## Testing a Certificate is Valid -- From your host: +!!! example "Connect to DMS on port 25" ```sh docker exec mailserver openssl s_client \ @@ -725,26 +720,42 @@ The local and internal paths may be whatever you prefer, so long as both `SSL_CE -CApath /etc/ssl/certs/ ``` -- Or: + The response should show the certificate chain with a line further down: `Verify return code: 0 (ok)` + + --- + + This example runs within the DMS container itself to verify the cert is working locally. + + - Adjust the `-connect` IP if testing externally from another system. Additionally testing for port 143 (Dovecot IMAP) is encouraged (_change the protocol for `-starttls` from `smtp` to `imap`_). + - `-CApath` will help verify the certificate chain, provided the location contains the root CA that signed your TLS cert for DMS. + +??? example "Verify certificate dates" ```sh docker exec mailserver openssl s_client \ - -connect 0.0.0.0:143 \ - -starttls imap \ - -CApath /etc/ssl/certs/ + -connect 0.0.0.0:25 \ + -starttls smtp \ + -CApath /etc/ssl/certs/ \ + 2>/dev/null | openssl x509 -noout -dates ``` -And you should see the certificate chain, the server certificate and: `Verify return code: 0 (ok)` +!!! tip "Testing and troubleshooting" -In addition, to verify certificate dates: + If you need to test a connection without resolving DNS, `curl` can connect with `--resolve` option to map an FQDN + Port to an IP address, instead of the request address provided. -```sh -docker exec mailserver openssl s_client \ - -connect 0.0.0.0:25 \ - -starttls smtp \ - -CApath /etc/ssl/certs/ \ - 2>/dev/null | openssl x509 -noout -dates -``` + ```bash + # NOTE: You may want to use `--insecure` if the cert was provisioned with a private CA not present on the curl client: + # Use `--verbose` for additional insights on the connection. + curl --resolve mail.example.com:443:127.0.0.1 https://mail.example.com + ``` + + Similarly with `openssl` you can connect to an IP as shown previously, but provide an explicit SNI if necessary with `-servername mail.example.com`. + + --- + + Both `curl` and `openssl` also support `-4` and `-6` for enforcing IPv4 or IPv6 lookup. + + This can be useful, such as when [DNS resolves the IP to different servers leading to different certificates returned][dms-discussion::gotcha-fqdn-bad-dns]. As shown in that link, `step certificate inspect` is also handy for viewing details of the cert returned or on disk. ## Plain-Text Access @@ -919,3 +930,13 @@ Despite this, if you must use non-standard DH parameters or you would like to sw [acme-companion::standalone]: https://github.com/nginx-proxy/acme-companion/blob/main/docs/Standalone-certificates.md [acme-companion::standalone-changes]: https://github.com/nginx-proxy/acme-companion/blob/main/docs/Standalone-certificates.md#picking-up-changes-to-letsencrypt_user_data [acme-companion::service-loop]: https://github.com/nginx-proxy/acme-companion/blob/main/docs/Container-utilities.md + +[web::caddy]: https://caddyserver.com +[dockerhub::caddy]: https://hub.docker.com/_/caddy +[github::caddy-docker-proxy]: https://github.com/lucaslorentz/caddy-docker-proxy +[dms-pr-feedback::caddy-provisioning-gotcha]: https://github.com/docker-mailserver/docker-mailserver/pull/3485/files#r1297512818 +[caddy-docs::tls-internal]: https://caddyserver.com/docs/caddyfile/directives/tls#syntax +[caddy-docs::key-type]: https://caddyserver.com/docs/caddyfile/options#key-type +[caddy::restrict-acme-provisioner]: https://caddy.community/t/is-there-a-way-on-a-caddyfile-to-force-a-specific-acme-ca/14506 + +[dms-discussion::gotcha-fqdn-bad-dns]: https://github.com/docker-mailserver/docker-mailserver/issues/3955#issuecomment-2027882633