Commit Graph
3 Commits
Author SHA1 Message Date
Deploymentcenter BotandClaude Opus 5 56b1d2631a fix(setup): Dateirechte bei Geheimnissen, plattformabhaengige Zielnamen
Zwei Beobachtungen aus der laufenden Integration.

1. Der Installer schrieb Geheimnisse weltlesbar
   SetupWriter benutzte File.WriteAllText ohne Rechteanpassung - unter Linux
   also die Standardmaske und damit ueblicherweise 644. In genau dieser Datei
   stehen Lizenzschluessel und Anwendungstoken; jeder Benutzer des Systems
   konnte sie lesen. Der Lizenz-Cache in StateStore wird aus demselben Grund
   seit jeher auf 600 gesetzt - der Installer zog nicht nach.
   Enthaelt ein Ziel mindestens einen geheimen Wert, wird die Datei jetzt auf
   den eigenen Benutzer beschraenkt. Als geheim gilt type=secret UND
   source=provision: ein so geholtes Token traegt oft den Typ "string", ist
   aber genauso schutzbeduerftig. Unter Windows bleibt es beim Profil-ACL.

2. Zielnamen koennen plattformabhaengig unterschiedlich sein
   %APPDATA%\MeineAnwendung gegen $XDG_CONFIG_HOME/meineanwendung - eine
   setup.json kannte nur eine Schreibweise. Die kleingeschriebene Form allein
   traegt, weil NTFS die Schreibweise ignoriert, aber nur solange das
   Dateisystem tatsaechlich unempfindlich ist; auf APFS mit Beachtung der
   Schreibweise oder bei groesseren Unterschieden entstuende ein zweites,
   leeres Verzeichnis neben dem, aus dem die Anwendung liest.
   Ziele haben deshalb optional fileWindows, fileLinux und fileMacOS; ohne
   Angabe gilt weiterhin file.

Die Dokumentation haelt ausserdem fest, dass der Installer bewusst Klartext
schreibt und die Anwendung selbst entscheidet, ob und wie sie ihn danach
schuetzt - und dass eine Entschluesselung, die Klartext durchreicht, deshalb
kein Altlast-Zweig mehr ist, sondern ein aktiv genutzter Pfad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 21:37:01 +02:00
Deploymentcenter BotandClaude Opus 5 7a3a5dad69 fix(releases): Lizenzschluessel nicht mehr im Klartext, Selbsttest, Zielorte
Vier Befunde aus einer externen Durchsicht der 2.4-Integration.

1. Die .htpasswd war eine Klartext-Kundenliste
   Das htpasswd-Format hasht nur die Passwortspalte. Benutzername UND Passwort
   waren der Lizenzschluessel - der Schluessel stand also im Klartext direkt
   neben seinem eigenen bcrypt-Hash, und der Hash war Dekoration. Geschuetzt
   hat das Ganze nur die FilesMatch-Regel in derselben Datei.
   Der Benutzername wird jetzt abgeleitet: lic_<sha256(schluessel), 16 Hex>.
   Die Datei enthaelt damit nur noch eine Einwegableitung und einen Hash ueber
   einen hochentropen Schluessel.
   Server und SDK muessen dabei zeichengenau uebereinstimmen; ein Test prueft
   die C#-Ableitung gegen die PHP-Formel.

2. Ein Formatwechsel blieb unbemerkt liegen
   Beim Umbau auf 1. faellt auf: reconcile() sah keinen Anlass zur
   Neuerzeugung, die Dateien behielten das alte Format, waehrend die Clients
   bereits das neue schickten. Die erzeugten Dateien tragen deshalb jetzt eine
   Formatkennung; weicht sie ab, wird neu erzeugt.

3. Doku beschrieb Nginx, der Schutz ist Apache-only
   .htaccess wird von Nginx ignoriert - dort waeren die Verzeichnisse offen und
   die .htpasswd oeffentlich abrufbar. Die Statusanzeige pruefte nur, ob die
   Dateien existieren, und haette in dem Fall "GESCHUETZT" gemeldet.
   Neu: ein echter Selbsttest ruft die eigene Paket-Adresse OHNE Zugangsdaten
   ab und erwartet 401. Er laeuft beim manuellen Erzeugen und nach jeder
   automatischen Neuerzeugung; das Ergebnis steht in der Oberflaeche, ein
   Fehlschlag im Log. Er findet nebenbei auch abgeschaltetes AllowOverride und
   Tippfehler in der erzeugten Datei. Doku korrigiert, Nginx-Vorlage ergaenzt.

4. Erstinstallation schrieb an einen Ort, an dem Linux-Anwendungen nicht lesen
   setup.json-Ziele waren immer installationsrelativ. Eine Anwendung, die sich
   unter Linux richtig verhaelt, liest aus $XDG_CONFIG_HOME - /opt/<app> ist
   fuer den Dienstbenutzer meist nicht schreibbar. Der Installer legte die
   Datei also dorthin, wo nie jemand nachsieht.
   Ziele haben jetzt ein "location": install (Vorgabe), config, data, home,
   plus ${VAR}- und %VAR%-Ersetzung in "file". Unbekannte Variablen bleiben
   stehen statt leer zu werden - ein Platzhalter faellt auf, ein falscher Pfad
   nicht. Der Installer gibt den aufgeloesten Pfad aus, weil bei config das
   Konto entscheidet, unter dem er laeuft.

Ausserdem
- Doku zeigte "status": "ok" fuer update/delete; Http::ok() erzeugt
  "status": "success".
- UPGRADE §16.1 deckte Neuprodukte nicht ab: Fuer ein Produkt ohne Release
  existiert /releases/<slug>/ nicht und wird uebersprungen. Das Verzeichnis
  entsteht erst mit dem ersten Upload, der naechste Tick schuetzt es. Der erste
  ausgelieferte Build muss die Zugangsdaten also schon mitbringen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 11:47:26 +02:00
Deploymentcenter BotandClaude Opus 5 c8f3e78635 feat(setup): Erstinstallation ueber den Update-Agent, Installationskonto, Downloads
Bisher gab es nur den Update-Weg: eine Anwendung musste bereits installiert und
eingerichtet sein, damit sich etwas aktualisieren liess. Die Erstinstallation
auf einem neuen System war Handarbeit - Paket kopieren, Konfiguration
abtippen, Token besorgen.

Setup-API (neu)
- POST /api/setup/v1/login tauscht Benutzername und Passwort gegen ein Token
  mit 30 Minuten Gueltigkeit und ausschliesslich setup:install. Es wird nicht
  mitgeschrieben und lebt im Installer nur im Speicher.
- GET /api/setup/v1/catalog zeigt nur, was zur Laufzeitkennung des anfragenden
  Systems passt. Ein Projekt mit ausschliesslich Windows-Paket taucht auf einem
  Linux-Rechner gar nicht erst auf.
- POST /api/setup/v1/token stellt das Dauertoken der Anwendung aus. Welche
  Rechte vergeben werden, entscheidet der Server; die Anfrage kann nur
  einschraenken. Sonst waere der Umweg ueber ein kurzlebiges Token wirkungslos.

Rollentrennung (Migration 012)
- dc_users bekommt role, disabled und last_login_at. Die Rolle "installer"
  darf sich ueber den Setup-Weg anmelden und nicht am WebUI. Die Zugangsdaten
  werden auf jedem Zielsystem eingetippt; mit einem Administratorkonto
  verteilte man damit den Zugang zu Tokens, Lizenzen und Monitoren auf jeden
  Rechner, auf dem je etwas installiert wurde.
- Auth::verifyCredentials() prueft sessionfrei, damit Setup- und WebUI-Login
  nicht zwei verschiedene Haertungsgrade haben (Drosselung, Timing-Angleichung,
  Rehash gelten fuer beide).
- Konten mit hinterlegtem TOTP-Geheimnis werden am Setup-Weg mit 501
  abgewiesen. Eine TOTP-Pruefung gibt es im Deploymentcenter noch nicht; sie
  stillschweigend zu uebergehen waere ein Rueckschritt.
- Benutzerverwaltung im WebUI - es gab bisher gar keine, nur den einen von
  install_db.php angelegten Admin. Das letzte aktive Administratorkonto laesst
  sich weder deaktivieren noch loeschen.

Installer
- update-agent --action install fuehrt durch Anmeldung, Auswahl,
  Zielverzeichnis, Installation und Einrichtung. Die Dateien kommen ueber
  denselben Pfad wie ein Update - mit Pruefsumme, Signatur, Staging und
  Rollback. Ein zweiter Download-Weg waere ein zweiter Ort fuer dieselben
  Fehler.
- --action configure holt die Einrichtung nachtraeglich.
- setup.json im Paket beschreibt die benoetigten Werte. Bewusst im Paket und
  nicht zentral: so ist sie mit der Anwendung versioniert.
- Gefragt wird nur, was uebrig bleibt: bereits gesetzt -> detect:... ->
  provision -> fragen. Platzhalter wie changeme oder <dein-wert> gelten dabei
  nicht als eingerichtet, sonst liefe die Anwendung mit der Vorlage los.
- SetupWriter erhaelt vorhandene Inhalte. Eine appsettings.json fuehrt neben
  den abgefragten Werten meist Logging und anderes; sie neu zu erzeugen waere
  bequemer und verloere das - bei einer Neuinstallation ohne Backup.
  int und bool landen als JSON-Typ, nicht als Zeichenkette.

Downloads
- scripts/build_installer.ps1 baut selbstenthaltende Einzeldateien fuer
  win-x64, linux-x64 und linux-arm64 (rund 34 MB, .NET-Laufzeit inbegriffen).
  Ohne NativeAOT und ohne Trimming: Spectre.Console loest ueber Reflexion auf
  und braeche sonst erst beim Anwender.
- scripts/upload_installer.py laedt sie nach /installer/. Getrennt von
  deploy.py, das client-dotnet bewusst ausklammert.
- Bereich "Installer" auf der UpdateService-Seite mit Groessen, Pruefsummen
  und den wget-Befehlen; die Angaben stammen aus installer.json statt aus fest
  eingetragenem Text.
- install.sh und install.ps1 laden, pruefen die Pruefsumme und legen ab -
  sie richten bewusst nichts selbst ein. Das Manifest wird BOM-frei
  geschrieben, sonst scheitert json_decode() daran.

Enthaelt ausserdem die bislang nicht committete Arbeit an den
RocketChat-Benachrichtigungen (Migrationen 010 und 011) sowie die Loesch- und
Editierfunktion des UpdateService; die betroffenen Dateien liessen sich nicht
getrennt stagen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 10:33:26 +02:00