Installation d'un serveur de containers
Plus des notes techniques pour ma mémoire défaillante qu’un véritable article, je vais compiler les étapes d’installation d’un serveur de containers. Quel système d’exploitation ? j’ai envie de dire on s’en cogne mais il est préférable de choisir une distribution poussée par Docker pour se simplifier la vie ; ce sera donc Debian 9 Stretch supportée sur Dedibox.
J’installe donc via l’interface d’administration Online. Je choisis le mot de passe root, le compte utilisateur et son mot de passe. Online me communique ma configuration réseau :
- Adresse IP : xxx.yyy.zzz.xxx
- Masque réseau : 255.255.255.0
- Passerelle : xxx.yyy.zzz.1
- DNS primaire : 62.210.16.6
- DNS secondaires : 62.210.16.7
SSH
Je vérifie qu’on peut se connecter au serveur :
ssh yax@xxx.yyy.zzz.xxx
Je me déconnecte et génère une clef avec ssh-keygen, en choisissant une clef RSA. On peut fournir une passphrase si on pense qu’elle peut tomber entre de mauvaises mains. Je nomme ma clef fr_mondomaine ; cela génère une clef publique fr_mondomaine_rsa.pub et une clef privée fr_mondomaine_rsa.
Je copie la clef publique sur le serveur :
ssh-copy-id -i fr_mondomaine_rsa.pub yax@xxx.yyy.zzz.xxx
On peut simplifier la connexion au serveur en modifiant la configuration du client SSH ~/.ssh/config de sa machine pour que l’utilisateur et la clef soient choisis sans le spécifier sur la ligne de commande :
Host fr.mondomaine
User yax
Hostname xxx.yyy.zzz.xxx
IdentityFile ~/.ssh/fr_mondomaine_rsa
Je vérifie qu’on se connecte sans saisir de mot de passe :
ssh fr.mondomaine
J’interdis complètement la connexion SSH au compte root. Pour cela, je me connecte au serveur, je passe en root avec su et j’édite le fichier de configuration du service SSH /etc/ssh/sshd_config pour fixer les paramètres suivants :
PermitRootLogin no
PasswordAuthentication no
Je redémarre le service SSH :
systemctl restart sshd
Et si on s’est raté ou si on perd la clef RSA sur notre PC à la maison ? Et bien on est bon pour redémarrer le serveur en mode secours avec la console Dedibox, monter les partitions et se chrooter pour arranger la configuration de SSH. C’est encore faisable facilement car systemd n’a pas encore binarisé tout le système (troll inside).
Je ne configure pas sudo, je veux pouvoir tout faire avec un utilisateur standard, les connexions à root par su doivent rares, principalement pour effectuer les mises à jours du système Debian.
Docker
Installation de Docker CE sur Debian Stretch avec la procédure officielle qui ajoute des dépôts APT Docker pour récupérer une version de Docker plus récente que celle fournie avec Debian Stretch : https://docs.docker.com/engine/installation/linux/docker-ce/debian/
Installation d’une version récente de Docker Compose en suivant aussi la procédure officielle : https://docs.docker.com/compose/install/
Ajout de l’utilisateur yax dans le groupe docker
usermod -aG docker yax
Démarrage automatique de Docker:
systemctl enable docker
Pare-feu
J’installe shorewall pour IPv4, un pare-feu puissant qui ne rajoute pas de service supplémentaire au système puisqu’il traduit ses règles assez complexes en règles iptables.
apt-get install shorewall
Par sécurité, le temps de mettre au point les règles, je désactive le démarrage automatique :
systemctl disable shorewall
Si ça foire, je pourrai toujours forcer un redémarrage électrique du serveur :-)
Docker a la particularité de gérer ses propres règles iptables pour l’accès aux containers ce qui rend sa cohabitation délicate avec tout pare-feu basé sur iptables Depuis quelques versions, Shorewall supporte Docker, ce qui revient à dire qu’il le laisse faire sa sauce et gère les règles iptables qui lui sont propres.
Donc j’édite /etc/shorewall/shorewall.conf et déclare que je vais utiliser Docker :
STARTUP_ENABLED=Yes
DOCKER=Yes
Puis j’édite /etc/shorewall/zones pour définir mes zones :
#ZONE TYPE OPTIONS
fw firewall
net ipv4
dock ipv4 # 'dock' is just an example
Et les règles de passage d’une zone à l’autre en éditant /etc/shorewall/policy :
#SOURCE DEST POLICY LEVEL
$FW net ACCEPT
net all DROP info
dock $FW REJECT
dock all ACCEPT
# last rule
all all REJECT info
Enfin, j’associe les zones aux interfaces physiques (attention enp1s0 est le nom de l’interface Ethernet de mon serveur), en éditant /etc/shorewall/interfaces :
?FORMAT 2
#ZONE INTERFACE OPTIONS
net enp1s0 dhcp,tcpflags,logmartians,nosmurfs,sourceroute=0
dock docker0 bridge,routeback=0 #Disallow ICC
Là on a un pare-feu opérationnel qui refuse toute connexion entrante ; on ajoute quelques règles pour autoriser le PING ICMP et les connexions SSH avec une limite de 3 connexions par minute (pour calmer les facheux).
Ca se passe dans le fichier /etc/shorewall/rules :
#ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/ MARK CONNLIMIT TIME HEADERS SWITCH HELPER
# PORT PORT(S) DEST LIMIT GROUP
?SECTION ALL
?SECTION ESTABLISHED
?SECTION RELATED
?SECTION INVALID
?SECTION UNTRACKED
?SECTION NEW
# Drop packets in the INVALID state
Invalid(DROP) net $FW tcp
# Drop Ping from the "bad" net zone.. and prevent your log from being flooded..
Ping(ACCEPT) net $FW
# Permit all ICMP traffic FROM the firewall TO the net zone
ACCEPT $FW net icmp
SSH(ACCEPT) net all - - - - s:1/min:3
Tester la sécurité
Là ça devient plus amusant ! Je reboote et je démarre manuellement shorewall puis je jette un oeil à la bonne cohabitation des règles iptables entre Shorewall et Docker :
iptables -L -n -v
D’abord je teste les accès distants :
- on peut scanner les ports avec NMAP depuis sa machine : on voit le port 22 ouvert
- on vérifie facilement que SSH est limité à 3 connexions par minute
Enfin je teste la sécurité intra-docker en déployant 3 containers Docker avec l’image tcpping :
version: "3"
services:
web1:
image: tcpping
expose:
- 80
networks:
- frontend
web2:
image: tcpping
networks:
- frontend
- backend
web3:
image: tcpping
ports:
- 8000:80
networks:
- backend
networks:
frontend:
backend:
Les containers web1 et web2 sont sur le réseau frontend Les containers web2 et web3 sont sur le réseau backend
Je vérifie les points suivants :
- chaque container peut accèder à Internet
- aucun container ne peut accéder au serveur : ni ping, ni SSH
- web1 et web2 se voient en ICMP et en TCP 80
- web2 et web3 se voient en ICMP et en TCP 80
Je refais un scan de port plus poussé avec NMAP et je vois que le port 22 et 8000 (celui de web3) sont accessibles depuis Internet.
Si je liste les règles iptables, je remarque que l’isolation des réseaux entre les containers donne lieu à des règles iptables supplémentaires.