• Docker
  • Bonnes pratiques pour un hôte Docker en "prod"

Merci Aerya pour l'info, je vais tester Anchore que je ne connaissais pas.

Pour la partition je ne sais pas non plus, actuellement sur mon hôte de test je tourne avec une partition séparée mais s'il n'y a pas d'intérêt je vais retourner à une partition unique.

Merrick, je n'ai pas trouvé le tuto dont tu parles, à moins que ce soit celui pour créer soi-même ses images Docker ?

Vous faîtes quoi comme bonnes pratiques niveau hôte et sécurité pour vos mises en prod ? Merci 😉

NicCo Pour le reverse proxy tout est déjà derrière Traefik v2 (sauf mon serveur VPN Wireguard car il est en UDP, ça me dérange un peu mais Traefik ne gère pas l'UDP pour le moment à priori donc pas possible de l'utiliser).

J'ai des dockers derrière traefik en udp (pour être exact un teamspeak) et cela fonctionne bien.

Si ça peut t'aider voici mon traefik.yml :

global:
  checkNewVersion: false
  sendAnonymousUsage: false

providers:
  file:
    directory: /etc/traefik/conf/
    watch: true

api:
  insecure: false
  dashboard: true

log:
  level: INFO
  filePath: /etc/traefik/traefik.log

accessLog: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  ftp:
    address: ":21"
  ts9987:
    address: ":9987/udp"
  ts9988:
    address: ":9988/udp"
  mc:
    address: ":25565/tcp"

Et mon teamspeak.yml :

udp:
  services:
    ts9987:
      loadBalancer:
        servers:
        - address: "teamspeak:9987"
    ts9988:
      loadBalancer:
        servers:
        - address: "teamspeak:9988"
  routers:
    ts9987:
      entryPoints:
        - "ts9987"
      service: "ts9987"
    ts9988:
      entryPoints:
        - "ts9988"
      service: "ts9988"

    Banip Merci pour l'info, j'avais effectivement vu quelque chose dans le genre. Par contre j'ai du mal à saisir l'interêt de le passer derrière Traefik si c'est pour ouvrir quand même un port supplémentaire ?!

      Salut,

      Pour une utilisation en production, j'aurais tendance à dire :

      • Utilisation des user-namespace, comme ça si dans le conteneur tu es root, sur l'hôte tu ne l'ai pas. Cela complexifie la gestion des volumes
      • éviter au maximum le partage du socket docker
      • Utiliser ces propres images, ou des images de confiance (linuxserver, binami, mondedie etc ...)
      • Separer les reseaux des conteneurs au strict minimum, si un conteneur n'a pas besoin du net, on lui mets un réseau que local
      • Utiliser iptables au lieu de docker-proxy
      • Limiter les ressources de chaque conteneur

      Pour moi c'est le minimum, après y'a des tweaks :

      • Utilisation d'un autre runtime comme firecracker ou kata-container, voir gvisor même si c'est un ptrace degueulasse
      • Utiliser docker en rootless voir podman qui est aussi daemonless

      Et je dirais que c'est deja pas mal.

        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.

        Merrick Merci 😉 j'ai lu le tuto mais par contre je n'ai rien trouvé concernant les user-namespaces et iptables

        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.

            Banip Merci, j'ai lu trop vite tes fichiers et j'ai cru que tu avais exposé les ports. Je vais regarder ta config, merci 😉

            xataz Merci, je vais regarder ça. Du coup avec ce type de config tu n'ouvres aucun port ? Même le 80/443 ?

            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 😉

            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 ?

            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 si USER 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...