postfix, spf, dkim, dmarc avec plusieurs domaines

Après m’être un peu battu, voilà mes notes 🙂

Cas d’utilisation:

{ Internet }----[ relai smtp dmz ]----[ serveur de mail réel bien à l'abri ]

Du coup, sur le relai en DMZ, niveau SMTP, je gère toute la partie:

  • vérification spf/dkim/dmarc des mails qui arrivent ;
  • signature (dkim) des messages qui partent ;
  • authentification pour ceux qui envoient les mails.

Tout ce qui est anti-spam/anti-virus se fait sur la machine réelle.

Bien entendu, je gère aussi les mails pour plusieurs domaines.

Prérequis/divers

  • un postfix qui marche (c’est à dire: reverse dns correct, pas en mode relai ouvert, et qui sait déjà envoyer/recevoir des mails) ;
  • avoir au moins jeté un œil sur les RFC et autres documentes de référence pour DKIM/DMARC et SPF histoire de savoir ce que c’est et comment ça marche1 ;
  • un serveur DNS fonctionnel (pas un truc qui bloque/filtre les enregistrement TXT/SRV)
  • pour info: la partie DKIM (signature+vérification) peut aussi se faire avec amavisd.

SPF

DNS

enregistrement TXT et/ou SRV (j’utilise un a:… et pas mx car tous les MX n’ont pas vocation à envoyer du mail pour un domaine donné):

example.net IN TXT "v=spf1 a:smtp.example.net ~all"
example.net IN SRV "v=spf1 a:smtp.example.net ~all"

note: On peut remplacer le ~all (softfail) par -all (fail)

Postfix

But, seulement avoir un entête Received-SPF: qui sera utilisé par la suite. Mais ceux qui veulent peuvent aussi avoir des bounces quand il n’y a pas d’enregistrement SPF ou quand le teste échoue.

postfix-policyd-spf-python

Installer le package si nécessaire, puis le configurer (/etc/postfix/policyd-spf.conf):

debugLevel = 1
defaultSeedOnly = 0

HELO_reject = False
Mail_From_reject = False
PermError_reject = False
TempError_Defer = False

skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0//104,::1//128,subnetLAN,ipPubliques

postfix/main.cf

smtpd_recipient_restrictions =
    […votre bazar habituel…]
    check_policy_service unix:private/policyd-spf

postfix/master.cf

policyd-spf    unix    -    n     n    -    0    spawn
    user=nobody argv=/usr/bin/python /usr/bin/policyd-spf /etc/postfix/policyd-spf.conf

DKIM

opendkim

/etc/default/opendkim

SOCKET="local:/var/spool/postfix/var/run/opendkim/opendkim.sock"

note: la socket doit-être dans le chroot de postfix, sinon, postfix ne la verra jamais (cf: saslauthd), ce qui implique:

mkdir /var/spool/postfix/var/run/opendkim/
chown opendkim:opendkim /var/spool/postfix/var/run/opendkim/
adduser postfix opendkim

/etc/opendkim.conf

Syslog yes
SyslogFacility mail
LogWhy no
X-Header no
UMask                002
AutoRestart          yes
AutoRestartCount     5
RequireSafeKeys      no
InternalHosts        /etc/postfix/dkim/trusted.dat
ExternalIgnoreList   /etc/postfix/dkim/trusted.dat
KeyTable             /etc/postfix/dkim/keys.dat
SigningTable         /etc/postfix/dkim/signing.dat
Canonicalization     relaxed/relaxed
Mode                 sv
DisableADSP          yes
OversignHeaders      From

LogWhy est utile en phase de debug/tests, période où le mettre à yes est utile. Idem pour X-Header qui sert juste à savoir si on est bien passé dans opendkim.

/etc/postfix/dkim/trusted.dat

## localhost
127.0.0.1
localhost
::1
## localnet (local.example.net)
192.168.1.0/24
fe80::/10
local.example.net

/etc/postfix/dkim/signing.dat

# "pattern" "%selector%._domainkey.%domain%
example.net example._domainkey.example.net
example.com example._domainkey.example.com
exemple.fr  example._domainkey.exemple.fr

notes:

  • On peut utiliser un selector différent par domaine si on le souhaite
  • On peut utiliser une clé différente par domaine si on le souhaite

/etc/postfix/dkim/keys.dat

#format:
# selector._domainkey.domain domain:selector:/path/to/private/key
example._domainkey.example.net example.net:example:/etc/postfix/dkim/example.private
example._domainkey.example.com example.com:example:/etc/postfix/dkim/example.private
example._domainkey.exemple.fr  exemple.fr:example:/etc/postfix/dkim/example.private

Clés

On génère la clé privée:

openssl genrsa -out /etc/postfix/dkim/example.private 2048

note: On peut se limiter à 1024, mais ça n’est pas recommandé.

On extrait la clé publique:

openssl rsa -inform PEM -outform PEM -in /etc/postfix/dkim/example.private -out /etc/postfix/dkim/example.txt -pubout

On la formatte pour l’enregistrement DNS:

grep -ve "BEGIN PUBLIC KEY" -ve "END PUBLIC KEY" -ve '^\s*$' /etc/postfix/dkim/example.txt | base64

Pour cette clé publique:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ugSjDXVPt9CnwGhtty
b3wG/uJW/Roi3D/woYrHBspi/ToYS3Su4KsNx44FS01AcOHwPkT3jBPcyjPc63rk
1lAskDSFoShZL8sTT5NB9a8NoA++vObNEGWSbqLRdgQ1KexEoC/90pSeBb9L2YDB
NKlyQYTq0ePDfPleTuotD0JFxyEHxXM+9oxa0FtbaAhJhqi9rgH0+PDgfJnPkKhS
Og6O1nI2cQm1zQmg+hoCW0s7j973uSIpyNhfXxfBIF2/8ff/enBCFAQzfZK8jiUx
KDOLmW2XwVGFGrQIAuuCZgX4O+r8e4WyVRYQBnlUaffrU+PpO7jVRmH25SLox1aV
gQIDAQAB
-----END PUBLIC KEY-----

ça donne:

TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwL3VnU2pEWFZQdDlDbndHaHR0eQpiM3dHL3VKVy9Sb2kzRC93b1lySEJzcGkvVG9ZUzNTdTRLc054NDRGUzAxQWNPSHdQa1QzakJQY3lqUGM2M3JrCjFsQXNrRFNGb1NoWkw4c1RUNU5COWE4Tm9BKyt2T2JORUdXU2JxTFJkZ1ExS2V4RW9DLzkwcFNlQmI5TDJZREIKTktseVFZVHEwZVBEZlBsZVR1b3REMEpGeHlFSHhYTSs5b3hhMEZ0YmFBaEpocWk5cmdIMCtQRGdmSm5Qa0toUwpPZzZPMW5JMmNRbTF6UW1nK2hvQ1cwczdqOTczdVNJcHlOaGZYeGZCSUYyLzhmZi9lbkJDRkFRemZaSzhqaVV4CktET0xtVzJYd1ZHRkdyUUlBdXVDWmdYNE8rcjhlNFd5VlJZUUJubFVhZmZyVStQcE83alZSbUgyNVNMb3gxYVYKZ1FJREFRQUIK

DNS

enregistrement TXT

example._domainkey.example.net    IN    TXT    "k=rsa; t=s; p=cléPublique"
example._domainkey.example.com    IN    TXT    "k=rsa; t=s; p=cléPublique"
example._domainkey.exemple.fr     IN    TXT    "k=rsa; t=s; p=cléPublique"

notes:

  • La clé est encodée en base64
  • Il ne faut pas oublier les ; entre les champs

Postfix

main.cf

En fin de fichier, on ajoute:

milter_default_action = accept
milter_protocol = 6
smtpd_milters = unix:/var/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock

note: Cette partie ne permet d’avoir que la vérification des éventuelles signatures des messages reçus

master.cf

Si on veut que les messages sortant soient bien signés:

submission inet n       -       -       -       -       smtpd
    […votre bazar habituel…]
    -o smtpd_milters=unix:/var/run/opendkim/opendkim.sock
smtps     inet  n       -       -       -       -       smtpd
    […votre bazar habituel…]
    -o smtpd_milters=unix:/var/run/opendkim/opendkim.sock

DMARC

Installation d’opendmarc

Pour ceux qui tournent en Debian stable, il est disponible dans les backports, il faut donc au préalable:

Ajouter la source qui va bien dans apt (/etc/apt/sources.list.d/deb_bpo.list):

deb http://ftp.fr.debian.org/debian/ wheezy-backports main contrib non-free

Ajouter le pining qui va bien pour ne pas installer par défaut un package backporté plutôt qu’un officiel (/etc/apt/preferences.d/pining):

Package: *
Pin: release a=stable
Pin-Priority: 900

Package: *
Pin: release a=wheezy-backports
Pin-Priority: 1

Package: *
Pin: release a=testing
Pin-Priority: -10

Package: *
Pin: release a=experimental
Pin-Priority: -10

Package: *
Pin: release a=oldstable
Pin-Priority: -10

Configuration d’opendmarc

/etc/default/opendmarc

SOCKET="local:/var/spool/postfix/var/run/opendmarc/opendmarc.sock"

note: Comme pour opendkim ou saslauthd, on met la socket dans le chroot de postfix:

mkdir /var/spool/postfix/var/run/opendmarc/
chown opendmarc:opendmarc /var/spool/postfix/var/run/opendmarc/
adduser postfix opendmarc

/etc/opendmarc.conf

AuthservID HOSTNAME
PidFile /var/run/opendmarc.pid
RejectFailures false
Syslog true
SyslogFacility mail
TrustedAuthservIDs HOSTNAME
UMask 0002
UserID opendmarc:opendmarc
IgnoreHosts /etc/postfix/dkim/trusted.dat
AuthservIDWithJobID true

notes:

  • On peut remplacer le HOSTNAME par le $myorigin ou le $myhostname de postfix.
  • On réutilise une partie de la conf de opendkim (le trusted.dat).

Configuration de postfix

postfix/main.cf

Il suffit de modifier les lignes smtpd_milters et non_smtpd_milters, en ajoutant à la fin le chemin vers la socket d’opendmarc (chemin relatif au chroot de postfix)

smtpd_milters = unix:/var/run/opendkim/opendkim.sock,unix:/var/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock,unix:/var/run/opendmarc/opendmarc.sock

DNS

Enregistrements TXT comme suit:

_dmarc.example.net IN TXT "v=DMARC1; rf=afrf; p=none; rua=mailto:postmaster@example.net"
_dmarc.example.com IN TXT "v=DMARC1; rf=afrf; p=none; rua=mailto:postmaster@example.com"
_dmarc.exemple.fr  IN TXT "v=DMARC1; rf=afrf; p=none; rua=mailto:postmaster@exemple.fr"

note:

  • Le mail dans le champs rua doit matcher le domaine dans _dmarc….
  • La mise en place de DMARC est progressive: on commence avec un p=none, puis un p=quarantine auquel on ajoute un pct=x, x variant de 0 à 100, puis on passe à p=reject avec le pct=x qui repart de 0 jusqu’à 100…

Docs:

Exemple:

Quand tout marche bien, on trouve ce genre de choses:

Jan 13 12:47:29 MX postfix/smtpd[3255]: 2557039028: client=mail-lb0-x230.google.com[2a00:1450:4010:c04::230]
Jan 13 12:47:29 MX postfix/cleanup[2864]: 2557039028: message-id=<blablabla@mail.gmail.com>
Jan 13 12:47:29 MX opendkim[61062]: 2557039028: mail-lb0-x230.google.com [2a00:1450:4010:c04::230] not internal
Jan 13 12:47:29 MX opendkim[61062]: 2557039028: not authenticated
Jan 13 12:47:29 MX opendmarc[2585]: 2557039028: gmail.com pass
Jan 13 15:13:06 MX postfix/smtpd[19051]: 8E86D39028: client=somehost.somedomain.net[x.y.z.t]
Jan 13 15:13:06 MX postfix/cleanup[19046]: 8E86D39028: message-id=<blablabla@somedomain.net>
Jan 13 15:13:06 MX opendkim[61062]: 8E86D39028: somehost.somedomain.net[x.y.z.t] not internal
Jan 13 15:13:06 MX opendkim[61062]: 8E86D39028: not authenticated
Jan 13 15:13:06 MX opendkim[61062]: 8E86D39028: no signature data
Jan 13 15:13:06 MX opendmarc[2585]: 8E86D39028: somedomain.net none