Docker par Portainer

Le choix a été fait ici de déployer docker sur une solution de type Ubuntu et Debian, qui sont très proches et relativement courantes.

Consultez le site officiel Docker pour une documentation approfondie sur le stockage et le réseau dans Docker.

Installation du serveur Ubuntu

Docker s’installe sur un serveur de base Ubuntu. Nous commençons donc par installer Ubuntu en version Serveur, sans GUI, pour le faire le plus léger possible. Une fois installé, assurez-vous que le serveur soit bien à jour : > sudo apt-get update > sudo apt-get upgrade

Pour mettre en place Docker, suivre ce lien Docker sur Ubuntu. En voici les différentes étapes :

Mise en place du repository Docker pour Ubuntu

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Installation des packages Docker > sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Test avec l’image hello-world récupérée automatiquement depuis le Docker hub > sudo docker run hello-world

Support Debian

Commencer par installer une machine Debian en version serveur car nous n’aurons pas besoin de GUI ou de programmes divers.

Rappel : les commandes root sont à taper en root. Contrairement à Ubuntu, il n’y a pas de sudo par défaut existant. Il faut commuter par > su -

Mettre à jour le serveur par > apt update && apt upgrade -y

Pour la mise en place de Docker sur Debian, suivre ce lien Docker sur Debian

Voici les étapes si vous ne voulez pas changer de page

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Installation de la dernière version de docker pour Debian > sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Vérifier si l’installation a bien fonctionné en exécutant le conteneur hello-world depuis l’image du même nom que docker devrait aller télécharger sur le docker hub > sudo docker run hello-world

Complément utilisateur

Par défaut, docker requiert un niveau d’exécution privilégié et il est courant de :

  1. dédier un utilisateur à l’environnement docker

$ adduser docker

  1. le rendre membre du groupe docker par

$ sudo usermod -aG docker docker

  1. éventuellement également lui octroyer les droits de sudo pour tout faire avec cet utilisateur

$ sudo usermod -aG sudo docker

Installer portainer sur Docker

Créer un dossier docker dans la racine ainsi qu’un sous-dossier portainer

$ cd /
$ sudo mkdir docker
$ cd docker
$ sudo mkdir portainer

Lancer simplement la commande docker suivante

docker run -d \
    --name portainer \
    -p 9000:9000 \
    -p 9443:9443 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /docker/portainer:/data \
    --restart=unless-stopped \
    portainer/portainer-ce

Créer un dossier pour heimdall dans docker

mkdir -p /docker/heimdall

Se connecter à Portainer par http://@ip-machine-docker:9000 Créer un compte administrateur local depuis l’interface de connexion Portainer.io

Installer Heimdall depuis Portainer

Cliquer sur l’environnement qui correspond au serveur sur lequel Portainer vient d’être installé.

Cliquer sur l’onglet Stacks et faire + Add Stack

Nommer la stack par heimdall et, dans l’onglet Web Editor, copier cette configuration

version: "2.1"
services:
    heimdall:
        image: lscr.io/linuxserver/heimdall:latest
        container_name: heimdall
        environment:
            - PUID-1000
            - PGID-1000
            - TZ=Europe/Paris
        volumes:
            - /docker/heimdall/config:/config
        ports:
            - 80:80
            - 443:443
        restart: unless-stopped

Un peu plus bas sur la page, cliquer sur le bouton Deploy the stack

Si le port 80 de la machine est déjà utilisé, changer la ligne - 80:80 par - 8080:80 par exemple

Se connecter

Depuis n’importe quel navigateur faire http://@ip-de-docker:port-de-docker pour accéder au portail heimdall

@ip-de-docker correspond à l’ip de votre serveur docker port-de-docker correspond au port que vous avez défini dans la stack heimdall

Un reverse Proxy ?

Un reverse proxy (l’inverse d’un proxy server) permet d’accéder à des services internes tout en permettant de sécuriser et de gérer le trafic depuis et vers Internet.

Il en existe plusieurs, notamment SWAG (Secure Web Application Gateway) et Nginx Proxy Manager (ou NPM mais attention à la confusion au gestionnaire de packages de Node.js). Nous resterons sur SWAG car dispose d’outils natifs comme CertBot pour les certificats et Fail2ban pour se protéger des intrusions. NGinx Proxy Manager dispose d’une GUI, SWAG non. SWAG est très extensible, Nginx Proxy Manager non…

Il reste aussi Traefik, très intégré à Docker, Portainer, Kubernetes mais transporte la complexité dans Docker avec des règles spécifiques au routage du reverse proxy. Contrairement à SWAG qui isole mieux le trafic entre le Reverse et Docker.

Et comme nous voulons utiliser notre environnement Docker, nous utiliserons un livrable au format Docker, qui simplifiera sa mise en place rapidement et isolant le Reverse Proxy du système d’exploitation.

Installer SWAG depuis Portainer

Depuis la Web console de portainer, choisissez l’environnement de travail correspondant à votre installation docker de travail.

Choix environnement de travail

Une fois dans cet environnement, cliquez sur le bouton Stack

Choix de la stack

Cliquez ensuite sur le bouton + Add stack en haut à droite

Bouton Ajout Stack

Dans l’éditeur fourni (le web editor), nous allons coller les informations récupérées depuis la documentation officielle de swag située ici –> Site officiel SWAG ou bien sur le github ici –> Github Swag

Il s’agit pour nous de prendre la déclaration au format YAML du docker compose. Pour simplifier les choses voici en substance une telle déclaration qu’il nous faudra modifier pour l’adapter à nos besoins.

---
version: "2.1"
services:
  swag:
    image: lscr.io/linuxserver/swag
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
      - URL=yourdomain.url
      - SUBDOMAINS=www,
      - VALIDATION=http
      - CERTPROVIDER= #optional
      - DNSPLUGIN=cloudflare #optional
      - DUCKDNSTOKEN=<token> #optional
      - EMAIL=<e-mail> #optional
      - ONLY_SUBDOMAINS=false #optional
      - EXTRA_DOMAINS=<extradomains> #optional
      - STAGING=false #optional
    volumes:
      - </path/to/appdata/config>:/config
    ports:
      - 443:443
      - 80:80 #optional
    restart: unless-stopped

Parmi les différents paramètres :

cat /etc/passwd

Au final nous obtenons ce contenu pour le fichier Docker Compose

---
version: "2.1"
services:
  swag:
    image: lscr.io/linuxserver/swag
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
      - URL=mon_url.duckdns.org
      - SUBDOMAINS=wildcard
      - VALIDATION=duckdns
      - DUCKDNSTOKEN=le_tocken_duckdns
      - EMAIL=votre_email
    volumes:
      - </path/to/appdata/config>:/config
    ports:
      - 443:443
    restart: unless-stopped

Une fois le fichier Docker-Compose réalisé, il reste à appuyer sur le bouton situé en bas de page Deploy the stack

Deploiement de la stack

Une fois la stack démarrée, elle est visible dans le menu stacks

Stack swag démarrée

Vous pouvez cliquer dessus, voir les logs, inspecter les configurations et autres actions disponibles sur cet environnement.

Routeur / box

Ne pas oublier de router les paquets entrants sur le port 443 de votre box et de les envoyer vers votre machine docker qui fait tourner le container swag.

Pour tester il suffira d’ouvrir un navigateur et de pointer vers https://test.<votre_sous_domaine_duckdns>.duckdns.org

Rappelez-vous que nous avons pris un certificat wildcard basé sur les sous-domaines. Donc nous sécurisons *.<votre_sous_domaine_duckdns>.duckdns.org.

Rappel sur les certificats SSL et les défis de validation des certificats

Généralement, c’est l’outil Certbot qui est utilisé pour la génération de certificats. Il est disponible ici : htps://certbot.eff.org

Certbot permet de créer le certificat initial mais également permet de renouveler automatiquement le certificat. Il est intégré à beaucoup de reverse proxy, comme SWAG par exemple.

Il faudra juste préciser à CertBot pour quel type de site vous renouvelez le certificat : domaine, sous-domaine, wildcard…

Pour cela, les outils de vérification de certificats comme Let’s Encrypt vont lancer des défis à votre site pour déterminer s’il est vraiment ce qu’il prétend être.

  1. Défi HTTP-01

Pour générer un certificat, il faudra que vous déposiez un token qui vous sera transmis par le service de génération de certificats. Ce sera sur l’arborescence correspond à http://votre-domaine/.well-known/acme-challenge/<TOKEN>

Comme ce défi communique avec un serveur en HTTP, il faut disposer d’un port 80 ouvert et disponible sur Internet pour que le challenge fonctionne.

Avec Let’s Encrypt, c’est une fois tous les 3 mois mais ce n’est pas la panacée de laisser ce port 80 ouvert sans protection au monde entier.

  1. Défi DNS-01

Si vous disposez de votre propre nom de domaine et que vous gérez votre zone DNS, alors vous pouvez participer à ce défi qui vous demandera de poser une valeur spécifique dans un champ TXT.

C’est un challenge plus compliqué à mettre en place mais beaucoup sécurisé et surtout qui n’implique pas de devoir ouvrir un port HTTP sur votre routeur. Tout se passe du côté du serveur DNS, avec les temps de propagation à prendre en compte dans ce cas.

  1. Autres défis

Il existe d’autres modes d’authentification des sites et services par SSL comme TLS-SNI-01 ou TLS-ALPN-01 mais ce sont des processus récents mal pris en compte par l’ensemble des navigateurs et services de certificats.

  1. Remarques

Si vous utilisez des services comme DuckDns pour la gestion des noms de dommaines sur des sites dynamiques, il faudra rester sur le défi HTTP-01 car DuckDNS ne gère les DNS que de façon très basique.

Ajouter une redirection SWAG

Pour permettre d’atteindre Portainer (ou tout autre service disponible) depuis l’Internet, toujours en mode sécurisé à travers le certificat SSL, nous devons nous rappeler que SWAG est avant tout un serveur Web disposant d’un certain nombre de configurations possibles autour de nginx.

Lors de la création de swag, nous avons rattaché un dossier déclaré localement sur le serveur Docker (du genre /docker/swag/config) à un point de montage /config déclaré par Docker. Dans ce dossier local nous allons trouver une certaine quantité de dossiers, dont le dossier nginx.

Etant donné que SWAG est un projet communautaire, le dossier proxy-confs contient déjà un grand nombre de configurations déjà réalisées qu’il nous suffit d’utiliser ou d’adapter à notre usage.

Prenons un exemple : nous allons donner accès à Portainer depuis notre reverse proxy en mode sécurisé :

  1. Se connecter au serveur Docker par SSH

  2. depuis le dossier proxy-confs, faire une copie du fichier portainer.subdomain.conf.sample sous le nom portainer.subdomain.conf

  3. éditez le fichier portainer.subdomain.conf afin de valider les champs :

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name portainer.*;

Le serveur nginx va écouter sur le port 443 et réagir si le nom du domaine commence par portainer.x.duckdns.org

Dans l’autre partie du fichier de configuration, nginx déclare les paramètres de redirection. Attention, il s’agit des informations de redirection sur le LAN, et non depuis le WAN. Donc portainer étant déclaré en réponse sur le port 9000 par http en local, c’est par ces informations que nginx va réaliser sa redirection.

        set $upstream_app portainer;
        set $upstream_port 9000;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;

Il n’est pas conseillé d’utiliser les IP directement car Docker réalise des opérations de renouvellement d’IP régulièrement. L’IP du conteneur de destination pourrait alors changer. Préférez l’usage des noms pour ces redirections.

Après modification, il est nécessaire de relancer le conteneur swag. Pour cela, rendez-vous sur la page principale de Portainer, dans la stack de Swag et pressez le bouton Restart.

Restart Portainer

Pour information la différence entre subdomain et subfolder est la suivante :

Si le service que vous souhaitez mettre en place n’existe pas dans la liste des services préconfigurés, il est possible de dupliquer le template qui se trouve dans le dossier et de le renommer avec service_name.subdomain.conf

Compléter la variable server_name avec le nom du service (en laissant .*)

Compléter ensuite les variables dans la partie proxy_pass pour indiquer à swag comment interagir avec le service. Rappel : les valeurs de proxy_pass sont celles qui correspondent à la façon dont l’URL est joignable sur le LAN (pas le WAN).

Redirection vers un conteneur Docker

A la différence de la redirection de service sur une machine distincte de l’environnement Docker, avec accès par l’IP du service, nous pouvons mettre des redirections vers des containers docker de la même façon.

Pour la gestion du nom du container, il suffit de s’assurer que le proxy fait bien référence au nom déclaré dans le container.

La difficulté qui va être rencontrée ici est que Swag va tenter de joindre le nom du container sur ses réseaux.

Gestion des réseaux avec Docker

Sur tous les environnements, qu’ils soient cloud, on-premise ou docker, la notion de réseau, sous-réseau ou subfolder est importante à comprendre.

Voici la liste des réseaux déclarés dans Docker.

Réseaux Docker

Docker déclare par défaut les réseaux suivants : * bridge : en général l’IP du bridge est 172.17.0.0/16. C’est ce qui permet de communiquer avec l’hôte et les différents conteneurs. * host : réseau communiquant avec le serveur docker directement. * none : aucun réseau. Utilisé uniquement lorsque des conteneurs ne doivent pas avoir de connectivité au réseau.

Chaque conteneur se voit affecté un réseau distinct et donc aucune communication n’est possible entre les différents conteneurs.

Lorsque l’on souhaite faire communiquer swag avec les autres conteneurs, il faut faire en sorte que les réseaux dialoguent. Pour cela, il faut se connecter sur la stack de swag, aller sur la gestion de son réseau.

Réseau swag

Dans la liste, choisir le réseau à atteindre et faire Join network Il sera dorénavant possible d’avoir une connexion entre les différents environnements.

Installer Nginx Proxy Manager

Nginx Proxy Manager est l’équivalent de Swag, à savoir un reverse proxy. Il est disponible également au format Docker et nous allons le mettre en place à travers la plateforme Docker. Il dispose d’une intégration avec Let’s Encrypt, ce qui est toujours intéressant pour sécuriser nos accès.

Il faudra disposer d’une IP publique, statique de préférence, mais une IP dynamique peut convenir. Nous utiliserons le service DuckDNS.org pour réaliser la redirection vers notre serveur auto-hébergé. Les ports qui devront transiter sont les ports 80 et 443, à destination du serveur Nginx donc, pour permettre le challenge de Let’s Encrypt.

Sur Portainer, créer une nouvelle Stack pour coller le docker compose suivant :

version: ‘3’ services: nginx: image: ‘jc21/nginx-proxy-manager:latest’ container_name: nginx ports: - ‘80:80’ # ne pas modifier - ‘81:81’ - ‘443:443’ # ne pas modifier restart: unless-stopped environment: DB_MYSQL_HOST: “db” DB_MYSQL_PORT: 3306 DB_MYSQL_USER: “utilisateur” # à modifier DB_MYSQL_PASSWORD: “mdp” # à modifier DB_MYSQL_NAME: “nginx” volumes: - /srv/docker/nginx-proxy-manager/data:/data - /srv/docker/nginx-proxy-manager/letsencrypt:/etc/letsencrypt depends_on: - db db: image: ‘jc21/mariadb-aria:latest’ container_name: nginx-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ‘npm’ # à modifier MYSQL_DATABASE: ‘nginx’ MYSQL_USER: ‘utilisateur’ # à modifier MYSQL_PASSWORD: ‘mdp’ # à modifier volumes: - /srv/docker/nginx-proxy-manager/mysql:/var/lib/mysql

Une fois la stack déployée vous pourrez vous connecter à nginx proxy manager sur le port 81. Lors de l’installation, les identifiants par défaut sont admin@example.com et changeme.

A la connexion, il vous sera demandé de créer une nouvelle identité et de changer le mot de passe par défaut.

Sécuriser par Authelia

Aller voir la documentation Authelia où se trouvent toutes les informations pour la mise en place depuis Docker ou directement sur une plateforme Linux. Mais également comment l’intégrer aux différents proxies existants, notamment Swag, HAProxy ou Nginx.

Il y a deux façons d’appréhender la mise en place :

C’est cette deuxième façon que nous allons utiliser en récupérant les informations du fichier docker-compose.yml disponible sur le github

Nous ajoutons ces éléments au même niveau de décalage que le service swag (soit avec deux espaces avant) :

authelia: image: authelia/authelia container_name: authelia environment: - TZ=Europe/Paris volumes: - /docker/authelia/config:/config restart: unless-stopped

Il reste à enregistrer et mettre à jour la stack par une pression sur le bouton Update the stack

Pour mettre en place la configuration d’Authelia, il faut éditer le fichier configuration.yml qui doit se trouver dans le dossier config que nous avons mis en place sur la machine docker pour ce container.

Dans ce fichier se trouve un secret, un identifiant, qui doit être défini pour la variable jwt_secret. Il sert à valider un mot de passe quand celui-ci est transmis par email.

Pour générer un secret, il est possible de le faire par ligne de commande :

tr -cd ‘[:alnum:]’ < /dev/urandom | fold -w 64 | head -n 1 | tr -d ‘’; echo

Cela va générer une chaine de 64 caractères (avec fold -w 64) provenant du générateur pseudo aléatoire de Linux dont on ne prendra que la première ligne et qui ne contiendra que des caractères alphanumériques, en retirant le ‘’ de fin de ligne.

La variable default_2fa_method peut être laissée sur une chaine vide.

Dans la section server: : hosts : 0.0.0.0 port: 9091 path: “authelia”

La partie importante est l’authentification des utilisateurs. Il est possible d’utiliser LDAP pour cela mais en environnement personnel cela risque d’être un peu compliqué. Pour quelques personnes, l’usage d’un fichier de mots de passe peut s’avérer une meilleure solution.

Pour ce faire, il faut modifier la section file: en activant les différents champs qui la composent. Ne laisser qu’un algorithme de chiffrement du genre :

file: path: /config/users_database.yml password: algorithm: argon2id iterations: 3 key_length: 32 salt_length: 16 memory: 65536 parallelism: 4

Pour ensuite définir le comportement du mot de passe (taille minimale, longueur, complexité) il faut jouer sur la variable password_policy:. Vous pouvez laisser les valeurs par défaut en définissant la variable enabled: sur true dans la section standard:

La partie la plus importante est celle qui va contenir le comportement des autorisations d’accès au-dessous de la variable access_control:

La valeur deny_policy: indique le comportement par défaut lorsqu’aucune règle ne correspond pour le contrôle d’accès. C’est généralement deny qui est définie.

Ensuite définissez l’ensemble des réseaux qui doivent être joints par Authelia. Prenez garde à bien rajouter votre réseau local sous peine de blocage d’accès, notamment pour les machines qui ne sont pas sous forme de conteneur et donc qui sortent du serveur lui-même.

On va ensuite définir les règles qui vont s’appliquer dans la section rules:. Elles sont constituées des lignes domain, policy et networks.

Par exemple une règle du genre :

domain: nextcloud.lgbswag.duckdns.org policy: bypass

laissera passer la requête de connexion sans intervenir

Une règle

domain: ’*.lgbswag.duckdns.org’ policy: one_factor networks: - internal

fera passer la demande à l’authentification d’Authelia si le réseau est identifié comme un réseau interne.

La règle

domain: ’*.lgbswag.duckdns.org’ policy: two_factor

activera l’authentification double facteurs pour les éléments qui correspondent à cette règle, donc ceux qui proviennent de l’extérieur du réseau interne.

Ensuite vient la partie de gestion des cookies, dans la section session:. Les cookies sont à affecter au domaine actuel correspondant à l’activité de swag (lgbswg.duckdns.org dans l’exemple). Et affecter le nom authelia_session pour le cookie.

Activez ensuite la section regulation: en spécifiant les paramètres de blocage en cas d’erreur de connexion.

Pour la partie storage:, décommentez la variable encryption_key: et précisez une clé d’au moins 20 caractères.

Pour le stockage, pour faire simple on peut utiliser un stockage local sous forme de base de données sqlite3.

Pour la partie notification et récupération de mots de passe, qui s’effectuent par email, la section smtp: peut être paramétrée pour contenir les éléments de configuration selon votre opérateur.

Al a fin de la configuration, il faut retourner dans la liste des containers et faire restart sur le conteneur Authelia.

On regarde ensuite les logs pour voir si tout se passe bien.

Installer Nextcloud sur Docker et swag