poste.io: Installation complète

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:

Déploiement de poste.io, 1ère partie
Déploiement de poste.io, 1ère partie
Déploiement de poste.io, 2ème partie
Déploiement de poste.io, 2ème partie

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.

Configuration initiale de poste.io
Configuration initiale de poste.io

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.

Service Mailserver fonctionnel
Service Mailserver fonctionnel

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.

Affichage des ports d'écoute sur le serveur mail
Affichage des ports d’écoute sur le serveur mail

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:

Validation certbot
Validation certbot

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.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *