0c20698fd514c35a8372ba78721e32fdb887660e
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
073d52ab7d |
feat(2.9.2): Nur geprueft in Hardware entstandene Schluessel als zweiter Faktor
Befund aus dem Betrieb: Ein Passkey aus 1Password liess sich als Hardwarekey registrieren. Fuer das Deploymentcenter hinnehmbar, fuer ein Protokoll, das ausdruecklich Hardware fordert, nicht. Zwei Dinge, die wie eine Pruefung aussahen und keine waren - beide seit 2.9.0 gesetzt: authenticatorAttachment "cross-platform" steuert, was der Browser anbietet, und ein Passwortverwalter meldet sich ebenso. Und response.authenticatorAttachment setzt der Client, also genau die Instanz, gegen die sich nichts beweisen laesst. Was traegt, ist Basic Attestation. Neu geprueft wird die Kette bis zu einer hinterlegten Herstellerwurzel; die drei FIDO-Wurzeln von Yubico liegen bei. Ein x5c allein genuegt ausdruecklich nicht: Der Fall "eigenes Zertifikat, Name Yubico, AAGUID eines echten YubiKey 5, gueltige Unterschrift" wurde nachgebaut und wird abgewiesen - an der Kette, nicht an der Signatur. Drei Stufen statt eines Schalters, weil alle bisher registrierten Schluessel als nicht nachgewiesen gelten - auch echte Yubikeys. Bei attestation "none" ersetzt der Browser die Geraetekennung durch Nullbytes, bevor der Server sie sieht; es gab also nie eine Herkunft. Ein einzelner Schalter haette beim Umlegen den eigenen Zugang gesperrt. Der Wechsel nach "strict" wird zusaetzlich abgelehnt, solange das eigene Konto keinen nachgewiesenen Schluessel hat. - Attestation.php: packed und fido-u2f, Kettenpruefung gegen eigene Wurzeln statt gegen den Speicher des Betriebssystems, Gueltigkeit, Gegenprobe der AAGUID aus der Zertifikatserweiterung - HardwarePolicy.php: off / register / strict, optionale Modellliste, Wurzeln aus der Konfiguration erweiterbar - Migration 021: attested und drei Anzeigespalten, Vorgabe 0 - WebUI: Herkunft je Schluessel, Regel vor der Beruehrung angesagt - testctl clear_credentials, damit ein Testlauf den naechsten nicht aussperrt - Testreihe "hardware", 5 Faelle, ohne Krypto-Bibliothek 184 von 184 Testfaellen gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f0ed67ad29 |
feat(2.9.0): Hardwarekey an der WebUI-Anmeldung und die Roadmap als Tafel
Zwei angeforderte Funktionen, dazu zwei Befunde, die ihnen im Weg lagen. == Zweite Stufe der Anmeldung (#666) == Nach korrektem Passwort verlangt das WebUI einen registrierten FIDO2-Schluessel. Die Pflicht haengt je Konto daran, OB ein Schluessel registriert ist - nicht an einem Schalter. Ein Schalter, den man vor der Registrierung umlegt, sperrt den Betreiber aus seiner eigenen Verwaltung aus. Ohne Fremdbibliothek gebaut (das Projekt hat bewusst kein Composer): src/Core/Webauthn/ mit CBOR-Leser, COSE-nach-DER-Wandlung und Pruefung ueber openssl_verify (ES256, RS256). Auth::login() heisst jetzt Auth::startLogin() und liefert drei Zustaende statt eines bool - aus "wahr heisst drin" ist "wahr heisst vielleicht drin" geworden, und ein uebersehener Aufrufer soll an einem fehlenden Methodennamen scheitern statt an einer truthy Zeichenkette. Die Setup-API der Zielsysteme geht ueber verifyCredentials() daran vorbei und bleibt unberuehrt. Notfalltoken als Ersatzweg: nur als Hash in der Datenbank, Klartext einmalig angezeigt, Einmalgebrauch. Laeuft als gewoehnliches Formular ohne JavaScript - ein Ersatzweg mit denselben Voraussetzungen wie der Hauptweg waere keiner. Fehlversuche auf der zweiten Stufe laufen in dieselbe Konto- und IP-Sperre wie das Passwort; zuvor zaehlte nur die erste Stufe. Benutzerverwaltung kann Konten umbenennen. Beim Loeschen eines Kontos und beim Zuruecksetzen der Testinstanz werden seine Schluessel mitgenommen: credential_id ist geraeteweit eindeutig, eine verwaiste Zeile haette denselben Yubikey dauerhaft unbrauchbar gemacht. == Roadmap-Tafel (#664) == Neuer Unterreiter Roadmap -> Tafel. Der Server liefert die fertige Gliederung (RoadmapBoard, ?action=board), die Seite rendert nur - sonst schneidet das WebUI anders als die API und ein spaeterer Export ein drittes Mal anders. Phasen stehen nicht im Code: gepflegt in der neuen Tabelle roadmap_phases, sonst an den groessten Luecken der Rangfolge geschnitten und sichtbar als geraten gekennzeichnet. Links die gekappte Spalte des Erledigten, rechts "Wird ueberlegt". Nachpruefung ist ein eigener Zustand und wird nicht als erledigt gezeichnet. Klick oeffnet ein Seitenpanel: Volltext, Verlauf, Kommentieren mit action_taken, Aendern von Bereich, Rang, Zielfassung, Status und Ausloeser - ohne Seitenwechsel. Das war der eigentliche Gewinn: Wer etwas bemerkte, musste sich bisher die Nummer merken und im Bugtracker danach suchen. Tiefe Verweise #roadmap/<projekt>/item/<id>. Ziehen zwischen Phasen ueber ?action=rank_between. Fremdtext wird ausschliesslich ueber textContent gesetzt, nie ueber innerHTML. == Befund: roadmap_rank war projektweit und bereichslokal zugleich (#177) == Entschieden: projektweit. move() sucht den Nachbarn ohne Bereichsfilter, reorder() schreibt keinen ganzen Bereich mehr auf 10/20/30 um, sondern vertauscht nur die Raenge, die die Eintraege ohnehin halten. rank_between rechnet die Mitte zwischen zwei Nachbarn und nummeriert nur dann einmal neu - unter Erhalt der Reihenfolge -, wenn dort kein Platz mehr ist; die Antwort sagt es dann. == Befund: CSRF an sitzungsauthentifizierten API-Schreibzugriffen == ApiAuth akzeptiert die WebUI-Sitzung, die Endpunkte prueften dabei kein CSRF-Token. Der Kommentar in Http::beginJson - "ausschliesslich per Token-Header, niemals per Cookie" - traf auf die API nie zu. Schreibende Anfragen im Sitzungsweg verlangen jetzt csrf_token bzw. X-CSRF-Token. Token-Clients sind nicht betroffen und wurden gegengeprueft. == Geprueft == tests/e2e/suite_tafel.py neu (16 Faelle), Suite gesamt 172 von 172 gegen die Testinstanz. Die WebAuthn-Zeremonien zusaetzlich gegen einen Software- Authenticator mit echtem P-256 und echtem CBOR: Registrieren, Anmelden, verfaelschte Signatur, veraltete Herausforderung und Zaehler-Rueckfall als Klonverdacht werden je mit 403 abgewiesen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
087f4392a9 |
feat: UpdateAgent Selbstaktualisierung & kopflose Installation, .NET 10 Migration, Backend- & SDK-Fixes
- .NET 10 Migration: Alle .NET-Projekte auf net10.0 umgestellt (#459) - Client-SDK: ResolveAgentPath bereinigt und DLL-Pruefung ergaenzt (#458) - Packager: Commit-Ermittlung aus publishDir / Assembly InformationalVersion (#456) - BuildInfo.targets: ExitCode-Abfang und Regex-Validierung gegen Git-Fehlertexte (#363) - UpdateService: Re-Publish erhaelt bestehende Manifeste und Werte (#457) - Bugtracker API: on_roadmap-Filter in manage?action=list durchgereicht (#349) - Watchdog API: project_slug-Filter in GET /status ergaenzt (#420) - UpdateService API: no_release_for_platform mit verfuegbaren Plattformen (#421) - Bugtracker Core: Fehler-Normalisierung gegen zersplitterte error_hashes (#419) - UpdateAgent: Kopfloser Installationsmodus via --answers (#89) - Installer & Agent: Automatische Selbstaktualisierung beim Start - Tooling: AGENTS.md, CLAUDE.md und Standalone-CLI scripts/dc.py hinzugefuegt |
||
|
|
687ee0cefc |
feat(installer): Laufzeitpruefung, Host-Ueberwachung, Zugang fuer Adminkonten
Drei Dinge, die beim ersten Lauf des Installers auf einer Linux-Maschine auffielen. 1. Die Release-Ablage wies Administratorkonten ab. ReleaseGuard nahm nur die Rolle 'installer' in die .htpasswd auf, waehrend Installskripte und Agent ausdruecklich sagten, ein Administratorkonto tue es auch: Anmeldung und Katalog gelangen, erst der Download endete mit 401 - und die Meldung sprach von abgelaufenen Lizenzen, die es bei einer Erstinstallation gar nicht geben kann. Adminkonten zaehlen jetzt zu den Installationskonten. FORMAT_VERSION auf 3, damit reconcile() die Dateien sofort neu schreibt statt erst beim naechsten turnusmaessigen Lauf; ein neu angelegtes Konto landet ausserdem unabhaengig von seiner Rolle sofort darin. Bei einem 401 mit Benutzerzugangsdaten nennt der Client jetzt Konto und zugangsberechtigte Rollen, und der Agent bricht ab, statt ueber die API weiterzusuchen und dieselbe Meldung ein paar Schritte spaeter ein zweites Mal zu zeigen. 2. Der Installer prueft die .NET-Laufzeit. Bisher endete eine gelungene Installation auf einer Maschine ohne .NET mit einer Anwendung, die sich nicht starten laesst - und die Fehlersuche begann beim Deploymentcenter, weil das der letzte bewusste Schritt war. Gelesen wird die runtimeconfig.json der Anwendung und mit "dotnet --list-runtimes" verglichen; fehlt etwas, nennt der Installer den Installationsbefehl fuer diese Plattform. Eigenstaendig veroeffentlichte Pakete werden nicht bemaengelt, rollForward wird beachtet. 3. Die Ueberwachung der Maschine entsteht im Installer. Zwei Fragen - Name im Dashboard und ob eingeplant werden soll - statt fuenf Schritten in der Oberflaeche an einem anderen Rechner. Monitor, Token mit genau watchdog:ping, Skript, Dateirechte, ein Heartbeat zur Probe und der Cron-Eintrag bzw. die geplante Aufgabe entstehen daraus. Fuer Maschinen ohne Installation: --action monitor. Die Agent-Skripte werden jetzt in src/Modules/Watchdog/AgentScript.php erzeugt - von Oberflaeche und Installer gemeinsam - und melden Last, Speicher, Plattenbelegung und Laufzeit mit, statt nur "status: ok". Beim Ausfuehren fielen zwei Fehler auf, die dort behoben sind: df -P verrutscht bei Geraetenamen mit Leerzeichen (gezaehlt wird jetzt von hinten), und ohne LC_ALL=C erzeugt awk auf einem deutschen System "12,5" und damit kaputtes JSON. Neu: POST /api/setup/v1/agent. SDK 2.6.0 mit SetupClient.RequestWatchdogAgentAsync(). Die OpenAPI-Beschreibung des neuen Endpunkts bleibt zunaechst aussen vor: public/api/openapi.php traegt gerade auch fremde, noch nicht committete Aenderungen aus einer parallel laufenden Arbeit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
ceb977187e |
feat(releases): Zugangsschutz ueber Lizenzschluessel
/releases/ wurde bisher offen ausgeliefert, damit ausgelieferte Anwendungen ohne Zugangsdaten nach Updates suchen koennen. Das bedeutete aber auch, dass jeder im Internet die vollstaendigen Pakete herunterladen konnte - mitsamt allem, was versehentlich darin liegt. Genau so lag ein echter API-Schluessel in einer mitgelieferten appsettings.json oeffentlich abrufbar. Zugang haengt jetzt am Lizenzschluessel: Wer eine gueltige Lizenz fuer ein Produkt hat, kommt an dessen Updates. Die Anwendung kennt ihren Schluessel ohnehin und versorgt sich damit selbst - es muss nichts verteilt werden. Server - ReleaseGuard erzeugt je Produktverzeichnis .htaccess und .htpasswd. Bewusst getrennt: eine gemeinsame Datei wuerde bedeuten, dass eine Lizenz fuer Produkt A auch Produkt B oeffnet. license_licenses.product_id bindet jeden Schluessel ohnehin an genau ein Projekt. - Eingetragen werden aktive, nicht abgelaufene Lizenzen (Benutzername = Passwort = Schluessel; Basic Auth braucht zwei Felder, es gibt aber nur ein Geheimnis) sowie alle Installationskonten - bei einer Erstinstallation gibt es noch keinen Schluessel, mit dem sich das Paket holen liesse. - Deren Hash wird unveraendert aus dc_users uebernommen: password_hash() erzeugt bcrypt im Format $2y$, genau das versteht Apache. Ein Klartextpasswort wird nirgends gebraucht. Argon2-Hashes werden erkannt und uebersprungen statt eine unbrauchbare Datei zu erzeugen. - Lizenzschluessel werden mit Kosten 8 gehasht statt 12: 29 Zeichen maschineller Zufall sind kein Menschenpasswort, Apache prueft aber bei *jeder* Anfrage neu. - Geschrieben wird ueber eine temporaere Datei mit rename() - ein Abbruch wuerde sonst eine halbe Zugangsdatei hinterlassen und in dem Moment die halbe Kundschaft aussperren. - Neu erzeugt bei jeder Lizenz- und Kontoaenderung. Abgelaufene Lizenzen loesen anders als ein Widerruf nichts aus; dafuer gleicht cli/tick.php nach und erzeugt spaetestens alle sechs Stunden neu. - Statusanzeige und Schaltflaeche im WebUI unter UpdateService. Client - ReleaseCredentials: Lizenzschluessel oder Installationskonto als Basic Auth. - UpdateClient und Agent senden sie fuer latest.json und package.tar.gz. - UpdateCheckResult.Unauthorized trennt "Lizenz traegt nicht mehr" von einem Netzwerkfehler. Ohne diese Unterscheidung sucht man an der falschen Stelle. - LaunchUpdateAgent reicht licenseKey als --license-key durch. - Der Installer benutzt die beim Anmelden eingegebenen Zugangsdaten auch fuer den Paketabruf; das Setup-Token taugt dafuer nicht, weil Apache prueft und nicht die Anwendung. Sonstiges - deploy.py klammert artifacts/ aus. Ohne das landeten die gebauten Installer-Binaries zusaetzlich unter /artifacts/ im Webroot. ACHTUNG Reihenfolge: Der Schutz sperrt jede Anwendung aus, die noch mit dem alten SDK gebaut ist. Erst ausliefern, dann scharfschalten - siehe UPGRADE.md §16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2388b5abe1 |
feat(updateservice): Plattform-Dimension, signierte Releases, Update mit Rollback
Behebt eine Reihe zusammenhaengender Fehler im Update-Weg, die zusammen verhindert haben, fuer mehr als eine Plattform auszuliefern - und die im Fehlerfall halb aktualisierte Installationen hinterliessen. Server - Migration 009: Spalte platform samt neuem Unique-Key. Zuvor verdraengte das zuletzt veroeffentlichte Paket alle anderen Plattformen derselben Version, weil ON DUPLICATE KEY auf (slug, version, channel) griff. Ein Linux-System zog sich damit das Windows-Paket. - Aufloesungsregel: je Version das plattformgenaue Paket, sonst das plattformunabhaengige. Ein Client ohne Plattformangabe sieht ausschliesslich 'any' - lieber kein Update als das falsche. - manifest_json wird endlich befuellt; die Spalte blieb bisher immer leer, wodurch die API nie der Rueckfall sein konnte, als der sie gedacht war. - Releases werden serverseitig mit RSA-SHA256 signiert, neuer Endpunkt /api/updateservice/v1/pubkey. Bewusst kein HMAC: der Pruefende laeuft auf fremden Systemen und darf den Signierschluessel nicht besitzen. Packager - Bricht ab, statt die Versionshistorie zu verlieren. Schlug das Lesen der bestehenden latest.json fehl, ersetzte ein leeres catch die komplette Historie durch einen einzigen Eintrag - ohne jede Meldung. - Echte Glob-Muster. Zuvor trafen "logs/**" und "scratch/**" aus der mitgelieferten Beispielkonfiguration nie zu. - preservePatterns: Konfigurationsvorlagen werden ausgeliefert, ersetzen am Ziel aber keine vorhandene Datei. Eine settings.json mit Zugangsdaten ueberschrieb bisher beim Update die Konfiguration jedes Zielsystems. - Warnt vor Dateien, die nach Zugangsdaten aussehen und auf keiner Liste stehen. - Prueft --version gegen die Hauptassembly. Eine Abweichung fuehrte zu einer Endlosschleife: Clients aktualisieren, melden weiter die alte Version, halten das Release erneut fuer neu. - --platform mit Ableitung aus dem Publish-Pfad. Agent - Anwenden mit Plan, Backup und vollstaendigem Rollback. Die Stelle war als "Atomic Replace with Backup" kommentiert und war eine Kopierschleife. - Verwaiste Dateien werden entfernt, aber nur solche aus dem Manifest der Vorversion. Was nicht aus einem Release stammt, bleibt liegen. - Das laufende Agent-Binary wird zur Seite gelegt statt ueberschrieben. - API-Rueckfall in FetchManifestAsync; bisher nur im SDK vorhanden, weshalb die Anwendung "Update verfuegbar" und der Agent "kein Release" sagen konnte. - Installierte Version aus --current-version oder manifest.json statt des Textes "Unbekannt", der als 0 gelesen wurde und jede Version neuer erscheinen liess. Reparatur funktioniert damit auch ohne manifest.json. - Setzt das Ausfuehrungsbit fuer Linux-Pakete, die unter Windows gebaut wurden. SDK - ResolveAgentPath() liefert den plattformrichtigen Namen; ein fest verdrahtetes "update-agent.exe" wird unter Linux nie gefunden. - LaunchUpdateAgent uebergibt jetzt --restart (wurde nie uebergeben, die Anwendung blieb nach dem Update zu), --wait-for-pid (kein Wettlauf mehr mit dem Herunterfahren) und --platform. Enthaelt ausserdem die bislang nicht committete Arbeit an Watchdog, Lizenz- Client und cli/tick.php samt Migration 008; die betroffenen Dateien liessen sich nicht getrennt stagen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5f9b0c5596 |
docs: Integrationsanleitungen auf Stand 2.0 bringen, Status "stopped" ergänzen
Watchdog-Anleitung vollständig überarbeitet - Alle drei Codebeispiele trugen das geseedete Demo-Token fest im Quelltext. Es ist an die Source "srv-db-01" gebunden — wer es übernommen hätte, wäre für jeden anderen Dienst abgewiesen worden. Jetzt Umgebungsvariable und eine Anleitung, wie man ein eigenes Token erzeugt. - Der Ratschlag "sende beim Beenden einen Ping mit Status stopped" beschrieb etwas, das die API nicht konnte. Statt die Anleitung an die Lücke anzupassen, ist die Lücke geschlossen: status akzeptiert jetzt "stopped" und "maintenance". Der Evaluator lässt solche Monitore in Ruhe, statt wenige Minuten nach jedem sauberen Shutdown einen Fehlalarm zu erzeugen. - Neu dokumentiert: checks (Gesundheitszustand per Push, ohne offene Ports), metrics samt Verlauf und Abweichungsvergleich, Alarmunterdrückung über die Hierarchie, die Schwellen des Evaluators (2x warning, 4x down) und der erforderliche Cron-Job. Lizenz-Anleitung - Neuer Abschnitt zum Antwortformat. Die Lizenz-Endpunkte antworten bewusst ohne den status/error-Umschlag der übrigen API; das Feld status auf oberster Ebene trägt den Lizenzzustand. Genau diese Besonderheit hatte ich beim Umbau übersehen, weshalb sie jetzt ausdrücklich festgehalten ist — samt Tabelle aller Zustände. - Ergänzt: Deaktivierung braucht den shared_key, mit Beispiel für den .NET- Client und curl. Verhalten bei Ratenbegrenzung. UpdateService-Anleitung - Prüf-Endpunkte dokumentiert (check, latest, releases) samt Antwortformat. - Tabelle zum Versionsvergleich mit den Fällen, die vorher falsch liefen. - Auto-Resolve beim Veröffentlichen beschrieben. Agent-Prompt-Vorlage - Fehler-Schnittstelle ergänzt, inklusive Hinweis auf "ignored": true, damit ein Agent bekannte Fehler nicht untersucht. Alle in der Dokumentation genannten API-Pfade und Scopes wurden maschinell gegen Routing und TokenManager abgeglichen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
60e34b29f6 |
feat(errors, watchdog): Fehler-Stream mit Ignore-Regeln, Metrik-Verlauf, Abhängigkeits-Alarme
Fehler-Schnittstelle - Neuer schlanker Eingang POST /api/errors/v1/report für den globalen Exception-Handler einer Anwendung. Titel und Dringlichkeit leitet der Server ab; gespeichert wird in derselben Tabelle wie der Bugtracker. Ein zweiter Speicher wäre nur ein zweiter Ort, an dem man suchen müsste. - error_level (fatal/error/warning) trennt die technische Art des Ereignisses von der geschäftlichen Dringlichkeit. Ein Duplicate-Entry ist technisch ein error, geschäftlich belanglos — beides zu vermischen war der Grund, warum solche Meldungen als Bug im Dashboard landeten. Ignore-Regeln gegen bekanntes Rauschen - bugtracker_ignore_rules mit contains/regex/exception_class, Pflichtfeld für die Begründung und optionaler Alarmschwelle. - Ein Treffer bedeutet nicht "wegwerfen": Der Fehler wird weiterhin erfasst und hochgezählt, bleibt aber aus der Übersicht heraus und löst keine Benachrichtigung aus. Der Zähler ist der eigentliche Zweck — dass ein bekannter Fehler auftritt, ist normal; dass er plötzlich hundertmal so oft auftritt, ist ein Signal. Dafür das rollende Stundenfenster und error.rate_exceeded. - Neue Regeln lassen sich rückwirkend auf bestehende Einträge anwenden. Gruppierung überarbeitet - Der Schlüssel nahm bisher 300 Zeichen Stacktrace auf. Derselbe Fehler zersplitterte dadurch, sobald ein Aufrufer den Stack einmal mitschickte und einmal nicht. Jetzt zählt der Ursprungsort: bevorzugt die Dateiangabe, sonst der erste Rahmen des Stacktrace. - Die Normalisierung ersetzte nur Zahlen ab vier Stellen, wodurch 'AA-1' und 'BB-2' getrennt blieben. Werte in Anführungszeichen, die Ziffern enthalten, gelten jetzt als veränderlich — der Schlüsselname bleibt erhalten, sodass verschiedene Unique-Keys unterscheidbar sind. Mit 9 Testfällen belegt. Metrik-Verlauf - watchdog_metrics speichert numerische Heartbeat-Werte mit Zeitstempel. Zuvor wurde metrics_json bei jedem Heartbeat überschrieben; damit ließ sich "die Platte läuft seit drei Tagen voll" nicht erkennen, nur "sie ist voll". - GET /api/watchdog/v1/metrics liefert den verdichteten Verlauf und die Abweichung vom eigenen Sieben-Tage-Durchschnitt. Dieser relative Ansatz braucht keine projektspezifischen Schwellwerte. - Aufbewahrung 14 Tage, Bereinigung stündlich durch den Evaluator. Health-Checks per Push statt Abruf - Der Heartbeat nimmt ein checks-Objekt entgegen, das die Anwendung selbst ermittelt. Das Deploymentcenter interpretiert die Namen nicht, es liest nur ok und message — was "gesund" bedeutet, entscheidet jede Anwendung selbst. Schlägt eine Prüfung fehl, wird ein als ok gemeldeter Heartbeat auf warning herabgestuft. - Bewusst ausgehend: auf den Zielmaschinen müssen keine Ports geöffnet werden. Abhängigkeitsbewusste Alarmierung - Fällt ein Monitor aus, dessen Parent selbst unten ist, wird der Alarm unterdrückt. Der Zustand bleibt sichtbar. Vorher erzeugte ein ausgefallener Hypervisor mit zwölf VMs dreizehn Meldungen für ein Problem. - Mehrere Ebenen und fehlerhafte Hierarchien (Zyklen, gelöschte Parents) sind abgesichert; mit 10 Testfällen belegt. WebUI - Neue Ansicht "Fehler-Stream" mit Filtern nach Projekt, Fehlerklasse, Umgebung, Zeitraum und Sichtbarkeit sowie Volltextsuche und Pagination. Stummgeschaltete Einträge sind standardmäßig ausgeblendet. - Verwaltung der Ignore-Regeln inklusive Trefferzähler. - Die Detailansicht zeigt Fehlerklasse, Stummschaltungsgrund und die Häufung im laufenden Stundenfenster. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e7fbc85db4 |
fix(security, core): Auth-Pflicht für Ingest-APIs, 500er-Ursachen beheben, Agenten-Workflow
Sicherheit
- install_db.php war ohne Authentifizierung erreichbar und setzte bei jedem
Aufruf das Admin-Passwort auf einen fest im Code stehenden Wert zurück.
Jetzt Auth-Pflicht; ein Konto wird nur bei leerer Benutzertabelle angelegt.
- Stored XSS im Bugtracker-Detail-Modal: Titel, Beschreibung, Fehlermeldung,
Stacktrace und Kommentare gingen ungefiltert durch innerHTML.
- report.php, projects.php und das Veröffentlichen von Releases verlangen jetzt
zwingend ein Token. Publish war zuvor völlig ungeschützt.
- CSRF-Token in allen Formularen, Session-Regenerierung nach Login,
Drosselung fehlgeschlagener Anmeldeversuche.
- Zugangsdaten aus der Versionskontrolle entfernt (Serverdaten.txt,
config.php, .htpasswd, deploy_config.json). Historie enthält sie weiterhin,
Rotation erforderlich (siehe docs/UPGRADE.md).
- Token-Validierung nur noch über SHA-256-Hash; expires_at wird ausgewertet.
Behobene 500er
- Audit::log() war in index.php weder eingebunden noch importiert. Jeder
Klick auf "Aktivierung freigeben" endete in einem Fatal Error.
- Derselbe benannte PDO-Platzhalter mehrfach je Statement (:id in
revokeToken/deleteToken, :q siebenfach in der Volltextsuche). Bei
EMULATE_PREPARES=false ist das nicht zulässig und warf HY093.
- Migration 005 nutzte dynamisches SQL, dessen Semikolons in String-Literalen
vom alten explode(';')-Installer als Statement-Ende gelesen wurden. Sie
schlug still fehl, wodurch push_id/target_agent/tags dauerhaft fehlten.
- Monitor-Umbenennung ohne Transaktion, verschachtelte Transaktionen im
RateLimiter.
Funktionale Korrekturen
- Der Watchdog-Evaluator fehlte vollständig: Monitor-Zustände änderten sich nur
beim Eintreffen eines Heartbeats, ein ausgefallenes System blieb dauerhaft
"up". Erster Lauf auf dem Produktivsystem: 7 von 10 Monitoren waren
tatsächlich seit über einem Tag nicht erreichbar.
- Das Feld "os" fehlte im Monitor-Dialog, wurde aber gespeichert und löschte
damit bei jedem Speichern das Betriebssystem.
- Der Resolve-Dialog existierte im HTML nicht; der Button war funktionslos.
- Versionsvergleich erfolgte lexikografisch, wodurch 1.9.0 als neuer galt
als 1.10.0.
- Schreiboperationen meldeten Erfolg auch für nicht existierende IDs.
- Post/Redirect/Get gegen doppelte Einträge beim Neuladen.
Neue Struktur
- src/bootstrap.php mit PSR-4-Autoloader ersetzt die require-Ketten.
- Core: Config, Http, Csrf, ApiAuth, Logger, Migrator, ErrorReporter.
- Migrator mit zeichenweisem SQL-Parser, dc_migrations und Baseline-Verfahren,
damit bestehende Installationen keine Beispieldaten zurückbekommen.
Agenten-Workflow
- Claim/Lease: Items werden exklusiv übernommen, damit nicht zwei Agenten am
selben Problem arbeiten. action=next holt und reserviert in einem Zug.
- Idempotenz über client_ref, Deduplizierung auch für Feature Requests,
Erkennung von Regressionen, automatische Eskalation des Schweregrads.
- Strukturierter Code-Kontext (repo_url, commit_sha, file_path, line_no).
- Delta-Abfragen über updated_since, Pagination, Bulk-Update.
- Beim Veröffentlichen eines Releases schließen sich Items mit passendem
resolved_in_build selbst.
- Ausgehende Webhooks mit HMAC-Signatur, /api/health, /api/openapi.json.
- Unbehandelte Fehler meldet die Plattform in ihren eigenen Bugtracker.
WebUI
- Serverseitige Filterung mit Pagination statt Rendern aller Datensätze.
- Migrations-Schranke, Evaluator-Warnung, Übersicht aktiver Agenten.
Zeitstempel liegen in der Datenbank durchgängig in UTC und werden für die
Anzeige in die App-Zeitzone umgerechnet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|