Drei Fehler, alle drei gemessen statt vermutet.
1. Freigaben wurden nie eingebunden ("mount error(1): Operation not
permitted"). Ursache war NICHT der Mount-Namensraum, sondern
SystemCallFilter=@system-service: darin fehlen mount/umount2, und
SystemCallErrorNumber=EPERM macht daraus genau diese Meldung. Nachgewiesen
in einem nachgebauten Sandkasten: mit "@mount" gelingt die Einbindung,
ohne nicht. Die Unit von TESM erlaubt jetzt "@system-service @mount"; alles
andere an der Haertung bleibt. Der zuvor eingebaute Umweg ueber nsenter ist
zurueckgenommen -- RestrictNamespaces=yes verbietet setns, er konnte nie
funktionieren.
Eingebunden wird damit im Namensraum des Webprozesses. Deshalb ist die
Aufraeumaufgabe fuer abgelaufene Einbindungen aus dem Ueberwachungsdienst in
den Webprozess gewandert: ein anderer Dienst sieht diese Einbindungen nicht
und haette Datenbankzeilen als geloest markiert, waehrend die Freigabe
eingebunden blieb.
2. apt-Installationen aus der Oberflaeche scheiterten mit "dpkg returned an
error code (1)". Der Sandkasten wird an dpkg und dessen Maintainer-Skripte
weitervererbt; mit ProtectKernelLogs=yes liefert "dmesg" ein EPERM und ein
Postinst-Skript bricht daran ab. apt laeuft jetzt ueber systemd-run, also
von PID 1 gestartet und damit ohne unsere Haertung.
3. Der RPC-Neustart loeste SSH aus. run_reboot entschied den Weg anhand von
device["category"] -- und dieser Schluessel bedeutet je Herkunft etwas
anderes: die Wartungsabfrage legt die Kategorie der *Zugangsdaten* darunter,
inventory.get_device die des *Geraets*. Ueber die Statusuebersicht kam ein
Windows-Geraet mit leerer Geraetekategorie an, wurde fuer Linux gehalten und
ueber SSH auf Port 22 angesprochen, bis das Zeitlimit griff. Der Weg wird
jetzt uebergeben -- entschieden wird er ohnehin schon vorher in
restart.resolve. Ausserdem zeigte die Neustart-Historie alles ausser "ssh"
als "PoE-Port"; RPC hat jetzt einen eigenen Eintrag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
4.2 KiB
Desktop File
92 lines
4.2 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
|
|
# "@mount" zusaetzlich zu @system-service: die Dateifreigaben werden ueber
|
|
# mount.cifs eingebunden, und @system-service enthaelt mount/umount2 nicht.
|
|
# Ohne diese Ergaenzung scheitert jede Einbindung mit "mount error(1):
|
|
# Operation not permitted" -- gemessen, und zwar unabhaengig von Rechten,
|
|
# Zugangsdaten und Mount-Optionen: aus einer normalen Root-Shell gelingt
|
|
# derselbe Aufruf sofort. Der Umweg ueber nsenter in den Namensraum des Wirts
|
|
# ist keine Alternative, weil "RestrictNamespaces=yes" setns verbietet.
|
|
#
|
|
# Eingebunden wird damit im Mount-Namensraum *dieses* Dienstes. Das ist genau
|
|
# richtig: nur er liest die Dateien, andere Prozesse haben dort nichts zu
|
|
# suchen, und mit dem Dienst verschwinden die Einbindungen wieder. Die
|
|
# Aufraeumaufgabe fuer abgelaufene Einbindungen laeuft deshalb im Webprozess --
|
|
# der Ueberwachungsdienst koennte sie nicht einmal sehen.
|
|
SystemCallFilter=@system-service @mount
|
|
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
|