Schreibzugriffe von Linux-Diensten begrenzen und Sperren prüfen

Schreibzugriffe von Linux-Diensten begrenzen und Sperren prüfen

Wenn mehrere Hintergrunddienste auf demselben physischen Server laufen, müssen die Programme meist nur in ihre Zustandsverzeichnisse schreiben. Zu großzügig vergebene Eigentumsrechte an Verzeichnissen können ihnen jedoch erlauben, auch bereitgestellte Programmdateien zu verändern. Ein eigenes Dienstkonto schafft nur eine erste Grenze. Erst wenn das Dateisystem aus Sicht des Dienstes schreibgeschützt ist und nur die erforderlichen Schreibpfade freigegeben bleiben, lässt sich der Bereich möglicher Fehlschreibzugriffe weiter eingrenzen. Bei der Abnahme genügt es nicht, dass der Dienst erfolgreich startet: Es muss auch nachgewiesen werden, dass Schreibzugriffe an gesperrten Stellen tatsächlich scheitern.

1. Erst die Schreibpfade erfassen, dann die Sandbox einrichten

Dieser Beitrag verwendet Ubuntu Server 24.04 LTS als Beispiel und verwaltet systemweite Dienste mit systemd. Die Schritte erfordern Administratorrechte und Python 3. Alle Pfade gehören zu einer separaten Testumgebung; bestehende produktive Units werden nicht verändert. Die Zugangsdaten für einen Dedicated Server von AmdMetal lassen sich nach der Zahlung in der Konsole abrufen. Der Zugriff auf Linux erfolgt über SSH.

Unterscheiden Sie zunächst zwischen Programmcode, persistenten Zustandsdaten, Laufzeitdateien und temporären Dateien. Der Code sollte vom Deployment-Konto aktualisiert werden; das Dienstkonto erhält nur Lesezugriff. Das Zustandsverzeichnis enthält Daten, die Neustarts überdauern müssen. Laufzeitdateien wie Sockets und Prozesskennungen sowie temporäre Dateien sind gesondert zu erfassen. Schreibt der Dienst seine Protokolle auf die Standardausgabe, kann das Logging-System sie übernehmen. Dann muss kein ganzer Verzeichnisbaum für Protokolldateien zum Schreiben freigegeben werden.

Die zentrale Einstellung ist ProtectSystem=strict: Sie setzt die Dateisystemhierarchie im Mount-Namespace des Dienstes auf schreibgeschützt, mit Ausnahmen unter anderem für bestimmte API-Dateisysteme. ReadWritePaths erhält anschließend die Schreibmöglichkeit für die angegebenen Verzeichnisse. Die Einstellung verändert weder den globalen Mount-Zustand des Hosts noch stellt sie eine vollständige Prozessisolation dar.

Schreibschutz bedeutet nicht Vertraulichkeit. Lesbare Dateien werden dadurch nicht automatisch verborgen, und auch der Netzwerkzugriff wird nicht automatisch eingeschränkt. Ebenso wenig ersetzt eine Schreibfreigabeliste die Verzeichnisberechtigungen.

2. Eine Berechtigungsbasis schaffen, die die Fehlerursache erkennen lässt

Für den Test werden zwei Verzeichnisse vorbereitet: In eines soll der Dienst schreiben dürfen. Im anderen erlauben die normalen Dateirechte zwar das Schreiben, die Sandbox soll es jedoch verhindern. Nur so lässt sich ein falsch positives Ergebnis ausschließen, bei dem der Zugriff ohnehin an fehlenden Berechtigungen scheitert. Prüfen Sie vorher, ob der folgende Kontoname und die Pfade bereits von bestehenden Diensten verwendet werden. Sie sind eigens für den Test vorgesehen und gehören nicht zu den standardmäßigen Anmeldekonten des Servers.

sudo useradd --system --no-create-home \
  --shell /usr/sbin/nologin sandbox-check

sudo install -d -o root -g root -m 0755 \
  /srv/sandbox-check

sudo install -d -o sandbox-check -g sandbox-check -m 0750 \
  /srv/sandbox-check/blocked \
  /var/lib/sandbox-check

sudo runuser -u sandbox-check -- /usr/bin/python3 -c \
'import tempfile, os; f, p = tempfile.mkstemp(dir="/srv/sandbox-check/blocked"); os.close(f); os.unlink(p)'

Der letzte Befehl muss erfolgreich enden: Er erstellt im später zu sperrenden Verzeichnis eine temporäre Datei mit zufälligem Namen und löscht sie sofort wieder. Schlägt das fehl, prüfen Sie zunächst die Berechtigungen zum Durchlaufen der Verzeichnisse, die ACLs und die Eigenschaften des zugrunde liegenden Mounts. Solange diese Ausgangslage nicht geklärt ist, dürfen spätere Fehler nicht der Sandbox zugeschrieben werden.

Das bereitgestellte Skript selbst bleibt im Eigentum des Administrators, damit das Dienstkonto die Prüflogik nicht verändern kann. Die Testverzeichnisse gehören bewusst einem Konto, das dort schreiben darf; dies dient ausschließlich dazu, die Ursache der Einschränkung nachzuweisen. Bei produktiven Anwendungen muss das Codeverzeichnis für das Dienstkonto weiterhin nicht beschreibbar sein. Diese Ausnahme aus dem Test darf nicht übernommen werden.

3. Schreibfreigaben und eine unabhängige Prüfung konfigurieren

Speichern Sie das folgende Skript unter /srv/sandbox-check/probe.py, setzen Sie den Administrator als Eigentümer und vergeben Sie die Berechtigung 0644. Der Schreibzugriff auf den freigegebenen Pfad muss gelingen. Beim gesperrten Pfad muss der Fehler für ein schreibgeschütztes Dateisystem zurückgegeben werden. Jeder andere Fehler gilt als fehlgeschlagene Abnahme.

import errno
import os
import tempfile

def create_and_remove(directory):
    fd, path = tempfile.mkstemp(prefix="probe-", dir=directory)
    try:
        with os.fdopen(fd, "w") as stream:
            stream.write("sandbox-check\n")
            stream.flush()
            os.fsync(stream.fileno())
    finally:
        os.unlink(path)

create_and_remove("/var/lib/sandbox-check")

try:
    create_and_remove("/srv/sandbox-check/blocked")
except OSError as exc:
    if exc.errno != errno.EROFS:
        raise
else:
    raise RuntimeError("Write boundary was not enforced")

print("Allowed write passed; blocked write returned EROFS")

Die zufälligen Dateinamen verhindern, dass vorhandene Daten überschrieben werden. fsync dient hier dazu, die Aufrufkette beim Schreiben zu prüfen; damit ist weder ein Leistungstest noch ein Nachweis der Datenbeständigkeit bei Stromausfall erbracht. Die Ausgabe des Prüfskripts enthält das Abnahmeergebnis, aber keine Zugangsdaten oder Anwendungsinhalte.

Erstellen Sie /etc/systemd/system/sandbox-check.service:

[Unit]
Description=Filesystem sandbox verification

[Service]
Type=oneshot
User=sandbox-check
Group=sandbox-check
ExecStart=/usr/bin/python3 /srv/sandbox-check/probe.py
ProtectSystem=strict
ReadWritePaths=/var/lib/sandbox-check
PrivateTmp=true
NoNewPrivileges=true

PrivateTmp=true stellt dem Dienst eine isolierte Sicht auf die temporären Verzeichnisse bereit. „Nur ein persistentes Verzeichnis ist zum Schreiben freigegeben“ bedeutet daher nicht, dass der gesamte Prozess nur einen einzigen beschreibbaren Ort hat. NoNewPrivileges=true verhindert den Erwerb zusätzlicher Privilegien durch die Ausführung von Programmen, entzieht aber keine bereits erteilten Dateizugriffsrechte.

4. Erfolgreiche und verweigerte Zugriffe gemeinsam prüfen

Prüfen Sie zunächst die Unit, laden Sie anschließend die Definitionen neu und führen Sie den Dienst aus:

sudo systemd-analyze verify /etc/systemd/system/sandbox-check.service
sudo systemctl daemon-reload
sudo systemctl start sandbox-check.service

sudo systemctl show sandbox-check.service \
  -p Result -p ExecMainStatus -p ProtectSystem -p ReadWritePaths

sudo journalctl -u sandbox-check.service -b --no-pager -n 30

Erwartet werden Result=success und ExecMainStatus=0 sowie die Erfolgsmeldung des Prüfskripts im Protokoll. Dass der einmalig ausgeführte Dienst nach seinem Ende nicht mehr als laufend angezeigt wird, ist normal. Weder ein dauerhafter Betrieb noch das Beibehalten des aktiven Zustands ist hier konfiguriert.

Die Konfigurationswerte allein belegen nicht, dass die Grenze wirksam ist. Ein belastbarer Nachweis umfasst alle drei Punkte: Außerhalb der Sandbox kann dasselbe Konto schreiben, innerhalb der Sandbox gelingt das Schreiben im freigegebenen Verzeichnis, und der Schreibversuch im gesperrten Verzeichnis liefert EROFS. Bei EACCES ist lediglich belegt, dass normale Berechtigungen oder eine andere Sicherheitsrichtlinie den Zugriff verweigern. Das erfüllt die Anforderungen dieser Prüfung nicht.

Bei produktiven Diensten auch die tatsächlichen Anwendungsabläufe testen

Nachdem das Prüfskript erfolgreich durchgelaufen ist, erstellen Sie eine separate Härtungskonfiguration für den eigentlichen Dienst. Führen Sie einen Anwendungstest durch, der Uploads, Cache-Erzeugung, das Speichern von Zustandsdaten und das Aufräumen temporärer Dateien abdeckt. Aktualisiert die Anwendung Daten über eine temporäre Datei mit anschließendem Umbenennen, muss auch das Zielverzeichnis selbst beschreibbar sein. Eine Ausnahme nur für eine einzelne Datei reicht häufig nicht aus.

Prüfen Sie außerdem die tatsächlichen Ziele symbolischer Links. Ein Link, der laut Konfiguration im Zustandsverzeichnis liegt, kann letztlich auf einen anderen, schreibgeschützten Pfad verweisen. Verdecken Sie solche Probleme nicht, indem Sie die Berechtigungen des gesamten übergeordneten Verzeichnisses ausweiten.

5. Fehler eingrenzen, Änderungen zurücknehmen und Ergebnisse dokumentieren

Scheitert der Dienst vor dem Start des Programms, prüfen Sie zuerst im Protokoll, ob der Mount-Namespace nicht eingerichtet werden konnte, ein Pfad fehlt oder ein Schreibfehler in der Konfiguration vorliegt. Tritt der Fehler erst nach dem Start auf, orientieren Sie sich am Ort des Dateizugriffs: Schreibt das Programm unbemerkt Cache-Dateien in das Codeverzeichnis? Benötigt es gemeinsam genutzte temporäre Dateien oder ein zusätzliches Laufzeitverzeichnis?

Benötigt ein produktiver Dienst ein Laufzeitverzeichnis, kann systemd dessen Lebenszyklus mit RuntimeDirectory verwalten. Testen Sie anschließend den tatsächlichen Zugriff. Nehmen Sie nicht pauschal ganz /var oder /srv in die Freigabeliste auf. Geben Sie stattdessen jeden Pfad einzeln anhand des nachgewiesenen Schreibbedarfs frei.

Nach einer Konfigurationsänderung lädt daemon-reload nur die Unit-Definitionen neu. Die neue Sandbox wird dadurch nicht auf bereits laufende Prozesse angewendet. Bei produktiven Diensten muss ein kontrollierter Neustart eine neue Prozessumgebung erzeugen. Danach sind sowohl die erlaubten als auch die zu verweigernden Schreibzugriffe erneut zu testen.

Um den Test zurückzunehmen, stoppen Sie die Unit, löschen die dafür erstellte Unit-Datei und laden die Definitionen erneut. Bereinigen Sie die Testdatenverzeichnisse und das Konto separat, nachdem Sie sich vergewissert haben, dass keine Prozesse sie mehr verwenden. Verwenden Sie keinen pauschalen Befehl, der produktive Verzeichnisse rekursiv löscht. Bei einem produktiven Dienst stellen Sie stattdessen die vor der Änderung gesicherte Konfiguration wieder her und prüfen den Dienst nach einem Neustart.

Dokumentieren Sie abschließend die wirksame Unit-Konfiguration, das Dienstkonto, die Liste der freigegebenen Schreibpfade, den Exit-Status des Prüfskripts und die Ergebnisse des Anwendungstests. Ziel der Abnahme ist kein höherer Sicherheits-Score, sondern der Nachweis, dass jeder notwendige Schreibzugriff gelingt und jeder erwartungsgemäß gesperrte Schreibzugriff nachvollziehbar und reproduzierbar verweigert wird.

Häufig gestellte Fragen

Erteilt ReadWritePaths fehlende Schreibrechte?

Nein. Die Einstellung lässt beschreibbare Pfade innerhalb der schreibgeschützten Dateisystem-Sandbox zu, verändert aber weder Eigentümer noch Modusbits oder ACLs. Das Dienstkonto benötigt zusätzlich die normalen Dateirechte.

Verhindert ProtectSystem=strict das Lesen geheimer Dateien?

Nein. Die Einstellung begrenzt hauptsächlich Schreibzugriffe. Lesbare Dateien werden nicht automatisch verborgen und Netzwerkzugriffe nicht gesperrt. Geheimnisrechte, ausgeblendete Pfade und Netzwerkgrenzen sind separat einzurichten.

AMD EPYC 7543P Dedicated Server

ab $458.7/Monat

AMD EPYC 7543P Server konfigurieren Mietpreise für Dedicated Server ansehen

Serverkonfiguration im Detail

Der AMD EPYC 7543P steht exklusiv als Komplettsystem bereit; Modell und Optionen werden auf der Konfigurationsseite zusammengestellt.