TESM-Lizenzserver 2.0.0 -- Neubau
Vollstaendiger Neubau der Anwendung. Der vorherige Stand bleibt unveraendert im Zweig SONNET5 erhalten. Aufbau: apps/tesm-license (Anwendung), packages/tesm-core (gemeinsamer Kern), packages/tesm-licensing (Lizenzprotokoll), deploy (Installation, systemd, privilegierter Helfer), docs, tests. Die verwaltete Anwendung liegt in ihrem eigenen Repository; beide Repositorien bringen die gemeinsamen Pakete mit, damit sich jedes allein installieren laesst. Die wichtigsten Unterschiede zum Vorgaenger: * Keine doppelte licensing.py -- ein Paket, das beide Anwendungen installieren, statt zweier Dateien, die byte-identisch bleiben sollen. * Der Webprozess laeuft unprivilegiert; alles, was Root braucht, geht ueber einen einzigen Helfer mit Positivlisten fuer jedes Argument. * CSRF-Schutz ueberhaupt -- der Vorgaenger hatte keinen. * Rechte werden serverseitig geprueft, nicht nur im Template ausgeblendet. * Keine Lizenz ohne master_endpoint: eine Ausstellung ohne Endpunkt wird abgelehnt statt eine Lizenz zu erzeugen, die sich nie aktivieren kann. * Offline-Aktivierung in beide Richtungen; die Lizenz bleibt als "Aktivierung offen" markiert, bis sie zurueckkommt. * Getrennte Signaturkontexte je Nachrichtenart, Nonce gegen Wiedereinspielung, seq gegen das Zurueckrollen auf eine aeltere Lizenz. * Kein Hostname im Maschinen-Fingerabdruck. * Verschachtelte Datenbankverbindungen sind ein Fehler, kein Deadlock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,74 +1,163 @@
|
||||
# TESM-Lizenzserver
|
||||
|
||||
Master-Lizenzserver für [TESM (TimEShepManager)](https://gitea.int.eertmoed.net/alientim/tesm) —
|
||||
verwaltet Kunden und deren Lizenzen, statt Netzwerkgeräte. Ursprünglich als
|
||||
Teil des TESM-Hauptrepos gebaut (gleiche Codebasis: Login/Benutzer/Gruppen,
|
||||
LDAP/AD, NGINX- und Systemeinstellungen, Live-Log/Verlauf/Auditlog,
|
||||
Im-/Export-Grundgerüst, das komplette Rechtesystem) und in dieses eigene
|
||||
Repo ausgelagert.
|
||||
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.
|
||||
|
||||
**Das Projekt ist ausschließlich für Linux ausgelegt** (Zielsystem: eine
|
||||
Linux-VM, identisches Deployment-Muster wie TESM selbst).
|
||||
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.
|
||||
|
||||
## Kernfunktionen
|
||||
## Zweige
|
||||
|
||||
- **Kunden-/Lizenzübersicht** als Dashboard (Typ, Module, Ablaufdatum,
|
||||
Aktivierungsstatus, letzter Heartbeat)
|
||||
- **Lizenz-Ausstellung**: Trial/Standard/Custom/Enterprise, frei wählbare
|
||||
Gültigkeitsdauer, bei Custom einzeln wählbare Module
|
||||
- **Ed25519-signierte Lizenzdateien** (siehe `srv/tesm-license/licensing.py`)
|
||||
— dieselbe Datei existiert byte-identisch im TESM-Repo, da Client
|
||||
(TESM) und Master (dieses Repo) exakt dasselbe Signier-/
|
||||
Verifikationsprotokoll sprechen müssen
|
||||
- **Online-Aktivierung/-Deaktivierung/-Heartbeat** über eine schlanke
|
||||
JSON-API (`/api/activate`, `/api/deactivate`, `/api/heartbeat`,
|
||||
unauthentifiziert per Design — die Signatur der Anfrage selbst ist der
|
||||
Berechtigungsnachweis)
|
||||
- **Offline-Fallback**: dieselbe Aktivierungs-/Deaktivierungs-/Heartbeat-
|
||||
Logik auch als manuell kopierbarer Code für Kunden ohne Netzwerkzugriff
|
||||
auf den Lizenzserver
|
||||
- **E-Mail-Versand** ausgestellter Lizenzen per Microsoft Graph
|
||||
(Client-Credentials-Flow, keine zusätzliche Abhängigkeit) inkl.
|
||||
Einrichtungsanleitung und Verbindungstest in der GUI
|
||||
- Eigene Bootstrap-Lizenz des Masters selbst (`create_master_license.py`,
|
||||
rein lokal, kein externer Super-Master nötig)
|
||||
| Zweig | Inhalt |
|
||||
|---|---|
|
||||
| `main` | dieser Neubau (Version 2.0.0) |
|
||||
| `SONNET5` | der Stand davor, unveraendert aufbewahrt |
|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
## Installation auf einem Server
|
||||
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
sudo bash deploy/install.sh --app tesm-license --https --domain lizenz.firma.de
|
||||
sudo -u tesm-license tesm-license-admin init-key
|
||||
sudo -u tesm-license tesm-license-admin bootstrap
|
||||
sudo -u tesm-license tesm-license-admin create-admin
|
||||
```
|
||||
|
||||
Richtet System-Pakete, die Flask-App (systemd: `tesm-license.service`)
|
||||
sowie nginx als Reverse-Proxy unter `/srv/tesm-license` ein. Erkennt
|
||||
selbstständig, ob dort bereits eine Installation existiert, und
|
||||
aktualisiert sie entsprechend (In-Place bei unverändertem
|
||||
Datenbank-Schema, sonst mit automatischem Backup + Health-Check +
|
||||
Rückroll bei Fehlschlag — siehe Kommentarkopf in `install.sh`).
|
||||
**`init-key` erzeugt den Signaturschluessel dieses Servers. Sichern Sie ihn
|
||||
sofort.** Ohne ihn laesst sich keine ausgestellte Lizenz mehr erneuern, und
|
||||
Kunden muessten einen Ausstellerwechsel bestaetigen.
|
||||
|
||||
Nach der Erstinstallation:
|
||||
Zielzustand: **Port 443 mit TLS, Port 80 leitet dorthin um** -- ausser dem
|
||||
ACME-Pfad, der erreichbar bleiben muss, sonst kann Let's Encrypt nie erneuern.
|
||||
Sitzt ein eigener Reverse Proxy davor, der TLS beendet, laeuft der Dienst ohne
|
||||
`--https` schlicht auf Port 80.
|
||||
|
||||
Der Lizenzserver darf neben TESM auf demselben Host laufen; nginx unterscheidet
|
||||
die beiden am Servernamen, und jede Installation hat ihren eigenen `SITE_KEY`
|
||||
fuer nginx-Site, logrotate, Zertifikate und Sitzungscookie.
|
||||
|
||||
### Aktualisieren
|
||||
|
||||
```bash
|
||||
sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_admin.py
|
||||
sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_master_license.py
|
||||
sudo bash deploy/update.sh --app tesm-license
|
||||
```
|
||||
|
||||
## Update (von einem bereits installierten System aus)
|
||||
Vor jedem Update entsteht ein vollstaendiges Backup, danach laeuft ein
|
||||
Health-Check mit automatischem Rollback.
|
||||
|
||||
---
|
||||
|
||||
## Release bauen
|
||||
|
||||
```bash
|
||||
sudo ./update.sh
|
||||
# oder für eine bestimmte Version statt "latest":
|
||||
TESM_RELEASE_TAG=v1.0.0 sudo -E ./update.sh
|
||||
git tag -a v2.0.0 -m "TESM-Lizenzserver 2.0.0" && git push origin v2.0.0
|
||||
bash deploy/release.sh v2.0.0
|
||||
```
|
||||
|
||||
Lädt das aktuelle Gitea-Release herunter und übergibt an `install.sh`.
|
||||
Heraus kommt `tesm-license-v2.0.0.tar.gz` samt SHA-256. Der Name kommt aus dem
|
||||
Repository -- ein Lizenzserver-Release, das `tesm-...` heisst, verwechselt man
|
||||
genau einmal.
|
||||
|
||||
## Zusammenspiel mit TESM
|
||||
---
|
||||
|
||||
Eine TESM-Instanz aktiviert sich gegen genau einen Lizenzserver
|
||||
(`master_endpoint`, in der Lizenzdatei hinterlegt). Ein Protokoll, zwei
|
||||
Transportwege: online automatisch per HTTPS/JSON, offline als manuell
|
||||
auszutauschender Code — beide Seiten nutzen dieselben Funktionen aus
|
||||
`licensing.py`. Details zum kryptografischen Format und Ablauf siehe die
|
||||
Docstrings in `srv/tesm-license/licensing.py` bzw. dem identischen Modul
|
||||
im [TESM-Repo](https://gitea.int.eertmoed.net/alientim/tesm).
|
||||
## 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
|
||||
(`tesm/license-file/v2\0`) -- eine Signatur aus einem Kontext ist in einem
|
||||
anderen nicht verwendbar.
|
||||
* Kanonisches JSON, damit dieselbe Aussage genau eine Signatur hat.
|
||||
* Nonce gegen Wiedereinspielung, `seq` gegen das Zurueckrollen auf eine aeltere
|
||||
Lizenz.
|
||||
* Aussteller wird beim ersten Kontakt gemerkt (TOFU); ein Wechsel muss
|
||||
ausdruecklich bestaetigt werden.
|
||||
* Der Fingerabdruck der Maschine enthaelt **keinen** Hostnamen -- Umbenennen
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Dokumentation
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| [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 |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## Was diese Fassung anders macht
|
||||
|
||||
* **Keine doppelte `licensing.py`.** Ein Paket, das beide Anwendungen
|
||||
installieren -- statt zweier Dateien, die byte-identisch bleiben sollen.
|
||||
* **Kein root fuer die Anwendung.** Ein einziger allowlisted Helfer mit
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user