poste.io: Installation complète
Après avoir cherché et comparé les différents serveurs mail dans un post précédent, j’ai choisi d’utiliser poste.io. Ce serveur mail se base sur Docker pour un déploiement facile et aisé des instances. Cependant une installation « tel que » ne me satisfait pas. Il manque en outre des détails que poste.io ne gère pas et qui restent à installer manuellement en-dehors du conteneur. Mon but est d’éviter le plus possible de toucher au conteneur afin d’éviter des problèmes lors d’une future mise à jour de poste.io. Je vais détailler ici l’installation complète du serveur mail, de la mise en route de l’hôte jusqu’au démarrage et la mise en prod du tout.
L’hôte: un VPS OVH
Pour la machine hôte, rien d’extraordinaire: un simple VPS SSD 1 de chez OVH fait très bien l’affaire. J’utilise Debian 8 comme OS hôte. A mon goût Debian est une distribution fonctionnelle, mais sans superflu contrairement à Ubuntu: au premier démarrage on n’a que le strict nécessaire.
Premier login
Un des premiers réflexes que j’ai à la livraison d’un serveur est la configuration des entrées A, AAAA, SSHFP et PTR du serveur sur ma zone DNS. Les champs A, AAAA et PTR se configurent très facilement sur le panel OVH (ou tout autre panel de configuration DNS). Concernant le champ SSHFP on peut obtenir les entrées DNS à rajouter dans la zone avec la commande ssh-keygen -r hostname .
La seconde étape consiste en la définition du hostname du serveur. Cela se fait dans /etc/hostname . Le mieux est donner le nom court du serveur (pas son FQDN donc). On continue dans /etc/hosts avec la ligne 127.0.0.1 hostname.domain.tld hostname localhost . Un petit reboot s’impose ensuite.
Authentification & SSH
Drop des privilèges
Ma best practice est de drop les privilèges le plus rapidement possible. On utilisera donc un utilisateur autre que root pour se login au serveur. Il faut donc désactiver l’utilisateur root , créer un utilisateur normal, ainsi qu’installer sudo . Cela se fait avec les quelques lignes suivantes:
apt-get update && apt-get upgrade && apt-get install sudo # Avant toute chose adduser server # On ajoute l'user server et on définit son mot de passe usermod -aG sudo server # On donne la possibilité à l'user d'utiliser sudo passwd -dl root # On désactive le compte root au login. Pensez à vérifier que l'utilisateur non privilégié puisse se login et utiliser sudo avant !
J’ai également l’habitude de générer une clé SSH pour l’utilisateur non privilégaié. Rien de très compliqué, je ne m’attarderai pas plus sur cette étape.
Google Authenticator
Troisième étape: utiliser un 2FA pour les logins avec mot de passe. J’utilise personnellement TOTP avec Google Authenticator en tant que 2FA. L’installation se fait comme suit:
sudo apt-get install libpam-google-authenticator # On installe TOTP # Dans /etc/ssh/sshd_config, changer PermitRootLogin without-password # ou bien "no" si vous ne prévoyez pas l'utilisation de scripts qui utilisent root ChallengeResponseAuthentication yes # Dans /etc/pam.d/sshd, rajouter auth [success=1 default=ignore] pam_succeed_if.so quiet user notingroup gauth auth required pam_google_authenticator.so # Le but ici est de forcer l'utilisation de 2FA si l'utilisateur fait partie de groupe "gauth". # Cela évite d'empêcher les utilisateurs n'ayant pas de 2FA de se login sur le server. # Toutefois dans l'idéal il ne faudrait pas laisser d'utilisateur sans 2FA. # Créer le groupe gauth et y ajouter l'utilisateur que nous avons créé plus haut sudo groupadd gauth sudo usermod -aG gauth server # Générer la clé 2FA et relancer SSH google-authenticator sudo service ssh restart
J’insisterai bien sur ce point: sauvegardez, imprimez et rangez vos clés dans un endroit sûr. Si vous perdez votre clé SSH et votre clé TOTP, il ne reste plus que votre générateur pour vous garantir un accès au serveur. Si vous vous faites ensuite voler ce générateur (un smartphone en général), bonne chance pour retrouver votre accès. C’est peut-être qu’un scénario catastrophe, toutefois une négligence puis un accident sont très vite arrivés. De même pensez à tester votre 2FA avant de vous fermer votre session SSH actuelle.
D’expérience j’ai eu des soucis de tokens invalides au bout de quelques mois, car l’heure du serveur n’était plus bonne. On va donc installer ntp pour pas que cela n’arrive: sudo apt-get install ntp
Installation de poste.io
Installation de Docker
Après ces quelques préparatifs, on en vient au vif du sujet: l’installation de poste.io. On va tout d’abord installer Docker afin de pouvoir déployer le serveur mail. Je me base sur la documentation officielle disponible ici.
# On prépare apt sudo apt-get install apt-transport-https ca-certificates gnupg2 sudo apt-key adv \ --keyserver hkp://ha.pool.sks-keyservers.net:80 \ --recv-keys 58118E89F3A912897C070ADBF76221572C52609D # Puis on rajoute ce dépôt dans /etc/apt/sources.list deb https://apt.dockerproject.org/repo debian-jessie main # Enfin on met à jour le cache APT et on installe Docker sudo apt-get update sudo apt-get install docker-engine
Installation de poste.io
Maintenant on peut enfin installer le serveur mail. Il faut savoir que par défaut le conteneur utilise sa propre stack réseau, il y a donc un réseau interne dans l’hôte et surtout une couche de NAT. Cependant Docker propose également de reprendre la stack réseau de l’hôte au lieu de faire du NAT. Il faudra cependant sécuriser derrière l’hôte derrière avec quelques règles iptables & fail2ban que l’on va appliquer plus tard.
# Pensez à supprimer tout trace d'exim avant de déployer le conteneur. # Remplacez "/postedata" par le répertoire que vous souhaitez utiliser pour sauvegarder les données du serveur mail. # Ajoutez `-e "HTTPS=OFF"` avant `-t` si vous souhaitez utiliser un reverse proxy. Cela évite une couche HTTPS inutile. sudo docker run -v /etc/localtime:/etc/localtime:ro -v /postedata:/data --name "mailserver" --net=host -t analogic/poste.io
Si tout se passe bien, vous devriez avoir une sortie similaire à celle-ci:
La vue de ce magnifique message surligné vert signifie que tout s’est bien passé. Vous pouvez alors faire CTRL+C pour quitter le prompt sans arrêter le conteneur. Vous pouvez maintenant passer à la configuration initiale du serveur mail en allant sur le serveur avec un navigateur.
Redémarrage automatique
Le problème qui se pose avec Docker est que les conteneurs ne redémarrent pas automatiquement lors du redémarrage de l’hôte. Il faut donc rajouter un service qui s’occupera de démarrer automatiquement le conteneur. Je me base sur cette documentation afin de mettre en place ce service.
Créer le fichier /etc/systemd/system/docker-mailserver.service et placer dedans ce qui suit:
[Unit] Description=Mailserver container Requires=docker.service After=docker.service [Service] Restart=always ExecStart=/usr/bin/docker start -a mailserver ExecStop=/usr/bin/docker stop -t 2 mailserver [Install] WantedBy=default.target
Il faut ensuite activer le service qu’on vient de créer.
sudo systemctl daemon-reload # Recharger systemctl sudo systemctl enable docker-mailserver.service # Activer le serveur au redémarrage
Vous pouvez ensuite vérifier le bon fonctionnement du script systemd en redémarrant l’hôte ou en testant directement le service avec sudo service docker-mailserver start afin de vérifier le bon fonctionnement du service.
Règles iptables
Identifications des ports à restreindre
Il convient évidemment de sécuriser un minimum le serveur à défaut d’un DNAT. On va donc interdire certains ports sur l’interface publique (eth0). On va laisser donc un accès total aux clients venant du tunnel VPN si vous en avez un.
On peut rapidement identifier certains ports critiques qu’il convient de fermer:
- TCP/80: nginx. Il s’agit du webmail et de l’interface d’administration. nginx devrait normalement écouter aussi en HTTPS, cependant j’ai désactivé cette option car je souhaite mettre en place un reverse proxy pour l’interface d’administration. Ne restreignez donc pas ce port si vous ne faites pas de reverse proxy ou si vous avez HTTPS activé. nginx vous redirigera automatiquement vers la version HTTPS du serveur web.
- TCP/4190: dovecot. Il s’agit de sieve pour le filtrage des mails. Il convient de ne laisser que les webmails accéder à sieve.
- TCP/13001: dovecot. Je n’ai pas identifié ce service. Dans le doute je préfère fermer ce port.
- TCP/22: sshd. C’est toujours une best practise de fermer SSH à l’accès public. Cependant il y a toujours un risque de perdre totalement l’accès au serveur en cas de défaillance du tunnel VPN. Je vais placer plutôt un jail fail2ban sur ce port. A noter que l’accès doit déjà être suffisamment protégé avec ce que l’on a fait précédemment.
Restriction des ports et persistance
Une autre best practice consiste à placer une policy DROP sur la chaîne INPUT de iptables, cependant pour des raisons de simplicité je ne la mettrai pas en œuvre. Le serveur dispose également d’une adresse IPv6, il faut donc aussi appliquer les règles en IPv6 avec ip6tables ! Afin que les règles iptables persistent avec au redémarrage de l’hôte, on peut utiliser le paquet iptables-persistent qui nous permettra de sauvegarder et restaurer automatiquement les règles du firewall.
# Mettons en place les règles sudo iptables -A INPUT -i eth0 -p tcp --dport 80 -j REJECT sudo iptables -A INPUT -i eth0 -p tcp --dport 4190 -j REJECT sudo iptables -A INPUT -i eth0 -p tcp --dport 13001 -j REJECT # Installons iptables-persistent # Répondre "Yes" aux 2 questions posées sudo apt-get install iptables-persistent # Vous pouvez par la suite sauvegarder les règles actuelles sudo service netfilter-persistent save
fail2ban & munin
poste.io place ses fichiers log dans le répertoire partagé entre l’hôte et le conteneur. Il est donc possible de faire fonctionner fail2ban et munin en leur demandant d’aller chercher les fichiers de log dans le répertoire partagé. Je ne traiterai cependant pas cette partie ici.
Let’s Encrypt
poste.io propose une fonctionnalité Let’s Encrypt. Cependant cette fonctionnalité est marquée comme « Expérimentale », nous n’allons donc pas nous en servir pour un serveur de production. De plus je souhaite spécifier plusieurs noms de domaines avec le paramètre SAN des certificats. Nous allons donc mettre en œuvre Let’s Encrypt de manière manuelle avec validation par DNS. Je me base sur cette documentation pour l’installation de certbot.
# Il faut avoir auparavant ajouté les dépôts backports de Debian. # Voir ici: https://backports.debian.org/Instructions/ sudo apt-get install certbot -t jessie-backports # Lors de la première mise en route de certbot, ce dernier vous demandera quelques informations, notamment adresse mail de secours, etc. # J'utilise ici la validation par DNS afin d'éviter de devoir mettre en route un serveur web sachant qu'iptables bloque également le port 80 et que nginx tourne déjà sur ce même port. sudo certbot certonly --manual --preferred-challenges dns-01 -d vos.hostnames.ici,second.hostname.etc
Certbot vous demandera alors de remplir quelques champs TXT sur votre zone DNS. Une fois la validation faite et fonctionnelle, certbot vous félicitera avec le message suivant:
Le dossier partagé de poste.io contient un répertoire « ssl » contenant les clés SSL que ce dernier utilise pour faire fonctionner dovecot, QPSMTPD et nginx sur un port sécurisé. Il faut donc publier les clés de Let’s Encrypt dans ce dossier partagé.
La première idée que j’ai eu consiste en la création d’un lien symbolique à partir de ce dossier vers /etc/letsencrypt/live/hostname/*.pem . Docker ne supporte cependant pas les liens symboliques qui pointent en-dehors du dossier partagé. La seconde idée consistait en l’utilisation de hardlinks au lieu de symlinks. Le détail qui casse tout cependant: les clés contenues dans /etc/letsencrypt/live/ sont déjà des symlinks vers /etc/letsencrypt/archive/ et ces symlinks changent à chaque renouvellement de certificat (tous les 3 mois). Faire un hardlink sur un symlink revient à faire un symlink. Donc le même problème qu’on a eu précédemment se pose. On pourrait alors simplement hardlink les fichiers se trouvant dans /etc/letsencrypt/archive/. Il faudra cependant changer le target du lien à chaque changement de certificat, c’est à dire tous les 3 mois. A ce moment-là autant faire un simple cp.
La seule solution « viable » serait donc de simplement cp les fichiers. On pourrait concevoir par la suite un script qui ira mettre à jour les fichiers lorsque Let’s Encrypt renouvellera automatiquement les certificats via un hook. En attendant voici les correspondances des fichiers entre Let’s Encrypt et poste.io:
- ca.crt => chain.pem
- server.key => privkey.pem
- server.crt => cert.pem
Pensez à arrêter le conteneur avant de copier les clés.
Note (21/12/2016): QPSMTPD ne semble pas prendre en compte les clés qu’on lui donne et continue d’utiliser sa propre clé auto-générée. Ce bug est apparemment connu. Dovecot et nginx ne semblent pas être affectés. Vous pouvez vérifier la clé utilisée avec curl -vk smtps://votre.hostname.ici .
Backup
J’ai chez moi un NAS sur lequel je récupère tous les soirs une sauvegarde de mes serveurs. J’utilise rsnapshot à cette fin. Ce logiciel se base sur rsync et des hardlinks pour effectuer des sauvegardes incrémentales sur X jours, semaines et mois. Tel que je l’ai configuré j’ai donc les snapshots des 7 derniers jours, 2 dernières semaines et du mois dernier (10 snapshots à tout moment donc). Cela me permet de pouvoir sauvegarder sur une certaine durée tout en minimisant l’utilisation disque. La sauvegarde se fait au travers d’un tunnel SSH avec l’utilisateur root des 2 côtés.
Nous commençons donc par générer une clé SSH sur le NAS:
# On génère une clé sans passphrase. ssh-keygen -b4096 -fnom-cle # Dans /root/.ssh/config, ajouter Host serveurmail Hostname url-ou-ip-serveur-mail BatchMode yes IdentityFile /chemin/vers/la/cle/nom-cle
Nous ajoutons ensuite la clé publique sur le serveur mail:
# Modifier le fichier /root/.ssh/authorized_keys et y ajouter # Remplacez A.B.C.D par l'IP du serveur de sauvegarde. Cela permet de restreindre la clé à un seul hôte et donc d'accroître la sécurité. from="A.B.C.D" ssh-rsa [...] root@nas
De retour sur le NAS, il faut maintenant vérifier le bon fonctionnement de l’accès SSH que nous venons de mettre en place:
# Il faut auparavant avoir accepté la Host Key du serveur mail, sinon rien ne fonctionnera. # Note: Les commandes suivantes sont effectuées en root (via sudo) car la configuration du client SSH que rsnapshot reprendra se trouve dans /root/.ssh sudo ssh root@url-ou-ip-serveur-mail # Tester ensuite la connexion. # Si vous obtenez un shell, tout est opérationnel ! sudo ssh root@serveurmail
Il faut maintenant mettre à jour /etc/rsnapshot.conf avec le nouvel hôte à sauvegarder:
# serveurmail # Mettre des TAB entre les termes, et non des espaces ! backup root@serveurmail:/etc/ serveurmail/etc/ backup root@serveurmail:/var/log/ serveurmail/log/ backup root@serveurmail:/postedata/ serveurmail/postedata/
Vous pouvez vérifier le bon fonctionnement de rsnapshot avec sudo rsnapshot configtest; sudo rsnapshot -t daily . La première commande teste la syntaxe du fichier de configuration tandis que la deuxième donne toutes les commandes qu’il exécutera lors d’une sauvegarde.
Reverse proxy: manager et webmail
(Étape facultative) J’ai prévu l’utilisation d’un reverse proxy sur un serveur à part pour le manager mail et le webmail. J’ai aussi prévu d’utiliser SOGo par la suite en tant que webmail et serveur CalDAV/CardDAV. On se contentera donc pour le moment du Roundcube fourni avec le serveur mail.
Sans attendre, voici la configuration du reverse proxy que j’utilise (nginx):
server { listen *:443 ssl; listen [::]:443 ssl; server_name mail.hostname.tld; ssl_certificate /etc/letsencrypt/live/mail.hostname.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mail.hostname.tld/privkey.pem; access_log /var/log/nginx/mail_access.log; error_log /var/log/nginx/mail_error.log; location /.well-known { alias /home/www/le-cha/mail/.well-known; } location / { proxy_read_timeout 900; proxy_pass_header Server; proxy_cookie_path ~*^/.* /; proxy_pass http://serveurmail/webmail/; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen *:443 ssl; listen [::]:443 ssl; server_name manager.hostname.tld; ssl_certificate /etc/letsencrypt/live/manager.hostname.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/manager.hostname.tld/privkey.pem; access_log /var/log/nginx/manager_access.log; error_log /var/log/nginx/manager_error.log; location /.well-known { alias /home/www/le-cha/manager/.well-known; } rewrite ^/$ /admin/ redirect; rewrite ^/admin$ /admin/ redirect; location / { proxy_read_timeout 900; proxy_pass_header Server; proxy_cookie_path ~*^/.* /; proxy_pass http://serveurmail/; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
Quelques explications:
- On écoute uniquement sur le port 443, une configuration existante redirige automatiquement l’hôte en HTTPS.
- Le bloc location /.well-known permet la validation par Let’s Encrypt du serveur web au travers d’un fichier qu’il place dans un dossier spécifié par l’alias.
- Les deux règles de rewrite permet de forcer l’accès au panel administrateur, et non au webmail. Je ne peux pas inclure /admin/ dans proxy_pass car le panel redirige vers des URL commençant par /admin/ . Cela casse donc le reverse proxy. Le problème ne semble pas se poser avec Roundcube.
Conclusion
J’ai donc ici déployé un serveur mail. J’ai en plus mis en place une vraie sécurisation de l’accès SSH, des règles de pare-feu, Let’s Encrypt, ainsi qu’une solution de sauvegarde et un reverse proxy. Cela me permet d’avoir un serveur mail beaucoup plus robuste face à divers problèmes (attaques, perte de données, etc.). On peut cependant aller plus loin encore: on peut mettre en place des plugins munin pour dovecot et QPSMTPD ou bien des jails fail2ban pour les daemons mails. Ces 2 derniers points étant plus complexes étant donné qu’on utilise Docker, je n’aborderai pas leur mise en place ici.