Accueil / Infrastructure / Termix : déployer un gestionnaire SSH Web avec Docker, Active Directory et FreeIPA

Termix : déployer un gestionnaire SSH Web avec Docker, Active Directory et FreeIPA

L’objectif de cette mise en place est de proposer un gestionnaire de connexions SSH moderne, accessible depuis un navigateur, tout en conservant une architecture simple à exploiter et adaptée à un environnement professionnel. Chaque utilisateur peut disposer de son propre espace, enregistrer ses hôtes SSH et ouvrir plusieurs sessions depuis une interface Web centralisée.

La solution est Termix, dans le contexte de ce post, déployée dans Docker sur une machine virtuelle Ubuntu Server 24.04 LTS hébergée sur une VM hosté sur un Proxmox. L’accès à l’interface est protégé par HTTPS avec un certificat issu d’une PKI Microsoft. L’authentification est déléguée à deux annuaires : Active Directory et FreeIPA. Les habilitations sont pilotées par des groupes LDAP et l’inscription locale libre est désactivée.

Un point particulier doit être pris en compte avec Active Directory : certains comptes peuvent avoir un CN comportant des caractères accentués. Dans Termix 2.6.1, la validation du mot de passe LDAP est réalisée en réutilisant le DN complet retourné par l’annuaire. Dans certains cas, ce bind peut échouer avec un DN contenant des caractères non ASCII, alors que le bind via l’UPN de l’utilisateur fonctionne correctement. Pour conserver des noms d’utilisateurs correctement accentués dans l’annuaire, un patch léger est donc appliqué afin d’utiliser userPrincipalName pour le bind Active Directory.

Architecture retenue

Proxmox
└── VM srv-termix-01
    └── Ubuntu Server 24.04 LTS
        └── Docker Engine
            └── Termix 2.6.1 + patch AD/UPN
                ├── Volume persistant termix-data
                ├── HTTPS natif
                │   └── Certificat PKI Microsoft
                ├── LDAP Active Directory
                │   └── Groupe Termix-Users
                └── LDAP FreeIPA
                    └── Groupe termix-users

Le service est publié uniquement en HTTPS sur le port TCP 443. Le port HTTP interne de Termix n’est pas exposé sur le réseau.

1. Créer la machine virtuelle Ubuntu

Une machine virtuelle complète est recommandée pour héberger Docker. Cette approche évite les contraintes spécifiques à l’exécution de Docker dans un conteneur système et simplifie les opérations de sauvegarde et de restauration.

ParamètreValeur recommandée
Nomsrv-termix-01
OSUbuntu Server 24.04 LTS
CPU2 vCPU
RAM4 Go
Disque32 Go
RéseauVirtIO, adresse IP fixe
QEMU Guest AgentActivé

Installer le QEMU Guest Agent

sudo apt update
sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent

L’option QEMU Guest Agent doit également être activée dans les paramètres de la VM côté Proxmox.

2. Installer Docker Engine

Docker est installé depuis le dépôt officiel afin de disposer de Docker Engine, containerd et du plugin Docker Compose dans leurs versions maintenues par Docker.

sudo apt update
sudo apt upgrade -y

sudo apt install -y ca-certificates curl git

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg   -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

sudo apt install -y   docker-ce   docker-ce-cli   containerd.io   docker-buildx-plugin   docker-compose-plugin

Le moteur Docker peut ensuite être validé avec :

sudo systemctl status docker --no-pager
sudo docker run --rm hello-world

3. Préparer l’arborescence Termix

sudo mkdir -p /opt/termix
sudo mkdir -p /opt/termix/ssl
sudo chmod 700 /opt/termix/ssl

Le répertoire /opt/termix contient le fichier Compose ainsi que les éléments liés au certificat. Les données persistantes de l’application seront stockées dans un volume Docker nommé.

4. Générer la clé privée et la CSR pour la PKI Microsoft

Termix peut présenter directement son certificat HTTPS. La clé privée est donc générée sur le serveur et reste locale. Seule la CSR est transmise à la PKI.

sudo openssl genpkey   -algorithm RSA   -pkeyopt rsa_keygen_bits:3072   -out /opt/termix/ssl/termix.key

sudo chmod 600 /opt/termix/ssl/termix.key
sudo chown root:root /opt/termix/ssl/termix.key

Dans cet exemple, le service est publié sous le FQDN termix.example.local.

ORG="EXAMPLE"
FQDN="termix.example.local"

sudo openssl req   -new   -sha256   -key /opt/termix/ssl/termix.key   -out /opt/termix/ssl/termix.csr   -subj "/C=FR/O=${ORG}/CN=${FQDN}"   -addext "subjectAltName=DNS:${FQDN}"   -addext "keyUsage=critical,digitalSignature,keyEncipherment"   -addext "extendedKeyUsage=serverAuth"

La CSR doit être contrôlée avant soumission :

sudo openssl req   -in /opt/termix/ssl/termix.csr   -noout   -subject

sudo openssl req   -in /opt/termix/ssl/termix.csr   -noout   -text | grep -A2 "Subject Alternative Name"

5. Installer et vérifier le certificat

Une fois le certificat émis par la PKI Microsoft, il est copié sous le nom termix.crt.

sudo cp /tmp/termix.crt /opt/termix/ssl/termix.crt
sudo chown root:root /opt/termix/ssl/termix.crt
sudo chmod 644 /opt/termix/ssl/termix.crt

Les informations du certificat peuvent être vérifiées avec :

openssl x509   -in /opt/termix/ssl/termix.crt   -noout   -subject   -issuer   -dates

openssl x509   -in /opt/termix/ssl/termix.crt   -noout   -text | grep -A2 "Subject Alternative Name"

Il est également recommandé de vérifier que le certificat correspond bien à la clé privée utilisée pour générer la CSR. Les deux empreintes doivent être identiques.

openssl x509   -in /opt/termix/ssl/termix.crt   -pubkey   -noout   | openssl pkey -pubin -outform DER   | sha256sum

sudo openssl pkey   -in /opt/termix/ssl/termix.key   -pubout   -outform DER   | sha256sum

6. Adapter les permissions SSL

Termix utilise l’UID et le GID 1000 dans le conteneur. Le répertoire SSL doit donc être accessible en lecture par ce groupe tout en maintenant une protection stricte de la clé privée.

sudo chown root:1000 /opt/termix/ssl
sudo chmod 750 /opt/termix/ssl

sudo chown root:1000 /opt/termix/ssl/termix.key
sudo chmod 640 /opt/termix/ssl/termix.key

sudo chown root:1000 /opt/termix/ssl/termix.crt
sudo chmod 644 /opt/termix/ssl/termix.crt

sudo chown root:root /opt/termix/ssl/termix.csr
sudo chmod 644 /opt/termix/ssl/termix.csr

Un test permet de confirmer que le processus Termix pourra lire les deux fichiers :

sudo docker run --rm   --user 1000:1000   --entrypoint sh   -v /opt/termix/ssl:/app/data/ssl:ro   ghcr.io/lukegus/termix:2.6.1   -c 'id && ls -la /app/data/ssl &&       test -r /app/data/ssl/termix.crt && echo "CRT OK" &&       test -r /app/data/ssl/termix.key && echo "KEY OK"'

7. Pourquoi un patch est nécessaire pour certains comptes Active Directory

Dans Active Directory, le CN d’un utilisateur peut contenir des caractères accentués. Un utilisateur peut par exemple avoir un DN de la forme CN=Jean Duponté,OU=Users,DC=EXAMPLE,DC=LOCAL tout en utilisant jdupont comme sAMAccountName et [email protected] comme UPN.

Termix 2.6.1 recherche correctement l’utilisateur avec le filtre LDAP configuré, mais utilise ensuite le DN complet retourné pour effectuer le bind qui valide le mot de passe. Dans certaines configurations, ce second bind échoue lorsque le DN contient des caractères non ASCII.

Plutôt que de renommer les utilisateurs dans Active Directory, le choix retenu est de conserver les CN et displayName tels quels, et d’utiliser userPrincipalName pour le bind Active Directory. Ce mode de bind est parfaitement adapté à AD et évite de dépendre du format du DN.

Le patch est volontairement limité aux providers dont Username Attribute vaut sAMAccountName. Les autres providers LDAP, comme FreeIPA avec l’attribut uid, conservent le comportement natif de Termix.

8. Construire l’image Termix patchée

Le patch est appliqué directement sur les sources du tag Termix 2.6.1.

sudo mkdir -p /opt/termix-build
sudo chown $USER:$USER /opt/termix-build

git clone   --branch release-2.6.1-tag   --depth 1   https://github.com/Termix-SSH/Termix.git   /opt/termix-build

cd /opt/termix-build

Le script suivant ajoute l’attribut userPrincipalName aux attributs récupérés, puis l’utilise comme identité de bind pour Active Directory.

python3 - <<'PY'
from pathlib import Path

path = Path("src/backend/database/routes/ldap-auth-routes.ts")
data = path.read_text()

old_attrs = '''      const attrList = [
        config.usernameAttribute || "uid",
        config.displayNameAttribute || "cn",
        "mail",
        "email",
      ];'''

new_attrs = '''      const attrList = [
        config.usernameAttribute || "uid",
        config.displayNameAttribute || "cn",
        "userPrincipalName",
        "mail",
        "email",
      ];'''

old_email = '''      const email = getAttr("mail") || getAttr("email") || "";'''

new_email = '''      const email = getAttr("mail") || getAttr("email") || "";
      const userPrincipalName = getAttr("userPrincipalName");'''

old_bind = '''        await ldapBind(userClient, userDN, password);'''

new_bind = '''        const useActiveDirectoryUPN =
          (config.usernameAttribute || "").toLowerCase() === "samaccountname" &&
          !!userPrincipalName;

        const userBindIdentity = useActiveDirectoryUPN
          ? userPrincipalName
          : userDN;

        await ldapBind(userClient, userBindIdentity, password);'''

for old in (old_attrs, old_email, old_bind):
    if old not in data:
        raise SystemExit(
            "ERREUR : le code attendu n'a pas été trouvé. Patch annulé."
        )

data = data.replace(old_attrs, new_attrs, 1)
data = data.replace(old_email, new_email, 1)
data = data.replace(old_bind, new_bind, 1)

path.write_text(data)

print("Patch AD/UPN appliqué avec succès.")
PY

Le patch est ensuite vérifié :

git diff --check

git diff --   src/backend/database/routes/ldap-auth-routes.ts

L’image locale peut alors être construite :

sudo docker build   -f docker/Dockerfile   -t termix:2.6.1-ad-upn   .

sudo docker image ls | grep termix

Il est utile de conserver le patch dans un fichier pour les futures montées de version :

cd /opt/termix-build
git diff > /opt/termix/termix-2.6.1-ad-upn.patch

9. Créer le Docker Compose final

services:
  termix:
    image: termix:2.6.1-ad-upn
    container_name: termix
    restart: unless-stopped

    ports:
      - "443:8443"

    volumes:
      - termix-data:/app/data
      - /opt/termix/ssl:/app/data/ssl:ro

    environment:
      PORT: "8080"

      PUID: "1000"
      PGID: "1000"

      ENABLE_SSL: "true"
      SSL_PORT: "8443"
      SSL_DOMAIN: "termix.example.local"
      SSL_CERT_PATH: "/app/data/ssl/termix.crt"
      SSL_KEY_PATH: "/app/data/ssl/termix.key"

volumes:
  termix-data:
    driver: local

La configuration est validée puis démarrée :

cd /opt/termix

sudo docker compose config
sudo docker compose up -d

sudo docker compose ps

Le conteneur doit apparaître en état healthy.

sudo docker inspect termix   --format '{{.Config.Image}}'

sudo ss -lntp | grep :443

10. Vérifier HTTPS

https://termix.example.local
openssl s_client   -connect termix.example.local:443   -servername termix.example.local   </dev/null 2>/dev/null   | openssl x509       -noout       -subject       -issuer       -dates

11. Configurer Active Directory dans Termix

Un compte de service LDAP dédié est utilisé pour effectuer les recherches dans l’annuaire. L’accès à Termix est contrôlé par le groupe Termix-Users.

Le filtre utilise la règle LDAP Microsoft 1.2.840.113556.1.4.1941, également appelée LDAP_MATCHING_RULE_IN_CHAIN, afin de prendre en compte les appartenances de groupes imbriquées.

Host:
dc01.example.local

Port:
389

Bind DN:
[email protected]

User Search Base:
DC=EXAMPLE,DC=LOCAL

User Search Filter:
(&(objectCategory=person)(objectClass=user)(sAMAccountName={{username}})(memberOf:1.2.840.113556.1.4.1941:=CN=Termix-Users,CN=Users,DC=EXAMPLE,DC=LOCAL))

Username Attribute:
sAMAccountName

Display Name Attribute:
displayName

Allowed Users:
[laisser vide]

L’utilisateur saisit simplement son sAMAccountName, par exemple jdupont. Termix recherche alors le compte, récupère son userPrincipalName et effectue le bind avec une identité de type [email protected]. Le nom affiché dans l’interface reste issu de l’attribut displayName.

Pour vérifier le groupe :

ldapsearch   -x   -H ldap://dc01.example.local:389   -D "[email protected]"   -W   -b "DC=EXAMPLE,DC=LOCAL"   "(&(objectClass=group)(cn=Termix-Users))"   dn cn member

Pour tester le filtre d’autorisation :

ldapsearch   -x   -H ldap://dc01.example.local:389   -D "[email protected]"   -W   -b "DC=EXAMPLE,DC=LOCAL"   "(&(objectCategory=person)(objectClass=user)(sAMAccountName=jdupont)(memberOf:1.2.840.113556.1.4.1941:=CN=Termix-Users,CN=Users,DC=EXAMPLE,DC=LOCAL))"   dn sAMAccountName displayName userPrincipalName

12. Configurer FreeIPA dans Termix

Un second provider LDAP peut être ajouté pour FreeIPA. Dans cet exemple, le Base DN est dc=example,dc=org.

dc=example,dc=org

L’accès est contrôlé par un groupe FreeIPA nommé termix-users.

ipa group-show termix-users

La configuration du provider est la suivante :

Host:
ipa01.example.org

Port:
389

Bind DN:
uid=svc_termix,cn=users,cn=accounts,dc=example,dc=org

User Search Base:
cn=users,cn=accounts,dc=example,dc=org

User Search Filter:
(&(objectClass=inetOrgPerson)(uid={{username}})(memberOf=cn=termix-users,cn=groups,cn=accounts,dc=example,dc=org))

Username Attribute:
uid

Display Name Attribute:
cn

Group Search Base:
cn=groups,cn=accounts,dc=example,dc=org

Allowed Users:
[laisser vide]

Le provider FreeIPA n’utilise pas le patch AD/UPN puisque Username Attribute vaut uid. Termix conserve donc son bind LDAP classique avec le DN retourné par FreeIPA.

La recherche peut être validée avec :

ldapsearch   -x   -H ldap://ipa01.example.org:389   -D "uid=svc_termix,cn=users,cn=accounts,dc=example,dc=org"   -W   -b "cn=users,cn=accounts,dc=example,dc=org"   "(uid=jdupont)"   dn uid cn mail memberOf

13. Paramètres d’inscription et d’auto-provisioning

  • OIDC Auto-Provision : activé. Ce réglage permet la création automatique du compte Termix lors de la première authentification via un provider externe.
  • Allow User Registration : désactivé. Les utilisateurs ne peuvent pas créer librement un compte local Termix avec un username et un mot de passe.

L’habilitation est ainsi pilotée exclusivement par les annuaires : l’utilisateur doit appartenir au groupe prévu dans Active Directory ou FreeIPA pour être retourné par le filtre LDAP.

14. Comprendre le volume Docker persistant

Le volume termix-data est indépendant du cycle de vie du conteneur. Il contient les données persistantes de Termix stockées sous /app/data dans le conteneur.

sudo docker volume ls
sudo docker volume inspect termix_termix-data

La suppression et la recréation du conteneur ne suppriment pas ce volume :

sudo docker compose down
sudo docker compose up -d

La commande docker compose down -v ne doit pas être utilisée dans une opération courante : l’option -v supprime explicitement les volumes du projet.

La sauvegarde de la VM reste une méthode simple pour protéger l’ensemble de la plateforme : système, Docker, volume Termix, configuration et certificats.

15. Commandes d’exploitation utiles

cd /opt/termix

# Etat
sudo docker compose ps

# Logs
sudo docker compose logs --tail=100 termix

# Logs en temps réel
sudo docker compose logs -f termix

# Redémarrage
sudo docker compose restart

# Recréation du conteneur
sudo docker compose up -d --force-recreate

# Image utilisée
sudo docker inspect termix --format '{{.Config.Image}}'

16. Gérer les futures mises à jour

L’image utilisée est une image locale construite à partir du tag officiel Termix 2.6.1. Lors d’une montée de version, il faut d’abord vérifier si le comportement LDAP Active Directory a été corrigé dans la version cible. Si le correctif n’est pas encore présent, le patch peut être réappliqué sur le nouveau tag avant reconstruction de l’image.

Une sauvegarde de la VM est recommandée avant toute montée de version.

17. Résultat final

Cette architecture fournit un gestionnaire SSH Web multi-utilisateur, sécurisé par HTTPS et intégré aux annuaires d’entreprise. Les comptes externes sont provisionnés automatiquement au premier login, les inscriptions locales sont désactivées et l’autorisation d’accès repose sur des groupes LDAP.

Active Directory
├── login : sAMAccountName
├── autorisation : groupe Termix-Users
├── affichage : displayName
└── bind utilisateur : userPrincipalName via le patch

FreeIPA
├── login : uid
├── autorisation : groupe termix-users
├── affichage : cn
└── bind utilisateur : DN natif

Termix
├── HTTPS natif / certificat PKI Microsoft
├── OIDC Auto-Provision : ON
├── Allow User Registration : OFF
└── volume Docker persistant

18. Sources

Étiquetté :

Répondre

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *