feat(cert-manager): Vorlagen für Zielsysteme, Modulversion 1.2.0 und Anleitungen

- Sieben Vorlagen (Nginx, Apache ab/vor 2.4.8, Windows/IIS, Nginx Proxy Manager, HAProxy, Tomcat) mit Dateien und Einrichtungszeilen
- Reiter „Vorlagen“ mit ZIP samt Anleitung, Schnipsel und Kopieren
- Modulversion 1.2.0, Modul-Changelog, CHANGELOG und drei Anleitungen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-09 15:57:48 +02:00
parent 65a1dca80f
commit 47b26219b2
19 changed files with 1434 additions and 14 deletions
+72
View File
@@ -870,3 +870,75 @@ Nextcloud (`ratelimit.protection.enabled`) nur für den Lauf aus und stellt die
`user:setting anna files_sharing default_accept`). Alles setzt der `trap` zurück. Der Zähler von
Tessera liegt im Prozess: `all` verbraucht 7 der 10 Plätze, nach jedem `all`-Lauf
`docker compose restart api`.
**Zertifikat-Manager, Arbeitsbereich und Ketten (quick-261009-ikt):** Der Zertifikat-Manager
(`apps/api/src/cert-manager`, `apps/web/src/app/(portal)/modules/cert-manager`) hält **nichts**
auf dem Server. Die Oberfläche führt eine Liste von Dateien nur im Arbeitsspeicher des Browsers
(`use-cert-workspace.ts`, rein mit `working-set.ts`) und schickt bei jeder Änderung die ganze Liste
an `POST analyze` (multipart, höchstens 30 Dateien zu je 5 MiB, zusammen 20 MiB); ältere Antworten
verwirft ein Anfragezähler. Es gibt genau drei POST-Routen, alle ohne Zustand: `analyze`, `build`
(JSON) und, ab dem Abschnitt zum Nachladen fehlender Zertifikate, `fetch-issuer`. Fehler tragen
immer einen Code im Körper (`{ code, message }`, Liste in `cert-types.ts`), den die Oberfläche unter
`certManager.errors.<code>` übersetzt.
*Ein Parser.* **`node:crypto` entscheidet alles über Zertifikate und Schlüssel** (`X509Certificate`,
`createPrivateKey`, `createPublicKey`, `KeyObject#export`), denn nur so laufen RSA **und** EC durch
denselben Weg. `node-forge` bleibt ausschließlich für PKCS#12 (Lesen und Schreiben) und als
allgemeiner ASN.1-Leser und -Schreiber (PKCS#7 lesen und bauen, Zertifikatsanfragen lesen). Kein
Produktivcode ruft `certificateFromPem`, `certificateFromAsn1`, `certificationRequestFromAsn1`,
`messageFromPem` oder `messageFromAsn1` auf, denn diese forge-Leser können nur RSA; das prüft das
Gate in der Aufgabenkette per `grep`. Die Erkennung (`cert-model.ts`, `detectBlob`) ist eine feste
Stufenfolge (ZIP, PEM-Blöcke, DER-Zertifikat, PKCS#12, PKCS#7, Schlüssel, Anfrage), jede Stufe in
`try/catch`: eine kaputte Datei wird `unknown`, nie ein Fehler der Anfrage. Erkannt wird am Inhalt,
nie an der Dateiendung.
*Ketten.* `cert-chain.ts` (`buildChains`) nimmt als Aussteller nur Zertifikate, für die
`C.checkIssued(I)` **und** `C.verify(I.publicKey)` gelten. `checkIssued` allein vergleicht nur Namen
und Schlüsselkennungen; ein gleichnamiges Zwischenzertifikat mit anderem Schlüssel (Fixture
`rsa-inter-decoy`) besteht es und würde ohne die Unterschriftsprüfung fälschlich genommen.
Selbstsigniert heißt: Unterschrift mit dem eigenen Schlüssel stimmt und (`checkIssued` gegen sich
selbst oder Aussteller gleich Inhaber). Mehrere Wege rangiert die Funktion fest (endet bei einer
Wurzel der Liste, weniger abgelaufene, kürzer, späteres Ablaufdatum, dann SHA-256), damit das
Ergebnis nie vom Zufall abhängt. Schlüssel und Anfragen ordnet `matchKeys` mit
`checkPrivateKey` bzw. dem Vergleich der öffentlichen Schlüssel zu, nie nach Namen. `build`
baut die Reihenfolge **immer neu** aus den gesendeten Zertifikaten; eine vom Browser mitgeschickte
Reihenfolge gäbe es nicht einmal als Feld.
*PFX schreiben.* forge schreibt von sich aus nur RSA. `cert-pkcs12.ts` (`writePkcs12`) tauscht
deshalb für die Dauer **eines** synchronen Aufrufs drei Funktionen von `forge.pki`
(`privateKeyToAsn1`, `wrapRsaPrivateKey`, `certificateToAsn1`) gegen Durchreicher aus und stellt
sie im `finally` wieder her; ASN.1 für Zertifikate und Schlüssel kommt aus `node:crypto`, die
Verschlüsselung und den MAC macht weiterhin forge. Das ist sicher, weil Node einfädig ist und der
Aufruf nicht abgibt; das Spec prüft die Wiederherstellung auch nach einem Fehler. Profile:
`compat` (3DES, SHA-1, Vorgabe, lesbar bis Windows Server 2016) und `modern` (AES-256, der MAC bleibt
bei forge SHA-1). Die Vorlagen für IIS und Tomcat nutzen dieselbe Funktion (`cert-templates.ts`);
die Vorlagen für Dateien bauen aus denselben Bausteinen wie `buildOutput` und kennen `cert-output.ts`
bewusst nicht (sonst entstünde eine Importschleife).
*ZIP-Grenzen.* `zip-expand.ts` prüft **alles vor dem ersten Entpacken** an den Kopfdaten: höchstens
100 Einträge, 1 MiB je Eintrag, Verhältnis entpackt zu gepackt höchstens 100, Summe 20 MiB, nur eine
Ebene (ein ZIP im ZIP wird gemeldet, nicht geöffnet), verschlüsselte Einträge werden gemeldet.
Eintragsnamen dienen nur der Anzeige und werden nie als Dateipfad benutzt.
*Grenze des Anfragekörpers von `build` (D-26).* Die Express-Voreinstellung von 100 kB reicht für die
größte erlaubte `build`-Anfrage nicht (Zertifikat, 20 Pool-Zertifikate, Schlüssel und Anfrage zu je
16 384 Zeichen ergeben rund 384 kB). Deshalb hat nur diese Route einen eigenen JSON-Leser mit
512 KiB (`cert-json-body.ts`), den `main.ts` per `app.use(CERT_BUILD_ROUTE, certBuildJsonBody,
certBuildBodyErrors)` **vor** `app.listen` einhängt (Nest registriert seine eigenen Leser erst in
`init()`). Zwei Fallen: Erstens liegt `express` nicht in `apps/api/node_modules`, der Leser kommt
deshalb über `createRequire(...)` aus der Kopie, die auch `@nestjs/platform-express` lädt. Zweitens
**darf die Funktion nicht `jsonParser` heißen**: Nests `ExpressAdapter` überspringt seinen eigenen
JSON-Leser für **alle** Routen, sobald schon eine Schicht mit diesem Funktionsnamen im Router liegt
(`isMiddlewareApplied`); deshalb heißt sie `certBuildJsonBody`. Eine größere Anfrage bekommt 413 mit
Code `tooLarge`, kaputtes JSON 400 mit `invalidInput`; alle anderen Routen behalten die 100 kB. Das
Spec `cert-json-body.spec.ts` rechnet die größte DTO-Anfrage aus den Konstanten in
`dto/cert-build.dto.ts` aus und bleibt rot, wenn jemand dort eine Obergrenze erhöht, ohne die
Grenze mitzuziehen.
*Fixtures.* Die Testdaten liegen unter `apps/api/src/cert-manager/__fixtures__/` und entstehen mit
`make-fixtures.sh` (OpenSSL 3.4 oder neuer; Passwort aller geschützten Dateien `Test-Pass-123`, die
Schlüssel der CAs werden nach der Erzeugung gelöscht). Dateinamen enden **nie** auf `.key`, denn die
`.gitignore` ignoriert `*.key` wegen des Updater-Schlüssels; Schlüssel heißen `-key.pem` oder
`-key-….der`. Die Specs lesen die Dateien mit `readFileSync` und rufen OpenSSL nie auf (die CI hat
es nicht); die Live-Prüfung mit OpenSSL-Gegenprobe (`openssl verify`, `pkcs12 -info`,
`pkcs7 -print_certs`, `pkey`) liegt im Skript `e2e-cert.sh` der Aufgabe.