1
1
Fork 0
mirror of https://github.com/docker-mailserver/docker-mailserver synced 2024-06-02 08:16:05 +02:00

docs: Updates to TLS page (Caddy, testing, etc) (#3981)

This commit is contained in:
Brennan Kinney 2024-04-20 11:25:02 +12:00 committed by GitHub
parent 942920615c
commit ac22caf74e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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"
```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"
}
}
]
}
}
}
}
```
The generated certificates can then be mounted:
```yaml
# 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
```
### Traefik v2
```caddyfile title="Caddyfile"
mail.example.com {
tls internal {
key_type rsa2048
}
[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.
# Optional, can be useful for troubleshooting
# connection to Caddy with correct certificate:
respond "Hello DMS"
}
```
[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).
While DMS does not need a webserver to work, this workaround will provision a TLS certificate for DMS to use.
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`.
- [`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).
DMS will select it's certificate from `acme.json` checking these ENV for a matching FQDN (_in order of priority_):
??? example "With `caddy-docker-proxy`"
1. `#!bash ${SSL_DOMAIN}`
2. `#!bash ${HOSTNAME}`
3. `#!bash ${DOMAINNAME}`
Using [`lucaslorentz/caddy-docker-proxy`][github::caddy-docker-proxy] allows you to generate a `Caddyfile` by adding labels to your services in `compose.yaml`:
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.
```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: ""
# 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}}"
```
!!! warning "Caddy certificate location varies"
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 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,18 +720,16 @@ 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)`
```sh
docker exec mailserver openssl s_client \
-connect 0.0.0.0:143 \
-starttls imap \
-CApath /etc/ssl/certs/
```
---
And you should see the certificate chain, the server certificate and: `Verify return code: 0 (ok)`
This example runs within the DMS container itself to verify the cert is working locally.
In addition, to verify certificate dates:
- 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 \
@ -746,6 +739,24 @@ docker exec mailserver openssl s_client \
2>/dev/null | openssl x509 -noout -dates
```
!!! tip "Testing and troubleshooting"
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.
```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
!!! warning
@ -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