xataz Merci à toi pour le minimum, tu as traité ces points dans ton tuto sur Docker ? Dans un autre ? Pas tous ?
Il faut que je regarde les user-namespace que je ne connais pas et que je regarde ce qu'il faut configurer sur iptables que je ne connais pas très bien.
Le partage du socket docker il faut que je regarde les containers qui l'utilisent, je n'ai que traefik et portainer il me semble.
J'utilise les images officielles ou celles de linuxserver le plus souvent.
Tous les containers derrière traefik sont sur un réseau traefik (+ un réseau mariadb pour les containers avec une BDD) et mon VPN est sur un réseau vpn.
Bonnes pratiques pour un hôte Docker en "prod"
Regarde par ici : https://mondedie.fr/d/7164-tuto-utilisation-de-docker c'est fait par quelque de très bien
C'est dans la configuration du daemon pour les deux.
Tout est bien expliquer dans la doc officiel.
Pour iptables, sur debian y'a un tweak à faire pour modifier l'alternative iptables-legacy au lieu de iptables-nftable
NicCo Je ne comprends pas de quel port supplémentaire tu parles ? J'ai un teamspeak derrière 9987 et l'autre derrière 9988.
Traefik pour une infra de prod c'est une des sécurisations évidentes, on masque les ports qui n'ont pas besoin d'être exposés de l’hôte vers le docker, dans mon exemple les ports 9987 et 9988 ne sont pas vraiment ouverts :
root@machine:~# netstat -ntpl |egrep "9987"
root@machine:~# netstat -ntpl |egrep "9988"
root@machine:~#
C'est simplement une entrée SRV dans le DNS qui indique que ts.domain.tld et ts2.domain.tld mènent aux ports 9987 et 9988 et c'est traefik qui dirige les requêtes vers le docker en question.
xataz J'ai lu la doc officielle et j'ai mis en place les user-namespaces (soit c'est hyper simple, soit mon environnement docker est simple, soit j'ai oublié de faire quelque chose). J'ai bien vérifié et le root dans le container ne permet plus de modifier les fichiers nécessitant une élévation sur l'hôte. Pour le moment je ne rencontre pas de problèmes, qu'est-ce qui est plus complexe sur la gestion des volumes (pour voir si je n'ai rien oublié) ? Je peux toujours utiliser un dossier docker (/home/myuser/docker) et une arborescence pour stocker les données permanentes ? Il faut quand même déclarer un PUID/GUID dans la déclaration docker-compose ? Et le PUID/GUID ne sont pas ceux de l'utilisateur utilisé pour le user-namespace ?
J'ai lu aussi la doc pour iptables mais à part restreindre l'accès à une ou plusieurs IP (ce que je ne veux pas vu que je veux accéder à mon hôte depuis plusieurs connexions qui peuvent changer d'IP), je ne vois pas quoi faire avec ?!
Merci
Aerya La partition dédiée n'est pas forcément utile parce que sauf à te planter dans le montage, normalement, on container n'a pas accès à tout ton serveur.
NON, un stockage séparé est indispensable en production, notamment par ce qu'en cas de saturation de l'espace disque le système hôte plante. Et pour d'autres raisons variable suivant la situation (fiabilité, performance, ...).
Effectivement. Après... je suis du genre à surveiller mon espace disque
Pour évité la saturation il y a un hack tout con pour rapidement revenir a la normal. Ca n'empéche pas le crash des apps.
https://brianschrader.com/archive/why-all-my-servers-have-an-8gb-empty-file/
Merci pour les infos, je vais partir sur des partitions séparées ça ne me coûtera pas plus cher
Par contre j'ai toujours un peu de mal à saisir si je comprends bien la différence entre un user-namespace, un "user: PID:GID" déclaré dans le fichier docker-compose et un "environment: PUID=/PGID=". Si quelqu'un a une explication un peu claire je suis preneur merci !
Si je ne me trompe pas, l'ARG n'est valide que pour la construction de l'image. Quand elle tourne c'est seulement l'ENV qui est pris en compte.
Merci déjà pour cette réponse.
Je ne sais pas si j'ai bien compris mais le user-namespace et l'arg "user: PID:GID" semblent correspondre à la même chose ? Sauf qu'avec le user-namespace tu ne déclares pas à chaque fois un user ? Et ce serait juste l'utilisateur qui exécute l'image ?
Par contre pour l'env ce serait l'utilisateur qui est utilisé dans l'image ?
- Modifié
Je plussoie le message de xataz, et je rajouterais pour le socket, dans le cas où tu as besoin de l'utiliser, d'avoir recours à un proxy granulaire comme celui-là : https://github.com/Tecnativa/docker-socket-proxy
Pour gérer l'isolation réseau des conteneurs utiliser des networks bridge que tu définis toi-même (plutôt que d'utiliser le bridge par défaut et d'utiliser des links, dépréciés d'ailleurs). Utilise le flag --internal
si tu veux isoler un réseau de l'Internet par exemple (doit y avoir quelques précisions en plus).
Pour iptables, depuis les versions récentes de Docker, utilise la chaîne DOCKER-USER
, puis dedans ajoute une chaîne de filtre qui accepte le traffic prévu vers l'extérieur, puis rejette le reste.
Enfin pour les runtimes je trouve que c'est un peu compliqué encore de conseiller ça, même si le niveau d'isolation est step up encore davantage. Je joue d'ailleurs avec actuellement pour voir comment bien le gérer (a priori c'est surtout niveau réseau que ça se corse car la résolution DNS par Docker ne fonctionne pas sans la stack réseau de l'hôte - mais tu peux y parer en définissant des adresses statiques).
Je ne sais pas si j'ai bien compris mais le user-namespace et l'arg "user: PID:GID" semblent correspondre à la même chose ? Sauf qu'avec le user-namespace tu ne déclares pas à chaque fois un user ? Et ce serait juste l'utilisateur qui exécute l'image ?
Alors y a plusieurs différences.
- Le paramètre
--user
définit le user (UID/GID) par défaut du conteneur. Par défaut, sauf siUSER
est déclaré dans un Dockerfile, ça sera root. - Le user namespace est une feature du kernel qui permet de mapper différents user pour faire en sorte, par exemple, que root dans le conteneur =/= root sur l'hôte (ce qui empêche des catastrophes en cas de compromission d'un conteneur).
- Enfin, certaines images proposent des variables d'environnement (
ENV
dans le Dockerfile) pour effectivement configurer un utilisateur. Ca ne garantit pas qu'il n'y a pas de root process dans le conteneur, par contre, mais c'est flexible et ça utilise la stratégie du "degrading privileges" (qui a ses défauts aussi).
En gros il faut utiliser un peu de tout ça selon ton besoin, tes images. Idéalement le namespace pour pas qu'un root escape soit dangereux pour la sécurité de l'hôte (d'ailleurs considère que n'importe quel user non-root qui a accès à Docker sans namespace sur l'hôte a tous les pouvoirs....), le paramètre user pour correspondre à tes besoins, et certaines images avec les variables qui "font tout".
Ce ne sont pas vraiment les mêmes choses mais ce sont des moyens mis en oeuvre pour plus ou moins le même but. Et c'est assez compliqué, j'en conviens...
Merci beaucoup @Wonderfall, c'est déjà un peu plus clair pour la partie userns, l'argument user et les variables user. Après comme tu dis reste à réussir à les mettre en pratique vu que c'est un peu compliqué surtout pour un débutant, j'ai ma VM de test je vais pouvoir faire des essais
Je vais regarder aussi pour le docker-socket-proxy qui avait déjà été abordé un peu plus haut par @xataz ainsi que pour la config UDP de Traefik proposée par @Banip (je me fais un mémo en même temps avant de noter tout ça proprement)
Pour gérer l'isolation des containers tu ne conseilles pas de passer l'ICC à false donc ? Si l'option est dépréciée, je n'aurais pas la possibilité de faire communiquer les containers entre eux... Par contre dans le cas d'un Traefik v2 en front de plusieurs containers, je suis obligé de tous les mettre dans le même réseau bridge (que j'ai créé avant, par exemple nommé frontend) ou il y a une autre solution ?
Pour iptables, la chaine DOCKER-USER est à configurer soi-même ou par défaut elle laisse passer le trafic de/vers l'extérieur ?
Le runtime et le rootless pour le moment je n'y touche pas, on verra un jour
Tu utilises un daemon.json pour configurer le daemon Docker ? Si oui, tu pourrais le partager pour avoir une idée ?
Merci !
- Modifié
C'est une excellente question quant à l'ICC et l'usage de --link.
Le link est effectivement déprécié : https://docs.docker.com/network/links/
Du coup pour ICC, je ne sais pas trop si c'est encore d'actualité, j'attendrais le retour de quelqu'un de plus expérimenté comme xataz. Je ferai des recherches approfondies dessus, mais il me semble que l'ICC aujourd'hui ne s'applique qu'au bridge par défaut.
L'idée c'est effectivement de gérer tes propres réseaux désormais. Par exemple imaginons tu un conteneur Postgres, il est inutile qu'il soit dans le réseau "frontend", et aussi inutile qu'il soit connecté à l'extérieur (d'où --internal). Tu pourras créer un réseau isolé dédié à l'app en question (app <-> db <-> redis par exemple).
Effectivement pour Traefik le plus commun est de créer un gros réseau dédié, c'est le plus simple. Par contre, toutes les apps de ce réseau peuvent communiquer entre "elles" donc il faut effectivement faire attention si par exemple la sécurité d'une app repose sur le proxy.
Alors il me semble (à vérifier) que Traefik doit simplement pouvoir router vers ces différents conteneurs, donc tu peux voir le sujet à l'envers et faire des réseaux uniques pour chaque lien Traefik <-> conteneur. Mais ça demande un peu plus de configuration, par contre c'est nécessaire pour vraiment bien isoler entre des conteneurs qui n'ont pas besoin de communiquer entre eux (et c'est la bonne pratique).
Pour iptables, la chaine DOCKER-USER est à configurer soi-même ou par défaut elle laisse passer le trafic de/vers l'extérieur ?
Elle est là par défaut, et oui elle laisse passer le traffic. Après pour bloquer spécifiquement l'accès d'un réseau Docker à l'extérieur, je n'ai pas utilisé iptables personnellement.
Tu utilises un daemon.json pour configurer le daemon Docker ? Si oui, tu pourrais le partager pour avoir une idée ?
Oui j'utilise daemon.json, le mien est très simple (enfin c'est relatif) et sur mon serveur perso je n'utilise pas encore le rootless. J'ai juste mis btrfs, le live restore activé, les runtimes alternatifs (gvisor, kata), etc. Rien de spécial !
{
"data-root": "/docker",
"debug": false,
"live-restore": true,
"userland-proxy": false,
"iptables": true,
"icc": false,
"runtimes": {
"runsc": {
"path": "/usr/bin/runsc"
},
"kata": {
"path": "/snap/bin/kata-containers.runtime"
}
},
"storage-driver": "btrfs"
}
(Pourquoi je n'utilise pas les user namespaces : pas compatibles avec kata et gvisor, et c'est redondant avec eux. gvisor par exemple fonctionne avec son namespace séparé de l'hôte. Sinon, je conseille de l'utiliser, c'est une isolation bonne à prendre avec runc.)
hydrog3n mouaif bouffer xGB d'espace libre pour ça je trouve ça un peu con ^^ Une machine ça se monitore et comme une des règles de base c'est 50% d'espace libre minimum c'est assez tôt que t'as une alerte qui te permet d'éviter un disque plein !
Je préfère de la surveillance (de toute façon il y a d'autres choses à surveiller donc il en faut) à une bidouille.
Merci encore @Wonderfall pour tous ces détails. J'allais commencer à tester l'usage de --link, je vais finalement m'en passer vu ton lien
Je vais aussi chercher de mon côté si je trouve des éléments sur ICC, dans tous les cas j'ai déjà un montage de réseau comme tu le décris en première partie, un frontend pour tout ce qui sort vers l'extérieur et un backend pour les DB, Redis ou autres qui n'ont pas besoin de sortir.
Je vais revoir ce point, passer le backend en internal (le paramètre est aussi valable pour un bridge ? Je n'ai trouvé des utilisations qu'avec overlay jusqu'à présent mais je n'ai qu'un hôte Docker).
Et pour le reste ça voudrait dire de créer un réseau (ou 2 si utilisation d'un backend) par app et tous les déclarer (pas les backend) dans le docker-compose de traefik ? Je n'en ai pas 100 non plus, peut-être une dizaine, c'est jouable. Les réseaux configurés au sein d'un même docker-compose n'ont pas de visibilité entre eux ?
Pour iptables, je suis loin d'être un expert Linux mais tu utilises quoi du coup ?
Merci pour le daemon.json, il y a déjà l'option data-root à côté de laquelle j'étais passé et qui peut être pratique. Les runtimes ce sera une étape future avant le rootless, je pense que mon niveau débutant ne me permet pas encore de m'aventurer là dedans
Le BtrFS il faudrait que je regarde aussi mais ça rajoute de la complexité en gestion si j'ai bien compris. Pour le moment je n'ai pas l'impression d'avoir rencontré de problème en ext4.
En priorité ça va être user-namespace et réseaux, la suite reste à déterminer
Dans les sources du script de bench sécurité du CIS, j'ai trouvé ceci :
Alternatively, you can follow the Docker documentation and create a custom network and only join containers that need to communicate to that custom network. The --icc parameter only applies to the default docker bridge, if custom networks are used then the approach of segmenting networks should be adopted instead.
Donc à priori effectivement ça ne s'applique qu'au default.
J'ai trouvé ceci aussi pour vérifier si l'ICC est actif ou non :
Get ICC setting for a specific network docker inspect -f '{{index .Options "com.docker.network.bridge.enable_icc"}}' [network]
Et ceci pour créer le réseau en désactivant l'ICC (ne peut être fait qu'à la création du réseau) :
Create a network and explicitly enable ICC docker network create -o com.docker.network.bridge.enable_icc=true [network]
Il faut que je teste si la désactivation de l'ICC dans le daemon.json avant la création de nouveaux réseaux applique automatiquement la désactivation de l'ICC