Vollstaendiger Neubau der Anwendung. Der vorherige Stand bleibt unveraendert im Zweig SONNET5 erhalten. Aufbau: apps/tesm (Anwendung), packages/tesm-core (gemeinsamer Kern), packages/tesm-licensing (Lizenzprotokoll), deploy (Installation, systemd, privilegierter Helfer), docs, tests. Der Lizenzserver liegt in seinem eigenen Repository; beide Repositorien bringen die gemeinsamen Pakete mit, damit sich jedes allein installieren laesst. Die wichtigsten Unterschiede zum Vorgaenger, jeweils an der Stelle im Code kommentiert, an der der Fehler entstanden ist: * 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. * Die nginx-Site wird bei jedem Lauf inhaltlich verglichen und erneuert. * Jede erzeugte Konfiguration wird vor dem Uebernehmen geprueft (nginx, Kea). * Kein Hostname im Lizenz-Fingerabdruck. * Zwei Installationen auf einem Host stoeren sich nicht (eigener SITE_KEY). * Verschachtelte Datenbankverbindungen sind ein Fehler, kein Deadlock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
3.3 KiB
Desktop File
79 lines
3.3 KiB
Desktop File
[Unit]
|
|
Description=TESM-Lizenzserver
|
|
Documentation=file:/srv/tesm-license/docs/BETRIEB.md
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=notify-reload
|
|
User=tesm-license
|
|
Group=tesm-license
|
|
WorkingDirectory=/srv/tesm-license
|
|
Environment=TESM_LICENSE_BASE_DIR=/srv/tesm-license
|
|
Environment=TESM_LICENSE_DATA_DIR=/srv/tesm-license/data
|
|
Environment=TESM_LICENSE_LOG_DIR=/var/log/tesm-license
|
|
Environment=TESM_LICENSE_WEB_PROCESS=1
|
|
Environment=TESM_LICENSE_COOKIE_SECURE=1
|
|
Environment=PYTHONUNBUFFERED=1
|
|
ExecStart=/srv/tesm-license/venv/bin/gunicorn \
|
|
--workers 1 --worker-class gthread --threads 8 \
|
|
--bind 127.0.0.1:5001 \
|
|
--access-logfile - --error-logfile - \
|
|
tesm_license.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-license/data /var/log/tesm-license /etc/nginx /etc/netplan /etc/logrotate.d /etc/tesm /run
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|