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>
296 lines
10 KiB
Markdown
296 lines
10 KiB
Markdown
# Betrieb
|
|
|
|
Dieses Dokument richtet sich an die Person, die die Anlage installiert,
|
|
aktualisiert und im Fehlerfall wieder zum Laufen bringt. Es beschreibt, was auf
|
|
dem Host tatsaechlich passiert -- nicht, wie die Oberflaeche bedient wird.
|
|
|
|
---
|
|
|
|
## 1. Was wo liegt
|
|
|
|
| Zweck | Pfad |
|
|
|---|---|
|
|
| Anwendung (Code, venv, statische Dateien) | `/srv/<name>/` |
|
|
| Instanzdaten (Datenbank, Schluessel, Lizenz) | `/srv/<name>/data/` |
|
|
| Protokolle | `/var/log/<name>/` |
|
|
| Umgebungsdatei | `/etc/tesm/<name>.env` |
|
|
| Zertifikate (selbstsigniert) | `/etc/tesm/certs/<name>/` |
|
|
| nginx-Site | `/etc/nginx/sites-available/<name>` |
|
|
| systemd-Unit | `/etc/systemd/system/<name>.service` |
|
|
| Privilegierter Helfer | `/usr/local/lib/tesm/tesm-helper` |
|
|
| Verwaltungswerkzeug | `/usr/local/bin/<name>-admin` |
|
|
|
|
`<name>` ist `tesm` bzw. `tesm-license` -- oder, bei einer zusaetzlichen
|
|
Instanz, `tesm-<instanz>`. Dieser Name (der **SITE_KEY**) benennt alles, was
|
|
ausserhalb von `/srv/<name>` liegt. Deshalb koennen zwei Installationen
|
|
derselben Anwendung nebeneinander laufen, ohne sich gegenseitig die
|
|
nginx-Site, die netplan-Datei oder das Sitzungscookie zu ueberschreiben.
|
|
|
|
Alles, was ein Mensch verlieren wuerde, liegt unter `data/`. Das Verzeichnis
|
|
wird von `install.sh` nie angefasst.
|
|
|
|
---
|
|
|
|
## 2. Installation
|
|
|
|
```bash
|
|
sudo bash deploy/install.sh --app tesm
|
|
sudo bash deploy/install.sh --app tesm-license
|
|
```
|
|
|
|
Wichtige Optionen:
|
|
|
|
| Option | Wirkung |
|
|
|---|---|
|
|
| `--https` | Port 443 mit TLS, Port 80 leitet dorthin um. Ohne `--domain` entsteht ein selbstsigniertes Zertifikat. |
|
|
| `--domain <name>` | Oeffentlicher Name. Setzt `server_name`; die Site ist dann **nicht** mehr die Vorgabe-Site. |
|
|
| `--acme-email <a>` | Erst zusammen mit `--domain` wird ein Let's-Encrypt-Zertifikat angefordert (und HSTS gesetzt). Ohne die Adresse bleibt es selbstsigniert -- interne Namen wie `lizenz.firma.local` kann keine CA bestaetigen. |
|
|
| `--instance <name>` | Zusaetzliche Instanz neben der Hauptinstallation. |
|
|
| `--port` / `--http-port` / `--https-port` | Abweichende Ports (nur fuer Testinstanzen sinnvoll). |
|
|
| `--skip-packages` | Keine Systempakete installieren. |
|
|
|
|
Danach:
|
|
|
|
```bash
|
|
sudo -u tesm tesm-admin create-admin
|
|
```
|
|
|
|
Beim Lizenzserver zusaetzlich, **in dieser Reihenfolge**:
|
|
|
|
```bash
|
|
sudo -u tesm-license tesm-license-admin init-key # Signaturschluessel -- sofort sichern!
|
|
sudo -u tesm-license tesm-license-admin bootstrap # eigene Master-Lizenz
|
|
sudo -u tesm-license tesm-license-admin create-admin
|
|
```
|
|
|
|
> **Der Signaturschluessel ist nicht wiederherstellbar.** Geht
|
|
> `data/master_signing_key.json` verloren, ist keine ausgestellte Lizenz mehr
|
|
> pruefbar. Sichern Sie ihn getrennt vom Server.
|
|
|
|
### Zwei Anwendungen auf einem Host
|
|
|
|
Beide wollen Port 80 und 443. nginx unterscheidet sie am Namen:
|
|
|
|
```bash
|
|
sudo bash deploy/install.sh --app tesm --https
|
|
sudo bash deploy/install.sh --app tesm-license --https --domain lizenz.firma.local
|
|
```
|
|
|
|
Die Installation **ohne** Domain wird zur Vorgabe-Site (`default_server`) und
|
|
beantwortet alles, was zu keinem Namen passt. Zwei namenlose Sites auf
|
|
demselben Port weist nginx ab -- richtig so, und beim Uebernehmen sofort
|
|
sichtbar.
|
|
|
|
---
|
|
|
|
## 3. Aktualisierung
|
|
|
|
```bash
|
|
sudo bash deploy/install.sh --app tesm
|
|
```
|
|
|
|
Derselbe Befehl. Das Skript erkennt an der vorhandenen Datenbank, dass es eine
|
|
Aktualisierung ist, und tut dann zusaetzlich:
|
|
|
|
1. **Vollstaendige Sicherung** nach `/srv/<name>-backup-<zeitstempel>` --
|
|
unabhaengig davon, ob sich das Schema geaendert hat.
|
|
2. Dateiabgleich; `data/` bleibt unangetastet.
|
|
3. Schema-Migrationen beim Start (versioniert, mit Pruefsumme).
|
|
4. **Gesundheitspruefung.** Antwortet die Anwendung nicht, wird der alte Stand
|
|
automatisch zurueckgeholt; die fehlgeschlagene Version bleibt unter
|
|
`/srv/<name>-failed-<zeitstempel>` zur Analyse liegen.
|
|
|
|
Eine bereits eingerichtete HTTPS-Konfiguration bleibt erhalten, auch ohne
|
|
`--https`.
|
|
|
|
---
|
|
|
|
## 4. HTTPS
|
|
|
|
Der Zielzustand ist immer: **Port 443 mit TLS, Port 80 leitet um, ausser dem
|
|
ACME-Pfad.**
|
|
|
|
Drei Wege dorthin:
|
|
|
|
**a) Bei der Installation** -- siehe oben, `--https`.
|
|
|
|
**b) Nachtraeglich ueber die Kommandozeile:**
|
|
|
|
```bash
|
|
sudo -u tesm tesm-admin web-setup --https --self-signed --apply
|
|
sudo -u tesm tesm-admin web-setup --https --domain tesm.firma.de \
|
|
--acme --acme-email it@firma.de --hsts --apply
|
|
```
|
|
|
|
`--print-config` zeigt die Datei, ohne etwas zu schreiben.
|
|
|
|
**c) In der Oberflaeche** unter *Einstellungen -> Webserver und TLS*.
|
|
|
|
Danach muss `COOKIE_SECURE` in `/etc/tesm/<name>.env` auf `1` stehen (alle drei
|
|
Wege erledigen das selbst) und der Dienst neu gestartet werden.
|
|
|
|
### Die Site-Datei wird immer neu erzeugt
|
|
|
|
Sie ist **kein** Ort fuer Handarbeit. Jede Uebernahme schreibt sie vollstaendig
|
|
aus der Vorlage neu. Was frueher von Hand hineingeschrieben wurde -- Domain,
|
|
Ports, Zertifikatspfade, HSTS, Upload-Grenze -- ist heute Einstellung in der
|
|
Anwendung.
|
|
|
|
Der Grund steht im Vorgaengerprojekt: dort wurde die Datei nur angelegt, wenn
|
|
sie fehlte. Ein einmal falscher `alias`-Pfad ueberlebte dadurch jahrelang jedes
|
|
Update, und die Oberflaeche kam die ganze Zeit ohne Design.
|
|
|
|
### Selbstsigniert oder Let's Encrypt?
|
|
|
|
* **Oeffentlich erreichbar, echter DNS-Name:** Let's Encrypt. Nur dann ist HSTS
|
|
sinnvoll.
|
|
* **Geschlossenes Netz:** selbstsigniert. Browser zeigen eine Warnung, die
|
|
Verbindung ist trotzdem verschluesselt und das Sitzungscookie darf `Secure`
|
|
tragen. **HSTS hier nicht einschalten** -- der Browser merkt sich die Vorgabe
|
|
monatelang und die Warnung laesst sich dann nicht mehr wegklicken.
|
|
|
|
Die Erneuerung von Let's Encrypt laeuft ueber `certbot certonly --webroot`
|
|
(bewusst nicht ueber das nginx-Plugin, das die erzeugte Site umschreiben
|
|
wuerde). Der ACME-Pfad bleibt auch bei aktiver Umleitung erreichbar.
|
|
|
|
---
|
|
|
|
## 5. Dienste
|
|
|
|
```bash
|
|
systemctl status tesm.service # Weboberflaeche (gunicorn)
|
|
systemctl status tesm-monitor.service # Ueberwachungsschleife (nur TESM)
|
|
systemctl status tesm-license.service
|
|
journalctl -u tesm -n 100 -f
|
|
```
|
|
|
|
Die Units laufen unprivilegiert (`User=tesm`) mit
|
|
`NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp` und einer engen
|
|
`RestrictAddressFamilies`-Liste. Schreibrechte gibt es nur auf `/srv/<name>/data`
|
|
und `/var/log/<name>`.
|
|
|
|
---
|
|
|
|
## 6. Root-Rechte
|
|
|
|
Die Anwendung hat keine. Alles, was Root braucht, geht durch **einen** Pfad:
|
|
|
|
```
|
|
/usr/local/lib/tesm/tesm-helper <verb> [argumente]
|
|
```
|
|
|
|
Der Helfer gehoert `root`, ist per sudoers ausschliesslich fuer genau diesen
|
|
Pfad mit `NOPASSWD` freigegeben und prueft jedes Argument gegen eine
|
|
Positivliste. Er reicht nie etwas an eine Shell weiter (kein `eval`, kein
|
|
`sh -c`).
|
|
|
|
Verben ansehen:
|
|
|
|
```bash
|
|
sudo /usr/local/lib/tesm/tesm-helper
|
|
grep -n ')$' /usr/local/lib/tesm/tesm-helper
|
|
```
|
|
|
|
Regel fuer Aenderungen: **Jedes neue Verb braucht eine Argumentpruefung.**
|
|
`tests/test_repo_hygiene.py` erzwingt, dass jedes der Anwendung bekannte Verb
|
|
im Helfer vorkommt und dass jede Pfadpruefung `..` abweist.
|
|
|
|
---
|
|
|
|
## 7. Sicherung und Wiederherstellung
|
|
|
|
In der Oberflaeche unter *Sicherung* -- verschluesselte Ausfuhr (Argon2id +
|
|
AES-256-GCM) mit einem selbst gewaehlten Kennwort. Der Import zeigt zuerst eine
|
|
Vorschau, bevor irgendetwas geschrieben wird.
|
|
|
|
Auf dem Host reicht:
|
|
|
|
```bash
|
|
sudo systemctl stop tesm
|
|
sudo tar -czf /root/tesm-data-$(date +%F).tar.gz -C /srv/tesm data
|
|
sudo systemctl start tesm
|
|
```
|
|
|
|
Unverzichtbar:
|
|
|
|
* `data/app.db` -- alle Daten
|
|
* `data/secret.key` -- ohne sie sind alle Sitzungen ungueltig
|
|
* `data/data.keys` -- ohne sie sind **alle gespeicherten Geheimnisse verloren**
|
|
* `data/license.json` und `data/license_key.json`
|
|
* Lizenzserver zusaetzlich: `data/master_signing_key.json`
|
|
|
|
---
|
|
|
|
## 8. Wenn etwas nicht geht
|
|
|
|
**Die Anwendung antwortet nicht.**
|
|
```bash
|
|
journalctl -u tesm -n 200 --no-pager
|
|
curl -s http://127.0.0.1:5000/gesundheit
|
|
```
|
|
Antwortet gunicorn, aber der Browser nicht, liegt es an nginx.
|
|
|
|
**nginx laesst sich nicht neu laden.**
|
|
```bash
|
|
sudo nginx -t
|
|
```
|
|
Die Uebernahme aus der Anwendung nimmt eine abgelehnte Konfiguration selbst
|
|
zurueck. Eine von Hand bearbeitete Datei tut das nicht -- in dem Fall hilft
|
|
`install.sh`, das eine unbrauchbare Site durch die Vorlage ersetzt.
|
|
|
|
**Anmeldung schlaegt ohne Fehlermeldung fehl.**
|
|
Fast immer `COOKIE_SECURE=1` ohne HTTPS. Der Browser bekommt dann ein Cookie,
|
|
das er ueber HTTP nie zuruecksendet.
|
|
```bash
|
|
grep COOKIE_SECURE /etc/tesm/tesm.env
|
|
```
|
|
|
|
**"Die Anfrage kam von einer fremden Herkunft (Origin/Referer)."**
|
|
Der Reverse Proxy reicht den Host falsch durch. In der Site-Datei muss
|
|
`proxy_set_header Host $http_host;` stehen -- **nicht** `$host`, das verwirft
|
|
den Port.
|
|
|
|
**Statische Dateien fehlen, die Oberflaeche ist unformatiert.**
|
|
```bash
|
|
grep alias /etc/nginx/sites-available/tesm
|
|
ls /srv/tesm/static /srv/tesm/static-core
|
|
```
|
|
Beide `alias`-Pfade muessen in das Installationsverzeichnis zeigen.
|
|
|
|
**Zwei Instanzen stoeren sich.**
|
|
`SITE_KEY` in beiden `/etc/tesm/*.env` vergleichen -- er muss sich
|
|
unterscheiden. Sonst teilen sich beide nginx-Site und Sitzungscookie.
|
|
|
|
**Die Wartung meldet "Der SSH-Host-Schluessel ist unbekannt".**
|
|
Kein Fehler, sondern die Absicht: ein Hintergrundauftrag darf keinen fremden
|
|
Schluessel akzeptieren. Die Freigabe erfolgt von Hand, in zwei Schritten, auf
|
|
der Detailseite des Geraets (*Clients -> Geraet -> SSH-Host-Schluessel*):
|
|
erst **Auslesen**, dann den Fingerprint mit einer unabhaengigen Quelle
|
|
vergleichen, dann **Freigeben**. Die Wartungsuebersicht zeigt in einer eigenen
|
|
Spalte, fuer welche Geraete das noch offen ist. Fuer Switche liegt dieselbe
|
|
Freigabe auf der Switch-Detailseite.
|
|
|
|
Der Schluessel wird nach **IP und Port** gespeichert, nicht nach Name -- TESM
|
|
verbindet sich immer ueber die hinterlegte IP-Adresse. Aendert sich die IP
|
|
eines Geraets, ist der Schluessel erneut freizugeben.
|
|
|
|
**Die Pruefkette des Aenderungsprotokolls ist gebrochen.**
|
|
Unter *Diagnose* sichtbar. Das Protokoll ist verkettet gehasht; ein Bruch heisst,
|
|
dass jemand direkt in die Datenbank geschrieben hat. Der Eintrag, ab dem es
|
|
nicht mehr stimmt, wird genannt.
|
|
|
|
---
|
|
|
|
## 9. Zurueck auf eine aeltere Version
|
|
|
|
```bash
|
|
sudo systemctl stop tesm
|
|
sudo mv /srv/tesm /srv/tesm-kaputt
|
|
sudo cp -a /srv/tesm-backup-<zeitstempel> /srv/tesm
|
|
sudo systemctl start tesm
|
|
```
|
|
|
|
Schema-Migrationen laufen nur vorwaerts. Ein Ruecksprung ueber eine
|
|
Schema-Aenderung hinweg braucht deshalb auch die Datenbank aus der Sicherung --
|
|
genau das liegt im Backup-Verzeichnis.
|