Coucou,

Un très très très long billet dont la longueur ne sera pas habituelle.

J’enchaîne la description de l’installation des différents services de mon serveur dedié. Aujourd’hui on passe par le classique serveur mail. Vu que — pour une fois — je n’étais pas trop pressé, j’ai eu le temps de faire ce que que je voulais à la base. Au programme donc, un cocktail assez traditionnel :

Il y a un peu plus de matière que pour les DNS, je vais essayer de rentrer un peu dans les détails. Gardez à l’esprit que mes classes Puppet (tsacha_mail et tsacha_ldap) servent de bonne documentation. Malgré le manque de commentaires, on y entrevoit assez facilement la chronologie de l’installation.

Pré-requis d’une authentification mail avec OpenLDAP

Pour centraliser un peu l’authentification, je vais passer par un LDAP situé sur un conteneur séparé.

Configuration d’OpenLDAP

Sous Debian, l’installation n’est pas très compliquée : on va avoir besoin d’installer slapd pour le serveur, et ldap-utils pour manipuler l’annuaire.

Au niveau de la configuration, on doit activer les protocoles voulus dans /etc/default/slapd. Disposant de certificats valides, déployés par Puppet, et n’étant pas spécialement concerné par des problématiques de performances, je conserve seulement le LDAPS (distant) et le LDAPI (local).

Pour les clients LDAP, on peut être un peu plus rigoureux sur la vérification du certificat en modifiant /etc/ldap/ldap.conf.

Voilà, c’est à peu près tout pour les fichiers à plat. La configuration de LDAP étant stockée elle-même dans un arbre LDAP, l’essentiel des réglages s’effectura à coups de LDIF.

Configuration de l’annuaire LDAP

Pour ces opérations, je suis pas spécialement fier de ma classe Puppet . C’est adapté pour un premier déploiement mais pour le test & repair ce n’est pas encore ça.

Premièrement, il ne faut perdre d’esprit que c’est une installation silencieuse, nous devons donc oublier le joli assistant d’installation du paquet slapd. Par défaut, les réponses en matière de suffixe LDAP sont assez éloignées de mon but : dc=localhost n’est pas satisfaisant.

La configuration du certificat est à inscrire dans cn=config ainsi que la modification les ACL pour que mes futurs services puissent accéder aux comptes utilisateurs, sans oublier quelques index bien placés.

Toutes ces opérations sont centralisées dans un seul fichier config.ldif qui sont appliquées par le biais d’une connexion en LDAPI.

/etc/init.d/slapd restart
ldapmodify -Y EXTERNAL -H ldapi:/// -f config.ldif

Toujours dans cn=config, j’en profite pour changer le mot de passe admin grâce à un second LDIF pass-db.ldif. Je stocke dans puppet le hash du mot de passe fourni par slappasswd.

# ldapmodify -Y EXTERNAL -H ldapi:/// -f pass-db.ldif

Au niveau des vérifications faites par Puppet, je contrôle si le domaine est bien changé avec une interrogation en LDAPI et j’effectue en connexion en LDAPS sur le compte admin LDAP pour tester le mot de passe.

ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b olcDatabase={1}hdb,cn=config | grep 'olcSuffix: dc=ldap,dc=s,dc=tremoureux,dc=fr'
ldapsearch -H ldaps://ldap.s.tremoureux.fr/ -x -D 'cn=admin,dc=ldap,dc=s,dc=tremoureux,dc=fr' -w '$tsacha_private::ldap::ldap_password' -b 'dc=ldap,dc=s,dc=tremoureux,dc=fr'

Modification du schéma inetOrgPerson

Je vais stocker mes adresses mails dans un schéma inetOrgPerson mais pour avoir des redirections fonctionnelles j’ai besoin de rajouter un champ mailAlias qui contiendra toutes les adresses mails secondaires d’une personne.

Mon schéma est disponible ici. Je l’applique ensuite naturellement dans cn=schema. Puppet vérifiera la bonne importation en regardant la présence ou non de mailAlias.

Création de l’arbre LDAP

Une fois tous les prérequis terminés, il faut recréer les bases de l’arbre LDAP. Je passe par un script pour faciliter l’écritures des règles Puppet.

Ce script va détruire l’arbre installé par Debian (avec la mauvaise racine localhost), et en recréer un tout beau. Je change ensuite le mot de passe du compte admin de l’arbre (à ne pas confondre avec le compte admin LDAP modifié plus haut).

La vérification de l’application de ce script passe par une authentification en LDAPS sur l’arbre avec l’utilisateur cn=admin,dc=ldap,dc=s,dc=tremoureux,dc=fr.

ldapsearch -H ldaps://ldap.s.tremoureux.fr/ -x -w '$tsacha_private::ldap::admin_password' -D cn=admin,dc=ldap,dc=s,dc=tremoureux,dc=fr -b dc=ldap,dc=s,dc=tremoureux,dc=fr | grep 'dn: cn=admin,dc=ldap,dc=s,dc=tremoureux,dc=fr'

Voilà, c’est à peu près tout pour LDAP.

Configuration complète de Dovecot en liaison avec LDAP

Installation de Dovecot

La mise en place de Dovecot va nécessiter les paquets suivants :

L’installation de ces paquets est décrite ici.

Configuration de Dovecot

J’utilise une recette Puppet pour déployer tous les fichiers de configurations de Dovecot. Rien à signaler au niveau de sa rédaction. En revanche, ces fichiers de configurations sont intéressants à décrire.

Je passe en revue les paramètres généraux :

dovecot.conf

Tous les fichiers suivants sont situés dans /etc/dovecot/conf.d/ :

10-mail.conf

10-master.conf

On désactive l’IMAP et on active l’IMAPS

inet_listener imap {
  port = 0
}

inet_listener imaps {
  port = 993
  ssl = yes
}

La liaison entre Postfix et Dovecot va passer par LMTP (plus performant que LDA).

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
	group = postfix
	mode = 0666
	user = postfix
  }
}

10-ssl.conf

On active et on spécifie l’emplacement des certificats.

15-ldap.conf

Ce fichier sert égalemement pour le LMTP.

Je rajoute également Sieve aux plugins :

protocol lda {
  mail_plugins = $mail_plugins sieve
}

15-mailboxes.conf

20-imap.conf

20-lmtp.conf

20-managesieve.conf

ManageSieve est un protocole pour permettre de gérer ses filtres Sieve à distance.

90-sieve.conf

Ici, quelques paramètres liés à l’arborescence du serveur :

Liaison à LDAP

Premièrement, on active d’authentification avec LDAP.

/etc/dovecot/conf.d/10-auth.conf

/etc/dovecot/conf.d/auth-ldap.conf

Dans ce fichier on spécifie comment utiliser LDAP. La recherche des utilisateurs est dans le bloc userdb.

userdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext
  default_fields = uid=vmail gid=vmail
}

Je surcharge l’UID et le GID pour être en accord avec mon utilisateur vmail qui est indépendant de la boîte.

Pour l’authentification, c’est dans le bloc passdb. J’y spécifie le même fichier de configuration.

passdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext
}

/etc/dovecot/dovecot-ldap.conf.ext

C’est ici que tout se passe. En enlevant tous les commentaires (verbeux) ça nous donne ça :

uris = ldaps://ldap.s.tremoureux.fr/
dn = cn=dovecot,dc=ldap,dc=s,dc=tremoureux,dc=fr
dnpass = <%= scope.lookupvar('tsacha_private::ldap::dovecot_password') %>
auth_bind = no
base = ou=users,dc=ldap,dc=s,dc=tremoureux,dc=fr
scope = subtree
user_attrs =
user_filter = (&(objectClass=person)(mail=%u))
pass_attrs = mail=user,userPassword=password
pass_filter = (&(objectClass=person)(|(mail=%u)(mailAlias=%u)))

J’utilise la configuration décrite dans la documentation officielle à cette adresse en adaptant à ma sauce les recherches pour prendre en compte mes alias. Je peux ainsi me log en IMAP avec des adresses un peu plus courte que mon adresse courante.

Peuplement de l’annuaire LDAP

Après la configuration de Dovecot, il lui faut des données à exploiter. Nous devons rajouter à coups de LDIF un utilisateur dans LDAP pour Dovecot.

À noter l’ajout d’une OrganizationalUnit qui contient tous mes utilisateurs.

Du côté de Puppet, ces ajouts sont fait depuis le compte admin de l’arbre. Le mot de passe du compte de Dovecot est réactualisé si l’authentification avec ce dernier ne se passe pas.

Les boîtes mails — qui ne sont pas publiées sur Git — ont cette forme :

dn: uid=sacha,ou=users,dc=ldap,dc=s,dc=tremoureux,dc=fr
cn: Sacha Trémoureux
givenName: Sacha
sn: Trémoureux
uid: sacha
mail: mail@domain.tld
mailAlias: m@domain.tld
mailAlias: postmaster@domain.tld
userPassword: {SSHA}graou
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top

C’est à peu près tout pour Dovecot.

Configuration de Postfix en liaison avec Dovecot

Next : Postfix.

Encore une fois l’installation n’est pas le problème et se résume à l’installation des paquets et au placement des fichiers de configuration au bon endroit. Les alias et les adresses virtuelles de Postfix sont regénérés par Puppet en cas de modification.

Voici le détail des fichiers de configuration :

/etc/postfix/main.cf

La majorité des réglages se fait ici. Je ne vais pas revenir sur l’ensemble des directives, un bon nombre de tutoriaux le fera mieux que moi. Les quelques points spécifiques à mes choix d’infrastructure sont les suivants :

La gestion des DKIM se fait avec un service situé sur le même conteneur. Je passe donc par un socket pour la communication entre Postfix et OpenDKIM.

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

C’est Dovecot qui va authentifier pour le compte de Postfix.

#enable SMTP auth for relaying
smtpd_sasl_auth_enable       = yes
broken_sasl_auth_clients     = yes
smtpd_sasl_type              = dovecot
smtpd_sasl_path              = private/auth

Et c’est toujours Dovecot qui va placer les mails au bon endroit grâce à LMTP.

mailbox_transport = lmtp:unix:private/dovecot-lmtp
local_recipient_maps =

/etc/postfix/master.cf

Le fichier master.cf centralise la gestion des processus intervenant dans Postfix.

On active Submission (port 587) :

submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

On active Spamassassin. En haut :

smtp      inet  n       -       -       -       -       smtpd
  -o content_filter=spamassassin

Et en bas !

spamassassin unix -     n       n       -       -       pipe
		user=debian-spamd argv=/usr/bin/spamc -f -e
		/usr/sbin/sendmail -oi -f ${sender} ${recipient}

/etc/postfix/ldap-virtual.cf

Ce fichier décrit comment trouver des alias dans LDAP. En entrée, Postfix effectue la requêté spécifiée par la directive query_filter et sélectionnera en sortie le champ mail décrit par result_attribute pour connaître le propriétaire de l’alias.

Et voilà pour Postfix.

Montrer patte blanche en sortie avec OpenDKIM et SPF

Deux solutions complémentaires sont à utiliser pour certifier que notre serveur mail est bien légitime lors de l’envoi de mail depuis un domaine spécifique.

SPF

La première est SPF : dans la zone du domaine en question, nous ajoutons un champ particulier qui contient la liste des IP des serveurs autorisés à émettre du mail avec ce domaine.

Sur mon domaine voilà le résultat :

dig tremoureux.fr TXT +short
"v=spf1 ip4:87.98.218.210 ip4:91.121.61.39 ip6:2001:41d0:1:49d2::/64 ip4:176.9.119.5 ip6:2a01:4f8:151:7307:1::4 -all"

Le -all prévient que tous les serveurs mails émettant des mails sous tremoureux.fr ne sont pas autorisés à le faire. Libre ensuite aux autres serveurs les recevant d’accepter ou non le mail.

Configuration d’OpenDKIM

La seconde solution est un peu plus longue à mettre en place est DKIM. Cela consiste à signer une partie des corps des messages la placer dans les en-tête. La clé publique est diffusée par DNS ensuite sur les domaines concernés. Le serveur recevant le mail vérifie compare ensuite ce nouvel en-tête avec le corps du message. Si il y a corrélation, il va avoir tendance à considérer le mail comme légitime.

Sous Debian le paquet opendkim sert à effectuer ce travail. Sa configuration est disponible ici.

Sur le même schéma que précédemment, voici la description des fichiers de configuration :

/etc/default/opendkim

Vu que Postfix est OpenDKIM tournent sur le même conteneur, j’ai jugé bon de ne pas ouvrir un énième port. On va juste placer le socket dans un endroit accessible à Postfix (qui tourne dans un chroot par défaut).

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

Attention aux droits dans ces dossiers : Postfix doit pouvoir y accéder.

/etc/opendkim/opendkim.conf

Pas grand chose à signaler dans ce fichier de configuration principal. On spécifie juste où est le reste :

KeyTable           /etc/opendkim/KeyTable
SigningTable       /etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts      /etc/opendkim/TrustedHosts

/etc/opendkim/TrustedHosts

Ici on place simplement la liste des domaines acceptés, séparés par des retour à la ligne.

/etc/opendkim/KeyTable

On y place la liste des emplacements des clés privées de chacun des domaines :

default._domainkey.domain.tld domain.tld:default:/etc/opendkim/keys/domain.tld/default.private

Dans le cas où on dispose de plusieurs serveurs SMTP, on peut avoir une clé par serveur. Ici, ce n’est pas mon cas, ma clé s’appelle donc default.

/etc/opendkim/SigningTable

Ici, on référence quel domaine utilise quelle clé :

domain.tld default._domainkey.domain.tld

Voilà pour la configuration.

Génération des clés DKIM

Après la configuration d’OpenDKIM, il nous faut générer une paire de clés pour chaque domaine géré.

mkdir -p /etc/opendkim/keys/domain.tld
cd /etc/opendkim/keys/domain.tld
opendkim-genkey -r -d domain.tld
chown -R opendkim:opendkim /etc/opendkim

La commande opendkim-genkey génère la clé publique sous un format assez original : la ligne à placer directement dans le fichier de zone du domaine en question.

En action :

dig default._domainkey.tremoureux.fr TXT +short
"v=DKIM1\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJBOzCOLJHGTIJhxZMlqUMg+YlMmgMozSOGcPixoqV9zAw6kSSPJ3GDNoxAjYi8NooRrlKalF0ZFDyTVdkJJz6ETpPUjgQfEoEGrIHIlvRPaiZXk0/umkqeW2WIxDN56Mrt8N269IaL/GoVyeMjx4zO9PRF4uUkZyz0ICmBCH2kQIDAQAB"

Et voilà !

L’antispam avec Spamassasin (et une dose de Sieve)

Comme d’habitude je passe l’installation simplissime de Spamassassin. L’intégration à Postfix a été déjà effectuée précédemment. Le reste du travail est disponible ici.

/etc/spamassassin/local.cf.erb

Ici, quelques configurations assez générales à rajouter.

Déplacement automatique des Spams

Avec Sieve la règle est la suivante :

require ["fileinto"];
# Move spam to spam folder
if header :contains "X-Spam-Flag" ["YES"] {
  fileinto "Junk";
  stop;
}

J’ai placé cette règle dans le fichier de script par défaut. Elle s’applique donc à tous mes comptes.

Apprentissage automatique

Pour renforcer au fil du temps la lutte contre les spams, il est préférable de donner régulièrement à manger à Spamassassin. J’ai donc un cron qui passe quotidiennement dans les boîtes mails et apprendre de l’ensemble des dossiers Spam des utilisateurs. J’ai prévenu les quelques personnes que j’héberge de faire attention au contenu de celle-ci.

Je dispose également d’une adresse mail qui sert pour les faux-positifs. Les utilisateurs forward les mails à cette adresse et Spamassassin essaiera de corriger le tir les prochaines fois.

Le cron en question :

#!/bin/sh
sa-learn --spam /srv/mail/*\@*/.Junk/cur
sa-learn --ham /srv/mail/ham@domain.tld/{cur,new}

Conclusion

Je crois qu’on a fait le tour.

Une architecture mail est en général assez compliquée : un grand nombre de briques logicielles sont imbriquées entre-elles.

Une fois encore, mon infrastructure est conçue ainsi avant tout à des fins d’entraînement mais elle fonctionne parfaitement pour mes maigres besoins. Du tunning est sûrement nécessaire si on augmente de nombre de comptes.

Plus on raconte de choses, plus grandes sont les chances de raconter des bêtises. Si des points vous sautent aux yeux, n’hésitez-pas à me contacter par mail ou Jabber.

La suite sera moins épaisse !