Sichere Verwaltung von Umgebungsvariablen in Systemd-Service-Units

Konfigurieren Sie Systemd-Umgebungsvariablen mit Environment, EnvironmentFile, Drop-ins und sichererem Umgang mit Geheimnissen.

Sichere Verwaltung von Umgebungsvariablen in Systemd-Service-Units

Umgebungsvariablen sind praktisch, aber nicht automatisch privat. In einem Systemd-Service können sie in Unit-Dateien, Drop-ins, systemctl show, Prozessinspektionstools, Absturzberichten, Debug-Logs oder kopierten Support-Bundles auftauchen. Das bedeutet nicht, dass Sie sie nie verwenden können. Es bedeutet, dass Sie bewusst entscheiden sollten, was hineinkommt und wer die Dateien lesen kann, die sie definieren.

Diese Anleitung behandelt die beiden gängigen Direktiven Environment= und EnvironmentFile=, zeigt dann, wie Sie Drop-ins verwenden, damit lokale Konfiguration von paketverwalteten Units getrennt bleibt.


Die Rolle von Umgebungsvariablen in Systemd

Umgebungsvariablen bieten eine unkomplizierte Möglichkeit, einen Service zu konfigurieren, ohne seinen Code zu ändern. Wenn Systemd einen Service startet, erstellt es die Prozessumgebung und wendet die in der Unit definierten Variablen an, bevor es ExecStart= ausführt.

Systemd bietet zwei primäre Direktiven im Abschnitt [Service] einer Unit-Datei zur Verwaltung dieser Variablen.

1. Direkte Definition: Die Environment-Direktive

Diese Methode erlaubt es Ihnen, Variablen direkt in der Systemd-Unit-Datei zu definieren. Dies ist geeignet für nicht-sensitive Konfigurationsparameter, die sich selten ändern.

Verwendung und Syntax

Die Environment-Direktive akzeptiert eine leerzeichengetrennte Liste von Variablenzuweisungen im Format "KEY=VALUE".

# /etc/systemd/system/my-app.service

[Unit]
Description=My Application Service

[Service]
User=myuser
WorkingDirectory=/opt/my-app

# Variablen direkt in der Unit-Datei definieren
Environment="APP_PORT=8080" "NODE_ENV=production"

ExecStart=/usr/local/bin/my-app --start

[Install]
WantedBy=multi-user.target

Einschränkungen und Sicherheit

Obwohl praktisch, ist die Environment-Direktive ein schlechter Ort für Passwörter, Tokens oder Datenbank-Anmeldedaten. Unit-Dateien werden oft in Konfigurationsmanagementsystemen gespeichert, in Tickets kopiert oder von Operatoren gelesen, die das Service-Verhalten überprüfen müssen, aber keine Geheimnisse sehen sollten. Verwenden Sie sie für Werte wie Ports, Feature-Flags, Log-Level und Pfade.

2. Externe Konfiguration: Die EnvironmentFile-Direktive

Für größere Konfigurationen ist das Laden von Variablen aus einer externen Datei normalerweise sauberer. Es erlaubt Ihnen, die Berechtigungen der Variablendatei unabhängig von der Haupt-Unit-Datei zu verwalten. Es hält auch paketbereitgestellte Units lesbar, während lokale Einstellungen in /etc leben.

Verwendung und Syntax

Die EnvironmentFile-Direktive nimmt einen absoluten Pfad zu einer Konfigurationsdatei. Systemd liest diese Datei zeilenweise und behandelt jede Zeile als potenzielle KEY=VALUE-Zuweisung.

[Service]
# Variablen aus einer externen Datei laden
EnvironmentFile=/etc/config/my-app-settings.conf

ExecStart=/usr/local/bin/my-app --start

Format der Umgebungsdatei

Die externe Datei muss einem einfachen shell-ähnlichen Format folgen:

  • Zeilen, die mit # beginnen, werden als Kommentare behandelt.
  • Zeilen, die mit einer leeren Variablenzuweisung beginnen (VAR=), löschen die Variable, falls sie zuvor gesetzt war.
  • Variablen werden als KEY=VALUE definiert.
  • Das Anführungszeichen des Werts (KEY="VALUE WITH SPACES") wird unterstützt.
# /etc/config/my-app-settings.conf

# Nicht-sensitive Variablen
MAX_WORKERS=4
LOG_LEVEL=INFO

# Sensitive Variable (erfordert strenge Dateiberechtigungen und sorgfältige Zugriffskontrolle)
DB_PASSWORD=SecureRandomString12345

Vermeiden Sie Shell-Gewohnheiten, die der Umgebungsdatei-Parser von Systemd nicht wie erwartet unterstützt. Schreiben Sie nicht export KEY=value. Setzen Sie keine Leerzeichen um das Gleichheitszeichen. Wenn der Wert Leerzeichen enthält, setzen Sie ihn in Anführungszeichen. Wenn der Wert wörtliche Anführungszeichen, Backslashes oder Zeilenumbrüche enthält, testen Sie ihn, bevor Sie sich in der Produktion darauf verlassen.

Umgang mit fehlenden Dateien

Standardmäßig schlägt der Service-Start fehl, wenn die von EnvironmentFile angegebene Datei nicht existiert. Wenn die Umgebungsdatei optional ist, können Sie dem Dateipfad einen Bindestrich (-) voranstellen:

EnvironmentFile=-/etc/config/optional-settings.conf

Wenn der Datei ein - vorangestellt ist, ignoriert Systemd Fehler, die durch das Fehlen der Datei verursacht werden.

Best Practice: Verwendung von Drop-in-Units für sensible Daten

Das Ändern der Kern-Unit-Datei (z.B. /usr/lib/systemd/system/my-app.service) wird generell nicht empfohlen, insbesondere wenn die Datei von einem Paketmanager verwaltet wird. Verwenden Sie stattdessen Drop-in-Unit-Dateien, um Konfigurationsüberschreibungen oder Ergänzungen anzuwenden.

Diese Praxis ist wichtig, weil sie die Standardeinstellungen des Anbieters von der lokalen Konfiguration trennt. Sie erleichtert auch Audits: Die Unit sagt, wo die Konfiguration geladen wird, und die Berechtigungen dieser Datei sagen, wer sie lesen kann.

Schritt-für-Schritt-Drop-in-Konfiguration

1. Drop-in-Verzeichnis finden/erstellen

Für einen Service namens my-app.service muss das Drop-in-Verzeichnis my-app.service.d/ heißen und sich in der Hierarchie /etc/systemd/system/ befinden.

sudo mkdir -p /etc/systemd/system/my-app.service.d/

2. Konfigurationsüberschreibung erstellen

Erstellen Sie eine Datei im Drop-in-Verzeichnis (z.B. secrets.conf). Diese Datei benötigt nur den Abschnitt [Service] und die spezifischen Direktiven, die Sie überschreiben oder hinzufügen möchten.

# /etc/systemd/system/my-app.service.d/secrets.conf

[Service]
# Die sichere Anmeldedatendatei laden
EnvironmentFile=/etc/secrets/my-app-credentials.env

3. Externe Umgebungsdatei sichern

Dies ist der kritischste Sicherheitsschritt. Stellen Sie sicher, dass die externe Datei mit den Geheimnissen restriktive Berechtigungen hat. Idealerweise sollte sie root:root gehören und nur vom Root-Benutzer oder dem Service-Benutzer selbst lesbar sein.

# Die Geheimnisdatei erstellen
sudo touch /etc/secrets/my-app-credentials.env

# Die Datei mit Geheimnissen befüllen
sudo sh -c 'echo "DB_PASS=S3cr3tP@ssw0rd" >> /etc/secrets/my-app-credentials.env'

# Restriktive Berechtigungen setzen
sudo chmod 600 /etc/secrets/my-app-credentials.env

Wenn die von EnvironmentFile referenzierte Datei Anmeldedaten enthält, halten Sie sie nur lesbar für das Konto, das den Service verwalten muss. 0600 root:root ist üblich, wenn Systemd die Datei liest, bevor es mit User= die Berechtigungen herabsetzt, aber einige Betriebsmodelle verwenden eine dedizierte root-eigene Gruppe und 0640. Wichtig ist, dass normale Benutzer die Datei nicht lesen können.

Seien Sie auch ehrlich über das verbleibende Risiko. Umgebungsvariablen sind einfacher zu handhaben als hartcodierte Befehlszeilenargumente, aber sie sind immer noch kein vollständiges Geheimnisverwaltungssystem. Für risikoreichere Anmeldedaten sollten Sie einen dedizierten Geheimnisspeicher, kurzlebige Anmeldedaten, Systemd-Anmeldedaten auf neueren Distributionen oder einen anwendungsspezifischen Mechanismus in Betracht ziehen, der eine geschützte Datei direkt liest.

Fehlerbehebung und Überprüfung

Nachdem Sie Änderungen an Unit-Dateien oder Drop-ins vorgenommen haben, müssen Sie die Systemd-Manager-Konfiguration neu laden.

sudo systemctl daemon-reload
sudo systemctl restart my-app.service

Um zu überprüfen, welche Umgebungsvariablen von Systemd für einen laufenden Service erfolgreich geladen wurden, verwenden Sie den Befehl systemctl show und fragen Sie speziell die Eigenschaft Environment ab:

systemctl show my-app.service --property=Environment

Beispielausgabe (zeigt geladene Variablen):

Environment=APP_PORT=8080 NODE_ENV=production DB_PASS=S3cr3tP@ssw0rd

Dieser Befehl ist nützlich für die Fehlerbehebung, aber er ist auch eine Erinnerung: Jeder, der die richtigen Inspektionsbefehle als Root ausführen darf, kann die Werte sehen. Fügen Sie diese Ausgabe nicht ohne Schwärzung in geteilte Chats, Tickets oder öffentliche Fehlerberichte ein.

Wenn der Service nicht startet, überprüfen Sie die Service-Logs mit journalctl -xeu my-app.service. Häufige Fehlerursachen im Zusammenhang mit Umgebungsvariablen sind:

  1. Falscher Dateipfad in EnvironmentFile.
  2. Fehlende Datei (und der Pfad wurde nicht mit - vorangestellt).
  3. Falsche Variablensyntax in der externen Umgebungsdatei (z.B. Leerzeichen um das =-Zeichen).

Praktische Muster, die funktionieren

Szenario Zu verwendende Direktive Best Practice für den Ort Sicherheitsaspekte
Statische, nicht-sensitive Konfiguration Environment Direkte Unit-Datei oder Drop-in Geringes Sicherheitsrisiko.
Sensitive Anmeldedaten (Geheimnisse) EnvironmentFile Externe Datei, referenziert über ein Drop-in (*.service.d/) KRITISCH: Umgebungsdatei muss 0600-Berechtigungen haben.
Modularität und Überschreibungen EnvironmentFile Drop-in-Unit-Datei Trennt Konfiguration von den Standardeinstellungen des Anbieters.

Durch die Nutzung der EnvironmentFile-Direktive innerhalb eines dedizierten Drop-in-Units und die Sicherstellung strenger Dateiberechtigungen können Administratoren Service-Konfigurationen sicher und flexibel verwalten, wobei die Prinzipien der geringsten Privilegien und der Trennung von Belangen eingehalten werden.

Für einen kleinen internen Service sieht ein vernünftiges Setup oft so aus:

# /etc/systemd/system/my-app.service.d/env.conf
[Service]
Environment="APP_ENV=production"
EnvironmentFile=/etc/my-app/runtime.env
EnvironmentFile=-/etc/my-app/local.env

runtime.env enthält erforderliche Werte. local.env ist optional und erlaubt es einem Operator, eine Einstellung während eines Wartungsfensters zu überschreiben, ohne die Haupt-Unit zu bearbeiten. Nach einer Änderung:

sudo systemctl daemon-reload
sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

Die sicherste Gewohnheit ist einfach: Halten Sie nicht-sensitive Standardwerte in der Unit oder einer normalen Konfigurationsdatei, halten Sie Geheimnisse aus paketeigenen Units heraus, sperren Sie jede Datei, die Anmeldedaten enthält, und überprüfen Sie die geladene Umgebung, ohne sie an Orte zu lecken, wo sie nicht hingehört.

Häufige Fehler, die es zu vermeiden lohnt

Der erste Fehler ist, Geheimnisse in ExecStart= zu setzen:

ExecStart=/usr/local/bin/my-app --db-password=s3cret

Das sieht harmlos aus, wenn Sie in Eile sind, aber Befehlszeilenargumente sind oft leichter offenzulegen als Umgebungsdateien. Sie können in Prozesslisten, Überwachungstools, Shell-Verlauf, Absturzberichten oder kopierten Service-Definitionen auftauchen. Wenn die Anwendung das Lesen einer geschützten Konfigurationsdatei unterstützt, ist das normalerweise besser. Wenn sie eine Umgebungsvariable erwartet, verwenden Sie eine geschützte EnvironmentFile= und halten Sie den Wert aus der Befehlszeile heraus.

Der zweite Fehler ist, die Anbieter-Unit direkt zu bearbeiten. Ein Paket-Update kann die Datei ersetzen, und der nächste Neustart kann Ihre Umgebungseinstellungen stillschweigend verwerfen. Verwenden Sie ein Drop-in:

sudo systemctl edit my-app.service

Fügen Sie dann nur die lokale Überschreibung hinzu:

[Service]
EnvironmentFile=/etc/my-app/my-app.env

Der dritte Fehler ist die Annahme, dass der Service dieselbe Shell-Umgebung sieht, die Sie in Ihrem Terminal sehen. Das tut er normalerweise nicht. Ihre interaktive Shell kann Variablen aus .bashrc, .profile, einer SSH-Sitzung oder einem Bereitstellungstool haben. Ein System-Service startet aus der verwalteten Umgebung von Systemd. Wenn die App PATH, JAVA_HOME, NODE_ENV, LD_LIBRARY_PATH oder einen ähnlichen Wert benötigt, definieren Sie ihn explizit oder verwenden Sie absolute Pfade.

Zum Beispiel ist dies fragil:

ExecStart=npm start

Dies ist einfacher zu durchschauen:

WorkingDirectory=/opt/my-app
Environment="NODE_ENV=production"
ExecStart=/usr/bin/npm start

Der vierte Fehler ist, die Umgebungsdatei für den Service-Benutzer beschreibbar zu machen, wenn es nicht nötig ist. Eine Web-App, die ihre eigene Umgebungsdatei überschreiben kann, kann einen normalen Anwendungsfehler in ein Persistenzproblem verwandeln. In vielen Setups sollte der Service-Benutzer Anwendungsdaten lesen und Logs oder Uploads schreiben können, aber er sollte nicht in der Lage sein, die Anmeldedaten neu zu schreiben, die zum Starten des Services verwendet werden.

Wann Umgebungsvariablen das falsche Werkzeug sind

Umgebungsvariablen sind beliebt, weil sie einfach sind, aber sie sind nicht immer die beste Schnittstelle. Wenn ein Wert groß, strukturiert, häufig rotiert oder von mehreren Services gemeinsam genutzt wird, ist eine echte Konfigurationsdatei oder ein Geheimnisspeicher normalerweise einfacher zu verwalten.

Eine Datenbank-URL ist eine vernünftige Umgebungsvariable:

DATABASE_URL=postgresql://[email protected]:5432/app

Ein vollständiges JSON-Service-Konto-Dokument ist weniger angenehm. Das Setzen von Anführungszeichen wird umständlich, versehentliche Zeilenumbrüche verursachen Fehler, und Leute neigen dazu, es während der Fehlerbehebung in Logs einzufügen. Speichern Sie in diesem Fall das JSON in einer geschützten Datei und übergeben Sie den Dateipfad:

GOOGLE_APPLICATION_CREDENTIALS=/etc/my-app/google-service-account.json

Schützen Sie dann die JSON-Datei separat:

sudo chown root:my-app /etc/my-app/google-service-account.json
sudo chmod 640 /etc/my-app/google-service-account.json

Das macht das Geheimnis nicht magisch. Die Anwendung kann es immer noch lesen. Root kann es immer noch lesen. Aber es vermeidet, ein komplexes Geheimnis in den Umgebungs-Parser von Systemd zu stopfen und macht die Prüfung auf Dateiebene klarer.

Eine sicherere Überprüfungs-Checkliste

Bevor Sie einen Service neu starten, der Umgebungsvariablen verwendet, überprüfen Sie vier Dinge:

systemctl cat my-app.service
sudo ls -l /etc/my-app/my-app.env
sudo systemd-analyze verify /etc/systemd/system/my-app.service
sudo systemctl daemon-reload

systemctl cat bestätigt, welche Drop-ins aktiv sind. ls -l bestätigt, dass die Berechtigungen Ihren Absichten entsprechen. systemd-analyze verify kann einige Unit-Syntax-Probleme erkennen, bevor Sie neu starten. Es wird nicht jede anwendungsspezifische Einstellung validieren, aber es ist dennoch eine nützliche Sicherheitsvorkehrung.

Überprüfen Sie nach dem Neustart das Journal auf Startfehler:

sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 100 --no-pager

Wenn Sie bestätigen müssen, dass eine Variable geladen wurde, fragen Sie sie sorgfältig ab und schwärzen Sie die Ausgabe, bevor Sie sie teilen. Für sensible Services überprüfe ich zuerst eine nicht-geheime Variable wie APP_ENV oder LOG_LEVEL. Wenn diese aus derselben Datei geladen wurde, sind der Dateipfad und die Parser-Syntax wahrscheinlich korrekt, und Sie müssen möglicherweise die geheimnistragenden Werte überhaupt nicht ausgeben.

Ein letzter praktischer Punkt: Planen Sie die Rotation, bevor Sie sie benötigen. Wenn ein Passwort oder Token in einer Umgebungsdatei gespeichert ist, notieren Sie, welcher Service nach der Wertänderung neu gestartet werden muss und ob dieser Neustart Ausfallzeiten verursacht. Eine Anmeldedaten, die leicht zu setzen, aber schwer zu rotieren ist, wird schließlich zu einem Vorfall. Für kleine Services kann das Rotations-Runbook nur vier Zeilen umfassen:

sudoedit /etc/my-app/my-app.env
sudo systemctl restart my-app.service
sudo systemctl status my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

Das reicht, wenn jeder den Schadensradius kennt. Für größere Systeme bevorzugen Sie Anmeldedaten, die sich während der Rotation überschneiden können, sodass Sie den neuen Wert bereitstellen, überprüfen und den alten Wert entfernen können, ohne ein überstürztes Ausfallfenster.