[GH-ISSUE #5567] Self-hosted, embedded IdP: User JWTs become invalid after first restart (cipher: message authentication failed) #10802

Closed
opened 2026-08-05 01:27:20 -04:00 by saavagebueno · 1 comment
Owner

Originally created by @HKrabbe on GitHub (Mar 10, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5567

Description

Hi NetBird team,

ich habe ein reproduzierbares Problem mit einer self-hosted NetBird-Installation (combined container, embedded IdP, Traefik) auf Docker:
Jeder frisch angelegte User wird nach dem ersten docker compose down && docker compose up -d unlesbar, d.h. alle Dashboard-Requests auf /api/users... enden mit 401 token invalid und im Log mit decrypt ... cipher: message authentication failed.

Das passiert bereits ohne Reverse Proxy Container – der eigentliche Auslöser war zwar der Versuch, das neue Reverse-Proxy-Feature zu aktivieren, aber der Fehler tritt auch in einem minimalen Setup (traefik + postgres + netbird-server + dashboard) auf.


Environment

  • NetBird Version (aus Log):
    • management server version 0.66.3
  • Deployment:
    • Docker Compose, combined container (netbirdio/netbird-server)
    • netbirdio/dashboard
    • postgres:16
    • traefik:v3.6 davor
  • Auth / IdP:
    • Embedded Dex IdP (kein externer IdP)
  • Config (Auszug config.yaml, sensible Daten maskiert):
server:
  listenAddress: ":80"
  exposedAddress: "https://<management-domain>:443"
  stunPorts:
    - 3478
  metricsPort: 9090
  healthcheckAddress: ":9000"
  logLevel: "info"
  logFile: "console"

  authSecret: "<redacted-auth-secret>"
  dataDir: "/var/lib/netbird"

  auth:
    issuer: "https://<management-domain>/oauth2"
    signKeyRefreshEnabled: true
    dashboardRedirectURIs:
      - "https://<management-domain>/nb-auth"
      - "https://<management-domain>/nb-silent-auth"
    cliRedirectURIs:
      - "http://localhost:53000/"

  reverseProxy:
    trustedHTTPProxies:
      - "172.30.0.10/32"

  store:
    engine: "postgres"
    dsn: "host=netbird-postgres user=netbird password=<redacted-db-password> dbname=netbird port=5432"
  • Volumes / Bind Mounts:
    • Postgres-Daten: /opt/netbird/data/postgres:/var/lib/postgresql/data
    • NetBird dataDir: /opt/netbird/data/netbird:/var/lib/netbird
    • config.yaml als Bind-Mount: ./config.yaml:/etc/netbird/config.yaml
  • OS:
    • Linux 6.8.0-101-generic

Minimal Steps to Reproduce

Wichtig: Der Fehler tritt bereits ohne Reverse-Proxy-Container auf.

  1. Komplett frischer Start (Datenverzeichnisse löschen)

    cd /opt/netbird
    
    # Stack stoppen
    docker compose down
    
    # ALLE NetBird-Daten löschen
    rm -rf /opt/netbird/data/postgres
    rm -rf /opt/netbird/data/netbird
    
  2. Stack mit aktuellen Images starten (ohne Reverse Proxy)

    docker compose pull   # zieht u.a. netbirdio/netbird-server:latest (= 0.66.3) und dashboard
    docker compose up -d traefik postgres netbird-server dashboard
    
  3. Erstkonfiguration

    • Browser im Inkognito/Private-Mode öffnen.
    • https://<management-domain> aufrufen.
    • Embedded IdP nutzt Standard-Flow; ersten User anlegen (lokaler User).
    • Dashboard lädt, alles sieht gut aus.
  4. Erster Restart (ohne Proxy, ohne weitere Konfigänderungen)

    docker compose down
    docker compose up -d traefik postgres netbird-server dashboard
    
  5. Dashboard nach Neustart

    • Erneut im Inkognito-Fenster https://<management-domain> aufrufen.
    • Login scheint zu funktionieren (Weiterleitung durch den IdP).
    • Die UI lädt jedoch nicht korrekt; in der Browser-Console:
      • Mehrere 401er auf:
        • GET /api/users
        • GET /api/users/current
        • GET /api/users?service_user=true
        • GET /api/users?service_user=false
      • und im UI eine „Token invalid“ / 401 Fehlermeldung.
  6. Server-Logs zu diesen Requests

Im netbird-server Log sieht man für jede dieser Anfragen (Benutzer-ID hier maskiert):

failed to get user by ID <masked-user-id>:
  decrypt user: decrypt email: decrypt: cipher: message authentication failed

Error when validating JWT: user <masked-user-id> not found

got a handler error: token invalid

HTTP response ...: GET /api/users status 401
HTTP response ...: GET /api/users/current status 401
HTTP response ...: GET /api/users?service_user=true status 401
HTTP response ...: GET /api/users?service_user=false status 401

Das Verhalten ist nach jedem „frischen“ Setup reproduzierbar:
Direkt nach dem Anlegen des Users funktioniert alles, nach dem ersten down/up sind alle User-Calls kaputt.


Expected Behavior

  • Nach einem normalen docker compose down && docker compose up -d sollten:
    • bestehende User weiterhin entschlüsselbar sein,
    • Dashboard-Calls /api/users... einen 200 liefern,
    • der Embedded IdP + Management-Server ihre eigene Datenbank konsistent lesen können.

Kurz: Ein einfacher Container-Restart darf die User-JWTs und Userdaten nicht unlesbar machen.


Actual Behavior

  • Direkt nach dem Erstellen des ersten Users:
    • Dashboard + API funktionieren wie erwartet.
  • Nach dem ersten Restart des Stacks (ohne Änderungen an config.yaml oder den Datenverzeichnissen):
    • Alle Aufrufe von /api/users, /api/users/current, /api/users?service_user=... enden mit 401.
    • Logs zeigen systematisch:
      • decrypt user: decrypt email: decrypt: cipher: message authentication failed
      • Error when validating JWT: user <masked-user-id> not found
      • got a handler error: token invalid

Das wirkt so, als ob sich das Key-Material, mit dem Benutzerdaten verschlüsselt wurden (entweder basierend auf authSecret oder Daten unter dataDir), zwischen „User anlegen“ und „nächstem Neustart“ ändert – obwohl weder config.yaml noch /opt/netbird/data/netbird angerührt werden.


Relation zum Reverse-Proxy-Feature

Mein ursprüngliches Ziel war, das Reverse-Proxy-Feature gemäß Doku zu aktivieren:
Migration Guide: Enable Reverse Proxy Feature

  • Der Reverse-Proxy-Container selbst verbindet sich sauber:
    • ProxyService registered on gRPC server
    • New proxy connection from <proxy-container-ip>
    • proxy connected, Proxy registered in cluster
  • Die oben beschriebenen cipher: message authentication failed Fehler treten jedoch auch dann auf, wenn der Proxy-Container gar nicht gestartet ist.

Deshalb scheint das Problem unabhängig vom Reverse-Proxy-Feature zu sein und eher im Zusammenspiel von:

  • Embedded IdP
  • Datenverschlüsselung von User-Objekten
  • und Persistenz über Docker-Volumes nach einem Neustart

zu liegen.


Additional Information

  • Ich habe mehrfach „hart“ neu aufgesetzt (Postgres- und NetBird-Datenverzeichnisse gelöscht, keine alten Daten gemischt).
  • config.yaml bleibt zwischen „User anlegen“ und dem Neustart unverändert.
  • Das Problem tritt konsistent auf, solange ich netbirdio/netbird-server:latest verwende (Version 0.66.3 laut Log).
  • Alle sensiblen Werte (Domain, Passwörter, Secrets, Token, User-IDs) sind in diesem Issue bewusst maskiert.
Originally created by @HKrabbe on GitHub (Mar 10, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5567 ### Description Hi NetBird team, ich habe ein reproduzierbares Problem mit einer **self-hosted** NetBird-Installation (combined container, embedded IdP, Traefik) auf Docker: **Jeder frisch angelegte User wird nach dem *ersten* `docker compose down && docker compose up -d` unlesbar**, d.h. alle Dashboard-Requests auf `/api/users...` enden mit `401 token invalid` und im Log mit `decrypt ... cipher: message authentication failed`. Das passiert **bereits ohne Reverse Proxy Container** – der eigentliche Auslöser war zwar der Versuch, das neue Reverse-Proxy-Feature zu aktivieren, aber der Fehler tritt auch in einem minimalen Setup (traefik + postgres + netbird-server + dashboard) auf. --- ### Environment - **NetBird Version** (aus Log): - `management server version 0.66.3` - **Deployment**: - Docker Compose, combined container (`netbirdio/netbird-server`) - `netbirdio/dashboard` - `postgres:16` - `traefik:v3.6` davor - **Auth / IdP**: - Embedded Dex IdP (kein externer IdP) - **Config (Auszug `config.yaml`, sensible Daten maskiert)**: ```yaml server: listenAddress: ":80" exposedAddress: "https://<management-domain>:443" stunPorts: - 3478 metricsPort: 9090 healthcheckAddress: ":9000" logLevel: "info" logFile: "console" authSecret: "<redacted-auth-secret>" dataDir: "/var/lib/netbird" auth: issuer: "https://<management-domain>/oauth2" signKeyRefreshEnabled: true dashboardRedirectURIs: - "https://<management-domain>/nb-auth" - "https://<management-domain>/nb-silent-auth" cliRedirectURIs: - "http://localhost:53000/" reverseProxy: trustedHTTPProxies: - "172.30.0.10/32" store: engine: "postgres" dsn: "host=netbird-postgres user=netbird password=<redacted-db-password> dbname=netbird port=5432" ``` - **Volumes / Bind Mounts**: - Postgres-Daten: `/opt/netbird/data/postgres:/var/lib/postgresql/data` - NetBird dataDir: `/opt/netbird/data/netbird:/var/lib/netbird` - `config.yaml` als Bind-Mount: `./config.yaml:/etc/netbird/config.yaml` - **OS**: - Linux 6.8.0-101-generic --- ### Minimal Steps to Reproduce > Wichtig: Der Fehler tritt bereits **ohne** Reverse-Proxy-Container auf. 1. **Komplett frischer Start (Datenverzeichnisse löschen)** ```bash cd /opt/netbird # Stack stoppen docker compose down # ALLE NetBird-Daten löschen rm -rf /opt/netbird/data/postgres rm -rf /opt/netbird/data/netbird ``` 2. **Stack mit aktuellen Images starten (ohne Reverse Proxy)** ```bash docker compose pull # zieht u.a. netbirdio/netbird-server:latest (= 0.66.3) und dashboard docker compose up -d traefik postgres netbird-server dashboard ``` 3. **Erstkonfiguration** - Browser im Inkognito/Private-Mode öffnen. - `https://<management-domain>` aufrufen. - Embedded IdP nutzt Standard-Flow; ersten User anlegen (lokaler User). - Dashboard lädt, alles sieht gut aus. 4. **Erster Restart (ohne Proxy, ohne weitere Konfigänderungen)** ```bash docker compose down docker compose up -d traefik postgres netbird-server dashboard ``` 5. **Dashboard nach Neustart** - Erneut im Inkognito-Fenster `https://<management-domain>` aufrufen. - Login scheint zu funktionieren (Weiterleitung durch den IdP). - Die UI lädt jedoch nicht korrekt; in der Browser-Console: - Mehrere 401er auf: - `GET /api/users` - `GET /api/users/current` - `GET /api/users?service_user=true` - `GET /api/users?service_user=false` - und im UI eine „Token invalid“ / 401 Fehlermeldung. 6. **Server-Logs zu diesen Requests** Im `netbird-server` Log sieht man für jede dieser Anfragen (Benutzer-ID hier maskiert): ```text failed to get user by ID <masked-user-id>: decrypt user: decrypt email: decrypt: cipher: message authentication failed Error when validating JWT: user <masked-user-id> not found got a handler error: token invalid HTTP response ...: GET /api/users status 401 HTTP response ...: GET /api/users/current status 401 HTTP response ...: GET /api/users?service_user=true status 401 HTTP response ...: GET /api/users?service_user=false status 401 ``` Das Verhalten ist nach jedem „frischen“ Setup reproduzierbar: **Direkt nach dem Anlegen des Users funktioniert alles, nach dem ersten `down`/`up` sind alle User-Calls kaputt.** --- ### Expected Behavior - Nach einem normalen `docker compose down && docker compose up -d` sollten: - bestehende User weiterhin entschlüsselbar sein, - Dashboard-Calls `/api/users...` einen `200` liefern, - der Embedded IdP + Management-Server ihre eigene Datenbank konsistent lesen können. Kurz: Ein einfacher Container-Restart darf die User-JWTs und Userdaten nicht unlesbar machen. --- ### Actual Behavior - Direkt nach dem Erstellen des ersten Users: - Dashboard + API funktionieren wie erwartet. - Nach dem ersten Restart des Stacks (ohne Änderungen an `config.yaml` oder den Datenverzeichnissen): - Alle Aufrufe von `/api/users`, `/api/users/current`, `/api/users?service_user=...` enden mit `401`. - Logs zeigen systematisch: - `decrypt user: decrypt email: decrypt: cipher: message authentication failed` - `Error when validating JWT: user <masked-user-id> not found` - `got a handler error: token invalid` Das wirkt so, als ob sich das Key-Material, mit dem Benutzerdaten verschlüsselt wurden (entweder basierend auf `authSecret` oder Daten unter `dataDir`), zwischen „User anlegen“ und „nächstem Neustart“ ändert – obwohl weder `config.yaml` noch `/opt/netbird/data/netbird` angerührt werden. --- ### Relation zum Reverse-Proxy-Feature Mein ursprüngliches Ziel war, das Reverse-Proxy-Feature gemäß Doku zu aktivieren: [Migration Guide: Enable Reverse Proxy Feature](https://docs.netbird.io/selfhosted/migration/enable-reverse-proxy) - Der Reverse-Proxy-Container selbst verbindet sich sauber: - `ProxyService registered on gRPC server` - `New proxy connection from <proxy-container-ip>` - `proxy connected`, `Proxy registered in cluster` - Die oben beschriebenen `cipher: message authentication failed` Fehler treten jedoch **auch dann** auf, wenn der Proxy-Container gar nicht gestartet ist. Deshalb scheint das Problem unabhängig vom Reverse-Proxy-Feature zu sein und eher im Zusammenspiel von: - Embedded IdP - Datenverschlüsselung von User-Objekten - und Persistenz über Docker-Volumes nach einem Neustart zu liegen. --- ### Additional Information - Ich habe mehrfach „hart“ neu aufgesetzt (Postgres- und NetBird-Datenverzeichnisse gelöscht, keine alten Daten gemischt). - `config.yaml` bleibt zwischen „User anlegen“ und dem Neustart unverändert. - Das Problem tritt konsistent auf, solange ich `netbirdio/netbird-server:latest` verwende (Version 0.66.3 laut Log). - Alle sensiblen Werte (Domain, Passwörter, Secrets, Token, User-IDs) sind in diesem Issue bewusst maskiert.
saavagebueno added the triage-needed label 2026-08-05 01:27:20 -04:00
Author
Owner

@HKrabbe commented on GitHub (Mar 10, 2026):

Nevermind Issue gefunden.
Der Encryptionkey war nicht korrekt gesetzt für die DB

<!-- gh-comment-id:4033104913 --> @HKrabbe commented on GitHub (Mar 10, 2026): Nevermind Issue gefunden. Der Encryptionkey war nicht korrekt gesetzt für die DB
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#10802