systemd-Credentials für Service-Geheimnisse - Daten übergeben, ohne sie in die Umgebung zu legen
Ein Hintergrunddienst benötigt ein Datenbankpasswort, bevor er sinnvoll arbeiten kann. Dieses Passwort in Environment= einzutragen, wirkt bequem: Die Anwendung kann bereits Umgebungsvariablen lesen und die Unit bleibt kurz. Doch diese Bequemlichkeit führt zu einer wichtigeren Frage: Ist die Prozessumgebung ein geeigneter Kanal für geheime Daten?
Das systemd-Handbuch zur Ausführungsumgebung beantwortet diese Frage direkt. Umgebungsvariablen eignen sich demnach nicht für Geheimnisse, weil für eine Unit konfigurierte Werte über die IPC-Schnittstellen des Managers offengelegt und entlang des Prozessbaums vererbt werden können, möglicherweise über Grenzen hinweg, die diese Werte nicht erhalten sollten. EnvironmentFile= kann einen Wert aus dem Unit-Text heraushalten, der Dienst erhält ihn aber weiterhin als Umgebungsvariable.
systemd-Credentials bieten einen anderen Übergabekanal. Sie beseitigen nicht die Notwendigkeit, ein Geheimnis zu schützen, zu rotieren oder zu widerrufen. Sie ermöglichen dem Dienst jedoch, es aus einer Datei pro Unit zu lesen, statt den Wert in seiner Umgebung mitzuführen. Diese engere Aussage ist auf einem kleinen Debian-Server nützlich, solange ihre Grenzen sichtbar bleiben.
Was ändert sich, wenn ein Geheimnis zum Credential wird?
Ein systemd-Credential ist ein Text- oder Binärobjekt begrenzter Größe, das an eine Unit übergeben wird. Mit LoadCredential= liest der Service Manager eine Quelldatei oder einen Socket und erstellt eine separate Kopie für die Unit. Der Dienst sieht diese Kopie in einem schreibgeschützten Credential-Verzeichnis. Laut systemd.exec(5) ist dieses Verzeichnis für den konfigurierten Benutzer der Unit und für root zugänglich. Wenn Plattform und Berechtigungen es erlauben, liegt es außerdem in nicht auslagerbarem Speicher.
Der Pfad zu diesem Verzeichnis steht dem Prozess als $CREDENTIALS_DIRECTORY zur Verfügung. In Unit-Einstellungen wird der Platzhalter %d zum selben Verzeichnis erweitert. Der Unterschied ist wichtig: Wenn ein Pfad wie /run/credentials/example.service/db-password in einer Umgebungsvariable steht, befindet sich dort nicht das Passwort selbst.
Dies verändert das Muster der Offenlegung, macht die Daten aber nicht geheimnisvoll. Ein Prozess, der das Passwort berechtigterweise benötigt, kann es lesen. root kann es lesen. Eine Schwachstelle, durch die ein Angreifer als Dienst handeln kann, kann ihm möglicherweise ebenfalls das Lesen erlauben. Credentials verringern vermeidbare Offenlegung; sie bilden keine Grenze gegenüber dem vorgesehenen Empfänger.
Eine minimale Einrichtung mit Klartextquelle
Angenommen, ein Dienst namens example-worker.service läuft unter einem eigenen Konto. Seine Unit kann eine von root kontrollierte Quelldatei unter /etc laden und der Anwendung mitteilen, wo das vorbereitete Credential erscheint:
[Unit]
Description=Example background worker
[Service]
Type=simple
User=example-worker
LoadCredential=db-password:/etc/example-worker/db-password
Environment=DB_PASSWORD_FILE=%d/db-password
ExecStart=/usr/local/bin/example-worker
Die Quelldatei muss weiterhin durch restriktive Eigentums- und Zugriffsrechte geschützt werden. Das unprivilegierte Dienstkonto benötigt keinen direkten Zugriff auf /etc/example-worker/db-password; der System-Service-Manager liest die Datei und stellt beim Start des Dienstes die Kopie pro Unit bereit. Das ist besonders mit einem eigenen oder dynamischen Dienstbenutzer nützlich, weil eine dauerhafte Klartextdatei nicht nur für den Dienststart breit lesbar gemacht werden muss.
Im Wort Kopie steckt eine wichtige Einschränkung: LoadCredential= verschlüsselt die Quelldatei nicht. Wird das Passwort unter /etc im Klartext gespeichert, bleibt es dort, in Backups und in jedem Snapshot dieses Pfads Klartext. Dateiberechtigungen bleiben Bestandteil des Sicherheitskonzepts.
Die Anwendung sollte einen Dateipfad akzeptieren
Die sauberste Anwendungsschnittstelle ist meist eine Einstellung, die den Pfad zu einer Geheimnisdatei akzeptiert. Dadurch bleibt der eigentliche Wert aus Kommandozeilenargumenten und Umgebungsvariablen heraus, während die Deployment-Schicht festlegen kann, wo die Datei liegt.
Ein kleiner PHP-Worker könnte den Pfad defensiv wie folgt lesen:
<?php
$passwordFile = getenv('DB_PASSWORD_FILE');
if ($passwordFile === false || $passwordFile === '') {
throw new RuntimeException('DB_PASSWORD_FILE is not configured');
}
$password = file_get_contents($passwordFile);
if ($password === false) {
throw new RuntimeException('Database password cannot be read');
}
$password = rtrim($password, "\r\n");
Nur abschließende Zeilenenden zu entfernen, ist beabsichtigt. Ein allgemeines trim() würde auch Leerzeichen und andere Zeichen entfernen, die zum Geheimnis gehören könnten. Ob Zeilenenden überhaupt entfernt werden sollten, hängt davon ab, wie das Credential erstellt wurde; Binärschlüssel sollten als Binärdaten behandelt und nicht wie Text bearbeitet werden.
Die Anwendung sollte den Wert weder protokollieren noch in eine Exception aufnehmen oder in die Umgebung eines Kindprozesses kopieren. Eine dateibasierte Übergabe verengt einen Offenlegungsweg, doch unvorsichtiges Anwendungsverhalten kann einen anderen öffnen.
Wenn Klartext im Ruhezustand das verbleibende Problem ist
Für eine Quelldatei, die nicht als Klartext auf dem Datenträger verbleiben soll, unterstützt systemd auch verschlüsselte Credentials. Das systemd-creds-Handbuch dokumentiert, wie ein Credential mit einem von einem TPM2-Gerät abgeleiteten Schlüssel, einem nur für root lesbaren Host-Schlüssel unter /var/lib/systemd/ oder einer Kombination aus beiden verschlüsselt und authentifiziert wird. Die genauen Möglichkeiten unterscheiden sich je nach systemd-Version und Host. Daher sind systemd-creds --version und das lokal installierte Handbuch bessere Kompatibilitätsprüfungen als ein Beispiel aus einer neueren Distribution.
Dieser Befehl fragt das Passwort ab, ohne es anzuzeigen, und schreibt ein verschlüsseltes Credential namens db-password:
sudo mkdir -p /etc/credstore.encrypted
systemd-ask-password -n | sudo systemd-creds encrypt \
--name=db-password \
- /etc/credstore.encrypted/db-password
Die Unit verwendet anschließend die verschlüsselte Variante der Direktive:
[Service]
User=example-worker
LoadCredentialEncrypted=db-password:/etc/credstore.encrypted/db-password
Environment=DB_PASSWORD_FILE=%d/db-password
ExecStart=/usr/local/bin/example-worker
Beim Start des Dienstes authentifiziert und entschlüsselt systemd das gespeicherte Objekt und stellt dem Prozess anschließend Klartext über dieselbe Credential-Dateischnittstelle bereit. Die Verschlüsselung schützt somit die gespeicherte Darstellung; sie verhindert nicht, dass der laufende Dienst den ursprünglichen Wert erhält.
Die Aussage zur Verschlüsselung sorgfältig wählen
Host-Schlüssel- und TPM2-gestützte Verschlüsselung beantworten unterschiedliche Bedrohungsmodelle. Ein nur für root lesbarer Host-Schlüssel kann eine kopierte Credential-Datei schützen, wenn der Schlüssel nicht ebenfalls verfügbar ist. Ein Angreifer mit ausreichendem Zugriff sowohl auf die verschlüsselte Datei als auch auf diesen Host-Schlüssel besitzt jedoch das für die Entschlüsselung notwendige Material. Eine TPM2-Bindung kann die Entschlüsselung an die lokale Hardware binden. Sie kann zugleich Fragen zu Wiederherstellung und Migration aufwerfen: Was geschieht, wenn sich Rechner, Hauptplatine, Betriebssysteminstallation oder Boot-Richtlinie ändern?
Ohne die Anforderungen an die Wiederherstellung gibt es keinen universell besten Modus. Bevor ein unersetzliches Geheimnis an einen einzelnen Rechner gebunden wird, sollte ein separat geschützter Wiederherstellungsweg vorhanden und getestet sein. Eine Verschlüsselung, die bis zum Ausfall des einzigen Servers perfekt funktioniert, ist kein vollständiger Verfügbarkeitsplan.
Was Credentials nicht verwalten
Der Name kann diese Funktion umfassender erscheinen lassen, als sie ist. systemd-Credentials sind ein Mechanismus zur Übergabe und optional zum Schutz gespeicherter Daten. Sie sind nicht automatisch ein vollständiger Secrets-Management-Dienst.
Das OWASP Secrets Management Cheat Sheet beschreibt einen umfassenderen Lebenszyklus: Zugriffskontrolle, Erstellung, Rotation, Widerruf, Ablauf, Auditierung, Backup und Wiederherstellung. Für ein statisches, von systemd geladenes Datenbankpasswort sind in jedem dieser Bereiche weiterhin Entscheidungen nötig. Die Verschlüsselung des lokalen Datenobjekts verkürzt nicht die Gültigkeitsdauer des Passworts, reduziert nicht seine Datenbankrechte und zeigt nicht an, dass jemand es nach dem Lesen durch den Dienst kopiert hat.
Auch die Rotation betrifft zwei Systeme. Die zugrunde liegende Datenbank oder API muss das neue Geheimnis akzeptieren, und die Credential-Quelle muss sicher ersetzt werden. Der Dienst benötigt im Allgemeinen einen Neustart oder einen anderen anwendungsspezifischen Reaktivierungsweg, bevor er ein neu vorbereitetes Credential erhält. Dieser Übergang sollte so geplant werden, dass weder der alte noch der neue Wert den Dienst aussperrt.
Eine vorsichtige Migrationscheckliste
- Lokale Version prüfen. Die installierten Handbuchseiten
systemd.exec(5)undsystemd-creds(1)lesen, nicht nur die neuesten Online-Seiten. - Dem Dienst eine eigene Identität geben. Ein auf ein gemeinsam genutztes Konto beschränktes Credential übernimmt weiterhin den großen Zugriffsbereich dieses Kontos.
- Eine Dateipfadschnittstelle hinzufügen. Die Anwendung sollte eine Einstellung wie
DB_PASSWORD_FILEakzeptieren, ohne ihren Inhalt zu protokollieren. - Mit dem Bedrohungsmodell beginnen. Entscheiden, ob restriktive Klartextspeicherung genügt oder ob Offline-Kopien und Backups verschlüsselte Credentials erfordern.
- Die Unit frei von wörtlichen Geheimnissen halten.
SetCredential=ist für nicht sensible Daten dokumentiert; für Geheimnisse die Lade- oder verschlüsselten Direktiven verwenden. - Vor dem Neustart validieren. Nach dem Ersetzen der Beispielnamen und -pfade durch reale Werte
sudo systemd-analyze verify /etc/systemd/system/example-worker.serviceausführen, anschließend den Manager neu laden und in einem kontrollierten Zeitfenster neu starten. - Prüfen, ohne den Wert auszugeben. Sicherstellen, dass die Unit startet, die Anwendung sich authentifizieren kann und Protokolle sowie Diagnosen das Credential nicht enthalten.
- Rotation und Wiederherstellung dokumentieren. Festhalten, wie das Geheimnis ersetzt wird, welcher Dienst neu starten muss und wie verschlüsselte Daten nach einem Hardwareverlust wiederhergestellt werden können.
Ein engerer Kanal, kein absoluter Schutzschild
Ein Geheimnis aus einer Umgebungsvariable in ein systemd-Credential zu verschieben, ist wertvoll, weil die Übergabe expliziter wird und der Wert eine engere Dateisystemschnittstelle erhält. Ein verschlüsseltes Credential kann die gespeicherte Kopie zusätzlich gegen bestimmte Offline-Bedrohungen schützen. Keiner der beiden Schritte ändert die grundlegende Tatsache, dass ein Dienst ein Geheimnis sehen muss, um es zu verwenden.
Die nützliche Frage lautet daher nicht „Macht dies das Passwort sicher?“, sondern „Welche unnötigen Leser und Speicherorte werden dadurch entfernt, und welche Risiken bleiben bestehen?“ Auf einem kleinen Server führt eine ehrliche Antwort häufig zu einem bescheidenen Entwurf: eine Dienstidentität, ein zweckgebundenes Credential, minimale Rechte, kein Geheimnis in Protokollen oder der Unit-Umgebung und ein Wiederherstellungsverfahren, das getestet wurde, bevor es benötigt wird.
