Merge branch 'docker-mailserver:master' into enhancement/mail-proxy

This commit is contained in:
Fürst 2024-04-21 14:28:54 +02:00 committed by GitHub
commit 1929cb4e29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 130 additions and 109 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.**

View File

@ -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