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ètre | Valeur recommandée |
|---|---|
| Nom | srv-termix-01 |
| OS | Ubuntu Server 24.04 LTS |
| CPU | 2 vCPU |
| RAM | 4 Go |
| Disque | 32 Go |
| Réseau | VirtIO, adresse IP fixe |
| QEMU Guest Agent | Activé |
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


