From 6ac2ed0a621e4386e086dbb550ac80b9a58c0f32 Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Wed, 2 Sep 2026 18:37:12 +0200 Subject: [PATCH] Nur noch, was zum Installieren und Betreiben des Lizenzservers noetig ist Entfernt: Tests, Architekturdokumentation und die Anwendung TESM. Beides liegt im Entwicklungsrepository alientim/TESM-DEV. Neu: packages/tesm-licensing-server -- die Serverhaelfte des Lizenzprotokolls (issue_license, resign_license, build_master_response, verify_client_request, Schluesselerzeugung). Sie war bisher Teil von tesm-licensing und lag damit auch in jeder Client-Installation. Die gemeinsame Haelfte bleibt in tesm-licensing und wird von beiden Seiten benutzt. install.sh installiert diese Serverhaelfte ausschliesslich fuer --app tesm-license und bricht ab, wenn sie fehlt. Co-Authored-By: Claude Opus 5 --- README.md | 108 +-- apps/tesm-license/src/tesm_license/cli.py | 3 +- .../src/tesm_license/services/activation.py | 5 +- .../src/tesm_license/services/issuance.py | 5 +- .../tesm_license/services/legacy_import.py | 5 +- .../src/tesm_license/services/master_key.py | 5 +- deploy/install.sh | 15 +- docs/ARCHITEKTUR.md | 166 ---- packages/tesm-licensing-server/pyproject.toml | 13 + .../src/tesm_licensing_server/__init__.py | 263 +++++++ .../src/tesm_licensing/__init__.py | 12 - .../src/tesm_licensing/canonical.py | 13 - .../src/tesm_licensing/license.py | 102 +-- .../src/tesm_licensing/protocol.py | 85 +-- tesm-license-v2.0.0.tar.gz | Bin 0 -> 305479 bytes tests/conftest.py | 211 ----- tests/e2e_license.py | 493 ------------ tests/e2e_license_server.py | 251 ------ tests/test_core_security.py | 721 ------------------ tests/test_core_smoke.py | 246 ------ tests/test_license_server.py | 703 ----------------- tests/test_licensing.py | 408 ---------- tests/test_repo_hygiene.py | 326 -------- tests/test_webserver.py | 521 ------------- 24 files changed, 344 insertions(+), 4336 deletions(-) delete mode 100644 docs/ARCHITEKTUR.md create mode 100644 packages/tesm-licensing-server/pyproject.toml create mode 100644 packages/tesm-licensing-server/src/tesm_licensing_server/__init__.py create mode 100644 tesm-license-v2.0.0.tar.gz delete mode 100644 tests/conftest.py delete mode 100644 tests/e2e_license.py delete mode 100644 tests/e2e_license_server.py delete mode 100644 tests/test_core_security.py delete mode 100644 tests/test_core_smoke.py delete mode 100644 tests/test_license_server.py delete mode 100644 tests/test_licensing.py delete mode 100644 tests/test_repo_hygiene.py delete mode 100644 tests/test_webserver.py diff --git a/README.md b/README.md index 7e019f4..5998478 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,24 @@ Ausgabe und Verwaltung der Lizenzen fuer TESM: Kunde -> Ticket -> Lizenz, Aktivierung online und offline, Kundenportal, Selbstbedienung, Versand ueber MS Graph und eine Hierarchie aus Unter-Lizenzservern. -Die verwaltete Anwendung liegt in einem eigenen Repository: -[alientim/tesm](https://gitea.int.eertmoed.net/alientim/tesm). Beide teilen sich -den Kern (`packages/tesm-core`) und das Lizenzprotokoll -(`packages/tesm-licensing`); diese Pakete sind hier mit enthalten, damit sich -das Repository allein installieren laesst. +**Dieses Repository enthaelt genau das, was zum Installieren und Betreiben des +Lizenzservers gebraucht wird** -- nichts darueber hinaus: + +``` +apps/tesm-license die Anwendung +packages/tesm-core gemeinsamer Kern (Anmeldung, Rechte, Sicherung, nginx, ...) +packages/tesm-licensing Lizenzprotokoll, gemeinsame Haelfte +packages/tesm-licensing-server Lizenzprotokoll, Serverhaelfte: ausstellen, erneuern, antworten +deploy/ install.sh, update.sh, release.sh, systemd, privilegierter Helfer +docs/ Betrieb und Sicherheit +``` + +Die verwaltete Anwendung liegt hier **nicht** mit drin. + +| Wo sonst noch | | +|---|---| +| [alientim/tesm](https://gitea.int.eertmoed.net/alientim/tesm) | TESM selbst | +| [alientim/TESM-DEV](https://gitea.int.eertmoed.net/alientim/TESM-DEV) | Entwicklung: beide Anwendungen, Tests, Architekturdoku | ## Zweige @@ -19,7 +32,7 @@ das Repository allein installieren laesst. --- -## Installation auf einem Server +## Installation ```bash sudo bash deploy/install.sh --app tesm-license --https --domain lizenz.firma.de @@ -52,6 +65,23 @@ Health-Check mit automatischem Rollback. --- +## Betrieb von der Kommandozeile + +```bash +sudo -u tesm-license tesm-license-admin status +sudo -u tesm-license tesm-license-admin init-key # Signaturschluessel +sudo -u tesm-license tesm-license-admin bootstrap # Grunddaten +sudo -u tesm-license tesm-license-admin create-admin +sudo -u tesm-license tesm-license-admin migrate-legacy # Altbestand uebernehmen +``` + +`migrate-legacy` liest die SQLite-Datei eines alten Lizenzservers **nur +lesend**, uebernimmt dessen Signaturschluessel und stellt die Lizenzen mit ihrer +restlichen Laufzeit neu aus. Wird der Schluessel nicht uebernommen, muessen +Kunden den Ausstellerwechsel einmal bestaetigen. + +--- + ## Release bauen ```bash @@ -65,42 +95,6 @@ genau einmal. --- -## Entwicklung - -```bash -python -m venv .venv && . .venv/bin/activate -pip install -e packages/tesm-licensing -e packages/tesm-core -e apps/tesm-license -pip install pytest -pytest tests -q -``` - -Ohne gesetzte Umgebungsvariablen legt die Anwendung ihre Daten unter -`instance/tesm-license/` im Arbeitsverzeichnis an -- kein Zugriff auf `/srv` -oder `/var/log` noetig. - -```bash -TESM_LICENSE_DEBUG=1 flask --app tesm_license run -``` - ---- - -## Verwaltung von der Kommandozeile - -```bash -sudo -u tesm-license tesm-license-admin status -sudo -u tesm-license tesm-license-admin init-key # Signaturschluessel -sudo -u tesm-license tesm-license-admin bootstrap # Grunddaten -sudo -u tesm-license tesm-license-admin create-admin -sudo -u tesm-license tesm-license-admin migrate-legacy # Altbestand uebernehmen -``` - -`migrate-legacy` liest die SQLite-Datei eines alten Lizenzservers **nur lesend**, -uebernimmt dessen Signaturschluessel und stellt die Lizenzen mit ihrer -restlichen Laufzeit neu aus. Wird der Schluessel nicht uebernommen, muessen -Kunden den Ausstellerwechsel einmal bestaetigen. - ---- - ## Lizenzprotokoll in Kurzform * Ed25519, getrennte Signaturkontexte je Nachrichtenart @@ -115,6 +109,8 @@ Kunden den Ausstellerwechsel einmal bestaetigen. brach im Vorgaenger die Bindung. * Offline-Aktivierung ueber einen kurzen Code in beide Richtungen; die Lizenz bleibt auf dem Server als "Aktivierung offen" markiert, bis sie zurueckkommt. +* Keine Lizenz ohne `master_endpoint`: eine Ausstellung ohne Endpunkt wird + abgelehnt statt eine Lizenz zu erzeugen, die sich nie aktivieren kann. --- @@ -122,31 +118,11 @@ Kunden den Ausstellerwechsel einmal bestaetigen. | | | |---|---| -| [docs/ARCHITEKTUR.md](docs/ARCHITEKTUR.md) | Aufbau, Schnitte und die Gruende dafuer | -| [docs/SICHERHEIT.md](docs/SICHERHEIT.md) | Anmeldung, Sitzungen, CSRF, Geheimnisse, Rechtetrennung | | [docs/BETRIEB.md](docs/BETRIEB.md) | Installation, Update, HTTPS, Sicherung, Fehlersuche | +| [docs/SICHERHEIT.md](docs/SICHERHEIT.md) | Anmeldung, Sitzungen, CSRF, Geheimnisse, Rechtetrennung | ---- - -## Tests - -```bash -pytest tests -q -``` - -Dazu zwei Skripte, die **auf dem Zielhost gegen laufende Installationen** -sprechen -- ueber nginx, gunicorn und echtes TLS. Sie werden nicht -mitinstalliert (`install.sh` laesst `tests/` aus); bei Bedarf dorthin kopieren: - -```bash -python3 tests/e2e_license_server.py https://lizenz.host admin 'Passwort' -python3 tests/e2e_license.py https://lizenz.host https://host admin 'Passwort' http://127.0.0.1:5001 -``` - -Das zweite spielt den vollstaendigen Lizenz-Lebenszyklus ueber **beide** -Anwendungen durch: ausstellen, herunterladen, einspielen, online aktivieren, -Module pruefen, Heartbeat, neue Revision, Widerruf. Es braucht deshalb auch eine -laufende TESM-Installation. +Aufbau und Entwurfsentscheidungen stehen in `docs/ARCHITEKTUR.md` im +Entwicklungsrepository, ebenso die Tests. --- @@ -158,6 +134,4 @@ laufende TESM-Installation. Argumentpruefung. * **CSRF-Schutz ueberhaupt.** Der Vorgaenger hatte keinen. * **Rechte werden serverseitig geprueft**, nicht nur im Template ausgeblendet. -* **Keine Lizenz ohne Endpunkt.** Eine Ausstellung ohne `master_endpoint` wird - abgelehnt statt eine Lizenz zu erzeugen, die sich nie aktivieren kann. * **Verschachtelte Datenbankverbindungen sind ein Fehler**, kein Deadlock. diff --git a/apps/tesm-license/src/tesm_license/cli.py b/apps/tesm-license/src/tesm_license/cli.py index 09d693d..a407759 100644 --- a/apps/tesm-license/src/tesm_license/cli.py +++ b/apps/tesm-license/src/tesm_license/cli.py @@ -20,6 +20,7 @@ from pathlib import Path from typing import Any import tesm_licensing as lic +import tesm_licensing_server as licsrv from tesm_core import audit from tesm_core.cli_web import add_web_setup, run_web_setup from tesm_core.db import Database @@ -73,7 +74,7 @@ def cmd_bootstrap(args: argparse.Namespace) -> int: "address": str(extension.settings.get(conn, "vendor_address") or ""), "logo_base64": "", } - bundle, _ = lic.issue_license( + bundle, _ = licsrv.issue_license( customer={"name": args.name, "contact_email": vendor["email"]}, license_type="enterprise", modules=[], diff --git a/apps/tesm-license/src/tesm_license/services/activation.py b/apps/tesm-license/src/tesm_license/services/activation.py index b20ebe1..5aeb7fc 100644 --- a/apps/tesm-license/src/tesm_license/services/activation.py +++ b/apps/tesm-license/src/tesm_license/services/activation.py @@ -29,6 +29,7 @@ from datetime import datetime, timedelta, timezone from typing import Any import tesm_licensing as lic +import tesm_licensing_server as licsrv from tesm_core import audit from tesm_core.db import Database @@ -72,7 +73,7 @@ def _signed_response( detail: str = "", license_update: dict[str, Any] | None = None, ) -> Outcome: - response = lic.build_master_response( + response = licsrv.build_master_response( str(request["action"]), license_id=str(request["license_id"]), fingerprint=str(request["fingerprint"]), @@ -124,7 +125,7 @@ def _verify( ) try: - lic.verify_client_request( + licsrv.verify_client_request( request, str(row["license_pubkey"]), expected_action=expected_action, diff --git a/apps/tesm-license/src/tesm_license/services/issuance.py b/apps/tesm-license/src/tesm_license/services/issuance.py index bc23e79..baf3b1e 100644 --- a/apps/tesm-license/src/tesm_license/services/issuance.py +++ b/apps/tesm-license/src/tesm_license/services/issuance.py @@ -11,6 +11,7 @@ from datetime import datetime, timezone from typing import Any, Iterable, Sequence import tesm_licensing as lic +import tesm_licensing_server as licsrv from tesm_core.db import Database TICKET_TYPES = lic.LICENSE_TYPES @@ -458,7 +459,7 @@ def issue_from_ticket( customer = get_customer(conn, int(ticket["customer_id"])) assert customer is not None - bundle, license_pub = lic.issue_license( + bundle, license_pub = licsrv.issue_license( customer={ "name": str(customer["name"]), "contact_email": str(customer["contact_email"] or ""), @@ -600,7 +601,7 @@ def resign_license( if row is None: raise IssuanceError("Diese Lizenz existiert nicht.") document = json.loads(str(row["license_document"])) - updated = lic.resign_license( + updated = licsrv.resign_license( document, master_private_key_b64=master_private_key, changes=changes ) conn.execute( diff --git a/apps/tesm-license/src/tesm_license/services/legacy_import.py b/apps/tesm-license/src/tesm_license/services/legacy_import.py index a88420a..e5f22c6 100644 --- a/apps/tesm-license/src/tesm_license/services/legacy_import.py +++ b/apps/tesm-license/src/tesm_license/services/legacy_import.py @@ -33,6 +33,7 @@ from pathlib import Path from typing import Any import tesm_licensing as lic +import tesm_licensing_server as licsrv from tesm_core.db import Database from tesm_core.keystore import Keystore @@ -124,7 +125,7 @@ def read_legacy_key(root: Path) -> tuple[str, str]: public = str(data.get("public") or data.get("public_key") or "") if not private or not public: raise LegacyImportError("Der alte Signaturschluessel ist unvollstaendig.") - if lic.public_from_private(private) != public: + if licsrv.public_from_private(private) != public: raise LegacyImportError( "Im alten Signaturschluessel passen privater und oeffentlicher Teil nicht " "zueinander. Die Datei ist beschaedigt." @@ -291,7 +292,7 @@ def import_all( ) continue - bundle, license_public = lic.issue_license( + bundle, license_public = licsrv.issue_license( customer={ "name": str(customer["name"]), "contact_email": str(customer["contact_email"] or ""), diff --git a/apps/tesm-license/src/tesm_license/services/master_key.py b/apps/tesm-license/src/tesm_license/services/master_key.py index 28dec98..8ff9402 100644 --- a/apps/tesm-license/src/tesm_license/services/master_key.py +++ b/apps/tesm-license/src/tesm_license/services/master_key.py @@ -24,6 +24,7 @@ from datetime import datetime, timezone from pathlib import Path import tesm_licensing as lic +import tesm_licensing_server as licsrv from tesm_core.keystore import check_private_file, write_private_file logger = logging.getLogger(__name__) @@ -59,7 +60,7 @@ def load(path: Path) -> MasterKey: public = str(data.get("public_key") or "") if not private or not public: raise MasterKeyError("Der Signaturschluessel ist unvollstaendig.") - if lic.public_from_private(private) != public: + if licsrv.public_from_private(private) != public: raise MasterKeyError( "Der oeffentliche Teil des Signaturschluessels passt nicht zum privaten. " "Die Datei ist beschaedigt oder wurde manipuliert." @@ -77,7 +78,7 @@ def ensure(path: Path) -> MasterKey: """ if path.is_file(): return load(path) - private, public = lic.generate_keypair() + private, public = licsrv.generate_keypair() payload = { "private_key": private, "public_key": public, diff --git a/deploy/install.sh b/deploy/install.sh index a27801a..8130693 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -229,11 +229,16 @@ fi # Immer ueber "python3 -m pip" -- der pip-Shim im venv baeckt einen absoluten # Pfad in seinen Shebang, der nach einer Umbenennung des Verzeichnisses bricht. "$ROOT/venv/bin/python3" -m pip install --quiet --upgrade pip setuptools wheel -"$ROOT/venv/bin/python3" -m pip install --quiet \ - "$ROOT/packages/tesm-licensing" \ - "$ROOT/packages/tesm-core" \ - "$ROOT/apps/$APP" \ - gunicorn +PAKETE=("$ROOT/packages/tesm-licensing" "$ROOT/packages/tesm-core") +# Die Serverhaelfte des Lizenzprotokolls -- ausstellen, erneuern, antworten -- +# gehoert ausschliesslich zum Lizenzserver. Ein Client verifiziert Lizenzen und +# stellt Anfragen; den Code zum Ausstellen soll er nicht einmal mitbringen. +if [[ "$APP" == "tesm-license" ]]; then + [[ -d "$ROOT/packages/tesm-licensing-server" ]] || + fail "packages/tesm-licensing-server fehlt -- ohne sie kann der Lizenzserver nichts ausstellen" + PAKETE+=("$ROOT/packages/tesm-licensing-server") +fi +"$ROOT/venv/bin/python3" -m pip install --quiet "${PAKETE[@]}" "$ROOT/apps/$APP" gunicorn if [[ "$APP" == "tesm" ]]; then "$ROOT/venv/bin/python3" -m pip install --quiet flask-sock fi diff --git a/docs/ARCHITEKTUR.md b/docs/ARCHITEKTUR.md deleted file mode 100644 index cdecece..0000000 --- a/docs/ARCHITEKTUR.md +++ /dev/null @@ -1,166 +0,0 @@ -# Architektur - -Zwei Anwendungen, ein gemeinsamer Kern, ein gemeinsames Lizenzprotokoll. - -``` -packages/ - tesm-licensing/ Lizenzformat und -protokoll (Ed25519). Keine Web-Abhaengigkeit. - tesm-core/ Der Kern: Datenbank, Rechte, Anmeldung, Sitzungen, Protokolle, - Einstellungen, Systemzugriff, Oberflaechenbausteine. -apps/ - tesm/ Geraete- und Switchverwaltung (PoE, DHCP, Dateifreigaben, Wartung). - tesm-license/ Lizenzserver (Kunden, Tickets, Lizenzen, Portal). -deploy/ Helfer, sudoers, systemd, nginx, logrotate, install.sh -tests/ Unit- und Integrationstests, dazu drei E2E-Skripte fuer den Zielhost. -``` - -## Warum genau diese Schnitte - -**`tesm-licensing` ist ein eigenes Paket, keine kopierte Datei.** -Im Vorgaengerprojekt lag `licensing.py` zweimal im Repo -- einmal beim Client, -einmal beim Server -- mit der Auflage, beide byte-identisch zu halten. Das ist -keine Invariante, die ein Mensch dauerhaft einhaelt. Hier gibt es das Modul -einmal; beide Anwendungen installieren dasselbe Paket. -`tests/test_repo_hygiene.py` schlaegt Alarm, sobald irgendwo wieder eine Datei -namens `licensing.py` auftaucht. - -**`tesm-core` enthaelt alles, was beide Anwendungen gleich machen.** -Nicht nur Technik (Datenbank, Sitzungen, CSRF), sondern auch ganze Seiten: -Benutzer- und Gruppenverwaltung, Protokollansichten, Papierkorb, Diagnose, -Netzwerk-, Webserver- und Verzeichnisdiensteinstellungen. Eine Seite, die in -beiden Anwendungen gleich aussehen soll, existiert genau einmal. - -**Die Apps bringen nur ihre Fachlichkeit mit** -- plus vier Beschreibungen, die -der Kern auswertet: `permissions.py` (Rechtebaum), `navigation.py`, -`schema.py` (Migrationen), `settings_spec.py`. - -## Datenbank - -Rohes `sqlite3`, kein ORM. Begruendung: das Datenmodell ist klein und stabil, -die Abfragen sind lesbar, und es gibt keine Schicht, die im Fehlerfall erst -verstanden werden muss. - -Die Klasse `tesm_core.db.Database` erzwingt drei Dinge: - -* WAL-Modus, `foreign_keys=ON`, `busy_timeout=15000`. -* Verschachtelte Transaktionen laufen ueber SAVEPOINTs. -* Ein `connect()` **waehrend** einer offenen Schreibtransaktion loest einen - `NestedConnectionError` aus, statt in einen Deadlock zu laufen. Das ist der - strukturelle Riegel gegen die haeufigste Fehlerklasse des Vorgaengers: eine - Funktion oeffnete waehrend eines Schreibvorgangs eine zweite Verbindung, und - die Anwendung blieb stehen, bis das Zeitlimit zuschlug. - -Migrationen sind versioniert nach `(komponente, version)` mit Pruefsumme. Sie -laufen ueber `split_statements()`, nicht ueber `executescript()` -- letzteres -committet implizit und zerlegt die umgebende Transaktion. - -## Rechte - -Ein Rechtebaum aus **Bereich -> Ressource -> Aktion**. Die Aktionen sind ein -festes Vokabular: `VIEW`, `CREATE`, `EDIT`, `DELETE`, `EXECUTE`, `EXPORT`, -`SECRETS`. - -* `SECRETS` und `EXPORT` sind eigene Rechte. Wer Zugangsdaten bearbeiten darf, - darf sie damit noch nicht im Klartext sehen oder ausfuehren. -* Bereiche haben einen Hauptschalter: ein abgeschalteter Bereich ist weg, - unabhaengig von Einzelrechten. -* **Keine Rechteausweitung:** niemand kann einem anderen Konto ein Recht geben, - das er selbst nicht hat (`guard_no_amplification`). Ebenso wenig laesst sich - der letzte Administrator entfernen oder sperren (`guard_last_admin`). -* Jede Pruefung ist serverseitig. Das Template blendet zusaetzlich aus, was - nicht erlaubt ist -- aber es *entscheidet* nichts. Im Vorgaenger wurden - Verzeichnisdienst und Webserver nur im Template geprueft; ein direkter POST - kam durch. - -## Lizenzierung - -Ed25519, mit den Haertungen, die ein Signaturprotokoll erst brauchbar machen: - -* **Getrennte Signaturkontexte** fuer Lizenz, Anfrage und Antwort. Eine - Signatur aus dem einen Kontext ist im anderen wertlos. -* **Kanonisches JSON** -- die Signatur haengt nicht an der Schluesselreihenfolge. -* **Nonce gegen Wiedereinspielung**, Antwort an die Anfrage gebunden. -* **Frischefenster** gegen alte, mitgeschnittene Antworten. -* **`seq` gegen Rueckstufung:** eine aeltere Revision wird abgelehnt. -* **Aussteller-Pinning (Trust on first use).** Die erste eingespielte Lizenz - pinnt den oeffentlichen Schluessel ihres Ausstellers. Ein Wechsel verlangt - eine ausdrueckliche Bestaetigung durch einen Administrator. -* **Fingerabdruck ohne Hostnamen.** Er bildet sich aus Maschinen-ID und - DMI-UUID. Der Hostname ist bewusst **nicht** enthalten: im Vorgaenger brach - jedes Umbenennen des Rechners die Bindung. - -Der Ablauf: Kunde -> Ticket -> Lizenz. Das Ticket haelt die Parameter (Typ, -Module, Laufzeit, Rolle); die Lizenz ist das signierte Ergebnis. Ein zweiter -Klick auf "Ausstellen" erzeugt keine zweite Lizenz, sondern zeigt die offene. - -Aenderungen an einer laufenden Lizenz brauchen keine Neuinstallation: der -Anbieter signiert eine neue Revision, der Client holt sie beim naechsten -Heartbeat. - -## Systemzugriff - -Der Webprozess laeuft unprivilegiert. Root-Aktionen gehen ausschliesslich durch -`deploy/tesm-helper`: ein Skript mit fester Verbliste, Argumentpruefung gegen -Positivlisten und ohne jede Weitergabe an eine Shell. sudoers gibt genau diesen -einen Pfad frei. - -Aktionen, die mehrere Schritte brauchen, sind **ein** Verb -- nicht drei. -`nginx-apply` etwa schreibt, prueft und laedt neu; scheitert `nginx -t`, stellt -es den vorherigen Stand wieder her. Getrennte Verben hatten hier eine echte -Luecke: `nginx-write` verlinkt sofort nach `sites-enabled`, und eine -anschliessend abgelehnte Datei blieb aktiviert liegen. Der laufende nginx merkte -nichts -- der naechste Reload durch irgendwen legte den ganzen Webserver lahm. - -## Mehrere Installationen auf einem Host - -Zwei Schluessel, mit klarer Aufgabenteilung: - -* **`app_key`** (`tesm`, `tesm-license`) -- was die Anwendung *ist*. Bestimmt - Env-Praefix, Rechtebaum, Migrationsnamensraum. -* **`site_key`** (`tesm`, `tesm-opus`, ...) -- welche Installation es *ist*. - Bestimmt alles ausserhalb von `/srv/`: nginx-Site, netplan-Datei, - logrotate-Regel, Zertifikatsverzeichnis und den Namen des Sitzungscookies. - -Ohne diese Trennung wuerde eine Testinstanz beim Speichern der -Webserver-Einstellungen die Site-Datei der Produktion ueberschreiben -- und -beide Installationen teilten sich dasselbe Sitzungscookie, weil Cookies keine -Ports unterscheiden. - -Die Adresse des Anwendungsservers (`BIND`) steht in derselben Umgebungsdatei, -aus der auch gunicorn seinen `--bind` bekommt. Sie ist zugleich das -`proxy_pass`-Ziel der erzeugten nginx-Site. Vorher war die `5000` fest im Code: -eine Instanz auf Port 5100 erzeugte damit eine Konfiguration, die auf die -*andere* Installation zeigte. - -## Oberflaeche - -Handgeschriebenes CSS in `@layer`n mit semantischen Tokens, drei Themen (hell, -dunkel, System). JavaScript als ES-Module, angebunden ueber -`data-behavior`-Attribute -- kein Inline-Script, kein Build-Schritt, und damit -vertraeglich mit einer strikten CSP, die `unsafe-inline` fuer Skripte nicht -erlaubt. - -## Tests - -| Datei | Was sie absichert | -|---|---| -| `test_licensing.py` | Format, Signaturen, Protokoll, Fingerabdruck | -| `test_core_smoke.py` | Kern: Datenbank, Migrationen, Schluesselspeicher, Sitzungen | -| `test_core_security.py` | Passwoerter, TOTP, Ratenbegrenzung, CSRF, Rechte | -| `test_webserver.py` | nginx-Vorlage, TLS, Zertifikate, Instanztrennung | -| `test_tesm_smoke.py` | TESM: Seiten, Validierung, Rechtematrix | -| `test_license_server.py` | Lizenzserver: Ausstellung, Portal, API | -| `test_repo_hygiene.py` | Zeilenenden, Geheimnisse im Repo, Helferpruefungen, sudoers | - -Dazu drei Skripte, die **auf dem Zielhost gegen die echte Installation** laufen -(nginx, gunicorn, systemd, TLS -- keine Attrappen): - -```bash -python3 tests/e2e_smoke.py https://host admin 'Passwort' -python3 tests/e2e_license_server.py https://lizenz.host admin 'Passwort' -python3 tests/e2e_license.py https://lizenz.host https://host admin 'Passwort' http://127.0.0.1:5001 -``` - -Das dritte spielt den vollstaendigen Lizenz-Lebenszyklus ueber beide -Anwendungen hinweg durch: ausstellen, herunterladen, einspielen, aktivieren, -Module pruefen, Heartbeat, Revision, Widerruf. diff --git a/packages/tesm-licensing-server/pyproject.toml b/packages/tesm-licensing-server/pyproject.toml new file mode 100644 index 0000000..10b9bd5 --- /dev/null +++ b/packages/tesm-licensing-server/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "tesm-licensing-server" +version = "2.0.0" +description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten" +requires-python = ">=3.11" +dependencies = ["tesm-licensing==2.0.0", "cryptography>=41"] + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/packages/tesm-licensing-server/src/tesm_licensing_server/__init__.py b/packages/tesm-licensing-server/src/tesm_licensing_server/__init__.py new file mode 100644 index 0000000..0cec61c --- /dev/null +++ b/packages/tesm-licensing-server/src/tesm_licensing_server/__init__.py @@ -0,0 +1,263 @@ +"""Die Serverhaelfte des Lizenzprotokolls: ausstellen, erneuern, antworten. + +Dieses Paket liegt **nur** beim Lizenzserver. Ein Client verifiziert Lizenzen +und stellt Anfragen -- ausstellen kann er nichts, und er soll den Code dafuer +auch nicht mitbringen. Die gemeinsame Haelfte (kanonisches JSON, Signieren und +Pruefen, Fingerabdruck, Statusbewertung, Client-Anfragen) steht in +``tesm_licensing`` und wird von beiden Seiten benutzt. + +Schluesselerzeugung gehoert ebenfalls hierher: der Server erzeugt seinen +Signaturschluessel und je Lizenz ein Client-Schluesselpaar. Der Client +*empfaengt* seinen Schluessel mit dem Bundle; er erzeugt nie einen. +""" + +from __future__ import annotations + +import base64 +import uuid +from datetime import datetime, timedelta +from typing import Any, Callable, Iterable, Mapping, Sequence + +from cryptography.hazmat.primitives.asymmetric import ed25519 + +from tesm_licensing import ( + ACTIONS, + ALL_MODULES, + FORMAT_VERSION, + LICENSE_ROLES, + LICENSE_TYPES, + LIFETIME_EXPIRES_AT, + MAX_REPORT_ROWS, + RESPONSE_STATES, + SIG_CONTEXT_LICENSE, + SIG_CONTEXT_REQUEST, + SIG_CONTEXT_RESPONSE, + ProtocolViolation, + ReplayDetected, + SignatureInvalid, + canonical_bytes, + iso, + key_id, + new_nonce, + parse_iso, + utcnow, +) +from tesm_licensing.canonical import _b64d, sign, verify +from tesm_licensing.protocol import _FINGERPRINT_RE, _NONCE_RE, check_freshness + +__all__ = [ + "build_master_response", + "generate_keypair", + "issue_license", + "public_from_private", + "resign_license", + "verify_client_request", +] + + +def generate_keypair() -> tuple[str, str]: + """Erzeugt ein Ed25519-Schluesselpaar. Rueckgabe: ``(private_b64, public_b64)``.""" + private = ed25519.Ed25519PrivateKey.generate() + return ( + base64.b64encode(private.private_bytes_raw()).decode("ascii"), + base64.b64encode(private.public_key().public_bytes_raw()).decode("ascii"), + ) + + +def public_from_private(private_key_b64: str) -> str: + """Leitet den oeffentlichen Teil aus einem privaten Schluessel ab.""" + key = ed25519.Ed25519PrivateKey.from_private_bytes( + _b64d(private_key_b64, field="private_key") + ) + return base64.b64encode(key.public_key().public_bytes_raw()).decode("ascii") + + +def _validate_issue_args( + license_type: str, role: str, modules: Sequence[str], lifetime: bool +) -> None: + if license_type not in LICENSE_TYPES: + raise ValueError(f"Unbekannter Lizenztyp: {license_type!r}") + if role not in LICENSE_ROLES: + raise ValueError(f"Unbekannte Rolle: {role!r}") + unknown = sorted(set(modules) - set(ALL_MODULES)) + if unknown: + raise ValueError(f"Unbekannte Module: {', '.join(unknown)}") + if lifetime and license_type == "trial": + # Dritte und letzte Absicherung derselben Regel (UI + Service pruefen + # vorher schon): eine unbefristete Trial-Lizenz waere eine permanente + # Lizenz ohne Module -- ein sinnloser Zustand. + raise ValueError("Trial-Lizenzen koennen nicht unbefristet sein") + if role != "customer" and modules: + raise ValueError("Lizenzserver-Lizenzen tragen keine TESM-Module") + + +def issue_license( + *, + customer: Mapping[str, Any], + license_type: str, + modules: Iterable[str], + valid_days: int, + master_private_key_b64: str, + master_public_key_b64: str, + master_endpoint: str, + vendor: Mapping[str, Any], + license_id: str | None = None, + role: str = "customer", + lifetime: bool = False, + seq: int = 1, + now: datetime | None = None, +) -> tuple[dict[str, Any], str]: + """Stellt eine Lizenz aus. + + Rueckgabe: ``(bundle, license_public_key)``. ``bundle`` ist die Datei, die + an den Kunden geht; ``license_public_key`` wandert in die Master-Datenbank, + damit der Master spaeter Client-Anfragen dieser Lizenz verifizieren kann. + """ + module_list = sorted({str(m) for m in modules}) + _validate_issue_args(license_type, role, module_list, lifetime) + if not lifetime and int(valid_days) <= 0: + raise ValueError("valid_days muss positiv sein") + + moment = now or utcnow() + client_private, client_public = generate_keypair() + payload: dict[str, Any] = { + "fmt": FORMAT_VERSION, + "license_id": license_id or str(uuid.uuid4()), + "seq": int(seq), + "customer": dict(customer), + "type": license_type, + "role": role, + "modules": module_list, + "lifetime": bool(lifetime), + "issued_at": iso(moment), + "not_before": iso(moment), + "expires_at": LIFETIME_EXPIRES_AT + if lifetime + else iso(moment + timedelta(days=int(valid_days))), + "license_pubkey": client_public, + "master_pubkey": master_public_key_b64, + "master_key_id": key_id(master_public_key_b64), + "master_endpoint": (master_endpoint or "").rstrip("/"), + "vendor": dict(vendor), + } + signature = sign(payload, master_private_key_b64, SIG_CONTEXT_LICENSE) + document = {**payload, "signature": signature} + bundle = { + "kind": "tesm-license-bundle", + "fmt": FORMAT_VERSION, + "license": document, + "client_key": client_private, + } + return bundle, client_public + + +def resign_license( + document: Mapping[str, Any], + *, + master_private_key_b64: str, + changes: Mapping[str, Any], + now: datetime | None = None, +) -> dict[str, Any]: + """Erzeugt eine neu signierte Revision eines bestehenden Lizenzdokuments. + + Wird fuer ``license_update`` im Heartbeat gebraucht (z. B. nachtraeglich + erweiterte Module oder verlaengerte Laufzeit). ``seq`` steigt dabei zwingend, + damit der Client eine aeltere Revision nicht als Update akzeptiert. + """ + payload = {k: v for k, v in document.items() if k != "signature"} + payload.update(changes) + payload["seq"] = int(document.get("seq") or 1) + 1 + payload["issued_at"] = iso(now or utcnow()) + _validate_issue_args( + str(payload.get("type")), + str(payload.get("role")), + [str(m) for m in payload.get("modules") or ()], + bool(payload.get("lifetime")), + ) + payload["signature"] = sign(payload, master_private_key_b64, SIG_CONTEXT_LICENSE) + return payload + + +def verify_client_request( + request: Mapping[str, Any], + license_public_key: str, + *, + expected_action: str | None = None, + now: datetime | None = None, + seen_nonce: Callable[[str], bool] | None = None, +) -> None: + """Verifiziert eine Client-Anfrage serverseitig. Wirft bei Problemen. + + ``seen_nonce`` ist ein Callback, der ``True`` zurueckgibt, wenn die Nonce + bereits verbraucht ist. Der Aufrufer entscheidet ueber die Persistenz + (in dieser Codebasis: Tabelle ``license_nonces``). + """ + if not isinstance(request, Mapping): + raise ProtocolViolation("Anfrage ist kein Objekt") + if int(request.get("fmt") or 0) != FORMAT_VERSION: + raise ProtocolViolation("Nicht unterstuetzte Protokollversion") + + action = str(request.get("action") or "") + if action not in ACTIONS: + raise ProtocolViolation("Unbekannte Aktion") + if expected_action and action != expected_action: + raise ProtocolViolation("Aktion passt nicht zum Endpunkt") + + nonce = str(request.get("nonce") or "") + if not _NONCE_RE.match(nonce): + raise ProtocolViolation("Nonce hat ein unerwartetes Format") + fingerprint = str(request.get("fingerprint") or "") + if not _FINGERPRINT_RE.match(fingerprint): + raise ProtocolViolation("Fingerprint hat ein unerwartetes Format") + + payload = {k: v for k, v in request.items() if k != "signature"} + if not verify( + payload, str(request.get("signature") or ""), license_public_key, SIG_CONTEXT_REQUEST + ): + raise SignatureInvalid("Signatur der Anfrage ist ungueltig") + + # Frische und Replay erst *nach* der Signaturpruefung: unsignierte Muellanfragen + # sollen keine Nonce-Eintraege in der Datenbank erzeugen koennen. + moment = now or utcnow() + check_freshness(str(request.get("timestamp") or ""), moment, "Anfrage") + if seen_nonce is not None and seen_nonce(nonce): + raise ReplayDetected("Diese Anfrage wurde bereits verarbeitet") + + report = request.get("report") + if report is not None: + if not isinstance(report, Mapping): + raise ProtocolViolation("Bericht ist kein Objekt") + total = sum(len(report.get(key) or ()) for key in ("customers", "tickets", "licenses")) + if total > MAX_REPORT_ROWS: + raise ProtocolViolation("Bericht ueberschreitet die Groessengrenze") + + +def build_master_response( + action: str, + *, + license_id: str, + fingerprint: str, + request_nonce: str, + master_private_key: str, + status: str = "ok", + detail: str = "", + license_update: Mapping[str, Any] | None = None, + now: datetime | None = None, +) -> dict[str, Any]: + """Baut eine signierte Master-Antwort, gebunden an die Nonce der Anfrage.""" + if status not in RESPONSE_STATES: + raise ProtocolViolation(f"Unbekannter Antwortstatus: {status!r}") + payload: dict[str, Any] = { + "fmt": FORMAT_VERSION, + "license_id": license_id, + "fingerprint": fingerprint, + "action": action, + "status": status, + "detail": detail, + "nonce": request_nonce, + "timestamp": iso(now or utcnow()), + } + if license_update is not None: + payload["license_update"] = dict(license_update) + payload["signature"] = sign(payload, master_private_key, SIG_CONTEXT_RESPONSE) + return payload diff --git a/packages/tesm-licensing/src/tesm_licensing/__init__.py b/packages/tesm-licensing/src/tesm_licensing/__init__.py index 20c2cc6..3e2a5c4 100644 --- a/packages/tesm-licensing/src/tesm_licensing/__init__.py +++ b/packages/tesm-licensing/src/tesm_licensing/__init__.py @@ -16,8 +16,6 @@ from .canonical import ( canonical_bytes, decode_code, encode_code, - generate_keypair, - public_from_private, sign, verify, ) @@ -44,12 +42,10 @@ from .license import ( ROLE_LABELS, TRIAL_DEFAULT_DAYS, LicenseStatus, - issue_license, iso, key_id, license_status, parse_iso, - resign_license, split_bundle, utcnow, verify_license, @@ -62,9 +58,7 @@ from .protocol import ( REQUEST_MAX_AGE_SECONDS, RESPONSE_STATES, build_client_request, - build_master_response, new_nonce, - verify_client_request, verify_master_response, ) @@ -100,26 +94,20 @@ __all__ = [ "TRIAL_DEFAULT_DAYS", "UntrustedIssuer", "build_client_request", - "build_master_response", "canonical_bytes", "decode_code", "encode_code", - "generate_keypair", - "issue_license", "iso", "key_id", "license_status", "machine_identity", "new_nonce", "parse_iso", - "public_from_private", - "resign_license", "sign", "split_bundle", "system_fingerprint", "utcnow", "verify", - "verify_client_request", "verify_license", "verify_master_response", ] diff --git a/packages/tesm-licensing/src/tesm_licensing/canonical.py b/packages/tesm-licensing/src/tesm_licensing/canonical.py index 19ad3aa..11c50c6 100644 --- a/packages/tesm-licensing/src/tesm_licensing/canonical.py +++ b/packages/tesm-licensing/src/tesm_licensing/canonical.py @@ -45,21 +45,8 @@ def _b64d(value: str, *, field: str) -> bytes: raise ValueError(f"{field} ist kein gueltiges Base64") from exc -def generate_keypair() -> tuple[str, str]: - """Erzeugt ein Ed25519-Schluesselpaar. Rueckgabe: ``(private_b64, public_b64)``.""" - private = ed25519.Ed25519PrivateKey.generate() - return ( - base64.b64encode(private.private_bytes_raw()).decode("ascii"), - base64.b64encode(private.public_key().public_bytes_raw()).decode("ascii"), - ) -def public_from_private(private_key_b64: str) -> str: - """Leitet den oeffentlichen Teil aus einem privaten Schluessel ab.""" - key = ed25519.Ed25519PrivateKey.from_private_bytes( - _b64d(private_key_b64, field="private_key") - ) - return base64.b64encode(key.public_key().public_bytes_raw()).decode("ascii") def sign(payload: Mapping[str, Any], private_key_b64: str, context: bytes) -> str: diff --git a/packages/tesm-licensing/src/tesm_licensing/license.py b/packages/tesm-licensing/src/tesm_licensing/license.py index feec766..c4b24ac 100644 --- a/packages/tesm-licensing/src/tesm_licensing/license.py +++ b/packages/tesm-licensing/src/tesm_licensing/license.py @@ -34,7 +34,7 @@ from dataclasses import dataclass, field from datetime import datetime, timedelta, timezone from typing import Any, Iterable, Mapping, Sequence -from .canonical import SIG_CONTEXT_LICENSE, generate_keypair, sign, verify +from .canonical import SIG_CONTEXT_LICENSE, sign, verify from .errors import InvalidLicenseFile, SignatureInvalid, UntrustedIssuer FORMAT_VERSION = 2 @@ -147,110 +147,10 @@ class LicenseStatus: return self.modules_active and name in self.modules -def _validate_issue_args( - license_type: str, role: str, modules: Sequence[str], lifetime: bool -) -> None: - if license_type not in LICENSE_TYPES: - raise ValueError(f"Unbekannter Lizenztyp: {license_type!r}") - if role not in LICENSE_ROLES: - raise ValueError(f"Unbekannte Rolle: {role!r}") - unknown = sorted(set(modules) - set(ALL_MODULES)) - if unknown: - raise ValueError(f"Unbekannte Module: {', '.join(unknown)}") - if lifetime and license_type == "trial": - # Dritte und letzte Absicherung derselben Regel (UI + Service pruefen - # vorher schon): eine unbefristete Trial-Lizenz waere eine permanente - # Lizenz ohne Module -- ein sinnloser Zustand. - raise ValueError("Trial-Lizenzen koennen nicht unbefristet sein") - if role != "customer" and modules: - raise ValueError("Lizenzserver-Lizenzen tragen keine TESM-Module") -def issue_license( - *, - customer: Mapping[str, Any], - license_type: str, - modules: Iterable[str], - valid_days: int, - master_private_key_b64: str, - master_public_key_b64: str, - master_endpoint: str, - vendor: Mapping[str, Any], - license_id: str | None = None, - role: str = "customer", - lifetime: bool = False, - seq: int = 1, - now: datetime | None = None, -) -> tuple[dict[str, Any], str]: - """Stellt eine Lizenz aus. - - Rueckgabe: ``(bundle, license_public_key)``. ``bundle`` ist die Datei, die - an den Kunden geht; ``license_public_key`` wandert in die Master-Datenbank, - damit der Master spaeter Client-Anfragen dieser Lizenz verifizieren kann. - """ - module_list = sorted({str(m) for m in modules}) - _validate_issue_args(license_type, role, module_list, lifetime) - if not lifetime and int(valid_days) <= 0: - raise ValueError("valid_days muss positiv sein") - - moment = now or utcnow() - client_private, client_public = generate_keypair() - payload: dict[str, Any] = { - "fmt": FORMAT_VERSION, - "license_id": license_id or str(uuid.uuid4()), - "seq": int(seq), - "customer": dict(customer), - "type": license_type, - "role": role, - "modules": module_list, - "lifetime": bool(lifetime), - "issued_at": iso(moment), - "not_before": iso(moment), - "expires_at": LIFETIME_EXPIRES_AT - if lifetime - else iso(moment + timedelta(days=int(valid_days))), - "license_pubkey": client_public, - "master_pubkey": master_public_key_b64, - "master_key_id": key_id(master_public_key_b64), - "master_endpoint": (master_endpoint or "").rstrip("/"), - "vendor": dict(vendor), - } - signature = sign(payload, master_private_key_b64, SIG_CONTEXT_LICENSE) - document = {**payload, "signature": signature} - bundle = { - "kind": "tesm-license-bundle", - "fmt": FORMAT_VERSION, - "license": document, - "client_key": client_private, - } - return bundle, client_public -def resign_license( - document: Mapping[str, Any], - *, - master_private_key_b64: str, - changes: Mapping[str, Any], - now: datetime | None = None, -) -> dict[str, Any]: - """Erzeugt eine neu signierte Revision eines bestehenden Lizenzdokuments. - - Wird fuer ``license_update`` im Heartbeat gebraucht (z. B. nachtraeglich - erweiterte Module oder verlaengerte Laufzeit). ``seq`` steigt dabei zwingend, - damit der Client eine aeltere Revision nicht als Update akzeptiert. - """ - payload = {k: v for k, v in document.items() if k != "signature"} - payload.update(changes) - payload["seq"] = int(document.get("seq") or 1) + 1 - payload["issued_at"] = iso(now or utcnow()) - _validate_issue_args( - str(payload.get("type")), - str(payload.get("role")), - [str(m) for m in payload.get("modules") or ()], - bool(payload.get("lifetime")), - ) - payload["signature"] = sign(payload, master_private_key_b64, SIG_CONTEXT_LICENSE) - return payload def split_bundle(bundle: Mapping[str, Any]) -> tuple[dict[str, Any], str]: diff --git a/packages/tesm-licensing/src/tesm_licensing/protocol.py b/packages/tesm-licensing/src/tesm_licensing/protocol.py index f053e45..ee8d8b7 100644 --- a/packages/tesm-licensing/src/tesm_licensing/protocol.py +++ b/packages/tesm-licensing/src/tesm_licensing/protocol.py @@ -95,7 +95,7 @@ def build_client_request( return payload -def _check_freshness(timestamp: str, now: datetime, what: str) -> None: +def check_freshness(timestamp: str, now: datetime, what: str) -> None: try: stamp = parse_iso(timestamp) except (ValueError, TypeError) as exc: @@ -106,89 +106,8 @@ def _check_freshness(timestamp: str, now: datetime, what: str) -> None: raise ReplayDetected(f"{what}: Zeitstempel ist zu alt") -def verify_client_request( - request: Mapping[str, Any], - license_public_key: str, - *, - expected_action: str | None = None, - now: datetime | None = None, - seen_nonce: Callable[[str], bool] | None = None, -) -> None: - """Verifiziert eine Client-Anfrage serverseitig. Wirft bei Problemen. - - ``seen_nonce`` ist ein Callback, der ``True`` zurueckgibt, wenn die Nonce - bereits verbraucht ist. Der Aufrufer entscheidet ueber die Persistenz - (in dieser Codebasis: Tabelle ``license_nonces``). - """ - if not isinstance(request, Mapping): - raise ProtocolViolation("Anfrage ist kein Objekt") - if int(request.get("fmt") or 0) != FORMAT_VERSION: - raise ProtocolViolation("Nicht unterstuetzte Protokollversion") - - action = str(request.get("action") or "") - if action not in ACTIONS: - raise ProtocolViolation("Unbekannte Aktion") - if expected_action and action != expected_action: - raise ProtocolViolation("Aktion passt nicht zum Endpunkt") - - nonce = str(request.get("nonce") or "") - if not _NONCE_RE.match(nonce): - raise ProtocolViolation("Nonce hat ein unerwartetes Format") - fingerprint = str(request.get("fingerprint") or "") - if not _FINGERPRINT_RE.match(fingerprint): - raise ProtocolViolation("Fingerprint hat ein unerwartetes Format") - - payload = {k: v for k, v in request.items() if k != "signature"} - if not verify( - payload, str(request.get("signature") or ""), license_public_key, SIG_CONTEXT_REQUEST - ): - raise SignatureInvalid("Signatur der Anfrage ist ungueltig") - - # Frische und Replay erst *nach* der Signaturpruefung: unsignierte Muellanfragen - # sollen keine Nonce-Eintraege in der Datenbank erzeugen koennen. - moment = now or utcnow() - _check_freshness(str(request.get("timestamp") or ""), moment, "Anfrage") - if seen_nonce is not None and seen_nonce(nonce): - raise ReplayDetected("Diese Anfrage wurde bereits verarbeitet") - - report = request.get("report") - if report is not None: - if not isinstance(report, Mapping): - raise ProtocolViolation("Bericht ist kein Objekt") - total = sum(len(report.get(key) or ()) for key in ("customers", "tickets", "licenses")) - if total > MAX_REPORT_ROWS: - raise ProtocolViolation("Bericht ueberschreitet die Groessengrenze") -def build_master_response( - action: str, - *, - license_id: str, - fingerprint: str, - request_nonce: str, - master_private_key: str, - status: str = "ok", - detail: str = "", - license_update: Mapping[str, Any] | None = None, - now: datetime | None = None, -) -> dict[str, Any]: - """Baut eine signierte Master-Antwort, gebunden an die Nonce der Anfrage.""" - if status not in RESPONSE_STATES: - raise ProtocolViolation(f"Unbekannter Antwortstatus: {status!r}") - payload: dict[str, Any] = { - "fmt": FORMAT_VERSION, - "license_id": license_id, - "fingerprint": fingerprint, - "action": action, - "status": status, - "detail": detail, - "nonce": request_nonce, - "timestamp": iso(now or utcnow()), - } - if license_update is not None: - payload["license_update"] = dict(license_update) - payload["signature"] = sign(payload, master_private_key, SIG_CONTEXT_RESPONSE) - return payload def verify_master_response( @@ -230,7 +149,7 @@ def verify_master_response( if str(response.get("status") or "") not in RESPONSE_STATES: raise ProtocolViolation("Unbekannter Antwortstatus") - _check_freshness(str(response.get("timestamp") or ""), now or utcnow(), "Antwort") + check_freshness(str(response.get("timestamp") or ""), now or utcnow(), "Antwort") update = response.get("license_update") if update is not None: diff --git a/tesm-license-v2.0.0.tar.gz b/tesm-license-v2.0.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7114eb6c79faee009385177e2817adcff91b2652 GIT binary patch literal 305479 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�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 literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index e4ec9ec..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Gemeinsame Testfixtures. - -Jeder Test bekommt eine frische Instanz in einem temporaeren Verzeichnis -- -eigene Datenbank, eigene Schluessel, eigenes Protokollverzeichnis. Nichts -beruehrt eine echte Installation. -""" - -from __future__ import annotations - -import os -from pathlib import Path -from typing import Any, Iterator - -import pytest - - -@pytest.fixture() -def instance_env(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: - base = tmp_path / "instance" - monkeypatch.setenv("TESM_BASE_DIR", str(base)) - monkeypatch.setenv("TESM_LICENSE_BASE_DIR", str(tmp_path / "instance-license")) - # Tests laufen ueber http:// -- ein Secure-Cookie wuerde nie gesetzt. - monkeypatch.setenv("TESM_COOKIE_SECURE", "0") - monkeypatch.setenv("TESM_LICENSE_COOKIE_SECURE", "0") - monkeypatch.setenv("TESM_DEBUG", "0") - monkeypatch.setenv("TESM_LICENSE_DEBUG", "0") - return base - - -@pytest.fixture() -def core_app(instance_env: Path) -> Iterator[Any]: - """Minimale App auf Basis von ``tesm_core`` -- ohne Fachlogik der beiden Apps.""" - from flask import render_template_string - - from tesm_core import load_core_config - from tesm_core.app_factory import create_base_app - from tesm_core.auth.models import login_required - from tesm_core.nav import NavItem, NavTree - from tesm_core.rbac.common import administration_area, observability_area - from tesm_core.rbac.model import Action, Area, PermissionTree, Resource, RolePreset - - demo = Area( - key="demo", - label="Demo", - icon="grid", - resources=( - Resource( - key="widgets", - label="Widgets", - actions=(Action.VIEW, Action.CREATE, Action.EDIT, Action.DELETE, Action.SECRETS), - ), - ), - ) - tree = PermissionTree([demo, administration_area(), observability_area()]) - nav = NavTree( - [ - NavItem(key="index", label="Start", icon="grid", endpoint="index"), - NavItem( - key="widgets", label="Widgets", icon="cpu", endpoint="widgets", - permissions=("widgets.view",), - ), - NavItem( - key="administration", - label="Verwaltung", - icon="sliders", - children=( - NavItem( - key="users", label="Benutzer", icon="users", endpoint="admin.users", - permissions=("users.view",), - ), - NavItem( - key="groups", label="Gruppen", icon="shield-users", endpoint="admin.groups", - permissions=("groups.view",), - ), - NavItem( - key="trash", label="Papierkorb", icon="trash", endpoint="trash.overview", - permissions=("trash.view",), - ), - NavItem( - key="diagnostics", label="Diagnose", icon="info", - endpoint="diagnostics.overview", permissions=("diagnostics.view",), - ), - ), - ), - ] - ) - presets = ( - RolePreset( - key="viewer", - name="Benutzer", - description="Nur lesen", - grants=(("widgets", (Action.VIEW,)),), - is_default=True, - ), - ) - - config = load_core_config( - app_key="tesm", app_name="TESM Test", env_prefix="TESM_", default_base=instance_env - ) - app = create_base_app( - config=config, - permissions=tree, - nav=nav, - role_presets=presets, - app_short="TT", - app_edition="Test", - enable_license=False, - ) - - @app.get("/") - def index(): # type: ignore[no-untyped-def] - return render_template_string( - '{% extends "tesm_core/base.html" %}{% block page_title %}Start{% endblock %}' - "{% block content %}

Start

{% endblock %}" - ) - - @app.get("/widgets") - @login_required - def widgets(): # type: ignore[no-untyped-def] - return render_template_string( - '{% extends "tesm_core/base.html" %}{% block page_title %}Widgets{% endblock %}' - "{% block content %}

Widgets

{% endblock %}" - ) - - app.config.update(TESTING=True) - yield app - - -@pytest.fixture() -def client(core_app: Any) -> Any: - return core_app.test_client() - - -@pytest.fixture() -def make_user(core_app: Any): - """Erzeugt ein Konto direkt in der Datenbank.""" - - def factory( - username: str = "tester", - password: str = "Sicher-Passwort-2026", - *, - is_admin: bool = False, - permissions: tuple[str, ...] = (), - is_locked: bool = False, - ) -> dict[str, Any]: - from tesm_core.db import Database - from tesm_core.extension import core - from tesm_core.security import passwords - - extension = core(core_app) - with extension.database.session() as conn, extension.database.transaction(conn): - cursor = conn.execute( - "INSERT INTO users (username, password_hash, is_admin, is_locked, auth_source, " - "created_at, updated_at) VALUES (?,?,?,?,'local',datetime('now'),datetime('now'))", - (username, passwords.hash_password(password), int(is_admin), int(is_locked)), - ) - user_id = int(cursor.lastrowid or 0) - if permissions: - group = conn.execute( - "INSERT INTO groups (name, description, created_at, updated_at) " - "VALUES (?,'Testgruppe',datetime('now'),datetime('now'))", - (f"grp-{username}",), - ) - group_id = int(group.lastrowid or 0) - conn.executemany( - "INSERT INTO group_permissions (group_id, permission) VALUES (?,?)", - [(group_id, p) for p in extension.permissions.sanitize(permissions)], - ) - conn.execute( - "INSERT INTO user_groups (user_id, group_id) VALUES (?,?)", (user_id, group_id) - ) - row = Database.one(conn, "SELECT * FROM users WHERE id=?", (user_id,)) - assert row is not None - return {**row, "password": password} - - return factory - - -@pytest.fixture() -def login(client: Any): - def do_login(username: str, password: str) -> Any: - return client.post( - "/login", - data={"username": username, "password": password, "csrf_token": _token(client)}, - follow_redirects=True, - ) - - return do_login - - -def _token(client: Any) -> str: - """Holt ein CSRF-Token aus der Anmeldeseite.""" - page = client.get("/login") - body = page.get_data(as_text=True) - marker = 'name="csrf_token" value="' - start = body.index(marker) + len(marker) - return body[start : body.index('"', start)] - - -@pytest.fixture() -def csrf_token(client: Any): - def get(path: str = "/login") -> str: - page = client.get(path) - body = page.get_data(as_text=True) - marker = 'name="csrf_token" value="' - if marker not in body: - marker = 'data-csrf="' - start = body.index(marker) + len(marker) - return body[start : body.index('"', start)] - - return get diff --git a/tests/e2e_license.py b/tests/e2e_license.py deleted file mode 100644 index dd52acc..0000000 --- a/tests/e2e_license.py +++ /dev/null @@ -1,493 +0,0 @@ -"""End-to-End: der komplette Lizenz-Lebenszyklus ueber beide Anwendungen hinweg. - -Laeuft auf dem Zielhost gegen zwei echte, laufende Installationen: - - Lizenzserver: Kunde -> Ticket -> Lizenz ausstellen -> Bundle herunterladen - TESM : Bundle einspielen -> aktivieren (online gegen den Lizenzserver) - -> Module freigeschaltet -> Heartbeat -> Revision -> Widerruf - -Das ist der Pfad, der im Vorgaengerprojekt nur von Hand pruefbar war -- und -genau dort sassen die teuren Fehler. Geprueft wird deshalb nicht nur "es geht", -sondern auch: Doppelausstellung wird verhindert, Module sind vor der -Aktivierung wirklich gesperrt, eine neue Revision nimmt Module wieder weg, und -ein Widerruf kommt beim Client an. - -Aufruf: - python3 e2e_license.py http://127.0.0.1:8081 http://127.0.0.1:8080 admin 'Passwort' -""" - -from __future__ import annotations - -import http.cookiejar -import json -import re -import ssl -import sys -import urllib.error -import urllib.parse -import urllib.request - -CSRF_RE = re.compile(r'name="csrf_token" value="([^"]+)"') -BODY_CSRF_RE = re.compile(r'data-csrf="([^"]+)"') - -PASSED: list[str] = [] -FAILED: list[str] = [] - - -def check(name: str, condition: bool, detail: str = "") -> None: - if condition: - PASSED.append(name) - print(f" [ok] {name}") - else: - FAILED.append(f"{name}: {detail}") - print(f" [FEHL] {name} -- {detail}") - - -class Response: - def __init__(self, status: int, body: bytes, url: str, headers: dict[str, str]) -> None: - self.status = status - self.raw = body - self.url = url - self.headers = headers - - @property - def text(self) -> str: - return self.raw.decode("utf-8", "replace") - - -class Client: - def __init__(self, base: str, label: str) -> None: - self.base = base.rstrip("/") - self.label = label - jar = http.cookiejar.CookieJar() - # Der Test laeuft auf dem Zielhost gegen dessen eigenes, selbst - # ausgestelltes Zertifikat. Geprueft wird, dass TLS steht und die - # richtige Anwendung antwortet -- nicht die Vertrauenskette. - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - self.opener = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(jar), - urllib.request.HTTPSHandler(context=context), - ) - self.opener.addheaders = [("User-Agent", "tesm-e2e-license/2.0")] - - def _open(self, request: urllib.request.Request) -> Response: - try: - with self.opener.open(request, timeout=60) as response: - return Response( - response.status, response.read(), response.geturl(), dict(response.headers) - ) - except urllib.error.HTTPError as exc: - return Response(exc.code, exc.read(), exc.url, dict(exc.headers)) - - def get(self, path: str) -> Response: - return self._open(urllib.request.Request(self.base + path)) - - def text(self, path: str) -> str: - return self.get(path).text - - def post(self, path: str, data: dict[str, object]) -> Response: - pairs = [ - (key, str(item)) - for key, value in data.items() - for item in (value if isinstance(value, (list, tuple)) else [value]) - ] - body = urllib.parse.urlencode(pairs).encode("utf-8") - request = urllib.request.Request( - self.base + path, - data=body, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - "Origin": self.base, - "Referer": self.base + path, - }, - ) - return self._open(request) - - def post_file( - self, path: str, field: str, filename: str, content: bytes, extra: dict[str, str] - ) -> Response: - boundary = "----tesm-e2e-boundary-3f88" - parts: list[bytes] = [] - for key, value in extra.items(): - parts.append( - f'--{boundary}\r\nContent-Disposition: form-data; name="{key}"\r\n\r\n' - f"{value}\r\n".encode() - ) - parts.append( - f'--{boundary}\r\nContent-Disposition: form-data; name="{field}"; ' - f'filename="{filename}"\r\nContent-Type: application/json\r\n\r\n'.encode() - + content - + b"\r\n" - ) - parts.append(f"--{boundary}--\r\n".encode()) - request = urllib.request.Request( - self.base + path, - data=b"".join(parts), - headers={ - "Content-Type": f"multipart/form-data; boundary={boundary}", - "Origin": self.base, - "Referer": self.base + path, - }, - ) - return self._open(request) - - def token(self, path: str) -> str: - body = self.text(path) - match = CSRF_RE.search(body) or BODY_CSRF_RE.search(body) - if not match: - raise AssertionError(f"{self.label}: kein CSRF-Token auf {path}") - return match.group(1) - - def login(self, username: str, password: str) -> bool: - token = self.token("/login") - response = self.post( - "/login", {"username": username, "password": password, "csrf_token": token} - ) - return response.status == 200 and "/login" not in response.url - - -def main( - server_base: str, - client_base: str, - username: str, - password: str, - endpoint: str = "", -) -> int: - # Der Endpunkt, den TESM spaeter anruft, muss nicht die Adresse sein, unter - # der ein Mensch den Lizenzserver bedient. Auf einem Host, der beide - # Anwendungen traegt, ist der direkte Weg ueber die Loopback-Adresse der - # ehrlichere -- er umgeht Namensaufloesung und Zertifikatspruefung, die - # zwischen zwei Diensten derselben Maschine nichts beitragen. - endpoint = (endpoint or server_base).rstrip("/") - server = Client(server_base, "Lizenzserver") - tesm = Client(client_base, "TESM") - print(f"\n== Lizenz-Lebenszyklus: {server_base} -> {client_base} ==\n") - - print("Anmeldung:") - check("Lizenzserver", server.login(username, password), "fehlgeschlagen") - check("TESM", tesm.login(username, password), "fehlgeschlagen") - if FAILED: - print("\nOhne Anmeldung ist der Rest sinnlos -- Abbruch.") - return 1 - - # -- Anbieterdaten: der Endpunkt landet spaeter in jeder Lizenz ----------- - print("\nAnbieterdaten und Endpunkt:") - token = server.token("/einstellungen/anbieter") - response = server.post( - "/einstellungen/anbieter", - { - "csrf_token": token, - "vendor_name": "WiS GmbH", - "vendor_email": "lizenzen@wis.invalid", - "vendor_phone": "+49 000 000000", - "vendor_address": "Musterweg 1, 12345 Musterstadt", - "master_endpoint": endpoint, - }, - ) - check( - "Anbieterdaten gespeichert", - "gespeichert" in response.text or "Keine Aenderungen" in response.text, - response.text[:150], - ) - token = server.token("/einstellungen/anbieter") - response = server.post( - "/einstellungen/anbieter", {"csrf_token": token, "master_endpoint": "ftp://falsch"} - ) - check("Ungueltiger Endpunkt abgelehnt", "muss mit http" in response.text, "keine Meldung") - - # -- Kunde --------------------------------------------------------------- - print("\nKunde:") - token = server.token("/kunden/") - response = server.post( - "/kunden/neu", - { - "csrf_token": token, - "name": "Beispiel AG", - "contact_email": "it@beispiel.invalid", - "contact_person": "Frau Muster", - "street": "Hauptstrasse", - "house_number": "7", - "postal_code": "12345", - "city": "Musterstadt", - }, - ) - check("Kunde angelegt", "angelegt" in response.text, response.text[:150]) - match = re.search(r"/kunden/(\d+)$", response.url) - customer_id = match.group(1) if match else "" - check("Kunden-Nummer erhalten", bool(customer_id), response.url) - - # -- Ticket --------------------------------------------------------------- - print("\nTicket:") - token = server.token("/tickets/neu") - response = server.post( - "/tickets/neu", - { - "csrf_token": token, - "customer_id": customer_id, - "type": "enterprise", - "modules": ["dhcp", "fileshare", "maintenance"], - "valid_days": "365", - "role": "customer", - "notes": "E2E-Test", - }, - ) - check("Ticket angelegt", "angelegt" in response.text, response.text[:150]) - match = re.search(r"/tickets/([^/]+)$", response.url) - ticket_id = match.group(1) if match else "" - check("Ticket-Kennung erhalten", bool(ticket_id), response.url) - - token = server.token("/tickets/neu") - response = server.post( - "/tickets/neu", - { - "csrf_token": token, - "customer_id": customer_id, - "type": "trial", - "valid_days": "30", - "lifetime": "1", - "role": "customer", - }, - ) - check( - "Testlizenz kann nicht unbefristet sein", - "nicht unbefristet" in response.text, - "Regel greift nicht", - ) - - # -- Lizenz ausstellen ---------------------------------------------------- - print("\nLizenz ausstellen:") - token = server.token(f"/tickets/{ticket_id}") - response = server.post(f"/lizenzen/aus-ticket/{ticket_id}", {"csrf_token": token}) - check("Lizenz ausgestellt", "ausgestellt" in response.text, response.text[:200]) - match = re.search(r"/lizenzen/([0-9a-f-]{36})$", response.url) - license_id = match.group(1) if match else "" - check("Lizenz-Kennung erhalten", bool(license_id), response.url) - - token = server.token(f"/tickets/{ticket_id}") - response = server.post(f"/lizenzen/aus-ticket/{ticket_id}", {"csrf_token": token}) - check( - "Doppelklick stellt keine zweite Lizenz aus", - "bereits eine Lizenz offen" in response.text, - "zweite Lizenz entstanden", - ) - - # -- Bundle herunterladen ------------------------------------------------- - print("\nAuslieferung:") - response = server.get(f"/lizenzen/{license_id}/datei") - check("Bundle heruntergeladen", response.status == 200, f"HTTP {response.status}") - check( - "Als Anhang ausgeliefert", - "attachment" in response.headers.get("Content-Disposition", ""), - response.headers.get("Content-Disposition", ""), - ) - check( - "Nicht zwischenspeicherbar", - "no-store" in response.headers.get("Cache-Control", ""), - response.headers.get("Cache-Control", ""), - ) - bundle_bytes = response.raw - try: - bundle = json.loads(bundle_bytes.decode("utf-8")) - except ValueError: - check("Bundle ist gueltiges JSON", False, bundle_bytes[:120].decode("latin1")) - return 1 - check("Bundle ist gueltiges JSON", True) - check( - "Dokument und privater Clientschluessel enthalten", - "license" in bundle and "client_key" in bundle, - str(sorted(bundle))[:100], - ) - document = bundle.get("license", {}) - check( - "Endpunkt eingebettet", - document.get("master_endpoint") == endpoint, - str(document.get("master_endpoint")), - ) - check( - "Alle drei Module enthalten", - set(document.get("modules", [])) == {"dhcp", "fileshare", "maintenance"}, - str(document.get("modules")), - ) - check("Rolle ist customer", document.get("role") == "customer", str(document.get("role"))) - - body = server.text(f"/lizenzen/{license_id}") - check("Zustellprotokoll vermerkt den Download", "ownload" in body, "kein Eintrag") - - # -- Einspielen in TESM --------------------------------------------------- - print("\nEinspielen in TESM:") - body = tesm.text("/lizenz/") - if "keine Lizenz installiert" in body: - check("Vorher keine Lizenz installiert", True) - else: - # Bei einem wiederholten Lauf liegt noch die Lizenz des Vorlaufs. Das ist - # kein Fehler -- das Einspielen ersetzt sie, und genau das wird gleich - # geprueft. - print(" [info] Es liegt bereits eine Lizenz -- wiederholter Lauf, wird ersetzt.") - - token = tesm.token("/lizenz/") - response = tesm.post_file( - "/lizenz/einspielen", "license_file", "lizenz.json", bundle_bytes, {"csrf_token": token} - ) - check("Lizenz eingespielt", "eingespielt" in response.text, response.text[:250]) - check( - "Aussteller beim ersten Mal gepinnt", - "Aussteller gepinnt" in response.text, - "kein Pinning erkennbar", - ) - check("Noch nicht aktiviert", "nicht aktiviert" in response.text, "Zustand unerwartet") - - print("\nModule vor der Aktivierung:") - for path in ("/dhcp/", "/wartung/", "/dateifreigaben/"): - check(f"{path} gesperrt", tesm.get(path).status == 404, "erreichbar") - - # -- Manipuliertes Bundle ------------------------------------------------- - print("\nManipuliertes Bundle:") - tampered = json.loads(bundle_bytes.decode("utf-8")) - tampered["license"]["modules"] = ["dhcp", "fileshare", "maintenance", "erfunden"] - token = tesm.token("/lizenz/") - response = tesm.post_file( - "/lizenz/einspielen", - "license_file", - "boese.json", - json.dumps(tampered).encode("utf-8"), - {"csrf_token": token}, - ) - check( - "Veraenderte Lizenz wird abgewiesen", - "eingespielt" not in response.text, - "Manipulation akzeptiert!", - ) - - # -- Aktivierung ---------------------------------------------------------- - print("\nAktivierung (online, echter HTTP-Aufruf an den Lizenzserver):") - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/aktivieren", {"csrf_token": token}) - check("Aktivierung erfolgreich", "jetzt aktiv" in response.text, response.text[:300]) - check("Zustand aktiviert", "nicht aktiviert" not in response.text, "Zustand unerwartet") - - print("\nModule nach der Aktivierung:") - for path, needle in ( - ("/dhcp/", "DHCP"), - ("/wartung/", "Wartung"), - ("/dateifreigaben/", "Dateifreigaben"), - ): - response = tesm.get(path) - check( - f"{path} freigeschaltet", - response.status == 200 and needle in response.text, - f"HTTP {response.status}", - ) - - print("\nSicht des Lizenzservers:") - body = server.text(f"/lizenzen/{license_id}") - check("Maschine gebunden", "noch nicht gebunden" not in body, "kein Fingerprint") - check("Aktivierungszeitpunkt vermerkt", "Aktiviert" in body, "fehlt") - - # -- Heartbeat ------------------------------------------------------------ - print("\nHeartbeat:") - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/heartbeat", {"csrf_token": token}) - check("Heartbeat bestaetigt", "bestaetigt" in response.text, response.text[:250]) - - # -- Neue Revision: Modul entziehen --------------------------------------- - print("\nNeue Revision (Modul entziehen):") - token = server.token(f"/lizenzen/{license_id}") - response = server.post( - f"/lizenzen/{license_id}/neu-signieren", - {"csrf_token": token, "modules": ["dhcp"], "extend_days": ""}, - ) - check("Revision signiert", "Revision" in response.text, response.text[:250]) - - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/heartbeat", {"csrf_token": token}) - check("Heartbeat holt die Revision", "bestaetigt" in response.text, response.text[:200]) - check("Entzogenes Modul gesperrt", tesm.get("/wartung/").status == 404, "weiter offen") - check("Verbliebenes Modul offen", tesm.get("/dhcp/").status == 200, "gesperrt") - - # -- Deaktivierung und erneute Aktivierung -------------------------------- - print("\nDeaktivierung (Systemwechsel):") - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/deaktivieren", {"csrf_token": token}) - check("Deaktivierung erfolgreich", "wurde deaktiviert" in response.text, response.text[:250]) - check("Module wieder gesperrt", tesm.get("/dhcp/").status == 404, "weiter offen") - body = server.text(f"/lizenzen/{license_id}") - check("Server kennt die Deaktivierung", "deaktiviert" in body, "unerwartet") - - print("\nErneute Aktivierung derselben Maschine:") - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/aktivieren", {"csrf_token": token}) - check("Wieder aktiv", "jetzt aktiv" in response.text, response.text[:250]) - check("Modul wieder offen", tesm.get("/dhcp/").status == 200, "gesperrt") - - # -- Widerruf ------------------------------------------------------------- - print("\nWiderruf durch den Anbieter:") - token = server.token(f"/lizenzen/{license_id}") - response = server.post( - f"/lizenzen/{license_id}/widerrufen", {"csrf_token": token, "reason": "E2E-Test"} - ) - check("Widerruf gespeichert", "widerrufen" in response.text, response.text[:250]) - - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/heartbeat", {"csrf_token": token}) - check("Client erfaehrt den Widerruf", "widerrufen" in response.text, response.text[:250]) - check("Module nach Widerruf gesperrt", tesm.get("/dhcp/").status == 404, "weiter offen") - check("Lizenzseite bleibt erreichbar", tesm.get("/lizenz/").status == 200, "gesperrt") - - # -- Aufraeumen ------------------------------------------------------- - # Der Test endet mit einem Widerruf -- danach sind DHCP, Wartung und - # Dateifreigaben gesperrt. Eine Pruefung, die die Installation unbrauchbar - # zuruecklaesst, ist keine gute Pruefung: wer danach die Wartung sucht, - # findet einen 404 und haelt ihn fuer einen Fehler. Also wird zum Schluss - # eine frische Lizenz ausgestellt und aktiviert. - print("\nAufraeumen -- Installation wieder betriebsbereit machen:") - token = server.token(f"/tickets/{ticket_id}") - response = server.post(f"/lizenzen/aus-ticket/{ticket_id}", {"csrf_token": token}) - check("Ersatzlizenz ausgestellt", "ausgestellt" in response.text, response.text[:200]) - match = re.search(r"/lizenzen/([0-9a-f-]{36})$", response.url) - replacement_id = match.group(1) if match else "" - check("Kennung der Ersatzlizenz", bool(replacement_id), response.url) - - response = server.get(f"/lizenzen/{replacement_id}/datei") - check("Ersatzbundle heruntergeladen", response.status == 200, f"HTTP {response.status}") - - token = tesm.token("/lizenz/") - response = tesm.post_file( - "/lizenz/einspielen", "license_file", "lizenz.json", response.raw, {"csrf_token": token} - ) - check("Ersatzlizenz eingespielt", "eingespielt" in response.text, response.text[:200]) - - token = tesm.token("/lizenz/") - response = tesm.post("/lizenz/aktivieren", {"csrf_token": token}) - check("Ersatzlizenz aktiv", "jetzt aktiv" in response.text, response.text[:250]) - for path in ("/dhcp/", "/wartung/", "/dateifreigaben/"): - check(f"{path} wieder erreichbar", tesm.get(path).status == 200, "weiter gesperrt") - - # -- Protokolle ----------------------------------------------------------- - print("\nProtokolle und Diagnose:") - body = server.text("/protokolle/aenderungen") - for action in ("license.issued", "license.downloaded", "license.resigned", "license.revoked"): - check(f"Eintrag {action}", action in body, "fehlt") - check( - "Diagnose Lizenzserver: Kette unversehrt", - "unversehrt" in server.text("/diagnose/"), - "gebrochen", - ) - - body = tesm.text("/protokolle/aenderungen") - for action in ("license.activate", "license.deactivate", "license.heartbeat"): - check(f"TESM-Eintrag {action}", action in body, "fehlt") - check("Diagnose TESM: Kette unversehrt", "unversehrt" in tesm.text("/diagnose/"), "gebrochen") - - print(f"\n== Ergebnis: {len(PASSED)} bestanden, {len(FAILED)} fehlgeschlagen ==") - for entry in FAILED: - print(f" FEHLER: {entry}") - return 1 if FAILED else 0 - - -if __name__ == "__main__": - if len(sys.argv) not in (5, 6): - print(__doc__) - raise SystemExit(2) - raise SystemExit(main(*sys.argv[1:])) diff --git a/tests/e2e_license_server.py b/tests/e2e_license_server.py deleted file mode 100644 index e5bb814..0000000 --- a/tests/e2e_license_server.py +++ /dev/null @@ -1,251 +0,0 @@ -"""End-to-End-Rauchtest des Lizenzservers gegen eine laufende Installation. - -Gegenstueck zu ``e2e_smoke.py`` (das prueft TESM). Beide Anwendungen teilen -sich den Kern; hier geht es um die Seiten und Schranken, die nur der -Lizenzserver hat -- Kunden, Tickets, Lizenzen, Kundenportal, Selbstbedienung -und die Maschinen-API. - -Aufruf: - python3 e2e_license_server.py https://lizenz.example.org admin 'Passwort' -""" - -from __future__ import annotations - -import http.cookiejar -import json -import re -import ssl -import sys -import urllib.error -import urllib.parse -import urllib.request - -CSRF_RE = re.compile(r'name="csrf_token" value="([^"]+)"') -BODY_CSRF_RE = re.compile(r'data-csrf="([^"]+)"') - -PASSED: list[str] = [] -FAILED: list[str] = [] - - -def check(name: str, condition: bool, detail: str = "") -> None: - if condition: - PASSED.append(name) - print(f" [ok] {name}") - else: - FAILED.append(f"{name}: {detail}") - print(f" [FEHL] {name} -- {detail}") - - -class Client: - def __init__(self, base: str) -> None: - self.base = base.rstrip("/") - self.https = self.base.startswith("https://") - jar = http.cookiejar.CookieJar() - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - tls = urllib.request.HTTPSHandler(context=context) - - self.opener = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(jar), tls - ) - self.opener.addheaders = [("User-Agent", "tesm-e2e-lic/2.0")] - - class _NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self, *_args, **_kwargs): # type: ignore[override] - return None - - self.plain = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(jar), _NoRedirect(), tls - ) - self.plain.addheaders = [("User-Agent", "tesm-e2e-lic/2.0")] - - def get(self, path: str) -> tuple[int, str, dict[str, str]]: - request = urllib.request.Request(self.base + path) - try: - with self.opener.open(request, timeout=30) as response: - return ( - response.status, - response.read().decode("utf-8", "replace"), - dict(response.headers), - ) - except urllib.error.HTTPError as exc: - return exc.code, exc.read().decode("utf-8", "replace"), dict(exc.headers) - - def get_raw(self, path: str) -> int: - try: - with self.plain.open(urllib.request.Request(self.base + path), timeout=30) as response: - return response.status - except urllib.error.HTTPError as exc: - return exc.code - - def post(self, path: str, data: dict[str, object]) -> tuple[int, str]: - encoded = urllib.parse.urlencode( - [ - (key, str(item)) - for key, value in data.items() - for item in (value if isinstance(value, (list, tuple)) else [value]) - ] - ).encode("utf-8") - request = urllib.request.Request( - self.base + path, - data=encoded, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - "Origin": self.base, - "Referer": self.base + path, - }, - ) - try: - with self.opener.open(request, timeout=45) as response: - return response.status, response.read().decode("utf-8", "replace") - except urllib.error.HTTPError as exc: - return exc.code, exc.read().decode("utf-8", "replace") - - def post_json(self, path: str, payload: dict[str, object]) -> tuple[int, str]: - request = urllib.request.Request( - self.base + path, - data=json.dumps(payload).encode("utf-8"), - headers={"Content-Type": "application/json"}, - ) - try: - with self.opener.open(request, timeout=30) as response: - return response.status, response.read().decode("utf-8", "replace") - except urllib.error.HTTPError as exc: - return exc.code, exc.read().decode("utf-8", "replace") - - def token(self, path: str) -> str: - _, body, _ = self.get(path) - match = CSRF_RE.search(body) or BODY_CSRF_RE.search(body) - if not match: - raise AssertionError(f"Kein CSRF-Token auf {path}") - return match.group(1) - - -def main(base: str, username: str, password: str) -> int: - client = Client(base) - print(f"\n== Rauchtest Lizenzserver gegen {base} ==\n") - - print("Oeffentlich erreichbar:") - status, body, headers = client.get("/gesundheit") - check( - "Gesundheitsendpunkt", - status == 200 and json.loads(body)["status"] == "ok", - body[:80], - ) - check( - "Antwortet als Lizenzserver", - json.loads(body).get("version") == "tesm-license", - body[:80], - ) - - status, body, headers = client.get("/login") - check("Anmeldeseite", status == 200 and "Anmelden" in body, f"HTTP {status}") - csp = headers.get("Content-Security-Policy", "") - check("CSP mit Nonce", "nonce-" in csp, csp[:80]) - check( - "X-Frame-Options", - headers.get("X-Frame-Options") == "DENY", - str(headers.get("X-Frame-Options")), - ) - if client.https: - cookie = headers.get("Set-Cookie", "") - check("Sitzungscookie mit Secure", "Secure" in cookie, cookie[:80] or "kein Cookie") - - print("\nKundenportal (ohne Anmeldung erreichbar):") - status, body, _ = client.get("/portal/") - check("Portalseite", status == 200 and "Kundenportal" in body, f"HTTP {status}") - check("Fragt nach der Kennung, nicht nach einem Passwort", "Passwort" not in body, "unerwartet") - - print("\nSchranken ohne Anmeldung:") - for path in ("/kunden/", "/tickets/", "/lizenzen/", "/verwaltung/benutzer", "/diagnose/"): - status = client.get_raw(path) - check(f"{path} verlangt Anmeldung", status in (302, 401), f"HTTP {status}") - - print("\nCSRF:") - status, body = client.post("/login", {"username": "x", "password": "y"}) - check("POST ohne Token wird abgewiesen", status == 403, f"HTTP {status}") - - print("\nMaschinen-API (eigene Authentisierung, kein Cookie):") - status, body = client.post_json("/api/v1/licenses/activate", {"unsinn": True}) - check( - "Unsinniger Antrag wird abgewiesen", - status in (400, 401, 403, 404, 422), - f"HTTP {status}", - ) - check( - "Antwort ist maschinenlesbar, nicht HTML", - body.lstrip().startswith("{") and "error" in body, - body[:100], - ) - check("Keine Interna in der Antwort", "Traceback" not in body, "Stacktrace nach aussen") - check( - "Kein CSRF-Token noetig (Maschinenschnittstelle)", - "Sicherheitstoken" not in body, - "API verlangt ein Formulartoken", - ) - - print("\nAnmeldung:") - token = client.token("/login") - status, body = client.post( - "/login", {"username": username, "password": password, "csrf_token": token} - ) - check("Anmeldung erfolgreich", status == 200 and "Anmelden" not in body, f"HTTP {status}") - - print("\nSeiten (angemeldet):") - pages = [ - ("/", "Uebersicht"), - ("/kunden/", "Kunden"), - ("/tickets/", "Tickets"), - ("/tickets/neu", "Ticket"), - ("/lizenzen/", "Lizenzen"), - ("/lizenzen/offline-code", "Offline"), - ("/lizenzen/sub-lizenzserver", "Lizenzserver"), - ("/eigene-lizenz/", "Lizenz"), - ("/einstellungen/system", "System"), - ("/einstellungen/anbieter", "Anbieter"), - ("/einstellungen/email", "Mail"), - ("/einstellungen/netzwerk", "Netzwerk"), - ("/einstellungen/webserver", "Webserver"), - ("/einstellungen/verzeichnisdienst", "Verzeichnisdienst"), - ("/protokolle/aenderungen", "Aenderungsprotokoll"), - ("/sicherung/", "Sicherung"), - ("/papierkorb/", "Papierkorb"), - ("/diagnose/", "Diagnose"), - ("/verwaltung/benutzer", "Benutzer"), - ("/verwaltung/gruppen", "Gruppen"), - ("/konto/sicherheit", "Sicherheit"), - ] - for path, needle in pages: - status, body, _ = client.get(path) - check( - f"{path}", - status == 200 and needle in body and "Interner Fehler" not in body, - f"HTTP {status}", - ) - - print("\nEigene Master-Lizenz:") - status, body, _ = client.get("/eigene-lizenz/") - check("Master-Rolle erkannt", "master" in body.lower(), "Rolle fehlt") - - print("\nDiagnose:") - status, body, _ = client.get("/diagnose/") - check("Audit-Kette unversehrt", "unversehrt" in body, "Kette gebrochen?") - check("Signaturschluessel vorhanden", "Signatur" in body, "kein Hinweis") - - print("\nAbmeldung:") - token = client.token("/konto/") - client.post("/logout", {"csrf_token": token}) - check("Nach Abmeldung gesperrt", client.get_raw("/kunden/") in (302, 401), "weiter offen") - - print(f"\n== Ergebnis: {len(PASSED)} bestanden, {len(FAILED)} fehlgeschlagen ==") - for entry in FAILED: - print(f" FEHLER: {entry}") - return 1 if FAILED else 0 - - -if __name__ == "__main__": - if len(sys.argv) != 4: - print(__doc__) - raise SystemExit(2) - raise SystemExit(main(sys.argv[1], sys.argv[2], sys.argv[3])) diff --git a/tests/test_core_security.py b/tests/test_core_security.py deleted file mode 100644 index 1b52c57..0000000 --- a/tests/test_core_security.py +++ /dev/null @@ -1,721 +0,0 @@ -"""Tests der Sicherheitsgrenzen des Kerns. - -Jeder Test hier deckt eine Grenze ab, die im Vorgaengerprojekt entweder fehlte -oder nur im Template durchgesetzt wurde. -""" - -from __future__ import annotations - -import json -import sqlite3 -from datetime import datetime, timedelta, timezone - -import pytest - -from tesm_core import audit -from tesm_core.db import Database, NestedConnectionError -from tesm_core.extension import core -from tesm_core.keystore import DecryptionError, Keystore -from tesm_core.migrations import split_statements -from tesm_core.rbac import service as rbac -from tesm_core.rbac.model import Action, Area, PermissionTree, Resource, RolePreset -from tesm_core.security import passwords, totp - - -# --------------------------------------------------------------------------- # -# Rechtebaum -# --------------------------------------------------------------------------- # - - -def _tree() -> PermissionTree: - return PermissionTree( - [ - Area( - key="ops", - label="Betrieb", - icon="cpu", - resources=( - Resource( - "devices", - "Geraete", - (Action.VIEW, Action.CREATE, Action.EDIT, Action.DELETE, Action.EXECUTE), - ), - Resource("switches", "Switche", (Action.VIEW, Action.EDIT)), - ), - ), - Area( - key="admin", - label="Verwaltung", - icon="sliders", - resources=(Resource("users", "Benutzer", (Action.VIEW, Action.CREATE)),), - ), - ] - ) - - -def test_area_columns_only_contain_used_actions(): - tree = _tree() - ops = tree.area("ops") - admin = tree.area("admin") - assert Action.EXECUTE in ops.columns() - assert Action.EXECUTE not in admin.columns() - assert Action.DELETE not in admin.columns() - - -def test_sanitize_adds_view_and_area_switch(): - tree = _tree() - result = tree.sanitize(["devices.delete"]) - assert result == ["devices.delete", "devices.view", "ops.view"] - - -def test_sanitize_drops_unknown_permissions(): - tree = _tree() - assert tree.sanitize(["gibtsnicht.view", "devices.view"]) == ["devices.view", "ops.view"] - - -def test_kill_switch_disables_all_children(): - tree = _tree() - granted = {"devices.view", "devices.edit", "switches.view"} - assert tree.effective(granted) == set() # ohne ops.view greift nichts - assert tree.effective(granted | {"ops.view"}) == granted | {"ops.view"} - - -def test_duplicate_resource_keys_rejected(): - with pytest.raises(ValueError): - PermissionTree( - [ - Area("a", "A", "grid", (Resource("x", "X", (Action.VIEW,)),)), - Area("b", "B", "grid", (Resource("x", "X", (Action.VIEW,)),)), - ] - ) - - -def test_resource_without_view_rejected(): - with pytest.raises(ValueError): - PermissionTree([Area("a", "A", "grid", (Resource("x", "X", (Action.EDIT,)),))]) - - -def test_preset_expansion(): - tree = _tree() - preset = RolePreset( - key="ops-read", - name="Betrieb lesen", - description="", - grants=(("devices", (Action.VIEW,)), ("switches", "*")), - ) - assert tree.expand_preset(preset) == [ - "devices.view", - "ops.view", - "switches.edit", - "switches.view", - ] - - -# --------------------------------------------------------------------------- # -# Eskalationsschutz -# --------------------------------------------------------------------------- # - - -class _Actor: - def __init__(self, is_admin: bool, permissions: set[str]): - self.is_admin = is_admin - self.id = 42 - self._permissions = permissions - - def has(self, *permissions: str) -> bool: - return self.is_admin or all(p in self._permissions for p in permissions) - - -def test_no_amplification_blocks_unknown_permission(): - tree = _tree() - actor = _Actor(False, {"ops.view", "devices.view"}) - with pytest.raises(rbac.PermissionDenied) as exc: - rbac.guard_no_amplification(actor, tree, ["devices.delete"]) - assert "nicht besitzen" in str(exc.value) - - -def test_no_amplification_allows_own_permissions(): - tree = _tree() - actor = _Actor(False, {"ops.view", "devices.view"}) - rbac.guard_no_amplification(actor, tree, ["devices.view", "ops.view"]) - - -def test_admin_may_grant_anything(): - tree = _tree() - rbac.guard_no_amplification(_Actor(True, set()), tree, ["devices.delete"]) - - -def test_group_editor_cannot_escalate_via_new_group(core_app, make_user): - """Der zentrale Fall: ein Gruppenverwalter darf sich nicht selbst hoeherstufen.""" - from tesm_core.auth.service import load_user - - extension = core(core_app) - row = make_user("gruppenchef", "Ein-gutes-Passwort-1", permissions=("groups.create", "groups.view")) - with core_app.app_context(), extension.database.session() as conn: - actor = load_user(conn, int(row["id"])) - assert actor is not None - with extension.database.transaction(conn): - with pytest.raises(rbac.PermissionDenied): - rbac.create_group( - conn, - name="Hintertuer", - description="", - permissions=["users.create", "administration.view"], - tree=extension.permissions, - actor=actor, - ) - - -def test_last_admin_cannot_be_demoted(core_app, make_user): - from tesm_core.auth.service import load_user - - extension = core(core_app) - admin_row = make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - with core_app.app_context(), extension.database.session() as conn: - actor = load_user(conn, int(admin_row["id"])) - second = conn.execute( - "INSERT INTO users (username, password_hash, is_admin, auth_source, created_at, updated_at) " - "VALUES ('zweiter','x',1,'local',datetime('now'),datetime('now'))" - ) - second_id = int(second.lastrowid or 0) - with extension.database.transaction(conn): - # Solange zwei Admins existieren, ist das Herabstufen erlaubt. - rbac.update_user( - conn, second_id, actor=actor, tree=extension.permissions, is_admin=False - ) - with extension.database.transaction(conn): - with pytest.raises(rbac.PermissionDenied): - rbac.guard_last_admin(conn, int(admin_row["id"]), action="Herabstufen") - - -def test_cannot_change_own_admin_flag(core_app, make_user): - from tesm_core.auth.service import load_user - - extension = core(core_app) - row = make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - with core_app.app_context(), extension.database.session() as conn: - actor = load_user(conn, int(row["id"])) - with extension.database.transaction(conn): - with pytest.raises(rbac.PermissionDenied): - rbac.update_user( - conn, int(row["id"]), actor=actor, tree=extension.permissions, is_admin=False - ) - - -def test_non_admin_cannot_touch_admin_account(core_app, make_user): - from tesm_core.auth.service import load_user - - extension = core(core_app) - admin_row = make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - editor_row = make_user("helfer", "Ein-gutes-Passwort-1", permissions=("users.edit", "users.view")) - with core_app.app_context(), extension.database.session() as conn: - actor = load_user(conn, int(editor_row["id"])) - with extension.database.transaction(conn): - with pytest.raises(rbac.PermissionDenied): - rbac.update_user( - conn, - int(admin_row["id"]), - actor=actor, - tree=extension.permissions, - is_locked=True, - ) - - -def test_system_group_permissions_locked(core_app, make_user): - from tesm_core.auth.service import load_user - - extension = core(core_app) - row = make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - with core_app.app_context(), extension.database.session() as conn: - actor = load_user(conn, int(row["id"])) - default = next(g for g in rbac.list_groups(conn) if g.is_default) - with extension.database.transaction(conn): - with pytest.raises(rbac.PermissionDenied): - rbac.update_group( - conn, - default.id, - permissions=[], - tree=extension.permissions, - actor=actor, - ) - # Mit ausdruecklicher Freischaltung geht es. - rbac.update_group( - conn, - default.id, - permissions=[], - tree=extension.permissions, - actor=actor, - unlock_system=True, - ) - - -def test_presets_are_not_reset_on_restart(core_app): - """Anders als im Vorgaenger bleiben Anpassungen an Systemgruppen erhalten.""" - extension = core(core_app) - with extension.database.session() as conn: - default = next(g for g in rbac.list_groups(conn) if g.is_default) - with extension.database.transaction(conn): - conn.execute("DELETE FROM group_permissions WHERE group_id=?", (default.id,)) - created = rbac.ensure_presets(conn, extension.permissions, extension.role_presets) - assert created == [] - assert rbac.group_permissions(conn, default.id) == [] - - -# --------------------------------------------------------------------------- # -# Datenbank -# --------------------------------------------------------------------------- # - - -def test_second_connection_during_write_transaction_raises(tmp_path): - database = Database(tmp_path / "test.db") - with database.session() as conn: - conn.execute("CREATE TABLE t (a INTEGER)") - with database.transaction(conn): - conn.execute("INSERT INTO t VALUES (1)") - with pytest.raises(NestedConnectionError) as exc: - database.connect() - assert "Schreibtransaktion" in str(exc.value) - - -def test_nested_transactions_commit_once(tmp_path): - database = Database(tmp_path / "test.db") - with database.session() as conn: - conn.execute("CREATE TABLE t (a INTEGER)") - with database.transaction(conn): - conn.execute("INSERT INTO t VALUES (1)") - with database.transaction(conn): - conn.execute("INSERT INTO t VALUES (2)") - assert Database.value(conn, "SELECT COUNT(*) FROM t") == 2 - - -def test_inner_rollback_keeps_outer(tmp_path): - database = Database(tmp_path / "test.db") - with database.session() as conn: - conn.execute("CREATE TABLE t (a INTEGER)") - with database.transaction(conn): - conn.execute("INSERT INTO t VALUES (1)") - try: - with database.transaction(conn): - conn.execute("INSERT INTO t VALUES (2)") - raise RuntimeError("abbruch") - except RuntimeError: - pass - assert Database.value(conn, "SELECT COUNT(*) FROM t") == 1 - - -def test_foreign_keys_enforced(tmp_path): - database = Database(tmp_path / "test.db") - with database.session() as conn: - conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY)") - conn.execute("CREATE TABLE child (p INTEGER REFERENCES parent(id))") - with pytest.raises(sqlite3.IntegrityError): - conn.execute("INSERT INTO child VALUES (99)") - - -def test_wal_mode_active(tmp_path): - database = Database(tmp_path / "test.db") - with database.session() as conn: - assert Database.value(conn, "PRAGMA journal_mode").lower() == "wal" - - -def test_split_statements_handles_comments_and_strings(): - script = """ - -- ein Kommentar mit ; Semikolon - CREATE TABLE t (a TEXT DEFAULT 'x;y'); - INSERT INTO t VALUES ('a;b'); /* Block; Kommentar */ - """ - statements = split_statements(script) - assert len(statements) == 2 - assert "x;y" in statements[0] - assert "a;b" in statements[1] - - -# --------------------------------------------------------------------------- # -# Schluessel und Verschluesselung -# --------------------------------------------------------------------------- # - - -def test_keystore_roundtrip_and_aad_binding(tmp_path): - keystore = Keystore(tmp_path / "data.keys") - token = keystore.encrypt("geheim", aad="credentials.password:1") - assert keystore.decrypt(token, aad="credentials.password:1") == "geheim" - # Dasselbe Chiffrat an anderer Stelle eingespielt -> Fehler. - with pytest.raises(DecryptionError): - keystore.decrypt(token, aad="credentials.password:2") - - -def test_keystore_rotation_keeps_old_values_readable(tmp_path): - keystore = Keystore(tmp_path / "data.keys") - old_token = keystore.encrypt("alt", aad="x") - new_id = keystore.rotate() - assert new_id == "2" - assert keystore.decrypt(old_token, aad="x") == "alt" - assert keystore.needs_reencrypt(old_token) is True - new_token = keystore.encrypt("neu", aad="x") - assert keystore.needs_reencrypt(new_token) is False - - -def test_keystore_file_is_private(tmp_path): - import os - import stat - - path = tmp_path / "data.keys" - Keystore(path) - if os.name == "posix": - mode = path.stat().st_mode - assert not mode & (stat.S_IRGRP | stat.S_IROTH) - - -def test_tampered_ciphertext_rejected(tmp_path): - keystore = Keystore(tmp_path / "data.keys") - token = keystore.encrypt("geheim", aad="x") - prefix, key_id, nonce, ciphertext = token.split(".", 3) - broken = f"{prefix}.{key_id}.{nonce}.{ciphertext[:-4]}AAAA" - with pytest.raises(DecryptionError): - keystore.decrypt(broken, aad="x") - - -# --------------------------------------------------------------------------- # -# Passwoerter und TOTP -# --------------------------------------------------------------------------- # - - -def test_argon2_hash_and_verify(): - stored = passwords.hash_password("Ein-gutes-Passwort-1") - assert stored.startswith("$argon2id$") - assert passwords.verify_password(stored, "Ein-gutes-Passwort-1") is True - assert passwords.verify_password(stored, "falsch") is False - assert passwords.needs_rehash(stored) is False - - -def test_bcrypt_legacy_hash_accepted_and_flagged_for_rehash(): - bcrypt = pytest.importorskip("bcrypt") - legacy = bcrypt.hashpw(b"Altes-Passwort-1", bcrypt.gensalt(rounds=4)).decode() - assert passwords.is_legacy_hash(legacy) is True - assert passwords.verify_password(legacy, "Altes-Passwort-1") is True - assert passwords.needs_rehash(legacy) is True - - -def test_password_normalisation_across_platforms(): - import unicodedata - - nfd = unicodedata.normalize("NFD", "Grüße-aus-München-1") - nfc = unicodedata.normalize("NFC", "Grüße-aus-München-1") - assert nfd != nfc - stored = passwords.hash_password(nfd) - assert passwords.verify_password(stored, nfc) is True - - -def test_password_policy_rules(): - policy = passwords.PasswordPolicy(min_length=12) - assert policy.check("Ein-gutes-Passwort-1") == [] - assert any("Zeichen erforderlich" in p for p in policy.check("kurz")) - assert any("Zeichenarten" in p for p in policy.check("aaaaaaaaaaaaaaa")) - assert any("Benutzernamen" in p for p in policy.check("Tester-Passwort-1", username="tester")) - assert any("haeufig" in p for p in policy.check("passwort1234")) - - -def test_totp_generates_and_verifies(): - secret = totp.generate_secret() - code = totp.current_code(secret) - assert totp.verify_code(secret, code) is True - assert totp.verify_code(secret, "000000") is False - assert totp.verify_code(secret, code, now=0) is False - - -def test_totp_drift_tolerance(): - secret = totp.generate_secret() - now = 1_700_000_000 - previous = totp.code_at(secret, int(now // totp.PERIOD) - 1) - assert totp.verify_code(secret, previous, now=now) is True - far = totp.code_at(secret, int(now // totp.PERIOD) - 5) - assert totp.verify_code(secret, far, now=now) is False - - -def test_totp_uri_contains_issuer_and_secret(): - secret = totp.generate_secret() - uri = totp.provisioning_uri(secret, account="anna", issuer="TESM Test") - assert uri.startswith("otpauth://totp/") - assert secret in uri - assert "issuer=TESM%20Test" in uri - - -# --------------------------------------------------------------------------- # -# Aenderungsprotokoll -# --------------------------------------------------------------------------- # - - -def test_audit_chain_detects_modification(core_app): - extension = core(core_app) - with extension.database.session() as conn: - with extension.database.transaction(conn): - for index in range(5): - audit.log(conn, actor="tester", action="test.event", target=f"ziel-{index}") - assert audit.verify_chain(conn)["ok"] is True - - with extension.database.transaction(conn): - conn.execute("UPDATE audit_log SET detail='manipuliert' WHERE id=3") - result = audit.verify_chain(conn) - assert result["ok"] is False - assert result["broken_at"] == 3 - assert "Hash" in result["reason"] - - -def test_audit_chain_detects_deletion(core_app): - extension = core(core_app) - with extension.database.session() as conn: - with extension.database.transaction(conn): - for index in range(5): - audit.log(conn, actor="tester", action="test.event", target=str(index)) - with extension.database.transaction(conn): - conn.execute("DELETE FROM audit_log WHERE id=3") - result = audit.verify_chain(conn) - assert result["ok"] is False - - -def test_audit_archiving_moves_full_days(core_app, tmp_path): - extension = core(core_app) - old_day = (datetime.now(timezone.utc) - timedelta(days=3)).replace(microsecond=0) - with extension.database.session() as conn: - with extension.database.transaction(conn): - for index in range(40): - audit.log( - conn, - actor="tester", - action="test.old", - target=str(index), - ts=old_day + timedelta(seconds=index), - ) - for index in range(5): - audit.log(conn, actor="tester", action="test.new", target=str(index)) - with extension.database.transaction(conn): - written = audit.archive_old_rows(conn, tmp_path / "archiv", threshold=10, target=5) - assert written - # Uebrig bleiben ausschliesslich Zeilen des laufenden Tages (inkl. der - # Startzeile, die die App beim Migrieren selbst geschrieben hat). - today = datetime.now(timezone.utc).date().isoformat() - remaining = Database.all(conn, "SELECT DISTINCT substr(ts,1,10) AS day FROM audit_log") - assert [row["day"] for row in remaining] == [today] - lines = (tmp_path / "archiv" / written[0].name).read_text(encoding="utf-8").splitlines() - assert len(lines) == 40 - assert json.loads(lines[0])["action"] == "test.old" - - -# --------------------------------------------------------------------------- # -# Sitzungen -# --------------------------------------------------------------------------- # - - -def test_session_id_rotates_on_login(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - client.get("/login") - before = client.get_cookie("tesm_session") - login("anna", "Ein-gutes-Passwort-1") - after = client.get_cookie("tesm_session") - assert after is not None - if before is not None: - assert before.value != after.value - - -def test_session_revocation_takes_effect_immediately(client, core_app, make_user, login): - from tesm_core.security import sessions as session_store - - row = make_user("anna", "Ein-gutes-Passwort-1") - login("anna", "Ein-gutes-Passwort-1") - assert client.get("/konto/").status_code == 200 - - extension = core(core_app) - with extension.database.session() as conn, extension.database.transaction(conn): - session_store.revoke_all_for_user(conn, int(row["id"]), reason="test") - - response = client.get("/konto/") - assert response.status_code == 302 - assert "/login" in response.headers["Location"] - - -def test_password_change_revokes_other_sessions(core_app, make_user): - from tesm_core.security import sessions as session_store - - extension = core(core_app) - row = make_user("anna", "Ein-gutes-Passwort-1") - first = core_app.test_client() - second = core_app.test_client() - for client_instance in (first, second): - page = client_instance.get("/login") - body = page.get_data(as_text=True) - marker = 'name="csrf_token" value="' - start = body.index(marker) + len(marker) - token = body[start : body.index('"', start)] - client_instance.post( - "/login", - data={"username": "anna", "password": "Ein-gutes-Passwort-1", "csrf_token": token}, - follow_redirects=True, - ) - with extension.database.session() as conn: - assert len(session_store.active_for_user(conn, int(row["id"]))) == 2 - - page = first.get("/konto/sicherheit") - body = page.get_data(as_text=True) - marker = 'name="csrf_token" value="' - start = body.index(marker) + len(marker) - token = body[start : body.index('"', start)] - first.post( - "/konto/passwort", - data={ - "current_password": "Ein-gutes-Passwort-1", - "new_password": "Neues-Passwort-2026", - "repeat_password": "Neues-Passwort-2026", - "csrf_token": token, - }, - follow_redirects=True, - ) - assert second.get("/konto/").status_code == 302 - assert first.get("/konto/").status_code == 200 - - -def test_reauth_required_for_sensitive_action(client, make_user, login, csrf_token): - make_user("anna", "Ein-gutes-Passwort-1") - login("anna", "Ein-gutes-Passwort-1") - - # Frisch angemeldet gilt die Bestaetigung -- danach kuenstlich altern lassen. - from flask import session as flask_session - - with client.session_transaction() as sess: - sess["_reauth_at"] = (datetime.now(timezone.utc) - timedelta(hours=2)).isoformat( - timespec="seconds" - ) - token = csrf_token("/konto/sicherheit") - response = client.post("/konto/zwei-faktor/starten", data={"csrf_token": token}) - assert response.status_code == 302 - assert "/bestaetigen" in response.headers["Location"] - assert flask_session is not None - - -# --------------------------------------------------------------------------- # -# Der Rechteweg muss tatsaechlich begehbar sein -# --------------------------------------------------------------------------- # - - -def test_units_do_not_disable_the_privilege_path(): - """``NoNewPrivileges`` schaltet setuid ab -- und damit sudo, und damit den Helfer. - - Auf beiden Testhosts stand "Helfer erreichbar", waehrend in Wahrheit jede - Systemaktion an ``sudo: the "no new privileges" flag is set`` scheiterte: - nginx uebernehmen, Netzwerk aendern, Zertifikat anfordern, Kea schreiben. - Die Haertung hatte genau den Mechanismus abgeschaltet, den sie schuetzen - sollte. - """ - from pathlib import Path - - root = Path(__file__).resolve().parent.parent / "deploy" / "systemd" - units = list(root.glob("*.service")) - assert units - for unit in units: - text = unit.read_text(encoding="utf-8") - assert "NoNewPrivileges=yes" not in text, ( - f"{unit.name}: verhindert sudo und damit jede Systemaktion." - ) - # Gemessen: auch ein eingeschraenktes Bounding Set bricht sudo mit - # "unable to change to root gid" -- inklusive CapabilityBoundingSet=CAP_NET_RAW. - for line in text.splitlines(): - if line.startswith("CapabilityBoundingSet="): - raise AssertionError( - f"{unit.name}: {line!r} nimmt sudo CAP_SETUID/CAP_SETGID." - ) - # Die eigentliche Grenze bleibt bestehen. - # "strict" sperrt auch den Helfer aus /etc aus -- gemessen. - assert "ProtectSystem=full" in text, unit.name - assert "ProtectSystem=strict" not in text, ( - f"{unit.name}: strict sperrt den Helfer aus /etc aus." - ) - assert "PrivateTmp=yes" in text, unit.name - assert "User=root" not in text, unit.name - - -def test_the_helper_is_probed_not_just_looked_for(): - """"Datei liegt da" ist keine Auskunft darueber, ob der Aufruf gelingt.""" - import inspect - - from tesm_core.sysops import SysOps - - assert hasattr(SysOps, "selftest") - source = inspect.getsource(SysOps.selftest) - assert 'self.run("service-status", "nginx")' in source - assert 'self.run("nginx-test")' not in source, ( - "Ein fehlgeschlagenes nginx -t heisst 'Konfiguration kaputt', nicht 'Helfer kaputt'." - ) - # Die beiden Fehlerbilder, die im Betrieb wirklich vorkommen, werden benannt. - assert "no new privileges" in source - assert "a password is required" in source - - -def test_selftest_explains_a_blocked_sudo(monkeypatch): - from tesm_core.sysops import Result, SysOps - - class Blocked(SysOps): - @property - def available(self) -> bool: - return True - - def run(self, verb, *args, stdin="", timeout=None): - return Result( - False, "", 'sudo: The "no new privileges" flag is set, which prevents sudo', 1 - ) - - result = Blocked(app_key="tesm").selftest() - assert not result.ok - assert "NoNewPrivileges" in result.message - assert "systemctl" in result.message - - -def test_selftest_reports_a_missing_sudoers_rule(): - from tesm_core.sysops import Result, SysOps - - class NeedsPassword(SysOps): - @property - def available(self) -> bool: - return True - - def run(self, verb, *args, stdin="", timeout=None): - return Result(False, "", "sudo: a password is required", 1) - - result = NeedsPassword(app_key="tesm").selftest() - assert not result.ok - assert "sudoers" in result.message - - -def test_selftest_says_so_when_the_helper_is_absent(tmp_path): - from tesm_core.sysops import SysOps - - result = SysOps(app_key="tesm", helper=tmp_path / "gibt-es-nicht").selftest() - assert not result.ok - assert "fehlt" in result.message - - -def test_authenticate_keeps_directory_groups(core_app, monkeypatch): - """Die Verzeichnisgruppen duerfen auf dem Weg nach oben nicht verlorengehen. - - ``_verify_credentials`` hat sie korrekt geliefert, ``authenticate`` hat aus - dem Ergebnis aber ein neues Objekt gebaut und das Feld dabei fallen lassen. - Sichtbar war davon nichts: die Anmeldung gelang, nur band sich keine - Freigabe mehr ein. Genau solche stillen Verluste faengt dieser Test. - """ - from tesm_core.auth import directory, service - from tesm_core.extension import core - - profil = directory.DirectoryProfile( - username="mitarbeiter", - dn="CN=mitarbeiter,OU=Benutzer,DC=firma,DC=local", - group_dns=("CN=GG_Technik,OU=Gruppen,DC=firma,DC=local",), - ) - monkeypatch.setattr(directory, "is_enabled", lambda conn: True) - monkeypatch.setattr( - directory, "authenticate", lambda conn, name, pw: directory.DirectoryResult("ok", profile=profil) - ) - - with core_app.test_request_context("/login"): - with core(core_app).database.session() as conn: - outcome = service.authenticate(conn, "mitarbeiter", "egal", ip="127.0.0.1") - - assert outcome.ok, outcome.message - assert outcome.group_dns == profil.group_dns, "die Gruppen sind unterwegs verlorengegangen" diff --git a/tests/test_core_smoke.py b/tests/test_core_smoke.py deleted file mode 100644 index 8f093b9..0000000 --- a/tests/test_core_smoke.py +++ /dev/null @@ -1,246 +0,0 @@ -"""Rauchtest des Grundgeruests: Start, Anmeldung, Rechte, CSRF, Header.""" - -from __future__ import annotations - -import re - - -def test_app_starts_and_health_ok(client): - response = client.get("/gesundheit") - assert response.status_code == 200 - assert response.get_json()["status"] == "ok" - - -def test_login_page_renders_and_sets_csrf(client): - response = client.get("/login") - assert response.status_code == 200 - body = response.get_data(as_text=True) - assert "Anmelden" in body - assert 'name="csrf_token"' in body - - -def test_security_headers_present(client): - response = client.get("/login") - csp = response.headers["Content-Security-Policy"] - assert "default-src 'self'" in csp - assert "'unsafe-inline'" not in csp.split("script-src")[1].split(";")[0] - assert re.search(r"nonce-[A-Za-z0-9_-]{10,}", csp) - assert response.headers["X-Frame-Options"] == "DENY" - assert response.headers["X-Content-Type-Options"] == "nosniff" - assert "camera=()" in response.headers["Permissions-Policy"] - - -def test_login_and_logout(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - response = login("anna", "Ein-gutes-Passwort-1") - assert response.status_code == 200 - assert "Willkommen" in response.get_data(as_text=True) - - page = client.get("/konto/") - assert page.status_code == 200 - assert "anna" in page.get_data(as_text=True) - - -def test_wrong_password_is_generic(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - response = login("anna", "falsch") - body = response.get_data(as_text=True) - assert "Benutzername oder Passwort ist falsch" in body - # Kein Hinweis darauf, dass das Konto existiert. - assert "gesperrt" not in body - - -def test_unknown_user_same_message(client, login): - body = login("gibtsnicht", "irgendwas").get_data(as_text=True) - assert "Benutzername oder Passwort ist falsch" in body - - -def test_locked_account_rejected(client, make_user, login): - make_user("gesperrt", "Ein-gutes-Passwort-1", is_locked=True) - body = login("gesperrt", "Ein-gutes-Passwort-1").get_data(as_text=True) - assert "gesperrt" in body - - -def test_rate_limit_blocks_after_many_attempts(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - messages = [login("anna", "falsch").get_data(as_text=True) for _ in range(9)] - assert any("voruebergehend gesperrt" in body for body in messages) - - -def test_protected_page_redirects_to_login(client): - response = client.get("/widgets") - assert response.status_code == 302 - assert "/login" in response.headers["Location"] - - -def test_permission_required_blocks_without_grant(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - login("anna", "Ein-gutes-Passwort-1") - assert client.get("/verwaltung/benutzer").status_code == 403 - - -def test_permission_granted_allows_access(client, make_user, login): - make_user("bob", "Ein-gutes-Passwort-1", permissions=("users.view",)) - login("bob", "Ein-gutes-Passwort-1") - response = client.get("/verwaltung/benutzer") - assert response.status_code == 200 - assert "Benutzer" in response.get_data(as_text=True) - - -def test_admin_sees_everything(client, make_user, login): - make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - login("root", "Ein-gutes-Passwort-1") - for path in ("/verwaltung/benutzer", "/verwaltung/gruppen", "/papierkorb/", "/diagnose/"): - assert client.get(path).status_code == 200, path - - -def test_csrf_missing_token_is_rejected(client, make_user, login): - make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - login("root", "Ein-gutes-Passwort-1") - response = client.post("/verwaltung/benutzer/neu", data={"username": "boese"}) - assert response.status_code == 403 - assert "Sicherheitstoken" in response.get_data(as_text=True) - - -def test_csrf_wrong_origin_is_rejected(client, make_user, login, csrf_token): - make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - login("root", "Ein-gutes-Passwort-1") - token = csrf_token("/verwaltung/benutzer") - response = client.post( - "/verwaltung/benutzer/neu", - data={"username": "boese", "csrf_token": token}, - headers={"Origin": "https://angreifer.example"}, - ) - assert response.status_code == 403 - - -def test_nav_hides_pages_without_permission(client, make_user, login): - make_user("anna", "Ein-gutes-Passwort-1") - login("anna", "Ein-gutes-Passwort-1") - body = client.get("/").get_data(as_text=True) - assert "Diagnose" not in body - assert "Widgets" not in body - - -def test_nav_shows_pages_with_permission(client, make_user, login): - make_user("bob", "Ein-gutes-Passwort-1", permissions=("widgets.view",)) - login("bob", "Ein-gutes-Passwort-1") - body = client.get("/").get_data(as_text=True) - assert "Widgets" in body - - -def test_default_group_created_from_preset(core_app): - from tesm_core.extension import core - from tesm_core.rbac import service as rbac - - extension = core(core_app) - with extension.database.session() as conn: - groups = rbac.list_groups(conn) - names = {group.name for group in groups} - assert "Benutzer" in names - default = next(group for group in groups if group.name == "Benutzer") - assert default.is_default is True - assert "widgets.view" in default.permissions - assert "demo.view" in default.permissions # Bereichsschalter wird mitgesetzt - - -def test_error_page_shows_request_id(client): - response = client.get("/gibt-es-nicht") - assert response.status_code == 404 - body = response.get_data(as_text=True) - assert "404" in body - assert "Vorgangsnummer" in body - - -# --------------------------------------------------------------------------- # -# Verzeichnisgruppen: suchen statt abtippen -# --------------------------------------------------------------------------- # - - -def test_group_search_is_open_to_fileshare_editors(): - """Wer Freigaben zuordnet, muss die Gruppen finden koennen. - - Sonst braeuchte er zusaetzlich das Recht, den Verzeichnisdienst zu - konfigurieren -- eine Rechteausweitung fuer eine reine Lesesuche. - """ - import inspect - - from tesm_core.views import hostsettings - - source = inspect.getsource(hostsettings.directory_group_search) - assert "fileshare.edit" in source - assert 'mode="any"' in source or "mode='any'" in source - - -def test_the_picker_never_forces_typing_a_dn(): - """Ein abgetippter DN ist ein Tippfehler, der erst bei der Anmeldung auffaellt.""" - from pathlib import Path - - root = Path(__file__).resolve().parent.parent - picker = ( - root / "packages" / "tesm-core" / "src" / "tesm_core" / "templates" - / "tesm_core" / "_group_picker.html" - ).read_text(encoding="utf-8") - assert "directory_group_search" in picker - assert "data-group-query" in picker - # Tippen bleibt trotzdem moeglich: ohne Dienstkonto gibt es keine Suche. - assert 'name="{{ dn_field }}"' in picker - - for template in ( - root / "packages" / "tesm-core" / "src" / "tesm_core" / "templates" - / "tesm_core" / "settings_directory.html", - root / "apps" / "tesm" / "src" / "tesm" / "templates" / "tesm" / "fileshare_mappings.html", - ): - text = template.read_text(encoding="utf-8") - assert "group_picker(" in text, template.name - - -def test_transport_choice_carries_its_port(): - """Umgestellter Transport mit altem Port ergibt einen Fehler ohne Aussage.""" - from pathlib import Path - - root = Path(__file__).resolve().parent.parent - page = ( - root / "packages" / "tesm-core" / "src" / "tesm_core" / "templates" - / "tesm_core" / "settings_directory.html" - ).read_text(encoding="utf-8") - assert 'data-behavior="transport-port"' in page - assert 'data-port-target="#ldap-port"' in page - - script = ( - root / "packages" / "tesm-core" / "src" / "tesm_core" / "static" - / "tesm_core" / "js" / "app.js" - ).read_text(encoding="utf-8") - assert "transport-port" in script - assert 'ldaps: "636"' in script - assert 'starttls: "389"' in script - # Ein absichtlich abweichender Port darf nicht ueberschrieben werden. - assert "known.includes(port.value)" in script - - -def test_permission_matrix_has_no_nameless_checkbox(client, make_user, login): - """In der Rechtematrix darf kein Kaestchen ohne ``name`` stehen. - - Fuer nicht vorgesehene Aktionen stand dort ein deaktiviertes Kaestchen. - Das JS hat beim Einschalten des Bereichs *alle* Kaestchen der Zeile - freigegeben -- auch dieses. Es liess sich anhaken, uebertrug wegen des - fehlenden ``name`` aber nichts: es sah erteilt aus und war nach dem - Speichern verschwunden. Ohne Eingabefeld kann das nicht wieder passieren. - """ - import re - - make_user("root", "Ein-gutes-Passwort-1", is_admin=True) - login("root", "Ein-gutes-Passwort-1") - body = client.get("/verwaltung/gruppen").get_data(as_text=True) - if "permission-matrix" not in body: - gruppe = re.search(r'href="(/verwaltung/gruppen/\d+)"', body) - assert gruppe, "keine Gruppe zum Pruefen gefunden" - body = client.get(gruppe.group(1)).get_data(as_text=True) - - assert "permission-matrix" in body, "Rechtematrix nicht gefunden" - kaestchen = re.findall(r"]*type=\"checkbox\"[^>]*>", body) - assert kaestchen, "keine Kaestchen in der Matrix" - ohne_namen = [k for k in kaestchen if "name=" not in k] - assert not ohne_namen, f"Kaestchen ohne name: {ohne_namen[:3]}" - # Der Platzhalter ist jetzt reine Darstellung. - assert "matrix__na" in body diff --git a/tests/test_license_server.py b/tests/test_license_server.py deleted file mode 100644 index e5bb0e2..0000000 --- a/tests/test_license_server.py +++ /dev/null @@ -1,703 +0,0 @@ -"""Tests des Lizenzservers: Ausstellung, Aktivierung, Portal, Rollen. - -Deckt die Regeln ab, die der Vorgaenger schmerzhaft gelernt hat -- und die -Haertungen, die hier neu sind (Replay, Nonce, Rollen-Sperre, Portal-TTL). -""" - -from __future__ import annotations - -import json -from typing import Any - -import pytest - -import tesm_licensing as lic - - -@pytest.fixture() -def ls_app(instance_env: Any): - from tesm_license import create_app - - return create_app(TESTING=True) - - -@pytest.fixture() -def ls_client(ls_app: Any): - return ls_app.test_client() - - -@pytest.fixture() -def ls_admin(ls_app: Any): - from tesm_core.extension import core - from tesm_core.security import passwords - - extension = core(ls_app) - with extension.database.session() as conn, extension.database.transaction(conn): - conn.execute( - "INSERT INTO users (username, password_hash, is_admin, auth_source, created_at, " - "updated_at) VALUES ('root',?,1,'local',datetime('now'),datetime('now'))", - (passwords.hash_password("Ein-gutes-Passwort-1"),), - ) - return {"username": "root", "password": "Ein-gutes-Passwort-1"} - - -def _token(client: Any, path: str = "/login") -> str: - body = client.get(path).get_data(as_text=True) - for marker in ('name="csrf_token" value="', 'data-csrf="'): - if marker in body: - start = body.index(marker) + len(marker) - return body[start : body.index('"', start)] - raise AssertionError(f"Kein CSRF-Token in {path}") - - -@pytest.fixture() -def ls_login(ls_client: Any, ls_admin: dict[str, str]): - def do() -> Any: - return ls_client.post( - "/login", - data={ - "username": ls_admin["username"], - "password": ls_admin["password"], - "csrf_token": _token(ls_client), - }, - follow_redirects=True, - ) - - return do - - -@pytest.fixture() -def bootstrapped(ls_app: Any, ls_client: Any, ls_login: Any): - """Angemeldeter Admin mit Signaturschluessel und Vendor-Angaben.""" - from tesm_core.extension import core - - ls_login() - extension = core(ls_app) - with ls_app.app_context(), extension.database.session() as conn: - with extension.database.transaction(conn): - extension.settings.set(conn, "vendor_name", "WiS GmbH", actor="test") - extension.settings.set( - conn, "master_endpoint", "https://lizenz.example.invalid", actor="test" - ) - return ls_client - - -def _create_customer(client: Any, name: str = "Beispiel AG") -> int: - token = _token(client, "/kunden/") - client.post( - "/kunden/neu", - data={ - "csrf_token": token, - "name": name, - "contact_email": "it@example.invalid", - "contact_person": "Frau Muster", - }, - follow_redirects=True, - ) - return 1 - - -def _create_ticket(client: Any, customer_id: int = 1, **overrides: Any) -> str: - token = _token(client, "/tickets/neu") - data = { - "csrf_token": token, - "customer_id": str(customer_id), - "type": "standard", - "valid_days": "365", - } - data.update(overrides) - response = client.post("/tickets/neu", data=data, follow_redirects=False) - assert response.status_code == 302, response.get_data(as_text=True)[:400] - return response.headers["Location"].rstrip("/").split("/")[-1] - - -# --------------------------------------------------------------------------- # -# Start -# --------------------------------------------------------------------------- # - - -def test_app_boots(ls_app): - from tesm_core.extension import core - - components = {row["component"] for row in core(ls_app).migration_status()} - assert components == {"core", "tesm_license"} - - -def test_dashboard_requires_login(ls_client): - """Anders als TESMs Geraeteboard ist diese Startseite intern.""" - response = ls_client.get("/") - assert response.status_code == 302 - assert "/login" in response.headers["Location"] - - -LS_PAGES = [ - "/", - "/kunden/", - "/tickets/", - "/tickets/neu", - "/lizenzen/", - "/lizenzen/offline-code", - "/lizenzen/sub-lizenzserver", - "/einstellungen/system", - "/einstellungen/anbieter", - "/einstellungen/email", - "/einstellungen/netzwerk", - "/einstellungen/webserver", - "/einstellungen/verzeichnisdienst", - "/eigene-lizenz/", - "/eigene-lizenz/signaturschluessel", - "/sicherung/", - "/papierkorb/", - "/diagnose/", - "/protokolle/laufend", - "/protokolle/aenderungen", - "/verwaltung/benutzer", - "/verwaltung/gruppen", -] - - -@pytest.mark.parametrize("path", LS_PAGES) -def test_pages_render(bootstrapped, path): - response = bootstrapped.get(path) - assert response.status_code == 200, f"{path} -> {response.status_code}" - assert "Interner Fehler" not in response.get_data(as_text=True) - - -def test_portal_pages_public(ls_client): - assert ls_client.get("/portal/").status_code == 200 - assert ls_client.get("/api/v1/licenses/hinweis").get_json()["service"] == "tesm-license" - - -# --------------------------------------------------------------------------- # -# Kunde, Ticket, Lizenz -# --------------------------------------------------------------------------- # - - -def test_customer_ticket_license_flow(bootstrapped, ls_app): - from tesm_core.extension import core - - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client, type="enterprise") - - detail = client.get(f"/tickets/{ticket_id}").get_data(as_text=True) - assert "Lizenz ausstellen" in detail - - response = client.post( - f"/lizenzen/aus-ticket/{ticket_id}", - data={"csrf_token": _token(client, f"/tickets/{ticket_id}")}, - follow_redirects=True, - ) - assert "ausgestellt" in response.get_data(as_text=True) - - extension = core(ls_app) - with extension.database.session() as conn: - rows = issuance.list_licenses(conn) - assert len(rows) == 1 - document = json.loads(str(rows[0]["license_document"])) - lic.verify_license(document) - assert set(document["modules"]) == set(lic.ALL_MODULES) - assert document["role"] == "customer" - assert rows[0]["client_key_encrypted"] - assert "client_key" not in json.dumps(document) - - -def test_only_one_open_license_per_ticket(bootstrapped, ls_app): - from tesm_core.extension import core - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client) - token = _token(client, f"/tickets/{ticket_id}") - client.post(f"/lizenzen/aus-ticket/{ticket_id}", data={"csrf_token": token}, follow_redirects=True) - second = client.post( - f"/lizenzen/aus-ticket/{ticket_id}", data={"csrf_token": token}, follow_redirects=True - ) - assert "bereits eine Lizenz offen" in second.get_data(as_text=True) - - extension = core(ls_app) - with extension.database.session() as conn: - assert len(issuance.list_licenses(conn)) == 1 - - -def test_trial_override_does_not_change_ticket(bootstrapped, ls_app): - from tesm_core.extension import core - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client, type="standard", valid_days="730") - client.post( - f"/lizenzen/aus-ticket/{ticket_id}", - data={"csrf_token": _token(client, f"/tickets/{ticket_id}"), "as_trial": "1"}, - follow_redirects=True, - ) - extension = core(ls_app) - with extension.database.session() as conn: - ticket = issuance.get_ticket(conn, ticket_id) - row = issuance.list_licenses(conn)[0] - assert ticket["type"] == "standard" - assert ticket["valid_days"] == 730 - assert row["type"] == "trial" - assert set(json.loads(str(row["modules"]))) == set(lic.ALL_MODULES) - - -def test_trial_lifetime_rejected(bootstrapped): - client = bootstrapped - _create_customer(client) - response = client.post( - "/tickets/neu", - data={ - "csrf_token": _token(client, "/tickets/neu"), - "customer_id": "1", - "type": "trial", - "valid_days": "30", - "lifetime": "1", - }, - follow_redirects=True, - ) - assert "nicht unbefristet" in response.get_data(as_text=True) - - -def test_ticket_delete_blocked_with_open_license(bootstrapped): - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client) - token = _token(client, f"/tickets/{ticket_id}") - client.post(f"/lizenzen/aus-ticket/{ticket_id}", data={"csrf_token": token}, follow_redirects=True) - response = client.post( - f"/tickets/{ticket_id}/loeschen", data={"csrf_token": token}, follow_redirects=True - ) - assert "offene Lizenz" in response.get_data(as_text=True) - - -def test_customer_delete_blocked_with_tickets(bootstrapped): - client = bootstrapped - _create_customer(client) - _create_ticket(client) - response = client.post( - "/kunden/1/loeschen", - data={"csrf_token": _token(client, "/kunden/1")}, - follow_redirects=True, - ) - assert "Ticket" in response.get_data(as_text=True) - - -def test_blocked_ticket_prevents_issuing(bootstrapped): - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client) - token = _token(client, f"/tickets/{ticket_id}") - client.post(f"/tickets/{ticket_id}/sperre", data={"csrf_token": token}, follow_redirects=True) - response = client.post( - f"/lizenzen/aus-ticket/{ticket_id}", data={"csrf_token": token}, follow_redirects=True - ) - assert "gesperrt" in response.get_data(as_text=True) - - -# --------------------------------------------------------------------------- # -# Rollen-Sperre -# --------------------------------------------------------------------------- # - - -def test_license_server_role_downgraded_without_master_license(bootstrapped, ls_app): - """Ohne eigene Master-Lizenz wird die Rolle still auf 'customer' gesetzt.""" - from tesm_core.extension import core - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client, role="license_server") - extension = core(ls_app) - with extension.database.session() as conn: - ticket = issuance.get_ticket(conn, ticket_id) - assert ticket["role"] == "customer" - - -def test_ticket_new_hides_license_server_option(bootstrapped): - body = bootstrapped.get("/tickets/neu").get_data(as_text=True) - assert "Lizenzserver-Lizenz" not in body - - -# --------------------------------------------------------------------------- # -# Aktivierungsprotokoll -# --------------------------------------------------------------------------- # - - -@pytest.fixture() -def issued_license(bootstrapped, ls_app): - from tesm_core.extension import core - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client, type="enterprise") - client.post( - f"/lizenzen/aus-ticket/{ticket_id}", - data={"csrf_token": _token(client, f"/tickets/{ticket_id}")}, - follow_redirects=True, - ) - extension = core(ls_app) - with ls_app.app_context(), extension.database.session() as conn: - row = issuance.list_licenses(conn)[0] - bundle = issuance.delivery_bundle( - conn, str(row["license_id"]), keystore=extension.keystore - ) - document, client_key = lic.split_bundle(bundle) - return {"document": document, "client_key": client_key, "row": row} - - -def test_activation_round_trip_over_api(ls_client, issued_license): - document = issued_license["document"] - request = lic.build_client_request( - "activate", document, issued_license["client_key"], hostname="kunde-01" - ) - response = ls_client.post("/api/v1/licenses/activate", json=request) - assert response.status_code == 200, response.get_json() - payload = response.get_json() - lic.verify_master_response( - payload, - document["master_pubkey"], - expected_license_id=document["license_id"], - expected_fingerprint=request["fingerprint"], - expected_action="activate", - expected_nonce=request["nonce"], - ) - assert payload["status"] == "ok" - - -def test_replayed_activation_is_rejected(ls_client, issued_license): - document = issued_license["document"] - request = lic.build_client_request("activate", document, issued_license["client_key"]) - assert ls_client.post("/api/v1/licenses/activate", json=request).status_code == 200 - replay = ls_client.post("/api/v1/licenses/activate", json=request) - assert replay.status_code == 409 - assert replay.get_json()["error"] == "replay" - - -def test_forged_signature_rejected(ls_client, issued_license): - document = issued_license["document"] - other_private, _ = lic.generate_keypair() - request = lic.build_client_request("activate", document, other_private) - response = ls_client.post("/api/v1/licenses/activate", json=request) - assert response.status_code == 400 - assert response.get_json()["error"] == "invalid_signature" - - -def test_activation_bound_to_fingerprint(ls_client, ls_app, issued_license): - from tesm_core.extension import core - - document = issued_license["document"] - request = lic.build_client_request("activate", document, issued_license["client_key"]) - ls_client.post("/api/v1/licenses/activate", json=request) - - # Zweite Maschine: anderer Fingerprint, gueltige Signatur. - second = lic.build_client_request("activate", document, issued_license["client_key"]) - second["fingerprint"] = "b" * 64 - second["signature"] = lic.sign( - {k: v for k, v in second.items() if k != "signature"}, - issued_license["client_key"], - lic.SIG_CONTEXT_REQUEST, - ) - response = ls_client.post("/api/v1/licenses/activate", json=second) - assert response.status_code == 409 - assert response.get_json()["error"] == "already_bound_elsewhere" - assert core(ls_app) is not None - - -def test_heartbeat_requires_active_license(ls_client, issued_license): - document = issued_license["document"] - request = lic.build_client_request("heartbeat", document, issued_license["client_key"]) - response = ls_client.post("/api/v1/licenses/heartbeat", json=request) - assert response.status_code == 409 - assert response.get_json()["error"] == "not_active" - - -def test_heartbeat_after_activation_delivers_update(ls_client, ls_app, issued_license): - from tesm_core.extension import core - from tesm_license.services import issuance - from tesm_license.services import master_key - - document = issued_license["document"] - activate = lic.build_client_request("activate", document, issued_license["client_key"]) - ls_client.post("/api/v1/licenses/activate", json=activate) - - # Der Anbieter erweitert die Lizenz nachtraeglich. - extension = core(ls_app) - with ls_app.app_context(), extension.database.session() as conn: - key = master_key.ensure(extension.registry["master_key_path"]) - with extension.database.transaction(conn): - issuance.resign_license( - conn, - document["license_id"], - master_private_key=key.private_key, - changes={"modules": ["dhcp"]}, - ) - - beat = lic.build_client_request("heartbeat", document, issued_license["client_key"]) - response = ls_client.post("/api/v1/licenses/heartbeat", json=beat) - assert response.status_code == 200 - payload = response.get_json() - assert payload["status"] == "ok" - assert payload["license_update"]["seq"] == document["seq"] + 1 - lic.verify_license(payload["license_update"]) - - -def test_revoked_license_reported_signed(ls_client, ls_app, issued_license): - from tesm_core.extension import core - from tesm_license.services import issuance - - document = issued_license["document"] - activate = lic.build_client_request("activate", document, issued_license["client_key"]) - ls_client.post("/api/v1/licenses/activate", json=activate) - - extension = core(ls_app) - with ls_app.app_context(), extension.database.session() as conn: - with extension.database.transaction(conn): - issuance.revoke_license(conn, document["license_id"], reason="Test") - - beat = lic.build_client_request("heartbeat", document, issued_license["client_key"]) - response = ls_client.post("/api/v1/licenses/heartbeat", json=beat) - assert response.status_code == 200 - payload = response.get_json() - assert payload["status"] == "revoked" - # Die Antwort ist signiert -- der Client loescht erst nach eigener Pruefung. - lic.verify_master_response( - payload, - document["master_pubkey"], - expected_license_id=document["license_id"], - expected_fingerprint=beat["fingerprint"], - expected_action="heartbeat", - expected_nonce=beat["nonce"], - ) - - -def test_offline_code_path_matches_api(bootstrapped, issued_license): - """Der Offline-Weg laeuft durch dieselbe Verarbeitung wie die API.""" - document = issued_license["document"] - request = lic.build_client_request("activate", document, issued_license["client_key"]) - code = lic.encode_code(request) - - response = bootstrapped.post( - "/lizenzen/offline-code", - data={"csrf_token": _token(bootstrapped, "/lizenzen/offline-code"), "code": code}, - follow_redirects=True, - ) - body = response.get_data(as_text=True) - assert "Antwortcode" in body - start = body.index('id="response-code">') + len('id="response-code">') - answer = body[start : body.index("", start)].strip() - decoded = lic.decode_code(answer) - lic.verify_master_response( - decoded, - document["master_pubkey"], - expected_license_id=document["license_id"], - expected_fingerprint=request["fingerprint"], - expected_action="activate", - expected_nonce=request["nonce"], - ) - assert decoded["status"] == "ok" - - -def test_unknown_license_rejected(ls_client, issued_license): - document = dict(issued_license["document"]) - document["license_id"] = "gibt-es-nicht" - request = lic.build_client_request("activate", document, issued_license["client_key"]) - response = ls_client.post("/api/v1/licenses/activate", json=request) - assert response.status_code == 404 - - -# --------------------------------------------------------------------------- # -# Self-Service und Portal -# --------------------------------------------------------------------------- # - - -def test_self_service_page_and_issue(bootstrapped, ls_app): - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client) - - fresh = ls_app.test_client() # ohne Anmeldung - page = fresh.get(f"/lizenz/{ticket_id}") - assert page.status_code == 200 - assert "Lizenz erstellen" in page.get_data(as_text=True) - - token = _token(fresh, f"/lizenz/{ticket_id}") - created = fresh.post( - f"/lizenz/{ticket_id}/ausstellen", data={"csrf_token": token}, follow_redirects=True - ) - assert "Lizenz wurde erstellt" in created.get_data(as_text=True) - - -def test_self_service_404_for_license_server_ticket(bootstrapped, ls_app): - from tesm_core.extension import core - from tesm_license.services import issuance - - client = bootstrapped - _create_customer(client) - ticket_id = _create_ticket(client) - extension = core(ls_app) - with extension.database.session() as conn, extension.database.transaction(conn): - conn.execute( - "UPDATE license_tickets SET role='license_server' WHERE id=?", (ticket_id,) - ) - fresh = ls_app.test_client() - assert fresh.get(f"/lizenz/{ticket_id}").status_code == 404 - - -def test_portal_answer_is_identical_for_unknown_email(ls_client): - token = _token(ls_client, "/portal/") - known = ls_client.post( - "/portal/", data={"csrf_token": token, "email": "gibt@es.nicht"}, follow_redirects=True - ).get_data(as_text=True) - assert "Falls diese E-Mail-Adresse bei uns hinterlegt ist" in known - - -def test_portal_token_is_single_use(bootstrapped, ls_app): - from tesm_core.extension import core - - client = bootstrapped - _create_customer(client) - - # Token direkt anlegen (Mailversand ist im Test nicht konfiguriert). - extension = core(ls_app) - with extension.database.session() as conn, extension.database.transaction(conn): - conn.execute( - "INSERT INTO portal_access_tokens (token, customer_id, created_at, expires_at) " - "VALUES ('testtoken', 1, datetime('now'), datetime('now', '+30 minutes'))" - ) - - fresh = ls_app.test_client() - first = fresh.get("/portal/zugang/testtoken", follow_redirects=True) - assert "Ihre Lizenzen" in first.get_data(as_text=True) - - second = ls_app.test_client() - again = second.get("/portal/zugang/testtoken", follow_redirects=True) - assert "ungueltig oder abgelaufen" in again.get_data(as_text=True) - - -def test_portal_expired_token_rejected(bootstrapped, ls_app): - from tesm_core.extension import core - - _create_customer(bootstrapped) - extension = core(ls_app) - with extension.database.session() as conn, extension.database.transaction(conn): - conn.execute( - "INSERT INTO portal_access_tokens (token, customer_id, created_at, expires_at) " - "VALUES ('alt', 1, datetime('now','-2 hours'), datetime('now','-1 hours'))" - ) - fresh = ls_app.test_client() - response = fresh.get("/portal/zugang/alt", follow_redirects=True) - assert "ungueltig oder abgelaufen" in response.get_data(as_text=True) - - -# --------------------------------------------------------------------------- # -# Ampel -# --------------------------------------------------------------------------- # - - -@pytest.mark.parametrize( - "days,expected", - [(120, "green"), (91, "green"), (90, "orange"), (31, "orange"), (29, "red"), (-1, "red")], -) -def test_traffic_light_boundaries(days, expected): - from datetime import timedelta - - from tesm_license.services.issuance import display_state - - now = lic.utcnow() - row = {"expires_at": lic.iso(now + timedelta(days=days, hours=1)), "lifetime": 0} - assert display_state(row, now=now)["color"] == expected - - -def test_lifetime_is_always_green(): - from tesm_license.services.issuance import display_state - - state = display_state({"expires_at": "2000-01-01T00:00:00Z", "lifetime": 1}) - assert state["color"] == "green" - assert state["is_expired"] is False - - -def test_unparsable_date_fails_safe(): - from tesm_license.services.issuance import display_state - - assert display_state({"expires_at": "kaputt", "lifetime": 0})["color"] == "red" - - -# --------------------------------------------------------------------------- # -# Endpunkt: ohne ihn keine Lizenz -# --------------------------------------------------------------------------- # - - -def test_issuing_without_an_endpoint_is_refused(): - """Der Endpunkt wird mitsigniert -- fehlt er, ist die Lizenz dauerhaft offline. - - Nachtraeglich laesst er sich nicht eintragen: er steht in der signierten - Nutzlast. Eine Warnung waere zu spaet, denn der Schaden entsteht in dem - Moment, in dem die Datei den Server verlaesst. - """ - import pytest as _pytest - - from tesm_license.services import issuance - - assert "master_endpoint" in issuance.issue_from_ticket.__code__.co_varnames - source = __import__("inspect").getsource(issuance.issue_from_ticket) - assert 'if not str(master_endpoint or "").strip():' in source - assert "IssuanceError" in source - assert _pytest is not None - - -def test_offline_activation_has_a_navigation_entry(): - """Ohne Eintrag findet niemand die Seite -- der Offline-Weg waere unbenutzbar.""" - from tesm_license.navigation import NAVIGATION - - endpoints = set() - - def walk(items): - for item in items: - if item.endpoint: - endpoints.add(item.endpoint) - if item.children: - walk(item.children) - - walk(NAVIGATION.items) - assert "licenses.manual_code" in endpoints - - -def test_revision_pending_survives_a_partial_row(): - """Die Funktion sitzt in Listen, Detailseiten und im Portal. - - Nicht jede Abfrage waehlt jede Spalte. Eine fehlende darf keine Ausnahme - werfen -- sonst faellt eine ganze Seite wegen einer Nebensaechlichkeit aus. - """ - from tesm_license.services.issuance import revision_pending - - assert revision_pending({"revision_at": "2026-01-02", "status": "active"}) is True - assert revision_pending({"revision_at": None}) is False - assert revision_pending({}) is False - assert revision_pending({"revision_at": "2026-01-02", "status": "revoked"}) is False - # Abgeholt: der Kontakt ist juenger als die Revision. - assert ( - revision_pending( - { - "revision_at": "2026-01-02T00:00:00+00:00", - "status": "active", - "last_heartbeat_at": "2026-01-03T00:00:00+00:00", - } - ) - is False - ) - # Noch nicht abgeholt. - assert ( - revision_pending( - { - "revision_at": "2026-01-04T00:00:00+00:00", - "status": "active", - "last_heartbeat_at": "2026-01-03T00:00:00+00:00", - } - ) - is True - ) diff --git a/tests/test_licensing.py b/tests/test_licensing.py deleted file mode 100644 index a945dc8..0000000 --- a/tests/test_licensing.py +++ /dev/null @@ -1,408 +0,0 @@ -"""Phase-0-Tests: das Lizenzprotokoll isoliert, ohne Flask, ohne Datenbank. - -Deckt bewusst auch die Angriffsfaelle ab, die im Vorgaengerprojekt nicht -abgesichert waren: Signatur-Wiederverwendung ueber Domaenengrenzen, Replay -alter Anfragen/Antworten, Downgrade eines nachgelieferten Lizenz-Updates und -Ausstellung durch einen nicht gepinnten Aussteller. -""" - -from __future__ import annotations - -import copy -from datetime import timedelta - -import pytest - -import tesm_licensing as lic - - -VENDOR = { - "name": "WiS GmbH", - "phone": "+49 000 000", - "email": "lizenzen@example.invalid", - "address": "Musterweg 1, 12345 Musterstadt", - "logo_base64": "", -} -CUSTOMER = {"name": "Beispiel AG", "contact_email": "it@example.invalid", "customer_ref": "K-1"} - - -@pytest.fixture() -def master_keys() -> tuple[str, str]: - return lic.generate_keypair() - - -def issue(master_keys, **overrides): - priv, pub = master_keys - params = dict( - customer=CUSTOMER, - license_type="standard", - modules=[], - valid_days=365, - master_private_key_b64=priv, - master_public_key_b64=pub, - master_endpoint="https://lizenz.example.invalid/", - vendor=VENDOR, - ) - params.update(overrides) - return lic.issue_license(**params) - - -# --------------------------------------------------------------------------- # -# Lizenzdatei -# --------------------------------------------------------------------------- # - - -def test_issued_license_verifies_and_splits(master_keys): - _, pub = master_keys - bundle, client_pub = issue(master_keys, modules=["dhcp", "maintenance"]) - document, client_priv = lic.split_bundle(bundle) - - lic.verify_license(document, trusted_master_keys=[pub]) - assert document["license_pubkey"] == client_pub - assert lic.public_from_private(client_priv) == client_pub - # Der private Schluessel darf nicht im Lizenzdokument stehen. - assert "client_key" not in document - assert "license_privkey" not in document - - status = lic.license_status(document, issuer_pinned=True) - assert status.modules == {"dhcp", "maintenance"} - assert status.modules_active is True - assert status.expired is False - assert status.days_left == 365 - assert status.warnings == () - - -def test_tampered_field_breaks_signature(master_keys): - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - document["modules"] = ["dhcp", "fileshare", "maintenance"] - with pytest.raises(lic.SignatureInvalid): - lic.verify_license(document) - - -def test_swapped_master_key_is_detected(master_keys): - """Ein Angreifer, der Payload *und* master_pubkey ersetzt, faellt am Pinning auf.""" - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - - evil_priv, evil_pub = lic.generate_keypair() - forged = lic.resign_license( - document, master_private_key_b64=evil_priv, changes={"modules": list(lic.ALL_MODULES)} - ) - forged["master_pubkey"] = evil_pub - forged["master_key_id"] = lic.key_id(evil_pub) - forged["signature"] = lic.sign( - {k: v for k, v in forged.items() if k != "signature"}, - evil_priv, - lic.SIG_CONTEXT_LICENSE, - ) - - # In sich konsistent -- genau das war die Schwaeche von V1. - lic.verify_license(forged) - # Mit gepinntem Aussteller fliegt es auf. - _, real_pub = master_keys - with pytest.raises(lic.UntrustedIssuer): - lic.verify_license(forged, trusted_master_keys=[real_pub]) - - -def test_key_id_mismatch_rejected(master_keys): - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - document["master_key_id"] = "0" * 16 - with pytest.raises(lic.InvalidLicenseFile): - lic.verify_license(document) - - -def test_license_signature_not_reusable_as_response(master_keys): - """Domaenentrennung: Lizenz-Signatur darf nicht als Antwort-Signatur gelten.""" - _, pub = master_keys - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - payload = {k: v for k, v in document.items() if k != "signature"} - assert lic.verify(payload, document["signature"], pub, lic.SIG_CONTEXT_LICENSE) is True - assert lic.verify(payload, document["signature"], pub, lic.SIG_CONTEXT_RESPONSE) is False - assert lic.verify(payload, document["signature"], pub, lic.SIG_CONTEXT_REQUEST) is False - - -def test_lifetime_license(master_keys): - bundle, _ = issue(master_keys, license_type="enterprise", lifetime=True, valid_days=0) - document, _ = lic.split_bundle(bundle) - lic.verify_license(document) - status = lic.license_status(document, issuer_pinned=True) - assert status.lifetime is True - assert status.expires_at is not None - assert status.days_left is None - assert status.expired is False - assert status.modules_active is True - - -def test_trial_lifetime_forbidden(master_keys): - with pytest.raises(ValueError): - issue(master_keys, license_type="trial", lifetime=True, valid_days=0) - - -def test_license_server_role_rejects_modules(master_keys): - with pytest.raises(ValueError): - issue(master_keys, role="license_server", modules=["dhcp"]) - - -def test_grace_period_and_expiry(master_keys): - bundle, _ = issue(master_keys, valid_days=10, modules=["dhcp"]) - document, _ = lic.split_bundle(bundle) - issued = lic.parse_iso(document["issued_at"]) - - warn = lic.license_status(document, now=issued + timedelta(days=9), issuer_pinned=True) - assert warn.expiring_soon is True and warn.expired is False and warn.modules_active is True - - grace = lic.license_status(document, now=issued + timedelta(days=25), issuer_pinned=True) - assert grace.expired is True - assert grace.grace_active is True - assert grace.modules_active is True # Kulanz laeuft noch - assert grace.days_since_expiry == 15 - - dead = lic.license_status(document, now=issued + timedelta(days=45), issuer_pinned=True) - assert dead.grace_active is False - assert dead.modules_active is False - - -def test_not_before_blocks_future_license(master_keys): - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - issued = lic.parse_iso(document["issued_at"]) - status = lic.license_status(document, now=issued - timedelta(hours=1), issuer_pinned=True) - assert status.not_yet_valid is True - assert status.modules_active is False - - -def test_status_without_pinning_warns(master_keys): - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - status = lic.license_status(document) - assert any("gepinnt" in w for w in status.warnings) - - -def test_role_defaults_to_customer_when_missing(master_keys): - bundle, _ = issue(master_keys) - document, _ = lic.split_bundle(bundle) - document.pop("role") - assert lic.license_status(document).role == "customer" - - -def test_v1_bundle_compatibility(master_keys): - """Eine alte Datei mit eingebettetem license_privkey laesst sich noch aufteilen.""" - bundle, _ = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - legacy = {**document, "license_privkey": client_priv} - doc2, priv2 = lic.split_bundle(legacy) - assert priv2 == client_priv - assert "license_privkey" not in doc2 - - -# --------------------------------------------------------------------------- # -# Aktivierungsprotokoll -# --------------------------------------------------------------------------- # - - -def test_activation_round_trip(master_keys): - master_priv, master_pub = master_keys - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - - request = lic.build_client_request("activate", document, client_priv, hostname="tesm-kunde") - lic.verify_client_request(request, license_pub, expected_action="activate") - - response = lic.build_master_response( - "activate", - license_id=request["license_id"], - fingerprint=request["fingerprint"], - request_nonce=request["nonce"], - master_private_key=master_priv, - ) - lic.verify_master_response( - response, - master_pub, - expected_license_id=document["license_id"], - expected_fingerprint=request["fingerprint"], - expected_action="activate", - expected_nonce=request["nonce"], - current_seq=document["seq"], - ) - - -def test_offline_code_round_trip_is_identical(master_keys): - master_priv, master_pub = master_keys - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - - request = lic.build_client_request("activate", document, client_priv) - code = lic.encode_code(request) - # Ein per Mail transportierter Code darf Umbrueche/Whitespace enthalten. - wrapped = "\n".join(code[i : i + 48] for i in range(0, len(code), 48)) - assert lic.decode_code(wrapped) == request - lic.verify_client_request(lic.decode_code(wrapped), license_pub) - - response = lic.build_master_response( - "activate", - license_id=request["license_id"], - fingerprint=request["fingerprint"], - request_nonce=request["nonce"], - master_private_key=master_priv, - ) - lic.verify_master_response( - lic.decode_code(lic.encode_code(response)), - master_pub, - expected_license_id=document["license_id"], - expected_fingerprint=request["fingerprint"], - expected_action="activate", - expected_nonce=request["nonce"], - ) - - -def test_request_signature_requires_correct_license_key(master_keys): - bundle, _ = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - other_priv, other_pub = lic.generate_keypair() - request = lic.build_client_request("heartbeat", document, client_priv) - with pytest.raises(lic.SignatureInvalid): - lic.verify_client_request(request, other_pub) - assert other_priv # nur zur Vollstaendigkeit - - -def test_request_replay_is_rejected(master_keys): - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - request = lic.build_client_request("activate", document, client_priv) - - used: set[str] = set() - - def seen(nonce: str) -> bool: - if nonce in used: - return True - used.add(nonce) - return False - - lic.verify_client_request(request, license_pub, seen_nonce=seen) - with pytest.raises(lic.ReplayDetected): - lic.verify_client_request(request, license_pub, seen_nonce=seen) - - -def test_stale_request_is_rejected(master_keys): - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - old = lic.utcnow() - timedelta(seconds=lic.REQUEST_MAX_AGE_SECONDS + 60) - request = lic.build_client_request("heartbeat", document, client_priv, now=old) - with pytest.raises(lic.ReplayDetected): - lic.verify_client_request(request, license_pub) - - -def test_unsigned_request_does_not_burn_nonce(master_keys): - """Nonce-Verbrauch erst nach gueltiger Signatur -- sonst waere die Nonce-Tabelle - ein kostenloser DoS-Vektor.""" - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - request = lic.build_client_request("activate", document, client_priv) - request["signature"] = lic.sign( - {"boese": True}, lic.generate_keypair()[0], lic.SIG_CONTEXT_REQUEST - ) - - calls: list[str] = [] - with pytest.raises(lic.SignatureInvalid): - lic.verify_client_request(request, license_pub, seen_nonce=lambda n: calls.append(n) or False) - assert calls == [] - - -def test_response_bound_to_request_nonce(master_keys): - master_priv, master_pub = master_keys - bundle, _ = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - - first = lic.build_client_request("activate", document, client_priv) - response = lic.build_master_response( - "activate", - license_id=first["license_id"], - fingerprint=first["fingerprint"], - request_nonce=first["nonce"], - master_private_key=master_priv, - ) - second = lic.build_client_request("activate", document, client_priv) - with pytest.raises(lic.ReplayDetected): - lic.verify_master_response( - response, - master_pub, - expected_license_id=document["license_id"], - expected_fingerprint=second["fingerprint"], - expected_action="activate", - expected_nonce=second["nonce"], - ) - - -def test_license_update_downgrade_rejected(master_keys): - master_priv, master_pub = master_keys - bundle, _ = issue(master_keys, modules=["dhcp"]) - document, client_priv = lic.split_bundle(bundle) - - updated = lic.resign_license( - document, master_private_key_b64=master_priv, changes={"modules": ["dhcp", "fileshare"]} - ) - assert updated["seq"] == document["seq"] + 1 - lic.verify_license(updated, trusted_master_keys=[master_pub]) - - request = lic.build_client_request("heartbeat", updated, client_priv) - stale = lic.build_master_response( - "heartbeat", - license_id=request["license_id"], - fingerprint=request["fingerprint"], - request_nonce=request["nonce"], - master_private_key=master_priv, - license_update=document, # alte Revision - ) - with pytest.raises(lic.ReplayDetected): - lic.verify_master_response( - stale, - master_pub, - expected_license_id=updated["license_id"], - expected_fingerprint=request["fingerprint"], - expected_action="heartbeat", - expected_nonce=request["nonce"], - current_seq=updated["seq"], - ) - - -def test_report_size_limit(master_keys): - bundle, license_pub = issue(master_keys, role="license_server") - document, client_priv = lic.split_bundle(bundle) - report = {"customers": [{"name": f"K{i}"} for i in range(lic.MAX_REPORT_ROWS + 1)]} - request = lic.build_client_request("heartbeat", document, client_priv, report=report) - with pytest.raises(lic.ProtocolViolation): - lic.verify_client_request(request, license_pub) - - -def test_malformed_fingerprint_and_nonce_rejected(master_keys): - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - base = lic.build_client_request("activate", document, client_priv) - - bad_fp = copy.deepcopy(base) - bad_fp["fingerprint"] = "nicht-hex" - with pytest.raises(lic.ProtocolViolation): - lic.verify_client_request(bad_fp, license_pub) - - bad_nonce = copy.deepcopy(base) - bad_nonce["nonce"] = "kurz" - with pytest.raises(lic.ProtocolViolation): - lic.verify_client_request(bad_nonce, license_pub) - - -def test_wrong_action_for_endpoint(master_keys): - bundle, license_pub = issue(master_keys) - document, client_priv = lic.split_bundle(bundle) - request = lic.build_client_request("heartbeat", document, client_priv) - with pytest.raises(lic.ProtocolViolation): - lic.verify_client_request(request, license_pub, expected_action="activate") - - -def test_fingerprint_is_stable_and_hex(): - first = lic.system_fingerprint() - assert first == lic.system_fingerprint() - assert len(first) == 64 - int(first, 16) diff --git a/tests/test_repo_hygiene.py b/tests/test_repo_hygiene.py deleted file mode 100644 index 3b48772..0000000 --- a/tests/test_repo_hygiene.py +++ /dev/null @@ -1,326 +0,0 @@ -"""Prueft das Repository selbst -- die Fehlerklassen, die erst beim Ausrollen auffallen. - -Der Vorgaenger hat sich hier zweimal die Finger verbrannt: CRLF-Zeilenenden aus -einer Windows-Buildmaschine machten Shell-Skripte auf dem Ziel unbrauchbar -("bad interpreter"), und Schluesseldateien landeten versehentlich im Repo. -Beides faellt hier auf, bevor ein Paket gebaut wird. -""" - -from __future__ import annotations - -import os -import re -import shutil -import subprocess -from pathlib import Path - -import pytest - -REPO_ROOT = Path(__file__).resolve().parent.parent - -SKIP_DIRS = { - ".git", - ".venv", - "__pycache__", - "instance", - ".pytest_cache", - ".ruff_cache", - "node_modules", -} -TEXT_SUFFIXES = { - ".py", ".sh", ".html", ".css", ".js", ".json", ".toml", ".md", - ".yaml", ".yml", ".cfg", ".ini", ".txt", ".service", ".conf", -} -#: Dateien ohne Endung, die trotzdem Text sind. -EXTENSIONLESS_TEXT = {"tesm-helper", "tesm", "tesm-license"} - -SECRET_NAMES = { - "data.keys", - "secret.key", - "license.json", - "license_key.json", - "master_signing_key.json", - "fernet.key", - "known_hosts", -} -SECRET_SUFFIXES = {".db", ".db-wal", ".db-shm", ".pem", ".p12", ".pfx"} - - -def _repo_files() -> list[Path]: - files: list[Path] = [] - for path in REPO_ROOT.rglob("*"): - if not path.is_file(): - continue - if any(part in SKIP_DIRS for part in path.relative_to(REPO_ROOT).parts): - continue - files.append(path) - return files - - -def _text_files() -> list[Path]: - return [ - path - for path in _repo_files() - if path.suffix in TEXT_SUFFIXES or path.name in EXTENSIONLESS_TEXT - ] - - -def test_no_crlf_line_endings(): - """CRLF im Paket macht Shell-Skripte auf dem Linux-Ziel unbrauchbar.""" - offenders = [ - str(path.relative_to(REPO_ROOT)) - for path in _text_files() - if b"\r\n" in path.read_bytes() - ] - assert not offenders, ( - "Diese Dateien haben CRLF-Zeilenenden und wuerden auf dem Zielsystem brechen:\n " - + "\n ".join(offenders) - ) - - -def test_no_secrets_committed(): - """Schluessel, Datenbanken und Lizenzdateien gehoeren nie ins Repo.""" - offenders = [ - str(path.relative_to(REPO_ROOT)) - for path in _repo_files() - if path.name in SECRET_NAMES or path.suffix in SECRET_SUFFIXES - ] - assert not offenders, "Geheimnisse im Repository:\n " + "\n ".join(offenders) - - -def test_shell_scripts_have_shebang_and_strict_mode(): - scripts = [path for path in _repo_files() if path.suffix == ".sh" or path.name == "tesm-helper"] - assert scripts, "Es wurden keine Shell-Skripte gefunden -- Test prueft ins Leere." - for script in scripts: - text = script.read_text(encoding="utf-8") - assert text.startswith("#!"), f"{script.name}: Shebang fehlt" - assert "set -euo pipefail" in text, f"{script.name}: 'set -euo pipefail' fehlt" - - -def test_helper_validates_every_verb(): - """Jedes Verb des privilegierten Helfers muss im case-Block auftauchen. - - Der Helfer ist die einzige Stelle mit Root-Rechten. Ein Verb, das die - Anwendung kennt, der Helfer aber nicht, waere entweder tot -- oder, - schlimmer, ein Hinweis auf eine ungeprueft durchgereichte Aktion. - """ - from tesm_core.sysops import HELPER_VERBS - - helper = (REPO_ROOT / "deploy" / "tesm-helper").read_text(encoding="utf-8") - # case-Muster koennen mehrere Verben mit | buendeln. - patterns: set[str] = set() - for line in helper.splitlines(): - match = re.match(r"^\s{2,4}([a-z|-]+)\)\s*$", line) - if match: - patterns.update(match.group(1).split("|")) - missing = [verb for verb in HELPER_VERBS if verb not in patterns] - assert not missing, f"Verben fehlen im Helfer: {missing}" - - -def test_helper_has_no_unquoted_argument_passthrough(): - """Der Helfer darf Argumente nie ungeprueft an eine Shell weiterreichen.""" - helper = (REPO_ROOT / "deploy" / "tesm-helper").read_text(encoding="utf-8") - for forbidden in ("eval ", "bash -c", "sh -c"): - assert forbidden not in helper, f"Der Helfer enthaelt {forbidden!r}" - - -def test_helper_blocks_path_traversal_everywhere(): - """Jede Pfadpruefung im Helfer muss ".." abweisen. - - Ohne diese Sperre laesst sich eine Positivliste ueber den Basisnamen - unterlaufen: ``/etc/nginx/sites-available/../../../root/tesm`` hat den - zulaessigen Basisnamen ``tesm``, liest aber ``/root/tesm``. - """ - helper = (REPO_ROOT / "deploy" / "tesm-helper").read_text(encoding="utf-8") - checks = re.findall( - r"^check_\w*(?:path|target|webroot)\w*\(\) \{(.*?)^\}", helper, re.S | re.M - ) - assert checks, "Es wurden keine Pfadpruefungen gefunden -- Test prueft ins Leere." - for body in checks: - assert '*".."*' in body, "Pfadpruefung ohne ..-Sperre: " + body - - -@pytest.mark.skipif(shutil.which("bash") is None, reason="bash nicht verfuegbar") -def test_helper_accepts_instances_but_no_invented_keys(): - """Der Namensraum einer Instanz ist eng begrenzt. - - Er landet ungefiltert in Pfaden wie ``/etc/nginx/sites-available/``. - """ - script = (REPO_ROOT / "deploy" / "tesm-helper").read_text(encoding="utf-8") - start = script.index("check_app_key() {") - end = script.index(chr(10) + "}", start) + 2 - prelude = 'APP_KEYS_ALLOWED=("tesm" "tesm-license")' + chr(10) + "fail() { exit 1; }" + chr(10) - snippet = prelude + script[start:end] - - def accepted(key: str) -> bool: - command = snippet + chr(10) + 'check_app_key "' + key + '"' - return subprocess.run(["bash", "-c", command], capture_output=True).returncode == 0 - - for good in ("tesm", "tesm-license", "tesm-opus", "tesm-license-opus", "tesm-test2"): - assert accepted(good), good - for bad in ("", "foo", "tesm-", "tesm-../etc", "../tesm", "tesm-GROSS", "tesm-" + "x" * 20): - assert not accepted(bad), bad - - - -def test_systemd_units_are_hardened(): - units = list((REPO_ROOT / "deploy" / "systemd").glob("*.service")) - assert units - for unit in units: - text = unit.read_text(encoding="utf-8") - assert "User=root" not in text, f"{unit.name} laeuft als root" - # ProtectSystem=full, nicht strict: strict sperrt auch den privilegierten - # Helfer aus /etc aus -- gemessen, siehe test_core_security.py. - for directive in ("ProtectSystem=full", "PrivateTmp=yes"): - assert directive in text, f"{unit.name}: {directive} fehlt" - # NoNewPrivileges waere hier kontraproduktiv: es schaltet setuid ab und - # damit sudo -- der Dienst koennte den privilegierten Helfer nicht mehr - # aufrufen, und jede Systemaktion aus der Oberflaeche waere tot. Genau - # so war es, und die Diagnoseseite meldete trotzdem "Helfer erreichbar". - assert "NoNewPrivileges=yes" not in text, ( - f"{unit.name}: NoNewPrivileges=yes verhindert sudo und damit den Helfer." - ) - - -def test_sudoers_grants_only_the_helper(): - for name in ("tesm", "tesm-license"): - raw = (REPO_ROOT / "deploy" / "sudoers" / name).read_text(encoding="utf-8") - # Kommentare erklaeren, was bewusst *nicht* getan wird -- sie duerfen den - # Test nicht ausloesen. - rules = [line for line in raw.splitlines() if line.strip() and not line.startswith("#")] - body = "\n".join(rules) - assert "NOPASSWD: ALL" not in body, f"{name}: zu weite sudo-Regel" - assert "/usr/local/lib/tesm/tesm-helper" in body - for line in rules: - if "NOPASSWD" in line: - assert line.strip().endswith("/usr/local/lib/tesm/tesm-helper"), ( - f"{name}: NOPASSWD-Regel erlaubt mehr als den Helfer: {line}" - ) - - -@pytest.mark.parametrize("app", ["tesm", "tesm-license"]) -def test_nginx_template_serves_static_from_app_root(app: str): - """Der alias-Pfad ist genau der Fehler, der im Vorgaenger jahrelang ueberlebte.""" - text = (REPO_ROOT / "deploy" / "nginx" / f"{app}.conf").read_text(encoding="utf-8") - assert f"alias /srv/{app}/static/;" in text - assert f"alias /srv/{app}/static-core/;" in text - - -def test_documentation_referenced_by_the_units_exists(): - """``Documentation=`` in der Unit muss auf eine Datei zeigen, die es gibt. - - Sonst laeuft ``systemctl help tesm`` ins Leere -- und schlimmer: der - Verweis suggeriert eine Anleitung, die niemand geschrieben hat. - """ - for unit in (REPO_ROOT / "deploy" / "systemd").glob("*.service"): - for line in unit.read_text(encoding="utf-8").splitlines(): - if not line.startswith("Documentation=file:"): - continue - target = line.split("file:", 1)[1].strip() - # /srv//docs/X -> docs/X im Repository - relative = target.split("/docs/", 1)[1] - assert (REPO_ROOT / "docs" / relative).is_file(), ( - f"{unit.name} verweist auf {target}, docs/{relative} fehlt" - ) - - -def test_readme_links_point_to_existing_files(): - readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") - for target in re.findall(r"\]\((docs/[^)#]+)\)", readme): - assert (REPO_ROOT / target).is_file(), f"README verweist auf {target}" - - -def _templates() -> list[Path]: - return [ - path - for path in _repo_files() - if path.suffix == ".html" and "templates" in path.parts - ] - - -def test_no_html_entities_inside_escaped_macro_calls(): - """Makros escapen ihren Text -- Entities darin erscheinen woertlich. - - Auf der Wartungsseite stand deshalb ``„Linux-Client“`` als - sichtbarer Text. Der Fehler ist unauffaellig, weil dieselbe Schreibweise - im uebrigen Vorlagentext voellig richtig ist. - """ - # Genau bis zum schliessenden "}}" des Aufrufs -- ein greedy Ausdruck - # verschluckte sonst den nachfolgenden Seiteninhalt und meldete dessen - # Tabellenmarkup als Treffer. - pattern = re.compile( - r"\{\{\s*(?:alert|empty_state|pill|stat)\((?:[^{}]|\{[^{}]*\})*?\)\s*" - r"(?:\|\s*safe\s*)?\}\}", - re.S, - ) - offenders: list[str] = [] - for path in _templates(): - for match in pattern.finditer(path.read_text(encoding="utf-8")): - call = match.group(0) - # Frueher stand hier eine Ausnahme fuer "| safe". Sie war falsch: - # das Filter wirkt auf das *Ergebnis* des Makros, escaped wurde aber - # schon beim Einsetzen des Arguments. Auf der Geraeteseite stand - # deshalb woertlich "und" im Text. - if re.search(r"&[a-zA-Z]{2,10};|", call): - offenders.append( - f"{path.relative_to(REPO_ROOT)}: {' '.join(call.split())[:100]}" - ) - assert not offenders, ( - "HTML im escapten Makrotext -- das erscheint woertlich auf der Seite:\n " - + "\n ".join(offenders) - ) - - -def test_refresh_regions_never_point_at_a_whole_page(): - """``data-refresh-url`` schreibt die Antwort in ``innerHTML``. - - Zeigt die URL auf eine vollstaendige Seite, landet die ganze Seite in - einem Ausschnitt -- bei jedem Durchlauf eine Ebene tiefer. Genau das ist - auf der Wartungsseite passiert: die Karte "Auftraege" holte sich - ``maintenance.overview``, also sich selbst. - - Erlaubt sind nur Endpunkte, deren Name sie als Ausschnitt ausweist. - """ - fragment_markers = ("fragment", "tiles", "output", "status", "partial", "ausschnitt") - pattern = re.compile(r"data-refresh-url=\"\{\{\s*url_for\('([^']+)'") - found = 0 - for path in _templates(): - for match in pattern.finditer(path.read_text(encoding="utf-8")): - endpoint = match.group(1) - found += 1 - assert any(marker in endpoint.lower() for marker in fragment_markers), ( - f"{path.relative_to(REPO_ROOT)}: data-refresh-url zeigt auf {endpoint!r} -- " - "das sieht nach einer ganzen Seite aus, nicht nach einem Ausschnitt." - ) - assert found, "Keine Aktualisierungsbereiche gefunden -- Test prueft ins Leere." - - -def test_refresh_loop_treats_zero_as_off(): - """``Number(x || 15000)`` macht aus einer 0 klammheimlich 15000.""" - script = ( - REPO_ROOT - / "packages" - / "tesm-core" - / "src" - / "tesm_core" - / "static" - / "tesm_core" - / "js" - / "app.js" - ).read_text(encoding="utf-8") - assert "refreshInterval || " not in script - assert "interval <= 0" in script, "Ein Intervall von 0 muss die Aktualisierung abschalten." - - -def test_licensing_package_is_shared_not_copied(): - """Es darf keine zweite Kopie des Lizenzprotokolls geben. - - Genau diese Doppelung -- zwei byte-identisch zu haltende ``licensing.py`` -- - war im Vorgaenger die gefaehrlichste Invariante. - """ - copies = [ - str(path.relative_to(REPO_ROOT)) - for path in _repo_files() - if path.name == "licensing.py" - ] - assert not copies, f"Kopien des Lizenzmoduls gefunden: {copies}" diff --git a/tests/test_webserver.py b/tests/test_webserver.py deleted file mode 100644 index 964e046..0000000 --- a/tests/test_webserver.py +++ /dev/null @@ -1,521 +0,0 @@ -"""Die nginx-Vorlage und die Trennung mehrerer Instanzen auf einem Host. - -Zwei Fehlerklassen werden hier festgenagelt: - -1. Der alias-Fehler des Vorgaengers -- statische Dateien zeigten auf ein - Verzeichnis, das es nicht gab, und die Oberflaeche kam jahrelang ohne - Design. Die erzeugte Datei muss die richtigen Pfade enthalten. -2. Zwei Installationen derselben Anwendung auf einem Host. Solange alles - ueber den ``app_key`` benannt wurde, hat eine Testinstanz beim Speichern - der Webserver-Einstellungen die Site-Datei der Produktion ueberschrieben - -- und beide teilten sich dasselbe Sitzungscookie, weil Cookies keine - Ports unterscheiden. -""" - -from __future__ import annotations - -import re - -import pytest -from tesm_core.system import webserver -from tesm_core.sysops import HELPER_VERBS, Result, SysOps - - -def make_config(**overrides) -> webserver.WebConfig: - base = { - "app_key": "tesm", - "upstream": "127.0.0.1:5000", - "static_root": "/srv/tesm/static", - } - return webserver.WebConfig(**{**base, **overrides}) - - -# --------------------------------------------------------------------------- # -# Nur HTTP -# --------------------------------------------------------------------------- # - - -def test_http_only_has_no_tls_block(): - text = webserver.render(make_config()) - assert "listen 80 default_server;" in text - assert "ssl_certificate" not in text - assert "return 308" not in text - - -def test_static_aliases_point_into_the_installation(): - text = webserver.render(make_config()) - assert "alias /srv/tesm/static/;" in text - assert "alias /srv/tesm/static-core/;" in text - - -def test_proxy_passes_the_host_including_port(): - """``$host`` verwirft den Port -- damit scheitert die Origin-Pruefung. - - Genau daran ist die Anmeldung hinter dem Reverse Proxy auf einem - abweichenden Port real gescheitert. - """ - text = webserver.render(make_config()) - assert "proxy_set_header Host $http_host;" in text - assert "proxy_set_header Host $host;" not in text - assert "proxy_set_header X-Forwarded-Port $server_port;" in text - - -def test_acme_path_is_always_reachable(): - text = webserver.render( - make_config(https_enabled=True, cert_path="/c.pem", key_path="/k.pem") - ) - # Auch bei aktiver Umleitung -- sonst kann Let's Encrypt nie erneuern. - assert "/.well-known/acme-challenge/" in text - challenge = text.index("acme-challenge") - redirect = text.index("return 308") - assert challenge < redirect - - -# --------------------------------------------------------------------------- # -# HTTPS -# --------------------------------------------------------------------------- # - - -def test_https_listens_on_443_and_redirects_80(): - text = webserver.render( - make_config(https_enabled=True, cert_path="/c.pem", key_path="/k.pem") - ) - assert "listen 443 ssl default_server http2;" in text - assert "listen [::]:443 ssl default_server http2;" in text - assert "return 308 https://$host$request_uri;" in text - assert "ssl_protocols TLSv1.2 TLSv1.3;" in text - - -def test_hsts_only_when_asked(): - without = webserver.render( - make_config(https_enabled=True, cert_path="/c.pem", key_path="/k.pem") - ) - assert "Strict-Transport-Security" not in without - - with_hsts = webserver.render( - make_config( - https_enabled=True, hsts_enabled=True, cert_path="/c.pem", key_path="/k.pem" - ) - ) - assert "Strict-Transport-Security" in with_hsts - # HSTS ohne TLS waere ein Selbstschuss: der Browser merkt sich, dass er die - # Seite nur noch ueber HTTPS aufrufen darf. - assert "ssl_certificate" in with_hsts - - -def test_hsts_is_ignored_without_https(): - text = webserver.render(make_config(hsts_enabled=True)) - assert "Strict-Transport-Security" not in text - - -def test_redirect_can_be_switched_off(): - text = webserver.render( - make_config( - https_enabled=True, redirect_http=False, cert_path="/c.pem", key_path="/k.pem" - ) - ) - assert "return 308" not in text - # Dann muss Port 80 die Anwendung weiter bedienen. - assert text.count("proxy_pass http://127.0.0.1:5000;") >= 2 - - -def test_deviating_ports_are_rendered_and_redirect_keeps_the_port(): - text = webserver.render( - make_config( - https_enabled=True, - cert_path="/c.pem", - key_path="/k.pem", - http_port=8080, - https_port=8443, - ) - ) - assert "listen 8080 default_server;" in text - assert "listen 8443 ssl default_server http2;" in text - # Ohne den Port im Ziel liefe die Umleitung ins Leere. - assert "return 308 https://$host:8443$request_uri;" in text - - -def test_standard_port_leaves_no_suffix(): - assert make_config(https_port=443).https_suffix == "" - assert make_config(https_port=8443).https_suffix == ":8443" - - -def test_websocket_location_only_when_configured(): - assert "proxy_set_header Upgrade" not in webserver.render(make_config()) - text = webserver.render(make_config(websocket_path="/ws/")) - assert "location /ws/ {" in text - assert "proxy_read_timeout 3600s;" in text - - -def _location_blocks(text: str) -> list[list[str]]: - """Zerlegt die erzeugte Datei in ihre ``location``-Bloecke.""" - blocks: list[list[str]] = [] - current: list[str] | None = None - for line in text.splitlines(): - stripped = line.strip() - if stripped.startswith("location ") and stripped.endswith("{"): - current = [] - elif stripped == "}" and current is not None: - blocks.append(current) - current = None - elif current is not None and stripped and not stripped.startswith("#"): - current.append(stripped) - return blocks - - -@pytest.mark.parametrize( - "config", - [ - make_config(websocket_path="/ws/"), - make_config( - websocket_path="/ws/", https_enabled=True, cert_path="/c.pem", key_path="/k.pem" - ), - make_config(https_enabled=True, hsts_enabled=True, cert_path="/c.pem", key_path="/k.pem"), - ], -) -def test_no_directive_appears_twice_in_one_block(config: webserver.WebConfig): - """nginx weist eine doppelte Direktive im selben Block ab. - - Auf POETEST real passiert: der Websocket-Block erbte ``proxy_read_timeout - 120s`` aus dem gemeinsamen Teil und setzte danach ``3600s``. nginx meldete - "directive is duplicate" und liess sich nicht mehr starten -- nicht nur - fuer diese Site, sondern fuer alle. - """ - for block in _location_blocks(webserver.render(config)): - directives = [line.split()[0] for line in block if line.endswith(";")] - repeated = { - name - for name in directives - # Wiederholung ist nur bei den Direktiven zulaessig, die nginx als - # Liste versteht. - if directives.count(name) > 1 and name not in {"proxy_set_header", "add_header"} - } - assert not repeated, f"Mehrfach im selben Block: {sorted(repeated)}" - - -def test_websocket_block_keeps_the_long_timeout(): - """Die lange Wartezeit ist der Zweck des Blocks -- sie darf nicht verlorengehen.""" - blocks = { - block[0]: block - for block in _location_blocks(webserver.render(make_config(websocket_path="/ws/"))) - } - websocket = next(b for b in blocks.values() if "proxy_set_header Upgrade $http_upgrade;" in b) - assert "proxy_read_timeout 3600s;" in websocket - assert "proxy_read_timeout 120s;" not in websocket - - -def test_http2_uses_the_old_spelling_by_default(): - """``http2 on;`` kennt erst nginx 1.25.1. - - Auf POETEST (nginx 1.24) fuehrte die neue Direktive zu "unknown directive" - -- und damit zu einem Webserver, der sich nicht mehr starten liess. Die - alte Schreibweise laeuft ueberall. - """ - text = webserver.render( - make_config(https_enabled=True, cert_path="/c.pem", key_path="/k.pem") - ) - assert "listen 443 ssl default_server http2;" in text - assert "http2 on;" not in text - - -def test_http2_directive_on_request(): - text = webserver.render( - make_config( - https_enabled=True, cert_path="/c.pem", key_path="/k.pem", http2_directive=True - ) - ) - assert " http2 on;" in text - assert "listen 443 ssl default_server;" in text - assert "ssl http2;" not in text - - -def test_stapling_only_with_a_real_certificate(): - """Ein selbstsigniertes Zertifikat hat keinen OCSP-Responder.""" - self_signed = webserver.render( - make_config(https_enabled=True, cert_path="/c.pem", key_path="/k.pem") - ) - assert "ssl_stapling" not in self_signed - - public = webserver.render( - make_config( - https_enabled=True, - domain="tesm.example.org", - cert_path="/c.pem", - key_path="/k.pem", - ) - ) - assert "ssl_stapling on;" in public - - -# --------------------------------------------------------------------------- # -# Domain -# --------------------------------------------------------------------------- # - - -@pytest.mark.parametrize( - "value", ["tesm.example.org", "a.b.c.de", "TESM.Example.ORG", "tesm.example.org."] -) -def test_valid_domains(value: str): - assert webserver.validate_domain(value) == value.lower().rstrip(".") - - -@pytest.mark.parametrize( - "value", ["kein-punkt", "-start.de", "ende-.de", "raum .de", "a..b", "http://x.de"] -) -def test_invalid_domains(value: str): - with pytest.raises(ValueError): - webserver.validate_domain(value) - - -def test_empty_domain_becomes_catch_all(): - assert make_config().server_name == "_" - assert make_config(domain="tesm.example.org").server_name == "tesm.example.org" - - -def test_only_the_nameless_site_claims_default_server(): - """Sonst entscheidet der Dateiname in sites-enabled, wer die Vorgabe wird. - - ``server_name _;`` ist bei nginx kein Sonderfall -- ohne - ``default_server`` gewinnt schlicht die zuerst geladene Datei. Bei zwei - Anwendungen auf einem Host waere das reiner Zufall. - """ - nameless = webserver.render(make_config()) - assert "listen 80 default_server;" in nameless - - named = webserver.render(make_config(domain="lizenz.example.org")) - assert "default_server" not in named - assert "server_name lizenz.example.org;" in named - - -# --------------------------------------------------------------------------- # -# Selbstsigniertes Zertifikat -# --------------------------------------------------------------------------- # - - -class FakeSysOps: - """Merkt sich den Aufruf, statt den Helfer wirklich zu starten.""" - - def __init__(self, result: Result) -> None: - self.result = result - self.calls: list[tuple[str, ...]] = [] - self.available = True - self.site_key = "tesm-opus" - - def issue_self_signed(self, common_name: str, *alt_names: str) -> Result: - self.calls.append((common_name, *alt_names)) - return self.result - - -def test_self_signed_returns_both_paths(): - sysops = FakeSysOps(Result(True, "/etc/tesm/certs/tesm/fullchain.pem\n/etc/tesm/certs/tesm/privkey.pem\n")) - result = webserver.create_self_signed( - sysops, common_name="poetest", alt_names=["poetest.local", "192.168.82.202"] - ) - assert result["ok"] - assert result["cert_path"].endswith("fullchain.pem") - assert result["key_path"].endswith("privkey.pem") - assert sysops.calls == [("poetest", "poetest.local", "192.168.82.202")] - - -def test_self_signed_rejects_a_hostile_name(): - """Der Name landet in einem openssl-Argument -- er wird hier geprueft.""" - sysops = FakeSysOps(Result(True, "a\nb\n")) - for hostile in ("a b", "a;rm -rf /", "$(whoami)", "../../etc", ""): - result = webserver.create_self_signed(sysops, common_name=hostile) - assert not result["ok"], hostile - assert sysops.calls == [] - - -def test_self_signed_drops_unusable_alternative_names(): - sysops = FakeSysOps(Result(True, "a\nb\n")) - webserver.create_self_signed( - sysops, common_name="poetest", alt_names=["poetest", "", "bad name", "ok.local"] - ) - assert sysops.calls == [("poetest", "ok.local")] - - -def test_self_signed_reports_a_failing_helper(): - sysops = FakeSysOps(Result(False, "", "openssl fehlt", 2)) - result = webserver.create_self_signed(sysops, common_name="poetest") - assert not result["ok"] - assert "openssl fehlt" in result["detail"] - - -def test_self_signed_rejects_an_unexpected_answer(): - sysops = FakeSysOps(Result(True, "nur eine Zeile\n")) - result = webserver.create_self_signed(sysops, common_name="poetest") - assert not result["ok"] - - -def test_certificate_info_survives_an_unreadable_file(tmp_path, monkeypatch): - """Ein unlesbares Zertifikat darf die Einstellungsseite nicht abwerfen. - - Auf POETEST real passiert: das Zertifikatsverzeichnis war 0750 root:root, - der Dienstbenutzer bekam beim blossen ``stat`` einen PermissionError -- und - die Seite "Webserver und TLS" antwortete mit HTTP 500. - """ - target = tmp_path / "fullchain.pem" - target.write_text("egal", encoding="utf-8") - - def deny(*_args, **_kwargs): - raise PermissionError(13, "Permission denied") - - monkeypatch.setattr("pathlib.Path.is_file", deny) - info = webserver.certificate_info(str(target)) - assert info["present"] is True - assert "nicht lesen" in info["error"] - - -def test_certificate_info_reports_a_missing_file(): - assert webserver.certificate_info("/gibt/es/nicht.pem") == {"present": False} - - -def test_certificate_info_reports_garbage_without_raising(tmp_path): - target = tmp_path / "fullchain.pem" - target.write_text("kein Zertifikat", encoding="utf-8") - info = webserver.certificate_info(str(target)) - assert info["present"] is True - assert info["error"] - - -def test_local_names_are_usable_in_a_certificate(): - names = webserver.local_names() - assert names, "Es sollte mindestens der Hostname gefunden werden." - for name in names: - assert re.fullmatch(r"[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?", name), name - - -# --------------------------------------------------------------------------- # -# Instanztrennung -# --------------------------------------------------------------------------- # - - -def test_apply_config_is_one_call_with_rollback(): - """Getrennte Aufrufe liessen eine abgelehnte Konfiguration aktiviert liegen. - - ``nginx-write`` verlinkt sofort nach ``sites-enabled``. Schlug danach - ``nginx -t`` fehl, blieb die kaputte Datei aktiv -- der laufende nginx - merkte nichts, aber der naechste Reload durch irgendwen legte den - Webserver lahm. Die Uebernahme muss deshalb *eine* privilegierte Operation - sein, die sich selbst zuruecknimmt. - """ - calls: list[str] = [] - - class Recording(SysOps): - def run(self, verb: str, *args: str, stdin: str = "", timeout: int | None = None) -> Result: - calls.append(verb) - return Result(True, "uebernommen") - - result = webserver.apply_config(Recording(app_key="tesm"), "server {}") - assert result["ok"] - assert calls == ["nginx-apply"] - assert "nginx-write" not in calls - - -def test_apply_config_reports_a_rejection_without_claiming_success(): - class Failing(SysOps): - def run(self, verb: str, *args: str, stdin: str = "", timeout: int | None = None) -> Result: - return Result(False, "", "unknown directive \"http2\"", 2) - - result = webserver.apply_config(Failing(app_key="tesm"), "server {}") - assert not result["ok"] - assert "alte Stand" in result["message"] - assert "http2" in result["detail"] - - -def test_sysops_writes_system_files_under_the_site_key(): - """Sonst ueberschreibt eine Testinstanz die Systemdateien der Produktion.""" - calls: list[tuple[str, ...]] = [] - - class Recording(SysOps): - def run(self, verb: str, *args: str, stdin: str = "", timeout: int | None = None) -> Result: - calls.append((verb, *args)) - return Result(True) - - sysops = Recording(app_key="tesm", site_key="tesm-opus") - sysops.write_nginx("x") - sysops.write_netplan("x") - sysops.write_logrotate("x") - sysops.issue_self_signed("poetest") - - assert calls == [ - ("nginx-write", "tesm-opus"), - ("netplan-write", "tesm-opus"), - ("logrotate-write", "tesm-opus"), - ("selfsigned-issue", "tesm-opus", "poetest"), - ] - - -def test_upstream_follows_the_configured_bind_address(monkeypatch): - """Die nginx-Site muss auf *diese* Instanz zeigen, nicht auf Port 5000. - - Mit fest verdrahteter 5000 erzeugte eine Instanz auf Port 5100 eine - Konfiguration, die Anfragen an die *andere* Installation weiterreichte -- - auf POETEST reproduziert: HTTPS stand, lieferte aber die alte Anwendung. - """ - from tesm_core.config import load_core_config - - monkeypatch.setenv("TESM_BIND", "127.0.0.1:5100") - config = load_core_config(app_key="tesm", app_name="TESM", env_prefix="TESM_") - assert config.bind == "127.0.0.1:5100" - - text = webserver.render(make_config(upstream=config.bind)) - assert "proxy_pass http://127.0.0.1:5100;" in text - assert "127.0.0.1:5000" not in text - - -def test_bind_address_is_validated(monkeypatch): - from tesm_core.config import load_core_config - - monkeypatch.setenv("TESM_BIND", "127.0.0.1:5100; rm -rf /") - with pytest.raises(ValueError): - load_core_config(app_key="tesm", app_name="TESM", env_prefix="TESM_") - - -def test_site_key_is_validated(monkeypatch): - from tesm_core.config import load_core_config - - monkeypatch.setenv("TESM_SITE_KEY", "../../etc/nginx") - with pytest.raises(ValueError): - load_core_config(app_key="tesm", app_name="TESM", env_prefix="TESM_") - - -def test_site_key_defaults_to_the_app_key(): - assert SysOps(app_key="tesm").site_key == "tesm" - - -def test_self_signed_verb_is_known(): - assert "selfsigned-issue" in HELPER_VERBS - - -def test_service_state_finds_unit_without_suffix(monkeypatch): - """``list-unit-files`` braucht ein Muster mit Endung. - - Ohne sie fand die Abfrage nichts und der Zustand lautete - "installiert: nein" -- auch bei laufendem Dienst. Auf der DHCP-Seite lud - das zum Klick auf "Kea installieren" ein. - """ - import tesm_core.sysops as sysops_modul - from tesm_core.sysops import Result, SysOps - - # Ohne systemctl bricht die Abfrage vorher ab -- auf dem Entwicklungsrechner - # gibt es keines. - monkeypatch.setattr(sysops_modul.shutil, "which", lambda name: "/bin/" + name) - - gesehen: list[list[str]] = [] - - class Attrappe(SysOps): - def __init__(self): - pass - - def read_command(self, argv): - gesehen.append(list(argv)) - if "list-unit-files" in argv: - treffer = "kea-dhcp4-server.service" in argv - return Result(True, "kea-dhcp4-server.service enabled" if treffer else "", "") - return Result(True, "active", "") - - zustand = Attrappe().service_state("kea-dhcp4-server") - assert zustand["installed"] == "ja", gesehen - # Eine bereits vollstaendige Unit bleibt unveraendert. - assert Attrappe().service_state("kea-dhcp4-server.service")["installed"] == "ja"