Files
tesm-license/deploy/systemd/tesm.service
T
alientimandClaude Opus 5 f7805a2180 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>
2026-09-02 18:01:23 +02:00

79 lines
3.3 KiB
Desktop File

[Unit]
Description=TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten
Documentation=file:/srv/tesm/docs/BETRIEB.md
After=network-online.target
Wants=network-online.target
[Service]
Type=notify-reload
User=tesm
Group=tesm
WorkingDirectory=/srv/tesm
Environment=TESM_BASE_DIR=/srv/tesm
Environment=TESM_DATA_DIR=/srv/tesm/data
Environment=TESM_LOG_DIR=/var/log/tesm
Environment=TESM_WEB_PROCESS=1
Environment=TESM_COOKIE_SECURE=1
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/tesm/venv/bin/gunicorn \
--workers 1 --worker-class gthread --threads 16 --timeout 0 \
--bind 127.0.0.1:5000 \
--access-logfile - --error-logfile - \
tesm.wsgi:app
Restart=always
RestartSec=5
# -- Haertung ---------------------------------------------------------------
# Der Dienst braucht kein Root, keine neuen Privilegien und keinen Zugriff auf
# das System ausserhalb seiner eigenen Verzeichnisse. Was er trotzdem braucht
# (systemctl, nginx, netplan), laeuft ueber den Helfer und sudo.
# Bewusst **kein** NoNewPrivileges: der Dienst ruft den privilegierten Helfer
# ueber sudo auf, und sudo ist setuid. Unter NoNewPrivileges verweigert der
# Kernel die Rechteerhoehung, sudo bricht ab -- und damit jede Systemaktion aus
# der Oberflaeche. Die Sicherheitsgrenze liegt nicht hier, sondern in der
# sudoers-Regel (genau ein erlaubter Pfad) und im Helfer selbst (jedes Argument
# gegen eine Positivliste). Siehe docs/SICHERHEIT.md.
PrivateTmp=yes
PrivateDevices=yes
# ProtectSystem=full statt strict: "strict" sperrt den **ganzen Prozessbaum**
# schreibgeschuetzt -- auch den Helfer, der als root gerade /etc/nginx,
# /etc/netplan und /etc/kea schreiben soll. Gemessen: unter "strict" scheitert
# schon "nginx -t". Mit "full" bleiben /usr und /boot schreibgeschuetzt, /etc
# ist im Namensraum wieder erreichbar -- der unprivilegierte Dienst kommt
# trotzdem nicht heran, denn die Dateien gehoeren root. Die Grenze ist damit
# die gewoehnliche Dateiberechtigung plus die sudoers-Regel, nicht dieser
# Schalter.
ProtectSystem=full
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
LockPersonality=yes
MemoryDenyWriteExecute=no
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# Bewusst **kein** einschraenkendes CapabilityBoundingSet: sudo braucht
# CAP_SETUID/CAP_SETGID, und der Helfer dahinter braucht Root. Was hier fehlt,
# kann auch der Helfer nicht mehr erlangen -- gemessen: jede Einschraenkung
# fuehrt zu "sudo: unable to change to root gid". Die Grenze ist die
# sudoers-Regel und die Argumentpruefung im Helfer, nicht dieser Schalter.
UMask=0027
# Genau die Pfade, die der privilegierte Helfer schreibt -- nicht mehr.
# ProtectSystem=full sperrt /usr, /boot **und /etc**; ohne diese Freigaben
# scheitert der Helfer mit "Read-only file system", obwohl er root ist
# (gemessen). Der unprivilegierte Dienst kommt hier trotzdem nicht heran: die
# Dateien gehoeren root, und sudo laesst nur den einen Helferpfad zu.
ReadWritePaths=/srv/tesm/data /var/log/tesm /mnt/tesm-shares /etc/nginx /etc/netplan /etc/kea /etc/logrotate.d /etc/tesm /var/lib/kea /var/log/kea /run
[Install]
WantedBy=multi-user.target