Next.js mit Next-Auth auf Entra hinter Proxy mit NGINX

Dezenter Titel, hier nochmal aufgeschlüsselt:

  • Next.js App
  • Next-Auth gegen Entra
  • das ganze sitzt auf einem Server der nur via Proxy ins Internet kommt
    (warum ist das wichtig? Next-Auth will Profil lesen und braucht dafür Internet, die verwendete http Library kann aber keinen Proxy)
  • das alles sitzt hinter einem NGINX der reverse Proxy für App oder Container mit der App spielt
    (warum ist das wichtig? Diverse Redirects die Next-Auth und Entra machen funktionieren nur mit bestimmten NGINX Proxy Settings)

Der Witz ist dass sich tatsächlich jemand gefunden hat der das Problem mit dem (forward) Proxy gelöst hat: https://next-auth.js.org/tutorials/corporate-proxy, muss so 2022 gewesen sein und wurde auch im Juni 2025 zuletzt aktualisert funktioniert so aber nicht (mehr) – mit next-auth 4.24.13 in meinem Projekt UND der Azure Teil hat im Original und in der Änderung massives Problem wenn ein Request schief geht, nämlich keinerlei Fehlerhandling, crashed einfach raus.

Das neue node_modules/next-auth/providers/azure-ad.js sieht also so aus:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = AzureAD;

// NEU >
const HttpsProxyAgent = require('https-proxy-agent');
// < NEU
function AzureAD(options) {
  var _options$tenantId, _options$profilePhoto;
  const tenant = (_options$tenantId = options.tenantId) !== null && _options$tenantId !== void 0 ? _options$tenantId : "common";
  const profilePhotoSize = (_options$profilePhoto = options.profilePhotoSize) !== null && _options$profilePhoto !== void 0 ? _options$profilePhoto : 48;
  return {
    id: "azure-ad",
    name: "Azure Active Directory",
    type: "oauth",
    wellKnown: `https://login.microsoftonline.com/${tenant}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`,
    authorization: {
      params: {
        scope: "openid profile email"
      }
    },
    async profile(profile, tokens) {
      var _image;
//      const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, {
//        headers: {
//          Authorization: `Bearer ${tokens.access_token}`
//        }
//      });
// NEU >
      let fetchOptions = {
           headers: {
              Authorization: `Bearer ${tokens.access_token}`
           }
      };
      if (process.env.http_proxy) {
        fetchOptions.agent = new HttpsProxyAgent.HttpsProxyAgent(process.env.http_proxy);
      }
      try {
         const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, fetchOptions);
      } catch (_unused) {}
// NEU <
      let image;
      // Zusatzprüfung auf leere response eingefügt (dort crashed er sonst raus)
      if (typeof response !== "undefined" && response.ok && typeof Buffer !== "undefined") {
        try {
          const pictureBuffer = await response.arrayBuffer();
          const pictureBase64 = Buffer.from(pictureBuffer).toString("base64");
          image = `data:image/jpeg;base64, ${pictureBase64}`;
        } catch (_unused) {}
      }
      return {
        id: profile.sub,
        name: profile.name,
        email: profile.email,
        image: (_image = image) !== null && _image !== void 0 ? _image : null
      };
    },
    style: {
      logo: "/azure.svg",
      text: "#fff",
      bg: "#0072c6"
    },
    options
  };
}

Das neue node_modules/next-auth/core/lib/oauth/client.js entspricht fast dem aus dem Artikel, nur das HttpsProxyAgent muss man auf HttpsProxyAgent.HttpsProxyAgent beim instanzieren ändern:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.openidClient = openidClient;

var _openidClient = require("openid-client");

var HttpsProxyAgent = require("https-proxy-agent");

async function openidClient(options) {
  const provider = options.provider;
//  if (provider.httpOptions) _openidClient.custom.setHttpOptionsDefaults(provider.httpOptions);
//  let issuer;
// NEU >
  let httpOptions = {};
  if (provider.httpOptions) httpOptions = { ...provider.httpOptions };
  if (process.env.http_proxy) {
    let agent = new HttpsProxyAgent.HttpsProxyAgent(process.env.http_proxy);
    httpOptions.agent = agent;
  }
// NEU <
  _openidClient.custom.setHttpOptionsDefaults(httpOptions);

  let issuer;
  if (provider.wellKnown) {
    issuer = await _openidClient.Issuer.discover(provider.wellKnown);
  } else {
    var _provider$authorizati, _provider$token, _provider$userinfo;
    issuer = new _openidClient.Issuer({
      issuer: provider.issuer,
      authorization_endpoint: (_provider$authorizati = provider.authorization) === null || _provider$authorizati === void 0 ? void 0 : _provider$authorizati.url,
      token_endpoint: (_provider$token = provider.token) === null || _provider$token === void 0 ? void 0 : _provider$token.url,
      userinfo_endpoint: (_provider$userinfo = provider.userinfo) === null || _provider$userinfo === void 0 ? void 0 : _provider$userinfo.url,
      jwks_uri: provider.jwks_endpoint
    });
  }
  const client = new issuer.Client({
    client_id: provider.clientId,
    client_secret: provider.clientSecret,
    redirect_uris: [provider.callbackUrl],
    ...provider.client
  }, provider.jwks);
  client[_openidClient.custom.clock_tolerance] = 10;
  return client;
}

Die NGINX Config die bei mir funktioniert:

server {
    listen 443 ssl http2;
    server_name meinserver.fqdn;

    ssl_certificate /path/mycert.pem;
    ssl_certificate_key /path/mycert.key;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
        proxy_busy_buffers_size   512k;
        proxy_buffers   4 512k;
        proxy_buffer_size   256k;
    }

}

Wazuh Dashboard Zertifikat wechseln

Es steht im Nachhinein gesehen eh klipp und klar in der Anleitung – nur server.ssl.key und server.ssl.certificate tauschen aber man ist es irgendwie so gewohnt und tauscht natürlich auch opensearch.ssl.certificateAuthorities aus was zum lt. Googlesuche beliebten „unable to verify first certificate“ führt weil das in Wahrheit das interne Zertifikat ist mit dem die Komponenten untereinander connecten….

Daher nur die Files hinter den beiden Parametern wechseln oder halt überschreiben:

Ob in server.ssl.certificate tatsächlich die ganze Chain enthalten sein muss weiß ich nicht wirklich – ich hab intern keine Intermediate CA und hab die Root CA nicht im PEM File.

Hier die OpenSearch Dokumentation – sollte einem eigentlich eh instant auffallen dass die einen Parameter mit „server“ beginnen und die anderen mit „opensearch“ – wär halt maximal fantastisch wenn das in getrennten Files landen würd?

micro und das Clipboard in SSH Session

Keine Ahnung wer auf die Idee gekommen ist das nicht als Default zu haben aber wenn man micro in einer SSH Session verwendet funktionieren Copy/Paste innerhalb vom Dokument nicht so wie man das erwarten würde – seltsame Dinge mit der Maus gehen aber Strg+C/V erzeugt irgendeinen seltsamen Haufen aus irgendwelchen Clipboards.

Lösung: ~/.config/micro/settings.json

{
   "clipboard":"terminal"
}

Quelle: https://github.com/zyedidia/micro/blob/master/runtime/help/copypaste.md

openDesk Evaluierungsergebnis

Liveartikel mit vermutlich Updates – mal begonnen um nicht wieder Dinge zu vergessen für etwaige Diskussionen.

Was ist gut?

  • Die Idee. Selbstredend. Niemand will alleine von den Googles und Microsofts dieser Welt abhängig sein.
  • Die Auswahl der Komponenten.
  • Die technische Umsetzung von Rollout und Update.
  • Das „Company in a box“ Feeling. Und das mit wenigen Schritten live (wenn man mal weiß wie 😀 ).
  • Viel Standardinfrastruktur die viele vielleicht schon live haben (Kubernetes, Redis, Postgres, etc.).

Was ist weniger gut? (unsortiert)

  • Verzahnung
    • Wie bei den guten Sachen geschrieben sind ein paar Applikationen miteinander verbunden aber bei weitem nicht in allen Situationen die mir auf Anhieb eingefallen wären.
    • Warum Nextcloud nur für Dateien eingefügt worden ist erschließt sich mir nicht auf Anhieb wenn Open-XChange auch ähnliche Features hat (ohne die jemals gesehen zu haben – kann auch mit den eingebauten Offices der zwei Produkte zu tun haben).
    • Generell frage ich mich wieso Nextcloud UND Open-Xchange weil OX ja auch ein Drive hat aber wird schon seinen Grund haben. Dann aber auch bitte mehr von Nextcloud.
  • 2FA
    • nur mit App was gut aber zuwenig ist, WebAuthn notwendig
    • immer enforced, man hat sich an conditional Access gewöhnt und ist nicht bereit im corporate LAN 2FA zu machen
  • Identity Management (Nubus von Univention)
    • nur das absolute Minimum vorhanden
    • ohne Zusätze nicht Enterprisetauglich (Plugins für vorhandenes Identity Management, Tools für 1st/2nd Level Support, Polices (für alle Produkte im Verbund) etc.)
    • So Kleinigkeiten wie dass ein OpenProject Admin nur wirksam wird wenn man ihn setzt bevor man OpenProject das erste mal benutzt mit dem betroffenen User werden hoffentlich gefixed und sind kein inheräntes Problem – zeigen aber den Sand im Getriebe.
    • wieder von einer Firma abhängig
  • Enterprise Edition
    • wieder proprietäre Extensions drinnen – nicht wie hier beschrieben die gleichen Inhalte, Unterschiede hier
      • OpenProject in der Community Edition sinnlos (ohne den Corporate Plan der EE)
      • OX/Dovecot Pro kann ich (noch) nicht bewerten
    • Community Edition kaum produktiv betreibbar (fairerweise wird, wenn auch inkonsistent, darauf hingewiesen)
    • Wieder License Keys. Wir wissen alle dass Lizenzen die dritte Geißel der EDV sind (Drucker und Zertifikate sind die anderen zwei falls sich jemand gefragt hat).
  • Setup
    • Einige Fußangeln was Versionen angeht, siehe meine Erfahrungen.
    • Die Releasegeschwindigkeit der Tools ist höher als die des Projekts – verständlich aber sollte irgendwann wieder annähernd gleichauf liegen.
  • Betrieb
    • Aktuell viele Updates – die alle Potenzial haben Dinge zu brechen. Wird sich naturgemäß ändern aber genauso gefährlich wie die monatlichen Updates bei M365 beispielsweise. Aber das Tempo muss man gehen und die auftretenden Disasterfälle behandeln können.
    • Für Produktion wird davon ausgegangen dass man viele Komponenten quasi im Bauchladen enterprisetauglich schon zur Verfügung hat (Redis, Postgres, S3, etc.) – völlig valider Ansatz aber muss einem klar sein – wenn mans selber machen will.
    • Dadurch dass viele Produkte zusammengewürfelt wurden bzw. vorausgesetzt werden muss man auch für alle Produkte Spezialisten haben die sich zu Themen des Betriebs (Verwaltung, Monitoring, Support, Backup/Restore, etc.) Gedanken machen – wenn mans selber machen will.
  • Kosten
    • Ich finde keine Kosten pro User/Monat für Enterprise Edition sowohl on prem als auch als SaaS. Wenn ich mir anschaue was alleine OpenProject so nimmt für seine Enterprise Features schwant mir nix Gutes. Und Univention will verständlicherweise für Nubus vermutlich auch Geld sehen.
  • SaaS
    • Schon valide weil wer hat schon die notwendige Expertise und Hardware vorrätig – aber gemäß Ausschreibungsgewinner liefert man sich dann halt der Schwarz IT als Betreiber und wer weiß wem als Ansprechpartner aus (für DE). Aber einen Gott muss man halt anbeten vermutlich (wenn mans nicht selbst machen kann oder will).
  • Theming
    • Schon weit fortgeschritten aber an der einen oder anderen Stelle zwickts noch. Vielleicht in der Enterprise Edition besser.

Zusammenfassung

Äußerst cooler Ansatz mit funktionierendem ersten Wurf samt jeder Menge Raum für Verbesserungen aber mit doch hohem Basisaufwand für Selbstbetreiber bzw. halt SaaS-Selbstauslieferung, dann halt immerhin in europäisch.

openDesk – single Node mit k3s/longhorn

Eigentlich ist ja alles unter https://www.opendesk.eu/en/blog/opendesk-on-k3s beschrieben nur dass es dann halt doch nicht so funktioniert, weil

  1. Die Minimum Requirements sind tatsächlich einzuhalten – ich bin mit 10 Cores gescheitert weil er dann einen Pod nicht schedulen konnte. Vielleicht Zufall/Pech aber mit 12 hatte ich konsistent keine Probleme – siehe Update 2026-01-02
    32 GB RAM sind machbar – am Ende sind ca. 20 GB in use – aber am Weg gehen 40 GB für Cache drauf – pendelt sich dann auch weit nach unten (10 GB) ein aber langsamer wirds damit sicher nicht.
  2. Mit irgendeiner OpenProject Version ist sticky bit Support beim Kubernetes Storage Provider Voraussetzung geworden, das Deployment bleibt mit dem Default local-path Provider dann hängen.
    (Fußnote 2 auf https://docs.opendesk.eu/operations/requirements/)
  3. Bei mir hat Helm nur bis 3.17.x funktioniert.
    (Fußnote 1 auf https://docs.opendesk.eu/operations/requirements/)
  4. Helm diff Plugin hat bei mir auch nur mit 3.12.4 funktioniert.
  5. Helmfile hat bei mir auch nur mit 1.2.2 funktioniert.

Weil wir anderes Storage brauchen auf Longhorn gewechselt, das hat aber Single Node auch spezielle Herausforderungen weil in der Defaultinstallation das Setup nach einem Reboot vom Host sonst kaputt ist.

Vorgehen:

  1. Das longhorn.yaml aus dem Setup nur runterladen aber nicht gleich applyen.
  2. Das Deployment für longhorn-driver-deployer um folgende Environmentvariablen erweitern damit nur jeweils ein Pod für Attacher/Provisioner/Resizer/Snapshotter erzeugt wird:
    - name: CSI_ATTACHER_REPLICA_COUNT
    value: "1"
    - name: CSI_PROVISIONER_REPLICA_COUNT
    value: "1"
    - name: CSI_RESIZER_REPLICA_COUNT
    value: "1"
    - name: CSI_SNAPSHOTTER_REPLICA_COUNT
    value: "1"
  3. ConfigMap longhorn-default-setting bei data/default-setting.yaml um folgende Settings erweitern (default-data-path natürlich optional, ich hatte zweite Disk eingehängt):
    default-data-path: "/longhorn"
    default-replica-count: 1
    node-drain-policy: "always-allow"
    auto-salvage: true
  4. ConfigMap longhorn-storageClass den parameter „numberOfReplicas“ von „3“ auf „1“ ändern
    numberOfReplicas: "1"
  5. Im Deployment longhorn-ui die Replicas von 2 auf 1 stellen
    replicas: 1
  6. Im values.yaml.gotmpl fürs openDesk deployment (Punkt 3.2 in der Anleitung „Customizing the environment“) Storage umstellen:
    persistence:
    storageClassNames:
    RWO: "longhorn"

Da die Wahrscheinlichkeit dass alle Longhorn Volumes nach einem Reboot korrupt sind bei annähernd 100% liegt sollte man vor so einem Reboot alle Deployments und Statefulsets im openDesk Namespace auf 0 setzen und danach wieder zurück auf 1.

Vorher:
kubectl scale deployment -n <openDesk-namespace> --replicas=0 --all
kubectl scale statefulset -n <openDesk-namespace> --replicas=0 --all

….warten bis alle Longhorn Volumes detached sind….

….Reboot….

Nachher:
kubectl scale deployment -n <openDesk-namespace> --replicas=1 --all
kubectl scale statefulset -n <openDesk-namespace> --replicas=1 --all

( Und ja das ist sicher gegen 2000 Kubernetes Gesetze und der Fahndungszettel für mich hängt schon überall aber für mich funktionierts 😀 )

Updates:

  • 2026-01-02: die CPU Problematik ist heute virulent geworden weil die Komponente openxchangeCoreMW CPU requests von 1 macht (anstatt vermutlich 0.1 wie die meisten anderen auch), daher in die values.yaml.gotmpl fürs Deployment eingefügt:
    resources:
    openxchangeCoreMW:
    requests:
    cpu: 0.1

Fedora 42: FreeRDP 3.18 bricht Remmina 1.4.41

Mit dem Update auf 3.18 von FreeRDP heute wurde Remmina RDP über Remote Desktop Gateway gebrochen (security negotiation failure oder irgendsowas).

Downgrade auf 3.12 hilft aber auch die Flucht nach vorne (3.20):

freerdp 3.20 von https://koji.fedoraproject.org/koji/packageinfo?packageID=11176 besorgen

  • freerdp-3.20.0-1.fc42
    • freerdp-3.20.0-1.fc42.x86_64.rpm
    • freerdp-libs-3.20.0-1.fc42.x86_64.rpm
    • libwinpr-3.20.0-1.fc42.x86_64.rpm
  • mit sudo dnf install ./freerdp-3.20.0-1.fc42.x86_64.rpm ./freerdp-libs-3.20.0-1.fc42.x86_64.rpm ./libwinpr-3.20.0-1.fc42.x86_64.rpm in einem Rutsch installieren

    Qubes OS: Mein Epson Drucker

    Da das jetzt alles viel länger gedauert hat als erwartet fürs nächste Mal hier dokumentiert.

    Drucker: Epson WF-7610 Series, steinalt aber läuft (bisher) wie ein Glöckerl
    + OS: Qubes OS, Debian 13 oder Fedora 42/43 Template based AppVM


    Ergibt: Spaß!

    Lösungsweg:

    1. In jedem Fall aus dom0: qvm-service <vm> cups on
    2. Software in AppVM (oder Template) installieren:
    3. Sicherstellen dass CUPS läuft: sudo systemctl start cups
    4. Drucker verbinden
      • sudo system-config-printer
      • Add / Network Printer / Find Network Printer / IP bei Host angeben und Find
      • Warten bis er Amok läuft und 2000 Einträge mit der IP-Adresse in der Liste baut – aber auch eben einen Eintrag „Epson WF-7610 (IP-Adresse)
      • Diesen Eintrag auswählen (Host wird mit Port 515 vorbefüllt und Queue „PASSTHRU“) und „Probe“ klicken, „Searching for drivers“ sollte aufgehen und verschwinden.
      • Forward und Apply

    Weg zum Treiber bei Epson:

    Samsung Odyssey G9 + AMD + HDMI + Linux

    Eine Traumkombination!

    • Der Odyssey G9 (ein S49FG910EU bei mir) hat 5120×1440 und kann 144 Hz.
    • Mein Mini PC (Ninkear K7 mit einer Ryzen 7430U) hat nur HDMI.
    • Damit 5120×1440 in höheren Refreshraten läuft braucht man HDMI 2.1.
    • AMD darf unter Linux aus irgendeinem dummen Grund nicht HDMI 2.1 machen.

    Ergo: Linux sieht als höchstes der Gefühle 3840×1080@120 oder so was natürlich nicht Sinn der Sache ist und auch noch Kacke aussieht.

    Lösung für 144 Hz: Display Port.

    Da ich aber keinen neuen Mini PC kaufen will funktioniert folgender 5120×1440@60 Workaround (da ich jetzt auf KDE/Arch fahre und demnächst auf QubesOS – beide mit X11- umsteige):

    /usr/share/sddm/scripts/Xsetup
    
    xrandr --newmode "5120x1440_60" 544.25 5120 5168 5200 5600 1440 1443 1453 1620 +hsync -vsync
    xrandr --addmode HDMI-A-1 "5120x1440_60"
    xrandr --output HDMI-A-1 --mode "5120x1440_60" --verbose
    

    Update: Ein Adapter von USB-C auf Display Port hat das Problem ohne neuen Mini PC gelöst 😀 (Qubes OS/X11 springt instant auf die native Auflösung in 120 Hz, 144 ist aber auch in der Liste)

    vCard via NFC von Webserver

    Weiß ja nicht ob es bei den Kollegen von c’t zufällig funktioniert hat aber bei mir war das unter https://www.heise.de/ratgeber/NFC-Visitenkarten-selbst-beschreiben-10794239.html beschriebene nicht funktionstüchtig…

    Falle 1:

    Scheinbar hat Apple ab iOS 16.2 beschlossen nur mehr vCard 3.0 oder höher zu unterstützen. Outlook, selbst in der neuesten M365 Version exportiert aber 2.1 – Mobile Safari spuckt dann lapidar „Safari cannot download this file“ aus.

    Falle 2:

    Also, man will ja auch was lernen, hab ich mit pythonvCard4 eine 4.0 vCard erzeugt. Mit Bild natürlich. Konnte iOS auch auf Anhieb öffnen, Android auch aber dort wurde ein Kontakt erzeugt der den Base64 Wert vom Bild im Namen hatte.

    Aus irgendeinem Grund macht Android das wenn die Namensfelder („FN“ und „N“) vorm PHOTO Feld stehen. Also zuerst PHOTO dann FN/N im vCard File.

    Falle 3:

    Weiß nimmer genau wer mit was umgehen kann aber auf alle Fälle hab ich jetzt das PHOTO zweimal drin, einmal mit

    PHOTO:data:image/jpeg;base64,...base64 Zeug....

    und einmal mit

    PHOTO;ENCODING=b;MEDIATYPE=image/jpgeg:...base 64 Zeug....

    nur so haben beide Betriebssystem konsistent ein Bild beim Import.

    Bissl Referenzen:

    Wikipedia: https://de.wikipedia.org/wiki/VCard
    vCard 2.1: https://www.rfc-editor.org/rfc/rfc2425.html
    vCard 3.0: https://www.rfc-editor.org/rfc/rfc2426.html
    vCard 4.0: https://www.rfc-editor.org/rfc/rfc6350.html