Un seul token pour tout le dashboard : le token de déploiement devient un moyen d'authentification à part entière


Vous déplacez le dashboard web Hermes sur un serveur interne pour que votre équipe puisse l’ouvrir dans un navigateur, vous passez l’adresse d’écoute de 127.0.0.1 à 0.0.0.0, et il refuse de démarrer. Le log réclame un auth provider et annonce qu’il ne se liera pas. Vous avez pourtant un token de déploiement — injecté depuis votre gestionnaire de secrets — mais rien ne le traite comme un identifiant.

La demi-heure qui suit est un classique : on vous pousse à configurer basic auth, la fenêtre de login du navigateur apparaît, les identifiants finissent dans .env, et l’application desktop signale « HTTP reachable, but the WebSocket connection failed ». Un pas de trop, et rien ne s’aligne.

Pourquoi ça échouait : une barrière en fail closed

La barrière d’authentification du dashboard s’est durcie en juin 2026 : toute écoute sur une adresse non-loopback exige un auth provider enregistré, et sans provider elle refuse de se lier — pas de port ouvert plutôt qu’un port négligé. Ce design est le bon.

La lacune portait sur les providers pris en compte. Seuls les interactifs comptaient : basic auth, un compte Nous, OIDC auto-hébergé. Or la forme courante en déploiement, c’est un token partagé unique — déchiffré par un gestionnaire de secrets dans un fichier, puis remis au service. L’ancien code non seulement l’ignorait, mais vous poussait en plus à écrire un jeu d’identifiants basic auth dans .env dont le déploiement n’avait aucun besoin.

Même avec le token provisionné, les deux jambes le rejetaient : une requête REST portant le bearer recevait 401 no_cookie, et l’upgrade /api/ws un 403 sec. Le desktop traduisait cela par « gateway joignable en HTTP, mais le WebSocket live a échoué » — déroutant, car le contrôle HTTP ne touche que la route publique /api/status et semble parfaitement sain.

Le correctif : le token est lui-même un provider

Hermes embarque un provider dashboard_auth/session_token. Il reste discret : dès que HERMES_DASHBOARD_SESSION_TOKEN est provisionné, il s’enregistre ; sans token, il ne fait strictement rien.

Deux façons de le provisionner :

  • une valeur littérale : HERMES_DASHBOARD_SESSION_TOKEN=<token> ;
  • un chemin de fichier : HERMES_DASHBOARD_SESSION_TOKEN=file:/run/secrets/hermes-dashboard-token.

La seconde forme est celle dont les gestionnaires de secrets ont besoin. Un chemin peut figurer dans une ligne Environment= de systemd, un plist launchd ou l’environnement de service rendu par Nix ; le secret lui-même, non. Les modules Nix de Hermes l’exposent via backend.sessionTokenFile et le rendent sous la forme file:. Au démarrage, le fichier est lu (espaces alentour supprimés, BOM toléré) ; s’il est illisible ou vide, le processus s’arrête sur une erreur explicite au lieu de tourner avec un identifiant vide.

La vérification utilise une comparaison à temps constant, donc le timing de la réponse ne permet pas de deviner le token. Les deux jambes d’un client en mode token — l’en-tête REST Authorization: *** et le paramètre ?token= de l’upgrade /api/ws — passent par la même couture, si bien que un seul token provisionné autorise les deux. C’est exactement ce que le champ « Session token » du desktop promettait depuis le début.

Un choix assumé : il n’y a aucune page de login. /login affiche une note expliquant que ce déploiement s’authentifie par token, plutôt qu’un bouton que personne ne peut compléter, et la redirection SSO automatique ne propose jamais ce provider. Le mode token, ce n’est pas une personne qui se connecte ; c’est une machine qui présente un identifiant.

Le déployer

# 1. générer un token aléatoire de 32 octets
openssl rand -base64 32 > /etc/hermes/dashboard-token
chmod 600 /etc/hermes/dashboard-token
chown hermes:hermes /etc/hermes/dashboard-token

# 2. passer un chemin au service, pas la valeur
export HERMES_DASHBOARD_SESSION_TOKEN=file:/etc/hermes/dashboard-token

# 3. écouter sur une adresse non-loopback (la barrière peut désormais être satisfaite)
hermes dashboard --host 0.0.0.0 --port 9119

Sur Nix, vous n’écrivez pas la variable vous-même : les modules prennent backend.sessionTokenFile, pointent vers le secret déchiffré et rendent l’entrée d’environnement file: — le store ne voit jamais que le chemin.

Vérifier que la porte est réellement fermée

# état de la barrière et providers enregistrés
curl -s http://YOUR_HOST:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["session-token"]

# avec le token : 200
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer ***" http://YOUR_HOST:9119/api/auth/me
# 200

# sans le token : c'est la partie qui compte
curl -s -o /dev/null -w '%{http_code}\n' http://YOUR_HOST:9119/api/auth/me
# 401

Pour le gateway distant du desktop, collez le même token dans le champ « Session token » ; il sert à la fois pour la sonde HTTP et pour l’upgrade WebSocket.

Tableau de dépannage

Symptôme Cause Correctif
Le processus s’arrête au démarrage en mentionnant un fichier de token illisible ou vide Mauvais chemin file:, mauvaises permissions, ou fichier vide cat le fichier avec le compte qui fait tourner le service
auth_providers est vide dans /api/status La variable n’a jamais atteint le processus du service Vérifiez Environment= dans systemd, le plist, ou l’env du conteneur
« HTTP reachable, but the WebSocket fails » Le contrôle HTTP tape une route publique tandis que /api/ws exige le token — ou le token a été tourné Collez le nouveau token ; une rotation implique de redéployer le secret
Vous voulez basic auth ou OIDC pour la barrière et le token seulement en interne Le provider est actif par défaut Mettez dashboard.session_token_auth.enabled: false

L’opt-out, en clair :

dashboard:
  session_token_auth:
    enabled: false

Quand ne pas l’utiliser

  • Un dashboard partagé par plusieurs personnes. Rien derrière ce token ne porte d’identité utilisateur — celui qui le détient est administrateur. S’il vous faut savoir qui a changé quoi, utilisez OIDC (Authentik, Keycloak, Zitadel, Authelia, Auth0, Okta et compagnie) ou le provider Nous.
  • Tout ce qui est exposé à Internet. Un bearer token à longue durée de vie n’est qu’à une fuite de l’accès complet, et la rotation oblige à mettre à jour chaque client. Sur un déploiement public, préférez un login interactif.
  • Les installations locales mono-utilisateur. Une écoute loopback n’a besoin d’aucun provider ; le desktop et la SPA locale utilisent le token interne. N’ajoutez pas de configuration dont vous n’avez pas besoin.

Une règle opérationnelle : traitez le token comme un mot de passe. Gardez-le dans un gestionnaire de secrets, jamais dans une image ni dans un dépôt, et faites la rotation en redéployant le secret plutôt qu’en éditant une variable d’environnement — toutes les copies côté client sont invalidées d’un coup, ce qui se manifeste par cet énigmatique « WebSocket fails ».

Pour le contexte de la barrière du dashboard, lisez le durcissement du login du dashboard. Si votre symptôme est celui de clients desktop qui échangent leurs tokens, voyez le papier sur la collision de session tokens, et pour les schémas d’auth à travers un tunnel, OAuth au-dessus de SSH. Les options et les ports par défaut sont sur la page de référence hermes dashboard.

Statut de la release

Le provider a été mergé dans main le 28 octobre 2026 (PR #146139, bâti sur la résolution de fichiers de la #146093 et le rendu Nix de la #146104). La release courante, v0.22.5 (23 octobre 2026), ne l’inclut pas encore.

Un token, deux jambes, aucune page de login — un déploiement interne n’a plus à inventer un système de mots de passe juste pour franchir la barrière.