Um token só para o dashboard inteiro: transformando o token de deployment em credencial de primeira classe


Você move o dashboard web do Hermes para um servidor interno para que o time alcance no navegador, muda o endereço de bind de 127.0.0.1 para 0.0.0.0 e ele se recusa a subir. O log diz que precisa de um auth provider e que não vai fazer bind. Você tem um token de deployment — injetado pelo seu secret manager — mas nada o trata como credencial.

A próxima meia hora é conhecida: você é empurrado para configurar basic auth, a caixa de login do navegador aparece, as credenciais vão parar no .env e o app desktop reporta “HTTP alcançável, mas a conexão WebSocket falhou”. Falta um passo e nada se encaixa.

Por que falhava antes: um gate que falha fechado

O gate de autenticação do dashboard apertou em junho de 2026: qualquer bind em endereço que não seja loopback exige um auth provider registrado, e sem um ele se recusa a fazer bind — nenhuma porta aberta em vez de uma porta descuidada. O desenho está certo.

A lacuna estava em quais providers contavam. Só contavam os interativos: basic auth, conta Nous, OIDC self-hosted. Só que o formato comum em um deployment é um token único compartilhado — descriptografado por um secret manager em um arquivo e entregue ao serviço. O código antigo não só o ignorava: também empurrava você a escrever um conjunto de credenciais de basic auth no .env que o deployment nunca pediu.

Mesmo com o token provisionado, as duas pernas o rejeitavam: uma requisição REST levando o bearer recebia 401 no_cookie, e o upgrade de /api/ws recebia um 403 puro. O desktop traduzia isso como “chegou ao gateway por HTTP, mas o WebSocket ao vivo falhou” — confuso, porque a checagem HTTP só toca o /api/status público e parece perfeitamente saudável.

A correção: o token é ele mesmo um provider

O Hermes traz um provider dashboard_auth/session_token embutido. Ele fica fora do caminho: assim que HERMES_DASHBOARD_SESSION_TOKEN é provisionado ele se registra sozinho, e sem token nenhum ele não faz absolutamente nada.

Duas formas de provisionar:

  • um valor literal: HERMES_DASHBOARD_SESSION_TOKEN=<token>;
  • um caminho de arquivo: HERMES_DASHBOARD_SESSION_TOKEN=file:/run/secrets/hermes-dashboard-token.

A segunda forma é a que os secret managers precisam. Um caminho pode ficar em uma linha Environment= do systemd, em um plist do launchd ou no ambiente de um serviço renderizado pelo Nix; o secret em si não pode. Os módulos Nix do Hermes expõem isso como backend.sessionTokenFile e renderizam na forma file:. Na inicialização o arquivo é lido (espaços em volta removidos, BOM tolerado); se estiver ilegível ou vazio, o processo sai com um erro explícito em vez de rodar com uma credencial vazia.

A checagem usa comparação de tempo constante, então o tempo de resposta não pode ser usado para adivinhar o token. As duas pernas que um cliente em modo token usa — o header Authorization: *** do REST e o parâmetro ?token= do upgrade em /api/ws — passam pelo mesmo ponto de resolução, então um token provisionado autoriza as duas. É exatamente o formato que o campo “Session token” do desktop sempre prometeu.

Uma escolha deliberada: não existe página de login. O /login renderiza um aviso explicando que esse deployment autentica com token, em vez de um botão que ninguém consegue completar, e o redirecionamento automático de SSO nunca oferece esse provider. O modo token não é uma pessoa entrando; é uma máquina apresentando uma credencial.

Fazendo o deploy

# 1. gera um token aleatório de 32 bytes
openssl rand -base64 32 > /etc/hermes/dashboard-token
chmod 600 /etc/hermes/dashboard-token
chown hermes:hermes /etc/hermes/dashboard-token

# 2. entrega ao serviço um caminho, não o valor
export HERMES_DASHBOARD_SESSION_TOKEN=file:/etc/hermes/dashboard-token

# 3. faz bind em um endereço que não seja loopback (o gate agora pode ser satisfeito)
hermes dashboard --host 0.0.0.0 --port 9119

No Nix você não escreve a variável à mão: os módulos recebem backend.sessionTokenFile, apontam para o secret descriptografado e renderizam a entrada de ambiente no formato file: — o store só vê o caminho.

Checando se a porta está realmente fechada

# estado do gate e providers registrados
curl -s http://YOUR_HOST:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["session-token"]

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

# sem o token: essa é a parte que importa
curl -s -o /dev/null -w '%{http_code}\n' http://YOUR_HOST:9119/api/auth/me
# 401

Para o gateway remoto do desktop, cole o mesmo token no campo “Session token”; ele é usado tanto na sonda HTTP quanto no upgrade de WebSocket.

Tabela de troubleshooting

Sintoma Causa Correção
O processo sai na inicialização mencionando um arquivo de token ilegível ou vazio Caminho file: errado, permissões erradas ou arquivo vazio Rode cat no arquivo com a conta que executa o serviço
auth_providers está vazio em /api/status A variável nunca chegou ao processo do serviço Confira o Environment= do systemd, o plist ou o env do container
“HTTP alcançável, mas o WebSocket falha” A checagem HTTP bate em uma rota pública enquanto o /api/ws precisa do token — ou o token foi rotacionado Cole o token novo; rotação significa redeployar o secret
Você quer basic auth ou OIDC para o gate e o token só internamente O provider vem ligado por padrão Defina dashboard.session_token_auth.enabled: false

O opt-out, por escrito:

dashboard:
  session_token_auth:
    enabled: false

Quando não usar

  • Um dashboard compartilhado por várias pessoas. Nada atrás desse token carrega identidade de usuário — quem o tem é administrador. Se você precisa saber quem mudou o quê, use OIDC (Authentik, Keycloak, Zitadel, Authelia, Auth0, Okta e companhia) ou o provider Nous.
  • Qualquer coisa exposta à internet. Um bearer token de vida longa está a um vazamento do acesso total, e rotação significa atualizar todo cliente. Prefira login interativo em deployments públicos.
  • Setups locais, de um usuário só. Um bind em loopback não precisa de provider nenhum; o desktop e o SPA local usam o token interno. Não adicione configuração de que você não precisa.

Uma regra operacional: trate o token como uma senha. Mantenha num secret manager, nunca em uma imagem ou em um repositório, e rotacione redeployando o secret em vez de editar uma variável de ambiente — toda cópia nos clientes é invalidada de uma vez, o que aparece como aquela mensagem enigmática de “WebSocket falha”.

Para o histórico do gate do dashboard, leia hardening do login do dashboard. Se o seu sintoma é clientes desktop trocando tokens entre si, veja o artigo sobre colisão de session token, e para formatos de auth sobre um túnel, OAuth sobre SSH. Flags e portas padrão ficam na página de referência do hermes dashboard.

Status da release

O provider entrou na main em 28 de outubro de 2026 (PR #146139, construído sobre a resolução de arquivo do #146093 e a renderização Nix do #146104). A release atual, v0.22.5 (23 de outubro de 2026), ainda não o inclui.

Um token, as duas pernas, nenhuma página de login — um deployment interno não precisa mais inventar um sistema de senhas só para passar pelo gate.