Dienstgeheimnisse mit systemd isolieren und ihre Rotation prüfen

Dienstgeheimnisse mit systemd isolieren und ihre Rotation prüfen

Dienstgeheimnisse mit systemd isolieren und ihre Rotation prüfen

Wenn auf einem Dedicated Server API-Dienste, Synchronisierungsaufgaben und Monitoring-Collector laufen, liegen Dienst-Tokens häufig verstreut in Startskripten, Umgebungsdateien und Bereitstellungsprotokollen. Das Auslesen von Prozessargumenten, Fehlerprotokolle oder eine archivierte Konfiguration können den Kreis der offengelegten Informationen vergrößern. Dieser Artikel zeigt unter Debian 12, wie systemd mit LoadCredential die Quelldateien mit den Geheimnissen in einem administrativ kontrollierten Verzeichnis belässt und nur dem vorgesehenen Dienst eine Laufzeitkopie bereitstellt. Wiederholbare Prüfungen sichern dabei den Rotationsablauf ab.

1. Zuerst die Grenzen der Credential-Isolation klären

Hier geht es darum, wie ein Dienst an ein Geheimnis gelangt — nicht um die Ausstellung von Schlüsseln, deren zentrale Verwahrung oder Protokolle zur entfernten Authentifizierung. System und Daten auf einem Dedicated Server von CloudDDR werden vom Nutzer verwaltet. Nach der Zahlung lassen sich die Zugangsdaten in der Konsole einsehen; der Zugriff auf das System erfolgt über SSH. Die folgenden Schritte erfordern Administratorrechte. Führen Sie sie zunächst mit einem Testdienst durch, ohne direkt den Start Ihres Produktivdienstes zu ändern.

Umgebungsvariablen eignen sich nicht als Standardtransport für Geheimnisse: Ihre Inhalte können in Debug-Ausgaben, der Umgebung von Kindprozessen oder Diagnosearchiven landen. Auch Startargumente können von Werkzeugen zur Prozessinspektion erfasst werden. Bei LoadCredential liest dagegen der System-Dienstmanager die Quelldatei und stellt beim Start des Dienstes ein Credential-Verzeichnis bereit. Das Programm findet die Datei über CREDENTIALS_DIRECTORY.

Die Isolation von Credential-Dateien schützt nicht vor Prozessen, die bereits Administratorrechte erlangt haben. Sie verhindert auch nicht, dass die Anwendung Geheimnisse ins Log schreibt. Die Schutzgrenze muss deshalb Dateiberechtigungen, Dienstkonto und Ausgabeverhalten der Anwendung gleichermaßen umfassen.

Die Quelldatei liegt in diesem Beispiel weiterhin im Klartext vor. LoadCredential sorgt nicht automatisch für eine Verschlüsselung der gespeicherten Daten. Auch vollständige Server-Backups, Administratorzugriffe und Aufzeichnungen zur Schlüsselausstellung müssen als sensibel behandelt werden.

2. Ein eigenes Konto und eine geschützte Quelldatei anlegen

Eine separate Prüfumgebung vorbereiten

Prüfen Sie die Ausgabe von systemctl --version. Das Beispiel verwendet das mit Debian 12 bereitgestellte systemd; zusätzlich wird Python 3 installiert. Stellen Sie sicher, dass der Kontoname nicht bereits von einem bestehenden Dienst verwendet wird. Führen Sie den folgenden Befehl zum Anlegen des Kontos nur aus, wenn es noch nicht existiert.

sudo apt-get update
sudo apt-get install python3
id credential-check
sudo useradd --system --no-create-home --shell /usr/sbin/nologin credential-check
sudo install -d -o root -g root -m 0700 /etc/service-secrets
sudo sh -c 'umask 077; printf "%s" "fixture-token-alpha" > /etc/service-secrets/api-token'
sudo stat -c '%U %G %a %n' /etc/service-secrets /etc/service-secrets/api-token

Führen Sie useradd erst aus, wenn id meldet, dass das Konto nicht existiert. Ist es bereits vorhanden, prüfen Sie seinen Verwendungszweck, statt ein unbekanntes Konto wiederzuverwenden. Erwartet werden die Rechte 700 für das Verzeichnis und 600 für die Datei; Eigentümer und Gruppe sind jeweils root. Die Beispielzeichenfolge ist ein Testwert, der keine Zugriffsrechte gewährt, und darf daher in der Befehlshistorie stehen. Ein echtes Token darf nicht auf diese Weise direkt in einen Befehl geschrieben werden.

Den echten Wert sollten Sie über eine kontrollierte Dateiübergabe erhalten und anschließend mit Administratorrechten am Zielort installieren. Verwenden Sie zum Übertragen von Geheimnissen weder temporäre Editordateien noch Terminalausgaben oder eine Shell mit aktiviertem Tracing. Das Dienstkonto darf die Quelldatei nicht lesen können:

sudo -u credential-check test -r /etc/service-secrets/api-token
printf '%s\n' "$?"

Hier wird der Exit-Code 1 erwartet. Bei 0 prüfen Sie zuerst die Verzeichnisrechte, die Datei-ACL und die Gruppenmitgliedschaften des Kontos. Setzen Sie die Bereitstellung nicht fort, bevor die Ursache geklärt ist.

3. Das Programm ausschließlich aus der Laufzeitkopie lesen lassen

Speichern Sie das folgende Programm als /usr/local/libexec/credential-check.py. Legen Sie zuvor das übergeordnete Verzeichnis an und setzen Sie für Verzeichnis und Programm root als Eigentümer sowie die Rechte 755. Das Dienstkonto darf weder in das Verzeichnis noch in die Programmdatei schreiben können. Das Programm meldet nur fest vorgegebene Fehler und gibt weder das Token noch dessen Länge oder Hashwert aus. So entsteht durch die Abnahmeprüfung kein zusätzlicher Leckagekanal.

import os
from pathlib import Path

directory = os.environ.get("CREDENTIALS_DIRECTORY")
if not directory:
    raise SystemExit("Credential directory missing")

try:
    token = (Path(directory) / "api-token").read_bytes()
except OSError:
    raise SystemExit("Credential unavailable") from None

if not token or b"\n" in token or b"\r" in token:
    raise SystemExit("Credential format rejected")

Dieses Beispiel setzt voraus, dass das Token nicht leer ist und keine Zeilenumbrüche enthält. Das ist keine allgemeingültige Regel für alle Schlüsselformate. Eine echte Anwendung muss das Format nach den Vorgaben des angebundenen Systems prüfen. Sind beispielsweise mehrzeilige PEM-Inhalte zulässig, darf diese Prüfung nicht übernommen werden.

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

[Unit]
Description=Service credential validation

[Service]
Type=oneshot
User=credential-check
Group=credential-check
LoadCredential=api-token:/etc/service-secrets/api-token
ExecStart=/usr/bin/python3 /usr/local/libexec/credential-check.py
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
UMask=0077

Diese Unit führt eine einmalige Prüfung aus. Dass sie nach erfolgreichem Abschluss den Zustand inactive hat, ist daher normal. Eine Aktivierung für den Systemstart ist nicht erforderlich; ebenso wenig sollte für den Test die Neustartstrategie eines Produktivdienstes übernommen werden. Eine echte Anwendung kann die Datei auf dieselbe Weise lesen. Die Dateisystembeschränkungen müssen jedoch unter Berücksichtigung ihrer Schreibverzeichnisse geprüft werden und dürfen nicht ungeprüft kopiert werden.

4. Lesbarkeit und Authentifizierung getrennt prüfen

sudo systemctl daemon-reload
sudo systemctl start credential-check.service
sudo systemctl show credential-check.service -p Result -p ExecMainStatus
sudo journalctl -u credential-check.service -n 20 --no-pager

Erwartet werden Result=success und ExecMainStatus=0. Damit ist nachgewiesen, dass das Dienstkonto die Credentials über das Laufzeitverzeichnis lesen kann. Zuvor wurde bereits geprüft, dass es die Quelldatei nicht direkt lesen kann. Verwenden Sie für diesen Nachweis weder cat noch eine vollständige Ausgabe der Umgebungsvariablen oder einen Dump des Prozessspeichers.

Diese Prüfung bestätigt nicht, dass das Token gültig ist. Nach der Anbindung an den echten Dienst muss zusätzlich eine authentifizierte Anfrage ausgeführt werden, die keine Geschäftsdaten verändert. Prüfen Sie dabei den Antwortstatus und die erwarteten Berechtigungen. Schlägt die Authentifizierung fehl, prüfen Sie getrennt das Credential-Format, die Aktivierung des neuen Tokens im angebundenen System, den tatsächlichen Neustart der Anwendung und eine mögliche Verwendung alter Verbindungen oder Cache-Einträge durch die Anfrage.

Das Prüfprotokoll soll nur die Version der Unit, die Reihenfolge der Schritte, die Exit-Codes und das Ergebnis der Authentifizierung enthalten — nicht den Token-Wert. Muss eine bestimmte Rotation zugeordnet werden, verwenden Sie eine interne Änderungsnummer statt eines Token-Hashwerts. Kontrollieren Sie beim Prüfen der Logs auch, dass Fehlerpfade weder Request-Header noch die vollständige Konfiguration ausgeben.

5. Rotation und Rückfall bei Fehlern mit atomarem Austausch durchführen

Eine Änderung der Quelldatei verändert nicht die Credential-Kopie eines bereits laufenden Dienstes. Die Reihenfolge bei einer Rotation lautet daher: neuen Wert beschaffen, Format prüfen, Datei atomar ersetzen, Dienst neu starten, Authentifizierung prüfen und schließlich den alten Wert nach den Regeln des angebundenen Systems widerrufen. Ein bloßes daemon-reload aktualisiert die Geheimnisse eines laufenden Dienstes nicht.

Die folgenden Befehle rotieren ausschließlich die Testdaten. Temporäre Datei und Zieldatei liegen im selben Verzeichnis, damit die Umbenennung innerhalb desselben Dateisystems erfolgt.

sudo sh -c 'umask 077; printf "%s" "fixture-token-beta" > /etc/service-secrets/api-token.next'
sudo chown root:root /etc/service-secrets/api-token.next
sudo chmod 0600 /etc/service-secrets/api-token.next
sudo mv -f /etc/service-secrets/api-token.next /etc/service-secrets/api-token
sudo systemctl restart credential-check.service
sudo systemctl show credential-check.service -p Result -p ExecMainStatus

Führen Sie anschließend einen Fehlertest durch: Ersetzen Sie die Test-Quelldatei durch eine leere Datei und starten Sie die Prüf-Unit erneut. Erwartet werden ein Exit-Code ungleich null und die fest vorgegebene Formatfehlermeldung. Stellen Sie danach einen nicht leeren Testwert wieder her und starten Sie die Unit erneut; nun sollte die Prüfung erfolgreich sein. So lässt sich nachweisen, dass die Anwendung bei einem fehlenden Geheimnis nicht stillschweigend weiterläuft. Führen Sie solche destruktiven Tests nicht mit produktiven Tokens durch.

Klären Sie vor einer echten Rotation, ob das angebundene System das alte und das neue Token gleichzeitig akzeptieren kann. Falls nicht, muss ein koordinierter Wechsel geplant werden. Wurde der alte Wert bereits widerrufen, stellt auch die Wiederherstellung der alten Datei die Authentifizierung nicht wieder her. Bewahren Sie den alten Wert nur während eines kontrollierten Rückfallzeitraums auf und schützen Sie ihn mit denselben Zugriffsrechten. Lassen Sie keine dauerhaften Kopien in Anwendungsverzeichnissen zurück.

Deaktivieren und entfernen Sie nach der Abnahme die Test-Unit, das Skript und die Test-Credentials. Führen Sie daemon-reload aus und prüfen Sie, dass keine temporären Dateien zurückgeblieben sind. Das Ergebnis sollte ein nachvollziehbarer Ablauf sein: Ein gewöhnliches Konto kann die Quelldatei nicht lesen, der vorgesehene Dienst kann auf die Laufzeit-Credentials zugreifen, ein fehlerhafter Wert führt zu einem eindeutigen Fehler, die tatsächliche Authentifizierung funktioniert nach der Rotation, und die Logs enthalten keine sensiblen Werte.

Häufig gestellte Fragen

Verbirgt LoadCredential Geheimnisse vor Administratoren?

Nein. Administratoren und ausreichend privilegierte Prozesse können weiterhin die Quelldatei oder den Dienstspeicher lesen. Der Mechanismus reduziert die Offenlegung über Argumente, Umgebungsvariablen und gewöhnliche Konten, bildet aber keine Schutzgrenze gegen Administratoren.

Übernimmt ein laufender Dienst eine ersetzte Quelldatei automatisch?

Nein. LoadCredential lädt die Zugangsdaten beim Dienststart. Nach dem atomaren Austausch muss der Dienst neu gestartet und die Anmeldung mit dem neuen Geheimnis geprüft werden. daemon-reload allein aktualisiert laufende Zugangsdaten nicht.

Dedizierter Server mit AMD EPYC 7543P ab $447/Monat.

Dedicated-Server-Konfiguration: Arbeitsspeicher wählen

AMD EPYC 7543P mit 128 GB oder 256 GB DDR4-3200 ECC.