Journal LinuxFr avec Docker

Posté par  (site web personnel, Mastodon) . Licence CC By‑SA.
53
24
sept.
2020

Salut Nal !

Je t'écris, parce que j'ai essayé de faire en sorte que n'importe quel de tes auteurs et/ou lecteurs puisse tenter de te hacker.

Bruno a accepté 3 Pull Request cette semaine qui permettent aux développeurs et autodidactes de tenter de monter l'infrastructure de LinuxFr sur sa machine perso. Les détails sont donnés en anglais sur le répertoire git.

Pour résumer en français, il faut:

  1. Installer Docker et l'outil docker-compose et penser à ajouter son utilisateur au groupe docker pour pouvoir travailler sans sudo.
  2. Ajouter la ligne 127.0.0.1 dlfp.lo image.dlfp.lo dans le fichier /etc/hosts (ou équivalent).
  3. Télécharger les sources de LinuxFr.org et libérer le port 80 s'il est actuellement utilisé.
  4. Exécuter dans un premier terminal docker-compose up (à la racine du code source)
  5. Attendre que docker-compose finisse son travail et que les containers de mariadb affichent les lignes:

    database-test_1 | 2020-09-21 16:03:12 140126029907968 [Note] mysqld: ready for connections.

    database-test_1 | Version: '10.1.46-MariaDB-1~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution

    database_1 | 2020-09-21 16:03:12 139820938893312 [Note] mysqld: ready for connections.

    database_1 | Version: '10.1.46-MariaDB-1~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution

  6. Dans un second terminal (aussi à la racine du code source), il faut préparer la base de donnée avec:
    docker-compose run linuxfr.org bin/rails db:setup

  7. Utiliser son navigateur préféré pour visiter l'adresse http://dlfp.lo et se connecter avec le compte admin et le mot de passe admin

  8. Hacker les sources de LinuxFr.org et voire les changements arriver dans votre navigateur avec un simple rafraîchissement de la page :-)

C'est une première version, ça tourne chez moi et on récupère volontiers des retours et/ou améliorations sur ce déploiement.

Dans les manques, il y a le système des fichiers Epub qui ne sont pas disponible et l'écriture de formule Latex (il y a du code à adapter). De plus, la webconsole Rails ne s'affiche pas en cas de bug. Par contre, les tribunes et le service de cache d'image doivent fonctionner.

À noter, que le fichier docker-compose.yaml fait utiliser le port 80 à nginx pour monter l'infrastructure, parce que le code de LinuxFr ne permet pas de préciser le port pour le lien sur les images. Vous pouvez donc modifier le port de ngninx, avec cette modification:

  nginx:
    image: nginx:stable
    env_file:
      - deployment/default.env
    volumes:
      - ./deployment/nginx/templates:/etc/nginx/templates
      - ./public/fonts:/var/linuxfr/fonts
      - data-uploads:/var/linuxfr/uploads
    ports:
-      - "80:80"
+      - "3000:80"
    depends_on:
      - linuxfr.org
      - linuxfr-board
      - linuxfr-img

Ensuite, il faut que le service qui utilise le port 80 redirige les requêtes pour le domaine dlfp.lo et image.dlfp.lo vers le nginx (et donc dans l'exemple, le port 3000).

Bref, cher lecteur, si tu as un peu de temps ce prochain trolldi ou ce weekend, les retours seront volontiers lus et, si possible, pris en compte :-)

  • # Root

    Posté par  . Évalué à 10.

    J’ai regardé rapidement les fichiers de build et de config et j’ai juste une remarque : tes containers tournent en root avec pour seule sécurité le « chroot » de Docker.

    Depuis que je bosse avec Kubernetes, j’ai pris l’habitude de faire en sorte que mes containers puisse tourner dans n’importe quel utilisateur non-privilégié et dans un container en lecture seul.

    Avec docker-compose, ça serait par exemple pouvoir ajouter ça dans la définition des containers (ce que je te conseille de faire dans ta config) :

    user: 65534:65534 # ici nobody, mais ça peut être n’importe quoi
    read_only: true
    cap_drop:
      - ALL

    De ce que je vois, ça ne devrait pas poser de problème et ça éviterait de faire tourner du RoR en root. Pour nginx c’est un peux plus compliqué, faut lui faire écrire ses fichier dans un dossier où il a les droit d’écrire, mais ça se fait aussi (au besoin je peux te donner un coup de main).

    Aussi, vu que linuxfr-image est un binaire, tu pourrais utiliser le muti-stage de Docker et avoir une image final qui ne contienne que ce binaire (par exemple avec distroless de Google) :

    FROM debian:stretch-slim as builder
    
    WORKDIR /linuxfr-img
    
    # Install dependencies
    RUN apt-get update \
      && apt-get install -y --no-install-recommends \
        golang git ca-certificates \
      && apt-get clean
    
    # Install linuxfr-img
    ENV GOPATH=/linuxfr-img/go
    RUN go get -u github.com/linuxfrorg/img-LinuxFr.org
    
    FROM gcr.io/distroless/base-debian10
    
    LABEL maintainer="adrien@adorsaz.ch"
    LABEL version="1.0"
    LABEL description="Run LinuxFr image caching service for LinuxFr.org Ruby on Rails website"
    
    COPY --from=builder /linuxfr-img/bin/dlfp-image /dlfp-image
    
    EXPOSE 8000
    
    # Tu laisse le soin à l’utilisateur de changer l’option -r au déploiement
    ENTRYPOINT ["img-LinuxFr.org", "-a", "0.0.0.0:8000", "-r", "redis://"]
    

    Pas testé, c’est juste pour l’exemple.

    En tout cas, c’est cool si ça permet de faciliter le développement :)

    • [^] # Re: Root

      Posté par  (site web personnel, Mastodon) . Évalué à 10.

      Pour le coup du root, j'avoue que je ne comprends pas bien comment faire pour le faire comme il faut.

      J'ai essayé au début de mettre l'instruction USER 1001 dans le Dockerfile pour linuxfr.org, mais ça m'a empêché de faire fonctionner l'upload d'avatars.

      Le problème, c'est que docker-compose fait monter le dossier public et upload, mais comme les fichiers ont mon UID (genre 20000 sur mon poste), et bien l'utilisateur du container est incapable de créer le dossier public/tmp/upload et ensuite de faire les dossiers dans upload.

      Ensuite, j'ai abandonné, car justement nginx a aussi besoin d'accéder à ces dossiers et que du coup, il fallait commencer à coordonner les UID entre l'hôte et tout ces containers.

      Finalement, j'ai laissé tomber, car ce n'est pas un déploiement pour de la production (avec Kubernetes et/ou OpenShift/okd), mais uniquement pour faire des tests sur sa machine.

      Ma conclusion sur ce sujet, c'est que je tombe exactement sur le même os que lorsque j'essaie de faire une clé USB partagée entre différents postes Linux avec des UID/GID différents. La seule solution viable, ça serait de travailler avec un système de fichier sans permissions, genre FAT32.

      Au vu de mes recherches sur le web, j'ai l'impression que tout le monde sait qu'il faudrait éviter d'utiliser root pour exécuter des Docker, mais que personne ne propose de solution pour permettre de partager les fichiers entre le host et le container.

      Si j'ai bien compris, même OpenShift essaie de forcer à ne pas utiliser l'utilisateur root, mais il fait utiliser le groupe 0 et du coup, j'ai l'impression que ça ne change rien.


      C'est une bonne idée pour les binaires, je ne connaissais pas le projet "distroless". Je n'ai pas cherché à faire plus, parce que j'utilise déjà une debian slim et le minimum de paquet nécessaire pour faire tourner les services.

      Peut être que dans l'exemple que tu as donné j'aurais pu enlevé le paquet golang, mais par contre, le ca-certificates est nécessaire pour que le service puisse se connecter aux sites exeternes en HTTPS.


      Merci beaucoup pour le retour, je vais faire des tests avec les pistes que tu m'as donné sur ces questions.

      • [^] # Re: Root

        Posté par  . Évalué à 4.

        Je suis passé de Docker à Podman récemment. Il permet (entre autres) de faire tourner des containers en tant que simple utilisateur.

        Il n'est pas directement compatible avec docker-compose (bien qu'il existe podman-compose), mais il intègre la notion de pods de Kubernetes, et permet de les gérer avec systemd.

        Peut-être une piste à creuser ?

        • [^] # Re: Root

          Posté par  (site web personnel) . Évalué à 5.

          Installer Docker et l'outil docker-compose et penser à ajouter son utilisateur au groupe
          docker pour pouvoir travailler sans sudo.

          Ou comment donner un accès root complet à sa machine… Ne jamais faire ça! En mettant ça pour un vacataire, on s'est fait hacker nos postes de travail.

          Et pour le coup, vive Podman, à mort Docker!

      • [^] # Re: Root

        Posté par  . Évalué à 6.

        Le problème, c'est que docker-compose fait monter le dossier public et upload, mais comme les fichiers ont mon UID (genre 20000 sur mon poste), et bien l'utilisateur du container est incapable de créer le dossier public/tmp/upload et ensuite de faire les dossiers dans upload.

        Pour les problèmes liés au non alignement des UID dans le container et sur l'host avec les bind mount, une solution est d'utiliser https://github.com/boxboat/fixuid. Basiquement c'est un entrypoint qui aligne l'UID utilisé dans le container par celui que tu passes à docker run.

        C'est bien plus compliqué que ce que n'importe qui voudrait. Mais pour les workflows de dev où tu as besoin de bind mount c'est la seule solution que je connaisse.

        • [^] # Re: Root

          Posté par  (site web personnel, Mastodon) . Évalué à 4.

          Ah excellent, ça ressemble au hack que j’ai mis plus bas sauf que:

          • Le conteneur tourne tout de suite avec le bon utilisateur non-root
          • Le binaire fixuid est avec le bitmask setuid (se lance avec les droits du fichier, ici root, et pas les droits de l’utilisateur qui lance le fichier)
          • Le binaire est static (sources en Go), donc facile à intégrer dans n’importe quel conteneur

          C’est top, merci beaucoup c’est exactement ça qu’il me fallait.

          Il va quand même falloir que j’étudie le code source de ce binaire, car vu qu’il est setuid root ça peut être un moyen de devenir root dans le conteneur (ce qui est un peu con vu que c’est ce qu’on évite de faire).

          N.B. mais pourquoi n’ai-je pas pensé avant à faire un binaire setuid root ??

      • [^] # Re: Root

        Posté par  (site web personnel, Mastodon) . Évalué à 4.

        Pour ce qui est du mapping des uid/gid, quand on veut utiliser un bind-mount (c'est à dire monter un répertoire depuis l'hôte dans le conteneur), c'est vraiment la merde car y'a pas d'API.

        Pourquoi alors que ce ne serait pas très compliqué (il y a un namespace pour les uid/gid dans le noyau Linux, donc ça serait sûrement assez peu de code) ? À mon avis parce que les personnes/entreprises qui développent pour docker/compose/kubernetes/whatever sont orientés vers du déploiement sur des infrastructure avec orchestrateur et pas pour un déploiement « dev local » comme ici.

        Cette situation dure depuis longtemps et il n'y a pas d'amélioration à l'horizon.

        Et pour cause, certains pensent aussi que pour du « dev local » c'est pas si grave de faire tourner des services en root. Le hic c'est que ça oblige à avoir un système de conteneurs différent pour le dev et pour la prod, ce qui en soit est forcément source de problème (si on fait tourner des conteneurs en prod).

        Il existe des hacks, plus ou moins réussis. En voici un :

        • utiliser le fichier .env pour il déclarer deux variables pour compose, UID et GID comme suit :
        UID=$(id -u)
        GID=$(id -g)
        • utiliser ces variables dans la section environments d'un service :
        mon_service:
          environments:
            - "UID=$UID"
            - "GID=$GID"
          volumes:
            - "$chemin_sur_l_hote:/chemin/final:ro"

        Utiliser un docker entrypoint qui va faire ce travail :

        • Si id -u est 0 (root), alors :
          • créer un utilisateur dans docker avec l'uid/gid défini dans les variables UID/GID
          • fixer d'autres permissions ou config en utilisant cet utilisateur
          • se relancer avec cet utilisateur
        • Sinon continuer la procédure classique de l'entrypoint.

        Avec cette solution, le conteneur démarre en root, mais perd ses privilèges dès l'entrypoint, ce qui fait qu'au final les processus dans le conteneurs ne tournent pas en root (et c'est ça qui compte).

        Par contre c'est assez casse-pied à mettre en place, surtout le rappel de l'entrypoint par lui-même : à base de exec et de su user /docker-entrypoint arg1 arg2 …

        Si quelqu'un a un meilleur hack, je suis preneur ;-)

  • # À propos de docker-compose

    Posté par  . Évalué à 10.

    Merci beaucoup ce travail. ;)

    J'en profite pour partager mon expérience sur l'utilisation de docker-compose.

    J'utilise docker-compose depuis des années et je me suis vite rendu compte de ses limitations.
    Si il permet de définir facilement une stack de containers, il n'est pas adapté pour effectuer nombre de tâches lors du lancement de l'application "containerisée".

    Par exemple, docker-compose n'est pas adapté pour:
    - Lancer l'initialisation des tables de la base de donnée
    - Générer des clés
    - Ajouter un ou plusieurs utilisateurs d'administration dans l'application "containerisée"
    - etc

    Si on essaye, on se retrouve à devoir jouer avec les scriptes de points d'entrée (entrypoint) des containers et ça peut vite devenir complexe.
    Comme au niveau pro je n'ai pas le choix des outils, je l'utilise malgré tout.

    Mais à coté, depuis longtemps, j'utilise aussi Ansible pour l'intégration des applications ("containerisée" ou pas) dans l'infrastructure:
    - Déploiement de l'application (ou de sa stack)
    - Déploiement des configurations
    - Initialisation des tables de la base de donnée
    - Génération de clés (et stockage pour plus tard)
    - Ajout d'utilisateurs
    - Configuration de reverse proxy ou load balancer
    - Ajout d'un sous-domaine
    - Configuration du système de backup
    - Configuration du système de teste de l'infra
    - Etc

    Les possibilités sont immense.

    Mais récemment j'ai décidé de me passer de docker-compose sur mon infrastructure personnelle et d'utiliser directement Ansible pour la définition de la stack de containers.
    L'idée c'est d'avoir un rôle Ansible qui déploie l'application et d'utiliser un playbook pour appliquer le rôle, personnaliser l'installation et l'intégrer dans l'infra.

    Le rôle s'occupe, par exemple, de:
    - Créer les containers
    - Déployer les fichiers de configuration avec la config par défaut
    - Initialiser la base de données

    Les paramètres de l'application ainsi que du déploiement sont personnalisable par des variables dans Ansible.

    Le playbook s'occupe de l'intégration dans mon infrastructure:
    - Il définit sur quel hôte l'application sera déployée
    - Personnalise l'installation
    - Créer des utilisateurs
    - Configure le reverse proxy
    - Configure un sous-domaine pour la nouvelle application

    Le rôle est réutilisable et facilement partageable tandis que le playbook est dépendant de l'infrastructure.
    Et si je crée un rôle qui déploie son application via des containers Docker, il peut être utilisé sur n'importe quel système où Docker est installé (ou presque).
    Et à coté, je crée aussi un playbook qui déploie l'application en locale pour du dev. Mais avec le système de tags d'Ansible ou pourrait avoir un seul playbook qui s'adapte en fonction de la cible (dev/test/prod).

    En plus avec l'arrivée des collections Ansible, on a un système qui permet de distribuer en une fois des rôles, des modules, des exemples de playbook et la doc en rapport avec un projet ou une application.

    En bref, Ansible m'apporte une plus grand liberté et flexibilité que docker-compose et ce commentaire est beaucoup trop long, j'aurai du créer un journal. :(

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.