GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Verwenden von systemd-Funktionen zum Sichern von Diensten

Alle neueren Versionen der beliebtesten Linux-Distributionen verwenden systemd um die Maschine zu booten und Systemdienste zu verwalten. Systemd bietet mehrere Funktionen, um das Starten von Diensten und einfacher zu machen sicherer. Dies ist eine seltene Kombination, und dieser Artikel zeigt, warum es nützlich ist, systemd zuzulassen die Ressourcen und das Sandboxing eines Dienstes verwalten.

Begründung

Warum sollten wir also systemd verwenden? für Sicherheits-Sandboxing? Erstens könnte man argumentieren, dass jedes Bit dieser Funktionalität bereits durch vorhandene und bekannte Tools verfügbar gemacht wird, die auf beliebige Weise skriptgesteuert und kombiniert werden können. Zweitens können insbesondere bei Programmen, die in C/C++ und anderen Low-Level-Sprachen geschrieben sind, geeignete Systemaufrufe direkt verwendet werden, wodurch eine schlanke Implementierung erreicht wird, die sorgfältig auf die Bedürfnisse eines bestimmten Dienstes zugeschnitten ist.

Es gibt vier Hauptgründe:

1. Sicherheit ist schwierig. Eine zentrale Implementierung im Service Manager bedeutet, dass ein Service, der davon profitiert, erheblich vereinfacht werden kann. Zweifellos ist diese zentralisierte Implementierung komplex, aber aufgrund ihrer breiten Verwendung gut getestet. Wenn wir bedenken, dass es über Tausende von Diensten wiederverwendet wird, ist das insgesamt Komplexität des Systems wird reduziert.

2. Sicherheitsprimitive variieren je nach System. Systemd gleicht die Unterschiede zwischen Hardware-Architekturen, Kernel-Versionen und Systemkonfigurationen aus.

Die Funktionalität, die eine Härtung von Diensten bereitstellt, wird so weit wie möglich auf einem bestimmten System implementiert. Zum Beispiel ein systemd Einheit kann sowohl AppArmor- als auch SELinux-Konfigurationen enthalten. Die erste wird auf Ubuntu/Debian-Systemen verwendet, die zweite auf Fedora/RHEL/CentoOS und keine von beiden für Distributionen, die kein MAC-System aktivieren. Die andere Seite dieser Flexibilität ist, dass man sich nicht nur auf diese Funktionen verlassen kann Eindämmungsmechanismus (oder dass solche Dienste nur auf Systemen verwendet werden, die alle erforderlichen Funktionen unterstützen).

3. Sicherheit erfordert ein einfaches Herumhantieren mit dem System. Vom Dienstmanager bereitgestellte Funktionen sind unabhängig von der Implementierungssprache des Dienstes, sodass es einfach ist, einen Dienst in einer Hochsprache zu schreiben, z. B. Shell oder Python oder was auch immer praktisch ist, und ihn trotzdem zu sperren.

4. Sicherheit erfordert Berechtigungen. Dies ist ein Paradoxon, aber Privilegien sind erforderlich, um Privilegien zu entziehen. Zum Beispiel müssen wir oft root sein, um einen benutzerdefinierten Mount-Namespace einzurichten, um die Ansicht des Dateisystems einzuschränken. Als weiteres Beispiel wird ein HTTP-Daemon oft nur als Root gestartet, um einen Port mit niedriger Nummer öffnen zu können, und Ports mit niedriger Nummer werden im Namen der Sicherheit eingeschränkt. Der Dienstmanager muss sowieso mit den höchsten Privilegien laufen, aber Dienste sollten das nicht, und die Einrichtung der Härtung ist oft der einzige Grund, warum höhere Privilegien erforderlich sind. Jegliche Fehler in der Implementierung des Dienstes in dieser Phase können gefährlich sein. Durch das Auslagern des Setups an den Dienstmanager können Dienste ohne diese frühe Phase erhöhter Berechtigungen gestartet werden.

Um dies in einen Zusammenhang zu bringen:Das kürzlich veröffentlichte Fedora 32 enthält fast 1800 verschiedene Unit-Dateien zum Starten von Diensten, die in C, C++, Python, Java, Ocaml, Perl, Ruby, Lua, Tcl, Erlang usw. geschrieben wurden – und nur einen systemd .

[ Benötigen Sie mehr zu systemd? Laden Sie den systemd-Spickzettel herunter, um weitere hilfreiche Hinweise zu erhalten. ]

Ein paar äquivalente Möglichkeiten, einen Dienst zu starten

Am häufigsten ist systemd Dienste werden durch eine Unit-Datei definiert :eine Textdatei im INI-Format, die die auszuführenden Befehle und verschiedene Einstellungen deklariert. Nachdem diese Unit-Datei bearbeitet wurde, systemctl daemon-reload aufgerufen werden, damit der Manager die neuen Einstellungen lädt. Die Ausgabe des Daemons landet im Journal und wird mit einem separaten Befehl angezeigt. Wenn Befehle interaktiv ausgeführt werden, ist das alles nicht sehr praktisch. Der systemd-run command weist den Manager an, einen Befehl im Namen des Benutzers zu starten, und ist eine großartige Alternative für die interaktive Verwendung. Der auszuführende Befehl wird ähnlich wie sudo angegeben . Das erste Positionsargument und alles danach ist der eigentliche Befehl, und alle vorangehenden Optionen werden von systemd-run interpretiert selbst. Der systemd-run Der Befehl hat Optionen, um bestimmte Einstellungen wie --uid anzugeben und --gid für den Benutzer und die Gruppe. Das -E Option setzt eine Umgebungsvariable, während eine "Catch-All"-Option -p akzeptiert beliebige Schlüssel=Wert-Paare ähnlich der Unit-Datei.

$ systemd-run whoami
Running as unit: run-rbd26afbc67d74371a6d625db78e33acc.service
$ journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
-- Logs begin at Thu 2020-04-23 19:31:49 CEST, end at Mon 2020-04-27 13:22:35 CEST. --
Apr 27 13:22:18 fedora systemd[1]: Started run-rbd26afbc67d74371a6d625db78e33acc.service.
Apr 27 13:22:18 fedora whoami[520662]: root
Apr 27 13:22:18 fedora systemd[1]: run-rbd26afbc67d74371a6d625db78e33acc.service: Succeeded.

systemd-run -t verbindet die standardmäßigen Eingabe-, Ausgabe- und Fehlerströme des Befehls mit dem aufrufenden Terminal. Dies ist großartig, um Befehle interaktiv auszuführen (beachten Sie, dass der Dienstprozess immer noch ein Kind des Managers ist).

$ systemd-run -t whoami
Running as unit: run-u53517.service
Press ^] three times within 1s to disconnect TTY.
root

Konsistente Umgebung

Eine Einheit beginnt immer in einer sorgfältig definierten Umgebung. Wenn wir eine Unit mit systemctl starten oder systemd-run , wird der Befehl immer als untergeordnetes Element des Managers aufgerufen. Die Umgebung der Shell wirkt sich nicht auf die Umgebung aus, in der die Dienstbefehle ausgeführt werden. Nicht alle Einstellungen, die in einer Unit-Datei angegeben werden können, werden von systemd-run unterstützt , aber die meisten sind, und solange wir uns an diese Teilmenge halten, der Aufruf über eine Unit-Datei und systemd-run sind gleichwertig. Tatsächlich systemd-run erstellt spontan eine temporäre Unit-Datei.

Zum Beispiel:

$ sudo systemd-run -M rawhide -t /usr/bin/grep PRETTY_NAME= /etc/os-release

Hier, sudo spricht mit PAM, um eine Rechteausweitung zuzulassen, und führt dann systemd-run aus als Wurzel. Als nächstes systemd-run stellt eine Verbindung zu einer Maschine namens rawhide her , wo es mit dem Systemmanager (PID 1 im Container) über dbus kommuniziert. Der Manager ruft grep auf , der seinen Zweck erfüllt. Das grep Befehl druckt auf stdout, das mit dem Pseudo-Terminal verbunden ist, von dem aus sudo aufgerufen wurde.

Sicherheitseinstellungen

Benutzer und dynamische Benutzer

Lassen Sie uns ohne weiteres über einige spezifische Einstellungen sprechen, beginnend mit den einfachsten und leistungsstärksten Primitiven.

Zuerst der älteste, grundlegendste und möglicherweise nützlichste Mechanismus zur Trennung von Berechtigungen:Benutzer. Sie können Benutzer mit User=foobar definieren im [Dienst] Abschnitt einer Unit-Datei oder systemd-run -p User=foobar , oder systemd-run --uid=foobar . Es mag offensichtlich erscheinen – und auf Android hat jede Anwendung ihren eigenen Benutzer –, aber in der Linux-Welt haben wir immer noch zu viele Dienste, die unnötigerweise als Root ausgeführt werden.

Systemd bietet einen Mechanismus zum Erstellen von Benutzern bei Bedarf. Bei Aufruf mit DynamicUser=yes wird für den Dienst eine eindeutige Benutzernummer vergeben. Diese Nummer wird in einen temporären Benutzernamen aufgelöst. Diese Zuordnung wird nicht in /etc/passwd gespeichert , sondern wird stattdessen spontan von einem NSS-Modul generiert, wenn die Nummer oder der entsprechende Name abgefragt wird. Nachdem der Dienst beendet wurde, kann die Nummer später für einen anderen Dienst wiederverwendet werden.

Wann sollte ein regulärer statischer Benutzer für einen Dienst verwendet werden und wann ein dynamischer? Dynamische Benutzer sind großartig, wenn die Benutzeridentität kurzlebig ist und keine Integration mit anderen Diensten im System erforderlich ist. Aber wenn wir eine Richtlinie in der Datenbank haben, um bestimmten Benutzern Zugriff zu gewähren, Verzeichnisse, die mit einer bestimmten Gruppe geteilt werden, oder irgendeine andere Konfiguration, bei der wir uns auf den Benutzernamen beziehen wollen, sind dynamische Benutzer wahrscheinlich nicht die beste Option.

Namensräume einbinden

Generell ist zu beachten, dass systemd ist oft nur Wrapping-Funktionalität, die vom Kernel bereitgestellt wird. Beispielsweise werden verschiedene Einstellungen, die den Zugriff auf den Dateisystembaum einschränken und Teile davon schreibgeschützt oder unzugänglich machen, erreicht, indem die entsprechenden Dateisysteme in einem nicht freigegebenen Mount-Namespace angeordnet werden.

Auf diese Weise werden mehrere nützliche Einstellungen implementiert. Die beiden nützlichsten und allgemeinsten sind ProtectHome= und ProtectSystem= . Der erste verwendet einen nicht freigegebenen Mount-Namespace, um /home zu erstellen entweder schreibgeschützt oder vollständig unzugänglich. Der zweite betrifft den Schutz von /usr , /boot , und /etc .

Eine dritte ebenfalls nützliche, aber sehr spezifische Einstellung ist PrivateTmp= . Es verwendet Mount-Namespaces, um ein privates Verzeichnis als /tmp sichtbar zu machen und /var/tmp für den Dienst. Die temporären Dateien des Dienstes werden vor anderen Benutzern verborgen, um Probleme aufgrund von Dateinamenkollisionen oder falschen Berechtigungen zu vermeiden.

Die Dateisystemansicht kann auf der Ebene einzelner Verzeichnisse durch InaccessiblePaths= verwaltet werden , ReadOnlyPaths= , ReadWritePaths= , BindPaths= und ReadOnlyBindPaths= . Die ersten beiden Einstellungen bieten allen oder nur Schreibzugriff auf Teile einer Dateisystemhierarchie. Der dritte betrifft die Wiederherstellung des Zugriffs, was nützlich ist, wenn wir nur einem bestimmten Verzeichnis tief in der Hierarchie vollen Zugriff gewähren möchten. Die letzten beiden ermöglichen das Verschieben von Verzeichnissen oder, genauer gesagt, das private Bind-Mounting an einem anderen Ort.

Zurück zum Thema DynamicUser=yes , sind solche vorübergehenden Benutzer nur möglich, wenn der Dienst keine dauerhaften Dateien auf der Festplatte erstellen darf. Wenn solche Dateien für andere Benutzer sichtbar wären, würde ihnen angezeigt werden, dass sie keinen Besitzer haben, oder schlimmer noch, der neue vorübergehende Benutzer könnte mit derselben Nummer auf sie zugreifen, was zu einem Informationsleck oder einer unbeabsichtigten Rechteerweiterung führt. Systemd verwendet Mount-Namespaces, um den größten Teil der Dateisystemstruktur für den Dienst unschreibbar zu machen. Um eine dauerhafte Speicherung zu ermöglichen, wird ein privates Verzeichnis in den Dateisystembaum eingebunden, der für den Dienst sichtbar ist.

Beachten Sie, dass diese Schutzmaßnahmen unabhängig vom grundlegenden Dateizugriffskontrollmechanismus sind, der den Dateibesitz und die Berechtigungsmaske verwendet. Wenn ein Dateisystem schreibgeschützt gemountet wird, können selbst Benutzer, die bestimmte Dateien basierend auf Standardberechtigungen ändern könnten, dies nicht tun, bis das Dateisystem wieder schreibgeschützt gemountet wird. Dies bietet einen Schutz vor Fehlern bei der Dateiverwaltung (schließlich ist es nicht ungewöhnlich, dass Benutzer gelegentlich die falsche Berechtigungsmaske setzen) und ist eine Schicht einer Tiefenverteidigungsstrategie.

Ressourcen, die mithilfe von Mount-Namensräumen implementiert werden, sind im Allgemeinen sehr effizient, da die Kernel-Implementierung effizient ist. Auch der Aufwand bei der Einrichtung ist meist vernachlässigbar.

Automatische Erstellung von Verzeichnissen für einen Dienst

Eine relativ neue Funktion, die systemd Dienste bietet, ist die automatische Verwaltung von Verzeichnissen. Unterschiedliche Pfade des Dateisystems haben unterschiedliche Speichereigenschaften und Verwendungszwecke, fallen jedoch in einige wenige Standardkategorien. Der FHS gibt an, dass /etc ist für Konfigurationsdateien, /var/cache ist für nicht-permanenten Speicher, /var/lib/ ist für semi-permanenten Speicher, /var/log für die Protokolle und /run für flüchtige Dateien. Ein Dienst benötigt häufig ein Unterverzeichnis an jedem dieser Speicherorte. Systemd richtet das automatisch ein, wie es durch ConfigurationDirectory= gesteuert wird , CacheDirectory= , StateDirectory= , LogsDirectory= und RuntimeDirectory= die Einstellungen. Der Benutzer besitzt diese Verzeichnisse. Das Laufzeitverzeichnis wird vom Manager entfernt, wenn der Dienst beendet wird. Die allgemeine Idee besteht darin, die Existenz dieser Dateisystem-Assets an die Lebensdauer des Dienstes zu binden. Sie müssen nicht vorher erstellt werden und werden entsprechend bereinigt, nachdem der Dienst beendet wurde.

$ sudo systemd-run -t -p User=user -p CacheDirectory=foo -p StateDirectory=foo -p RuntimeDirectory=foo -p PrivateTmp=yes ls -ld /run/foo /var/cache/foo /var/lib/foo /etc/foo /tmp/
Running as unit: run-u45882.service
Press ^] three times within 1s to disconnect TTY.
drwxr-xr-x  2 user    user   40 Apr 26 08:21 /run/foo           ← automatically created and removed
drwxr-xr-x  2 user    user 4096 Apr 26 08:20 /var/cache/foo     ← automatically created
drwxr-xr-x. 2 user    user 4096 Nov 13 21:50 /var/lib/foo       ← automatically created
drwxr-xr-x. 2 root    root    4096 Nov 13 21:50 /etc/foo           ← automatically created, but not owned by the user, since the service (usually) shall not modify its own configuration
drwxrwxrwt  2 root    root      40 Apr 26 08:21 /tmp/              ← "sticky bit" is set, but this directory is not the one everyone else sees

Natürlich sind diese sieben Speicherorte (unter Berücksichtigung von PrivateTmp= als zwei) decken nicht die Anforderungen aller Dienste ab, sollten aber für die meisten Situationen ausreichen. Für andere Fälle manuelle Einrichtung oder eine entsprechende Konfiguration in tmpfiles.d ist immer eine Option.

Die automatische Verzeichnisverwaltung passt gut zu DynamicUser= Einstellung und automatisch erstellte Benutzer, indem ein Dienst bereitgestellt wird, der als separater Benutzer ausgeführt wird und den größten Teil des Dateisystembaums nicht ändern darf (selbst wenn die Dateizugriffsberechtigungen dies zulassen würden). Der Dienst kann immer noch auf ausgewählte Verzeichnisse zugreifen und dort Daten speichern, ohne andere Einstellungen als die Unit-Dateikonfiguration.

Beispielsweise könnte ein Python-Webdienst wie folgt ausgeführt werden:

$ systemd-run -p DynamicUser=yes -p ProtectHome=yes -p StateDirectory=webserver --working-directory=/srv/www/content python3 -m http.server 8000

oder über die entsprechende Unit-Datei:

[Service]
DynamicUser=yes
ProtectHome=yes
StateDirectory=webserver
WorkingDirectory=/srv/www/content
ExecStart=python3 -m http.server 8000

Wir stellen sicher, dass der Dienst als vorübergehender Benutzer ausgeführt wird, ohne die Möglichkeit, das Dateisystem zu ändern oder Zugriff auf Benutzerdaten zu haben.

Die hier beschriebenen Einstellungen können als "hohes Niveau" betrachtet werden. Auch wenn die Implementierung knifflig sein mag, die Konzepte selbst sind leicht verständlich und die Auswirkungen auf den Service sind klar. Es gibt eine große Anzahl anderer Einstellungen, um verschiedene Berechtigungen und Funktionen zu entfernen, Netzwerkprotokolle und einstellbare Kernel zu sperren und sogar einzelne Systemaufrufe zu deaktivieren. Diese sind nicht Gegenstand dieses kurzen Artikels. Siehe umfangreiche Referenzdokumentation.

All dies nutzen

Wenn wir ein gutes Verständnis davon haben, was der Dienst tut und benötigt, können wir überlegen, welche Privilegien erforderlich sind und was wir mitnehmen können. Die offensichtlichen Kandidaten laufen als unprivilegierter Benutzer und beschränken den Zugriff auf Benutzerdaten unter /home . Je mehr wir systemd zulassen Dinge für uns einzurichten (zum Beispiel durch Verwendung von StateDirectory= und Freunde), desto wahrscheinlicher ist es, dass der Dienst erfolgreich als nicht privilegierter Benutzer ausgeführt werden kann. Oft benötigt der Dienst Zugriff auf ein bestimmtes Unterverzeichnis, und wir können dies mit ReadWritePaths= erreichen und ähnliche Einstellungen.

Es ist unmöglich, Sicherheitsmaßnahmen auf irgendeine Art und Weise automatisch hinzuzufügen. Ohne ein gutes Verständnis dafür, was der Dienst in verschiedenen Konfigurationsszenarien und für verschiedene Operationen benötigt, können wir keine nützliche Sandbox definieren. Das bedeutet, dass das Sandboxing von Diensten am besten von ihren Autoren oder Betreuern durchgeführt wird.

Bewertung und Status quo

Die Anzahl möglicher Einstellungen ist groß und mit jeder Veröffentlichung von systemd kommen neue hinzu . Da mitzuhalten ist schwer. Systemd stellt ein Werkzeug bereit, um die Verwendung von Sandboxing-Direktiven in der Unit-Datei zu evaluieren. Die Ergebnisse sollten als Hinweise betrachtet werden – schließlich ist, wie oben erwähnt, die automatische Erstellung einer Sicherheitsrichtlinie schwierig, und jede Bewertung besteht darin, nur zu zählen, was verwendet wird und was nicht, ohne ein tiefes Verständnis dafür, was für einen bestimmten Dienst wichtig ist.

$ systemd-analyze security systemd-resolved.service
  NAME                    DESCRIPTION                                                       EXPOSURE
...
✓ User=/DynamicUser=      Service runs under a static non-root user identity                       
✗ DeviceAllow=            Service has a device ACL with some special devices                0.1
✓ PrivateDevices=         Service has no access to hardware devices                                
✓ PrivateMounts=          Service cannot install system mounts                                     
  PrivateTmp=             Service runs in special boot phase, option does not apply                
✗ PrivateUsers=           Service has access to other users                                 0.2
  ProtectHome=            Service runs in special boot phase, option does not apply                
✓ ProtectKernelLogs=      Service cannot read from or write to the kernel log ring buffer          
✓ ProtectKernelModules=   Service cannot load or read kernel modules                               
✓ ProtectKernelTunables=  Service cannot alter kernel tunables (/proc/sys, …)                      
  ProtectSystem=          Service runs in special boot phase, option does not apply                
✓ SupplementaryGroups=    Service has no supplementary groups                                      
...

→ Overall exposure level for systemd-resolved.service: 2.1 OK 🙂

$ systemd-analyze security httpd.service
  NAME                    DESCRIPTION                                                        EXPOSURE
...
✗ User=/DynamicUser=      Service runs as root user                                          0.4
✗ DeviceAllow=            Service has no device ACL                                          0.2
✗ PrivateDevices=         Service potentially has access to hardware devices                 0.2
✓ PrivateMounts=          Service cannot install system mounts                                  
✓ PrivateTmp=             Service has no access to other software's temporary files             
✗ PrivateUsers=           Service has access to other users                                  0.2
✗ ProtectHome=            Service has full access to home directories                        0.2
✗ ProtectKernelLogs=      Service may read from or write to the kernel log ring buffer       0.2
✗ ProtectKernelModules=   Service may load or read kernel modules                            0.2
✗ ProtectKernelTunables=  Service may alter kernel tunables                                  0.2
✗ ProtectSystem=          Service has full access to the OS file hierarchy                   0.2
  SupplementaryGroups=    Service runs as root, option does not matter                          
...

→ Overall exposure level for httpd.service: 9.2 UNSAFE 😨

Auch dies bedeutet nicht, dass der Dienst unsicher ist, sondern dass er nicht systemd verwendet Sicherheitsprimitive.

Betrachtet man die Ebene der gesamten Distribution:

$ systemd-analyze security '*'

Wir sehen, dass die meisten Dienste sehr hoch (d. h. schlecht) abschneiden. Wir können solche Statistiken über verschiedene interne Dienste nicht sammeln, aber es scheint vernünftig anzunehmen, dass sie ähnlich sind. Es gibt sicherlich eine Menge niedrig hängender Früchte, und die Anwendung einiger relativ einfacher Sandboxing-Methoden würde unsere Systeme sicherer machen.

Schluß

Lassen Sie systemd Dienste verwalten und Sandboxing können eine großartige Möglichkeit sein, Ihren Linux-Servern eine Sicherheitsebene hinzuzufügen. Erwägen Sie, die obigen Konfigurationen zu testen, um zu sehen, was Ihrer Organisation zugute kommen könnte.

In diesem Artikel haben wir sorgfältig jede Erwähnung von Netzwerken vermieden. Dies liegt daran, dass der zweite Teil über Socket-Aktivierung und Sandboxing von Diensten sprechen wird, die das Netzwerk verwenden.

[ Vergessen Sie nicht, den systemd-Spickzettel für weitere hilfreiche Hinweise zu lesen. ]


Linux
  1. 10 praktische systemd-Befehle:Eine Referenz

  2. So verwenden Sie den Systemctl-Befehl zum Verwalten von Systemd-Diensten

  3. So erstellen Sie einen Systemd-Dienst unter Linux

  4. Hinzufügen eines neuen Dienstes zu Linux systemd

  5. Überprüfen Sie die laufenden Dienste unter Linux

Systemctl-Befehle zum Verwalten des Systemd-Dienstes

Verwalten von cgroups mit systemd

So richten Sie die automatische Ausführung eines Python-Skripts mit Systemd ein

Netzwerkdienste

Korrekte Verwendung von Ubuntu systemctl zur Steuerung von Systemd

systemd - Gibt meinem Dienst mehrere Argumente