mailcow dockerized sécurisé
Ce billet est une suite à mailcow dockerized.
Prérequis
On suppose que votre configuration réseau est pleinement fonctionnelle.
En ajout au précédent article, je précise que :
- Le DNS inverse (Reverse DNS ou rdns) doit être activé et paramétré
- Vous devez avoir un IP v4 fixe
Cette configuration est à réaliser avec votre fournisseur d’accès internet.
Chez Free, cela passe par la gestion de votre compte; partie “Ma Freebox”, “Fonctionnalités avancées” :
- Demander une adresse IP fixe V4 full-stack
- Personnaliser mon reverse DNS
Introduction
L’objectif ici est de sécuriser, autant que possible, une installation mailcow: dockerized.
Le nom de domain ‘example.com’ est ici générique ; à vous de le remplacer par le votre !
Avec pour objectifs :
- Une intégration avec Crowdsec (bouncer)
- Un support IPv4 et IPv6
Ce qui suppose un réseau IPv4/v6 & une instance Crowdsec (LAPI) OK
Intégration avec Traefik (revue)
Nous partons sur une configuration avec Traefik 3 sous Docker avec gestion des services par fichiers yaml.
traefik.yml
Dans le fichier traefik.yml (configuration principale) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
global:
checkNewVersion: false
sendAnonymousUsage: false
api:
debug: false
dashboard: true
insecure: false
ping: {}
entryPoints:
http:
address: ":80"
forwardedHeaders:
trustedIPs: &trustedIps # cloudflare (only usefull if cloudflare usage)
- "173.245.48.0/20"
- "103.21.244.0/22"
- "103.22.200.0/22"
- "103.31.4.0/22"
- "141.101.64.0/18"
- "108.162.192.0/18"
- "190.93.240.0/20"
- "188.114.96.0/20"
- "197.234.240.0/22"
- "198.41.128.0/17"
- "162.158.0.0/15"
- "104.16.0.0/13"
- "104.24.0.0/14"
- "172.64.0.0/13"
- "131.0.72.0/22"
- "2400:cb00::/32"
- "2606:4700::/32"
- "2803:f800::/32"
- "2405:b500::/32"
- "2405:8100::/32"
- "2a06:98c0::/29"
- "2c0f:f248::/32"
# local & lan ip ranges
# - "127.0.0.1/16"
# - "192.168.xxx.xxx/24"
# - "172.xxx.xxx.xxx/12"
# - "IPv6::/64"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
forwardedHeaders:
trustedIPs: *trustedIps
http3:
advertisedPort: 443
http:
tls:
certResolver: letsencrypt
domains:
# adjust 'example.com' with your main base domain name
- main: example.com
sans:
- "*.example.com"
certificatesResolvers:
letsencrypt:
acme:
caServer: https://acme-v02.api.letsencrypt.org/directory # production
# https://acme-staging-v02.api.letsencrypt.org/directory # staging/testing
storage: /etc/traefik/acme.json
# here we use DNS Challenge, adjust if you use another challenge
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
- "1.0.0.1:53"
- "8.8.4.4:53"
providers:
docker:
endpoint: "unix:///var/run/docker.sock" # adjust if you use Docker Socket Proxy
exposedByDefault: false
network: proxy
watch: true
file:
directory: etc/traefik/traefik.d/
watch: true
log:
level: "INFO" # DEBUG
maxSize: 32
maxBackups: 16
maxAge: 7
compress: true
accessLog:
filePath: "/log/access.log"
format: common
filters:
statusCodes:
- "200-299"
- "400-599"
bufferingSize: 0
fields:
headers:
defaultMode: drop
names:
User-Agent: keep
experimental:
fastProxy: {}
plugins: # adjust versions to keep on 'latest'
# Refer to crowdsec.yml
bouncer: # Crowdsec Bouncer plugin (to communicate with Crowdsec LAPI and Appsec)
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: "v1.4.4"
# Refer to geoblock.yml
geoblock: # Block access based on geographic location (detected by the ip address)
moduleName: "github.com/PascalMinder/geoblock"
version: "v0.3.3"
Réseau Traefik (Docker)
La création du réseau Docker pour Traefik se fait comme suit :
1
2
3
4
5
6
7
8
9
docker network create \
--driver bridge \
--attachable \
--subnet=xxx.xxx.xxx.xxx/xx \
--ip-range=xxx.xxx.xxx.xxx/xx \
--opt "com.docker.network.bridge.name=traefik" \
--opt "com.docker.network.driver.mtu=1500" \
--opt "com.docker.network.enable_ipv6=true" \
traefik
docker-compose.yml
Pour informations, le docker-compose.yml ressemble à ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
networks:
traefik:
external: true
services:
traefik:
extends: # used to define common settings over my other composes
file: ../_common/common.yml
service: x-common
container_name: traefik
hostname: traefik
image: traefik:${VERSION} # aka latest
restart: always
healthcheck:
test: traefik healthcheck || exit 1
ports:
- "${PORT_HTTP}:80"
- "${PORT_HTTPS}:443/tcp"
- "${PORT_HTTPS}:443/udp"
expose:
- "80"
- "443"
networks:
traefik:
ipv4_address: xxx.xxx.xxx.xxx # ip adress of previously created traefik docker network
secrets:
- cloudflare_api_email
- cloudflare_api_key
- cloudflare_zone_api_key
env_file:
- ./env/traefik.env
deploy:
resources:
limits:
memory: 2G
tmpfs:
- /tmp:rw,async,noatime,nodiratime,uid=${VM_UID},gid=${VM_GID},noexec,nosuid,size=1G
- /plugins-storage:rw,async,noatime,nodiratime,uid=${VM_UID},gid=${VM_GID},noexec,nosuid,size=512M
volumes:
- ./conf/traefik.yml:/etc/traefik/traefik.yml:ro # main traefik configuration file
- ./conf/traefik:/etc/traefik/traefik.d:ro # dynamic services as single yaml file
- ./conf/bouncer/ban.min.html:/ban.html:ro # html page to serve banned from Crowdsec plugin
- ./datas/traefik/acme.json:/etc/traefik/acme.json:rw # ssl certificate contents for Traefik
- /var/logs/traefik:/log:rw # log files (formatted for simplified analysis)
traefik.env
Le fichier traefik.env est relativement simple :
1
2
3
4
5
6
7
# If you use Cloudflare as DNS providers, adjust for others
CF_API_EMAIL_FILE: /run/secrets/cloudflare_api_email
CF_API_KEY_FILE: /run/secrets/cloudflare_api_key
GIN_MODE: 'release'
MASTER_DOMAIN: 'example.com'
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL: 'user@provider.com'
mailcow.yml
Dans le fichier mailcow.yml (gestion dynamique) :
Les routeurs sont séparés si vous utilisez “kereis/traefik-certs-dumper” pour obtenir des certificats SSL individuels pour les sous-domaines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
http:
routers: # routers ar separate in case you use 'kereis/traefik-certs-dumper' to get subdomains individual ssl certificates
mailcow-autodiscover:
entryPoints:
- https
rule: Host(`autodiscover.example.com`)
middlewares:
- mailcow@file
tls:
certResolver: letsencrypt
service: mailcow@file
mailcow-autoconfig:
entryPoints:
- https
rule: Host(`autoconfig.example.com`)
middlewares:
- mailcow@file
tls:
certResolver: letsencrypt
service: mailcow@file
mailcow-mta-sts:
entryPoints:
- https
rule: Host(`mta-sts.example.com`)
middlewares:
- mailcow@file
tls:
certResolver: letsencrypt
service: mailcow@file
mailcow-mail:
entryPoints:
- https
rule: Host(`mail.example.com`)
middlewares:
- mailcow@file
tls:
certResolver: letsencrypt
service: mailcow@file
mailcow-webmail:
entryPoints:
- https
rule: Host(`webmail.example.com`)
middlewares:
- mailcow@file
tls:
certResolver: letsencrypt
service: mailcow@file
services:
mailcow:
loadBalancer:
servers:
- url: https://ip:port # ip:port of mailcow instance
middlewares:
httpsredirect:
redirectScheme:
scheme: https
ratelimit:
rateLimit:
average: 256
burst: 512
period: 10s
defaults:
headers:
frameDeny: false
customFrameOptionsValue: SAMEORIGIN
browserXssFilter: false
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 15552000
hostsProxyHeaders:
- "X-Forwarded-Host"
- "X-Forwarded-Proto"
- "X-Forwarded-For"
customRequestheaders:
Alt-Svc: "h3=':443'; ma=86400"
customResponseHeaders:
Alt-Svc: "h3=':443'; ma=86400"
security:
headers:
customRequestheaders:
X-Content-Type-Options: ""
X-Forwarded-Proto: https
customResponseHeaders:
Permissions-Policy: "fullscreen=(*), display-capture=(self), accelerometer=(), battery=(), camera=(), autoplay=(self), vibrate=(self), geolocation=(self), midi=(self), notifications=(*), push=(*), microphone=(self), magnetometer=(self), gyroscope=(self), payment=(self)"
X-Forwarded-Proto: https
X-Permitted-Cross-Domain-Policies: "none"
X-Content-Type-Options: ""
sslProxyHeaders:
X-Forwarded-Proto: https
referrerPolicy: strict-origin-when-cross-origin
mailcow:
chain:
middlewares:
- httpsredirect@file
- ratelimit@file
- defaults@file
- security@file
crowdsec.yml
Dans le fichier crowdsec.yml (gestion dynamique) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http:
middlewares:
crowdsec:
plugin:
bouncer:
enabled: true
logLevel: ERROR
updateIntervalSeconds: 60
updateMaxFailure: 0
defaultDecisionSeconds: 60
httpTimeoutSeconds: 10
forwardedHeadersCustomName: 'X-Custom-Header'
remediationHeadersCustomName: 'cs-remediation'
forwardedHeadersTrustedIPs:
# Cloudflare IPs
# same as 'trustedIPs: &trustedIps' in traefik.yml
clientTrustedIPs:
# same as 'local & lan ip ranges' in traefik.yml
crowdsecMode: stream
crowdsecLapiKey: "[CHANGEME]"
crowdsecLapiHost: "[IP]:[PORT]" # ip:port of crowdsec LAPI instance
crowdsecLapiScheme: http
crowdsecLapiTLSInsecureVerify: false
CrowdsecAppsecEnabled: true
CrowdsecAppsecHost: "[IP]:[PORT]" # ip:port of crowdsec Appsec instance
CrowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
banHTMLFilePath: "./ban.html" # html page to display "you're banned" message
redisCacheEnabled: true # false if you don't want to use Redis for caching
redisCacheHost: "[IP]:[PORT]" # ip:port of Redis instance
geoblok.yml
Dans le fichier geoblok.yml (gestion dynamique) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http:
middlewares:
geoblock:
plugin:
geoblock:
silentStartUp: true
allowLocalRequests: true
logLocalRequests: false
logAllowedRequests: false
logApiRequests: false
api: https://get.geojs.io/v1/ip/country/{ip}
apiTimeoutMs: 750
cacheSize: 512
forceMonthlyUpdate: true
allowUnknownCountries: false
unknownCountryApiResponse: "nil"
addCountryHeader: true
allowedIPAddresses:
# same as 'local & lan ip ranges' in traefik.yml
blackListMode: true # all country codes listed in 'countries' are not allowed to access
countries:
- RU # Russian Federation (the)
Mailcow
docker-compose.override.yml
L’utilité de la surcharge du docker-compose.yml est de rendre les modification apportés non modifiables à chaque mise à jour de mailcow: dockerized.
Cela permet de définir les points suivants :
- Spécification des serveur DNS à utiliser pour les résolutions
- Health check pour chaque conteneur ou c’est possible
- Limitation de mémoire (et possiblement de cpu)
- Exclusion de la prise en charge par Watchtower
- Simplification du nommage des conteneurs (utile pour paramétrer Uptime Kuma)
- Ajout des rapport DMARC de RSpamd (Ofelia)
- utilisation de traefik-certs-dumper pour récupérer les certificats SSL depuis Traefik
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
x-defaults: &x-defaults
healthcheck:
interval: 60s
timeout: 10s
retries: 5
start_period: 60s
deploy:
resources:
limits:
memory: 64M
x-labels: &x-labels
<<: *x-defaults
labels:
logging: "metrics"
com.centurylinklabs.watchtower.enable: false
x-common: &x-common
<<: *x-labels
dns:
- [DNS N° 1]
- [DNS N° 2]
services:
unbound-mailcow:
<<: *x-labels
container_name: ${COMPOSE_PROJECT_NAME}-unbound
deploy:
resources:
limits:
memory: 256M
mysql-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-mysql
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 512M
redis-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-redis
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 128M
clamd-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-clamd
deploy:
resources:
limits:
memory: 4G
rspamd-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-rspamd
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 2G
labels:
ofelia.enabled: "true"
ofelia.job-exec.rspamd_dmarc_reporting_yesterday.schedule: "@every 24h"
ofelia.job-exec.rspamd_dmarc_reporting_yesterday.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/bin/rspamadm dmarc_report $(date --date yesterday '+%Y%m%d') > /var/lib/rspamd/dmarc_reports_last_log 2>&1 || exit 0\""
php-fpm-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-phpfpm
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 256M
sogo-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-sogo
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 512M
dovecot-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-dovecot
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 512M
postfix-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-postfix
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 1G
ulimits:
nofile:
soft: 65535
hard: 65535
memcached-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-memcached
healthcheck:
test: uname -a || exit 1
nginx-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-nginx
healthcheck:
test: uname -a || exit 1
acme-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-acme
healthcheck:
test: uname -a || exit 1
netfilter-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-netfilter
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 128M
watchdog-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-watchdog
environment:
- CHECK_UNBOUND=0
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 128M
dockerapi-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-dockerapi
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 256M
olefy-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-olefy
healthcheck:
test: uname -a || exit 1
ofelia-mailcow:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-ofelia
healthcheck:
test: uname -a || exit 1
deploy:
resources:
limits:
memory: 1G
depends_on:
- rspamd-mailcow
certdumper:
<<: *x-common
container_name: ${COMPOSE_PROJECT_NAME}-certdumper
image: ghcr.io/kereis/traefik-certs-dumper:latest
command: --restart-containers mailcowdockerized-postfix,mailcowdockerized-dovecot,mailcowdockerized-nginx
restart: unless-stopped
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
deploy:
resources:
limits:
memory: 512M
network_mode: none
environment:
POST_HOOK_FILE_PATH: "/hook/hook.sh"
DOMAIN: ${MAILCOW_HOSTNAME}
ACME_FILE: /acme.json
OVERRIDE_UID: 0
OVERRIDE_GID: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/docker/traefik/datas/traefik:/traefik:ro # acme.json folder
- /opt/mailcow/output:/output:rw
- /opt/mailcow-dockerized/data/assets/ssl:/certificates:rw
- /opt/mailcow/posthook.sh:/hook/hook.sh
Précision concernant certdumper :
- Il est nécessaire de créer le répertoire /opt/mailcow/output (sinon ajuster dans le compose)
- Et le script shell posthook.sh suivant doit être créé :
1
2
3
#!/bin/bash
cp -r /output/. /certificates/
exit 0
start/stop/restart .sh
L’utilité de ces petits scripts est minime , sauf dans le cas d’intéractions par des commandes externes :)
start.sh
1
2
3
4
5
6
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" 1>&2
exit 1
fi
docker compose up -d
exit 0
stop.sh
1
2
3
4
5
6
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" 1>&2
exit 1
fi
docker compose down
exit 0
restart.sh
1
2
3
4
cd /opt/mailcow-dockerized # adjust if you installed elsewhere
sh stop.sh
sh start.sh
exit 0
mailcow.conf
Les seuls éléments à modifier/vérifier ou mettre à jour dans ce fichier de configuration sont :
- MAILCOW_HOSTNAME (qui doit être le nom complet du serveur de mail, exemple: mail.example.com)
- HTTP_PORT / HTTPS_PORT (si les ports 80 et 443 sont déjà pris)
- TZ (qui définit la zone horaire du serveur)
- ADDITIONAL_SAN (qui doit contenir au minimum mta-sts.*)
- ADDITIONAL_SERVER_NAMES (comme pour ADDITIONAL_SAN)
- WATCHDOG_NOTIFY_EMAIL (si vous désirez recevoir des notifications sur la surveillance du serveur)
- WATCHDOG_NOTIFY_BAN (à y si vous souhaitez être notifié par mail des IP bannies)
- WATCHDOG_NOTIFY_START (à y si vous souhaitre recevoir un mail indiquant le démarrage du serveur)
- **IPV4NETWORK** _(à ajuster si au démarrage du serveur il vous insulte parce que des IPs sont déjà utilisées…)
main.cf
Il s’agit du fichier de configuration principale de Postfix ; situé dans le dossier data/conf/postfix.
Si vous mettez à jour votre instance mailcow, alors je vous recommande de la stopper. Ensuite vous retirez tout ce qui se trouve sous les lignes suivantes :
1
2
# DO NOT EDIT ANYTHING BELOW #
# User overrides #
extra.cf
Il s’agit de la sucharge du fichier main.cf, situé dans le même dossier, dans lequel vous mettez juste :
1
2
myhostname = mail.example.com
smtp_helo_name = mail.example.com
Rapports DMARC
La configuration, côté conteneur, est déjà spécifiée dans docker-compose.override.yml.
DMARC est une technologie qui utilise SPF et DKIM pour permettre aux propriétaires de domaines de publier des politiques concernant la manière dont les messages électroniques comportant leur domaine dans le champ “From” (RFC5322) doivent être traités.
Par exemple, DMARC peut être configuré pour exiger qu’un MTA récepteur mette en quarantaine ou rejette les messages qui ne disposent pas d’un identifiant DKIM ou SPF aligné.
DMARC peut également être configuré pour demander des rapports aux MTA distants concernant ces messages afin d’aider à identifier les abus et/ou les erreurs de configuration et de faciliter la prise de décisions éclairées concernant l’application des politiques.
Sources:
Pour activer ces rapports, il est nécessaire de créer le fichier data/conf/rspamd/local.d/dmarc.conf comme suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
reporting {
enabled = true;
email = 'noreply@example.com';
domain = 'example.com';
org_name = 'Example';
helo = 'rspamd';
smtp = 'postfix';
smtp_port = 25;
from_name = 'DMARC Report';
msgid_from = 'rspamd.mail.example.com';
max_entries = 2k;
keys_expire = 2d;
}
L’adresse noreply@example.com doit exister (ou être un alias valide)
Paramètres des boites mails
Pour passer certains tests de validations du serveur de mail, est nécessaire de désactiver les options suivantes :
- Appliquer le TSL entrant
- Appliquer le TSL sortant
Courriel / Configuration / Boites de réception / Edition de la boite de réception / Politique de chiffrement
DNS
Entrées DNS à mettre en place
RUA : rapports DMARC agrégé (vue complète de l’ensemble du trafic d’un domaine)
RUF : rapports DMARC médico-légal (copies expurgées des courriels individuels qui ne sont pas 100% conformes à DMARC)
Description | Name | Type | Value |
SPF | @ | TXT | “v=spf1 a ra=postmaster -all” |
DKIM | dkim._domainkey | TXT | “v=DKIM1;k=rsa;t=s;s=email;p=(1)” |
DMARC | _dmarc | TXT | “v=DMARC1; p=reject; rua=mailto:(2)@dmarc-reports.cloudflare.net,mailto:postmaster@example.com; ruf=mailto:postmaster@example.com” |
MTA STS | mta-sts | CNAME | mail.example.com |
MTA STS | _mta-sts | TXT | “v=STSv1; id=20220324000000Z” |
A noter ce qui suit :
- (1) : la valeur de p est indiquée dans la configuration du domain dans la webui de Mailcow : Domaine: example.com (dkim._domainkey)
- (2) : Cette entrée est propre à Cloudflare et ajoutée automatiquement une fois activée la gestion de DMARC
MTS STS
La configuration des entrées DNS a été mise en place comme ci-dessus (MTA STS).
Il convient aussi de créer le fichier mta-sts.txt dans le répertoire data/web/.well-known/ de mailcow dockerized comme suit :
1
2
3
4
5
version: STSv1
mode: enforce
max_age: 172800
mx: mail.example.com
mx: *.example.com
Vous devriez être en mesure de l’afficher en allant sur l’addresse ci-dessous:
1
https://mta-sts.example.com/.well-known/mta-sts.txt
Enregistrement DS
Cet enregistrement dns est à paramétrer chez votre gestionnaire de nom de domaine.
L’enregistrement DS est utilisé pour vérifier l’authenticité des zones enfants des zones DNSSEC. L’enregistrement de clé DS d’une zone parent contient un hachage du KSK d’une zone enfant. Un résolveur DNSSEC peut donc vérifier l’authenticité de la zone enfant en hachant son enregistrement KSK et en le comparant à celui de l’enregistrement DS de la zone parent.
Par exemple, chez OVH avec Cloudflare ça se présente ainsi :
Key Tag | Flag | Algorithme | Clé publique (encodée base64) |
2371 | 257 | 13 | (3) |
La correspondance des champs OVH – Cloudflare est la suivante:
- key tag : Balise clé
- Flag : Indicateurs
- Algorithme : Algorithme + Type Digest – 2
- Clé publique (encodée base64) : Clé publique
(3) La valeur de la clée publique à renseigner dans le formulaire d’OVH est indiquée chez Cloudflare dans les paramètres du domaine :
1
Domain / DNS / Settings / DNSSEC / DS Record / Public Key
Tests
Une fois que la configuration de l’ensemble est correctement réalisée et que mailcow démarre bien, il est temps de tester le serveur.
Il faut au préalable avoir paramétrer un domain ainsi qu’une boite mail
dnssec-debugger.verisignlabs.com
Ce premier test permet de vérifier la partie DNSSEC du serveur.
Pour informations, toutes les cases doivent être cochées vertes.
mail-tester.com
Ce second test permet de vérifier par un envoit / réception de mail qu’il est fonctionnel.
Il suffit d’envoyer un mail quelconque à l’adresse spécifié sur le formulaire puis de cliquer sur le bouton de vérification.
Dans l’absolu, un score de 10/10 est attendu.
checktls.com/TestReceiver
Ce site permet de tester un peu plus profondément le serveur.
Voici comment procéder :
- eMail Target : renseigner une adresse email valide
- More Options : dépliez pour avoir toutes les options
- Check MTA-STS : cochez
- Check DANE : cochez
- Check Cert Sigs : cochez
- IPv4 : cochez
- IPv6 : cochez (si vous souhaitez tester la partie IP v6)
- Check DNSSEC : cochez
- No DNS Cache : cochez (pour être certain d’utiliser les informations à jour)
- Run Test : cliquez !
La aussi, dans l’absolu, vous devez avoir OK partout.
Vous pouvez analyser le rapport détaillé des actions en dessous du tableau récapitulatif
email-security-scans.org
Alors ce dernier test est à faire uniquement si les précédents indiquent que tout est ok.
Sinon, ça ne sert pas à grand chose de le faire.
Pensez à désactiver la limitation d’envoi sur le domaine et/ou la boite mail dans la webui de mailcow
Voici comment procéder :
- Vous saisissez une adresse mail valide
- Vous cliquez sur *start
- Vous attendez qu’il vous dise que les mails sont parti et qu’il attend la réponse
- Vous ouvrez votre client mail
- Vous ouvrez le mail reçus de la part de email-security-scans.org
- Vous faire “Répondres à Tous” puis vous envoyez LES mails
Vous n’avez plus qu’à attendre que le site réceptionne tous les mails que vous venez de lui envoyer (c’est long).
Si vous n’avez pas de dns inverse en IP v6, les tests liés échoueront !
Concernant la partie MTS-STS et Dane, il se peut que le serveur échoue.
Lisez bien les annotation de ces tests ; elles précisent que les tests ne portent pas sur votre serveur de mail mais sur sa capacité à répondre aux exigeances de email-security-scans.org.
Et là j’ai encore à fouiller pour savoir à quoi ça correspond :p
Résultats
Globalement, voici à quoi vous attendre comme résultats sur ces différents tests :
- dnssec-debugger.verisignlabs.com : tout est coché au vert
- mail-tester.com : score de 10/10
- checktls.com/TestReceiver : tout sur OK
- email-security-scans.org : note de 7/10
Conclusion
Avec cette configuration, normalement, vous disposez d’un serveur :
- Opérationnel en IPv4
- Opérationnel en IPv6
- Protégé par Crowdsec
- Protégé par Geoblock
- Supportant DNSSEC ainsi que SPF, DKIM, DMARC
Ça vous donne un serveur pleinement opérationnel, protégé et à jour des mesures de protection efficace.