docs: Updates to TLS page (Caddy, testing, etc) (#3981)
This commit is contained in:
parent
942920615c
commit
ac22caf74e
|
@ -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
|
||||
!!! example
|
||||
|
||||
```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
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
```caddyfile title="Caddyfile"
|
||||
mail.example.com {
|
||||
tls internal {
|
||||
key_type rsa2048
|
||||
}
|
||||
```
|
||||
}
|
||||
|
||||
If you are instead using a json config for Caddy v2, you can set it in your site's TLS automation policies:
|
||||
|
||||
??? 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
# 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:
|
||||
- [`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).
|
||||
|
||||
??? example "With `caddy-docker-proxy`"
|
||||
|
||||
Using [`lucaslorentz/caddy-docker-proxy`][github::caddy-docker-proxy] allows you to generate a `Caddyfile` by adding labels to your services in `compose.yaml`:
|
||||
|
||||
```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}}"
|
||||
```
|
||||
|
||||
### Traefik v2
|
||||
!!! warning "Caddy certificate location varies"
|
||||
|
||||
[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.
|
||||
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].
|
||||
|
||||
[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).
|
||||
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].
|
||||
|
||||
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`.
|
||||
### Traefik
|
||||
|
||||
DMS will select it's certificate from `acme.json` checking these ENV for a matching FQDN (_in order of priority_):
|
||||
[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.
|
||||
|
||||
1. `#!bash ${SSL_DOMAIN}`
|
||||
2. `#!bash ${HOSTNAME}`
|
||||
3. `#!bash ${DOMAINNAME}`
|
||||
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).
|
||||
|
||||
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.
|
||||
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/
|
||||
```
|
||||
|
||||
And you should see the certificate chain, the server certificate and: `Verify return code: 0 (ok)`
|
||||
|
||||
In addition, to verify certificate dates:
|
||||
|
||||
```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
|
||||
```
|
||||
```
|
||||
|
||||
!!! 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
|
||||
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue