Linux Archiv - credativ®

Einführung

Wenn es um die Bereitstellung von erweitertem Support für End-of-Life (EOL) Linux-Distributionen geht, kann die traditionelle Methode, Pakete aus Quellen zu erstellen, bestimmte Herausforderungen mit sich bringen. Eine der größten Herausforderungen ist die Notwendigkeit für einzelne Entwickler, Build-Abhängigkeiten in ihren eigenen Build-Umgebungen zu installieren. Dies führt oft zur Installation unterschiedlicher Build-Abhängigkeiten, was zu Problemen bei der Binärkompatibilität führt. Zusätzlich sind weitere Herausforderungen zu berücksichtigen, wie die Einrichtung einer isolierten und sauberen Build-Umgebung für verschiedene parallele Paket-Builds, die Implementierung einer effektiven Strategie zur Paketvalidierung und die Verwaltung der Repository-Veröffentlichung usw.

Um diese Herausforderungen zu meistern, wird dringend empfohlen, eine dedizierte Build-Infrastruktur zu haben, die das Erstellen von Paketen auf konsistente und reproduzierbare Weise ermöglicht. Mit einer solchen Infrastruktur können Teams diese Probleme effektiv angehen und sicherstellen, dass die notwendigen Pakete und Abhängigkeiten verfügbar sind, wodurch die Notwendigkeit für einzelne Entwickler entfällt, ihre eigenen Build-Umgebungen zu verwalten.

Der Open Build Service (OBS) von OpenSUSE bietet eine weithin anerkannte öffentliche Build-Infrastruktur, die RPM-basierte, Debian-basierte und Arch-basierte Paket-Builds unterstützt. Diese Infrastruktur ist bei Anwendern, die angepasste Paket-Builds auf wichtigen Linux-Distributionen benötigen, hoch angesehen. Zusätzlich kann OBS als private Instanz bereitgestellt werden, was eine robuste Lösung für Organisationen bietet, die private Softwarepaket-Builds durchführen. Mit seinem umfangreichen Funktionsumfang dient OBS als umfassende Build-Infrastruktur, die die Anforderungen von Teams erfüllt, um sicherheitsgepatchte Pakete auf Basis bereits vorhandener Pakete zu erstellen, um beispielsweise den Support von End-of-Life (EOL) Linux-Distributionen zu verlängern.

Fakten

Am 30. Juni 2024 haben zwei wichtige Linux-Distributionen, Debian 10 „Buster“ und CentOS 7, ihren End-of-Life (EOL)-Status erreicht. Diese Distributionen fanden in zahlreichen großen Unternehmensumgebungen weite Verbreitung. Aufgrund verschiedener Faktoren können Organisationen jedoch Schwierigkeiten haben, Migrationsprozesse rechtzeitig abzuschließen, was eine vorübergehende erweiterte Unterstützung für diese Distributionen erforderlich macht.

Wenn eine Distribution ihren End-of-Life (EOL)-Support vom Upstream erreicht, werden Spiegelserver und die entsprechende Infrastruktur typischerweise entfernt. Dies stellt eine erhebliche Herausforderung für Organisationen dar, die diese Distributionen noch verwenden und erweiterten Support suchen, da sie nicht nur Zugang zu einem lokalen Offline-Spiegel benötigen, sondern auch die Infrastruktur, um Pakete so neu zu erstellen, dass dies mit ihren früheren originalen Upstream-Builds konsistent ist, wann immer Pakete aus Sicherheitsgründen neu erstellt werden müssen.

Ziel

Um Dienstprogramme bereitzustellen, die Teams beim Erstellen von Paketen unterstützen, kann OBS ihnen helfen. Dabei hilft es ihnen auch, eine Build-Infrastruktur zu haben, die alle vorhandenen Quell- und Binärpakete zusammen mit ihren Sicherheitspatches in einer semi-originalen Build-Umgebung erstellt und so den Support für EOL Linux-Distributionen weiter verlängert. Dies wird erreicht, indem Paket-Builds auf einem ähnlichen Qualitäts- und Kompatibilitätsniveau wie ihre früheren originalen Upstream-Builds gehalten werden.

Anforderungen

Um das oben genannte Ziel zu erreichen, sind zwei wesentliche Komponenten erforderlich:

  1. Lokaler Spiegel der EOL Linux-Distribution
  2. Private OBS-Instanz.

Um eine private OBS-Instanz einzurichten, können Sie OBS aus dem Upstream-GitHub-Repository beziehen und auf Ihrem lokalen Rechner installieren sowie bauen, oder alternativ können Sie einfach den OBS Appliance Installer herunterladen, um eine private OBS-Instanz als neue Build-Infrastruktur zu integrieren.

Beachten Sie, dass der lokale Spiegel typischerweise nicht die Sicherheitspatches enthält, um den Support der EOL-Distribution ordnungsgemäß zu erweitern. Das bloße Neuerstellen der vorhandenen Pakete verlängert den Support solcher Distributionen nicht automatisch. Entweder müssen diese Sicherheitspatches von den Teams selbst gepflegt und manuell zum Paket-Update hinzugefügt werden, oder sie werden von einem Upstream-Projekt bezogen, das diese Patches bereits bereitstellt.

Nichtsdestotrotz kann die private OBS-Instanz, sobald sie erfolgreich installiert ist, mit dem lokalen Offline-Spiegel verknüpft werden, für den das Team den Support erweitern möchte, und als semi-originale Build-Umgebung konfiguriert werden. Dadurch kann die Build-Umgebung so nah wie möglich an die ursprüngliche Upstream-Build-Umgebung konfiguriert werden, um ein ähnliches Qualitäts- und Kompatibilitätsniveau zu gewährleisten, auch wenn Pakete für Sicherheitsupdates mit neuen Patches erstellt werden.

Auf diese Weise kann der bereits beendete Support einer Linux-Distribution verlängert werden, da ein dafür vorgesehenes Team nicht nur Pakete selbst erstellen, sondern auch Patches für Pakete bereitstellen kann, die von Fehlern oder Sicherheitslücken betroffen sind.

Ergebnis

Die OBS-Build-Infrastruktur bietet eine semi-originale Build-Umgebung, um den Paket-Build-Prozess zu vereinfachen, Inkonsistenzen zu reduzieren und Reproduzierbarkeit zu erreichen. Durch diesen Ansatz können Teams ihre Entwicklungs- und Test-Workflows vereinfachen und die Gesamtqualität und Zuverlässigkeit der generierten Pakete verbessern.

Demonstration

Wir möchten Ihnen demonstrieren, wie OBS als Build-Infrastruktur für Teams genutzt werden kann, um die benötigte Build- und Veröffentlichungs-Infrastruktur zur Verlängerung des Supports von End-of-Life (EOL) Linux-Distributionen bereitzustellen. Bitte beachten Sie jedoch, dass die folgenden Beispiele ausschließlich Demonstrationszwecken dienen und den spezifischen Ansatz möglicherweise nicht exakt widerspiegeln.

OBS konfigurieren

Sobald Ihre private OBS-Instanz eingerichtet ist, können Sie sich über die WebUI bei OBS anmelden. Sie müssen das Konto „Admin“ mit dem Standardpasswort „opensuse“ verwenden. Danach können Sie ein neues Projekt für die EOL Linux-Distribution erstellen sowie Benutzer und Gruppen für Teams hinzufügen, die an diesem Projekt arbeiten werden. Zusätzlich müssen Sie Ihr Projekt mit einem lokalen Spiegel der EOL Linux-Distribution verknüpfen und Ihr OBS-Projekt anpassen, um die frühere Upstream-Build-Umgebung zu replizieren.

OBS unterstützt die Funktion Download on Demand (DoD), die eine Verbindung zu einem lokalen Spiegel auf der OBS-Instanz mit dem dafür vorgesehenen Projekt innerhalb von OBS herstellen kann, um Build-Abhängigkeiten während des Build-Prozesses bei Bedarf abzurufen. Dies vereinfacht den Build-Workflow und reduziert den Bedarf an manuellen Eingriffen.

Hinweis: Einige Konfigurationen sind nur mit dem Admin-Konto verfügbar, darunter Benutzer verwalten, Gruppen und Download on Demand (DoD)-Repository hinzufügen.

Tauchen wir nun in die OBS-Konfiguration eines solchen Projekts ein.

DoD-Repository hinzufügen

Nachdem Sie ein neues Projekt in OBS erstellt haben, finden Sie die Option „DoD-Repository hinzufügen“ im Reiter „Repositories“ unter dem Admin-Konto.

Dort müssen Sie auf die Schaltfläche „DoD-Repository hinzufügen“ klicken, wie unten gezeigt:

DoD-Repository im Reiter „Repositories“ auf privater OBS-Instanz hinzufügen.

Lokalen Spiegel über DoD verknüpfen

Als Nächstes können Sie eine URL angeben, um Ihren lokalen Spiegel über „DoD-Repository hinzufügen“ zu verknüpfen.

Dadurch konfigurieren Sie das DoD-Repository. Dafür müssen Sie:

Lokalen Spiegel als DoD-Repository konfigurieren.

Die Build-Abhängigkeiten sollten nun für OBS automatisch über die DoD-Funktion zum Download verfügbar sein.

Wie Sie im folgenden Beispiel sehen können, konnte OBS alle Build-Abhängigkeiten für das gezeigte Paket „kernel“ über DoD abrufen, bevor es tatsächlich mit dem Erstellen des Pakets begann:

Download on Demand (DoD): Abrufen von Build-Abhängigkeitspaketen von einem lokalen Offline-Spiegel.

Nachdem Sie den lokalen Spiegel mit der EOL Linux-Distribution verbunden haben, sollten wir auch die Projektkonfiguration anpassen, um die semi-originale Build-Umgebung des früheren Upstream-Projekts in unserem OBS-Projekt neu zu erstellen.

Projektkonfiguration

OBS bietet eine anpassbare Build-Umgebung über die Projektkonfiguration, die es Teams ermöglicht, spezifische Projektanforderungen zu erfüllen.

Es ermöglicht Teams, die Build-Umgebung über die Projektkonfiguration anzupassen, wodurch sie Makros, Pakete, Flags und andere Build-bezogene Einstellungen definieren können. Dies hilft, ihr Projekt an alle Anforderungen anzupassen, die zur Aufrechterhaltung der Kompatibilität mit den Originalpaketen erforderlich sind.

Die Projektkonfiguration finden Sie hier:

OBS bietet eine anpassbare Build-Umgebung.

Werfen wir einen genaueren Blick auf einige Beispiele, die zeigen, wie die Projektkonfiguration für das Erstellen von RPM-basierten oder Debian-basierten Paketen aussehen könnte.

Je nach dem tatsächlichen Pakettyp, der von der EOL Linux-Distribution verwendet wurde, muss die Projektkonfiguration entsprechend angepasst werden.

RPM-basierte Konfiguration
Hier ist ein Beispiel für eine RPM-basierte Distribution, wo Sie Makros, Pakete, Flags und andere Build-bezogene Einstellungen definieren können.

Bitte beachten Sie, dass dieses Beispiel nur zu Demonstrationszwecken dient:

So könnte die Projektkonfiguration aussehen.

Debian-basierte Konfiguration

Dann gibt es ein weiteres Beispiel für eine Debian-basierte Distribution, wo Sie Repotype: debian angeben, vorinstallierte Pakete definieren und Build-bezogene Einstellungen in der Build-Umgebung festlegen können.

Auch hier gilt: Bitte beachten Sie, dass dieses Beispiel nur zu Demonstrationszwecken dient:

Anpassbare Build-Umgebung.

Wie Sie in den obigen Beispielen sehen können, definiert die Repotype das Repository-Format für die veröffentlichten Repositories. Preinstall definiert Pakete, die in der Build-Umgebung verfügbar sein sollten, während andere Pakete erstellt werden. Weitere Details zur Syntax finden Sie im Kapitel Build-Konfiguration im von OpenSuSE bereitgestellten Handbuch.

Mit dieser anpassbaren Build-Umgebungsfunktion beginnt jeder Build in einer sauberen Chroot- oder KVM-Umgebung, was Konsistenz und Reproduzierbarkeit im Build-Prozess gewährleistet. Teams können unterschiedliche Build-Umgebungen in verschiedenen Projekten haben, die isoliert sind und parallel erstellt werden können. Die getrennten Projekte, zusammen mit dedizierten Chroot- oder KVM-Umgebungen für das Erstellen von Paketen, verhindern Interferenzen oder Konflikte zwischen Builds.

Benutzerverwaltung

Zusätzlich zu Projekten bietet OBS eine flexible Zugriffssteuerung, die es Teammitgliedern ermöglicht, unterschiedliche Rollen und Berechtigungen für verschiedene Projekte oder Distributionen zu haben. Dies gewährleistet eine effiziente Zusammenarbeit und ein optimiertes Workflow-Management.

Um diese Funktion ordnungsgemäß zu nutzen, müssen wir Benutzer und Gruppen über das Admin-Konto in OBS erstellen. Den Reiter „Benutzer“ finden Sie innerhalb des Projekts, wo Sie Benutzer und Gruppen mit unterschiedlichen Rollen hinzufügen können.

Das folgende Beispiel zeigt, wie Benutzer und Gruppen in einem bestimmten Projekt hinzugefügt werden:

Integrierte Zugriffssteuerung: verschiedene Benutzer und Gruppen in unterschiedlichen Rollen.

Flexible Projekteinrichtung

OBS ermöglicht Teams, separate Projekte für Entwicklungs-, Staging- und Veröffentlichungszwecke zu erstellen. Dieser kontrollierte Workflow gewährleistet eine gründliche Prüfung und Validierung von Paketen, bevor sie zur Veröffentlichung an das Produktionsprojekt übermittelt werden.

Teststrategie mit Projekten

Bevor Pakete freigegeben und veröffentlicht werden, ist eine ordnungsgemäße Prüfung dieser Pakete entscheidend. Um dies zu erreichen, bietet OBS eine Repository-Veröffentlichungs-Integration, die bei der Implementierung einer Teststrategie hilft. Entwickler können ihre Pakete zuerst in einem entsprechenden Staging-Repository veröffentlichen lassen, bevor der Validierungsprozess der Pakete beginnt, damit sie an das Produktionsprojekt übermittelt werden können. Von dort aus werden sie dann veröffentlicht und anderen Systemen zur Verfügung gestellt.

Es integriert sich nahtlos in die lokale Repository-Veröffentlichung, wodurch sichergestellt wird, dass alle Pakete im Build-Prozess sofort verfügbar sind. Dies eliminiert die Notwendigkeit zusätzlicher Konfigurationen auf internen oder externen Repositories.

Das nächste Beispiel zeigt ein Projekt ‚home:andrew:internal-os:10:staging‘, das als Staging-Repository verwendet wird. Dort importieren wir das Nginx-Quellpaket, um es anschließend zu erstellen und zu validieren.

Build-Prozess starten

Sobald die Build-Abhängigkeiten über DoD heruntergeladen wurden, startet der Build-Prozess automatisch.

Bootstrapping in sauberer Chroot- oder KVM-Umgebung und Start des Build-Prozesses. Zeigt den Fortschritt über die WebUI an.

Sobald der Build-Prozess für ein Paket wie „nginx“ erfolgreich ist, werden wir es auch in der Oberfläche sehen:

Staging-Repository: Build-Ergebnis in der WebUI.

Zu guter Letzt wird das Paket automatisch in einem Repository veröffentlicht, das bereit ist, mit Paketmanagern wie YUM verwendet zu werden. In unserem Testfall hilft uns das YUM-bereite Repository, die Validierung einfach durchzuführen, indem wir Tests auf VMs oder tatsächlicher Hardware durchführen.

Unten sehen Sie ein Beispiel dieses Staging-Repositorys (Index des YUM-bereiten Staging-Repositorys):

Repository-Veröffentlichungs-Integration: automatisch im Staging-Repository veröffentlicht, sobald das Paket erstellt wurde.

Bitte beachten Sie: Um die Teststrategie in unserem Beispiel zu erfüllen, sollten Pakete nur an das Produktionsprojekt übermittelt werden, sobald sie den Validierungsprozess im Staging-Repository erfolgreich durchlaufen haben.

Nach Validierung an Produktion übermitteln

Sobald wir die Funktionalität eines Pakets im Staging-Projekt und -Repository validiert haben, können wir fortfahren, indem wir das Paket an das Produktionsprojekt übermitteln. Unterhalb der Paketübersichtsseite (z. B. in unserem Fall das Paket „nginx“) finden Sie die Schaltfläche „Paket übermitteln“, um diesen Prozess zu starten:

Paket übermitteln

In unserem Beispiel verwenden wir das Projekt ‚internal-os:10:prod‘ für das Produktions-Repository. Zuvor haben wir das Paket „nginx“ im Staging-Repository erstellt und validiert. Nun möchten wir dieses Paket in das Produktionsprojekt übermitteln, das wir ‚internal-os:10:prod‘ nennen.

Sobald Sie auf „Paket übermitteln“ klicken, sollte das folgende Dialogfeld erscheinen. Hier können Sie das Formular ausfüllen, um das neu erstellte Paket an die Produktion zu übermitteln:

Ein Paket vom Staging zur Produktion übermitteln.

Füllen Sie dieses Formular aus und klicken Sie auf „Erstellen“, sobald Sie bereit sind, das neue Paket an das Produktionsprojekt zu übermitteln.

Anfragen finden

Neben der Übermittlung eines Pakets muss ein sogenannter Repository-Master die neu erstellten Anfragen für das vorgesehene Projekt genehmigen. Dieser Master findet eine Liste aller Anfragen im Reiter Anfragen, wo er/sie die Anfragen-Überprüfung durchführen kann.

Der Master findet „Anfragen“ auf der Projektübersichtsseite wie folgt:

Anfragen anzeigen

Hier sehen Sie ein Beispiel eines Pakets, das vom Staging an das Produktionsprojekt übermittelt wurde.

Der Master muss auf den mit einem roten Kreis markierten Link klicken, um die Anfragen-Überprüfung durchzuführen:

Liste der Anfragen

Dies öffnet die Oberfläche für die Anfragen-Überprüfung, in der der Repository-Master des Produktionsprojekts die Paketanfrage ablehnen oder annehmen kann:

Überprüfung durch den Produktions-Repository-Master zum Annehmen oder Ablehnen.

Im Produktionsprojekt reproduzieren

Sobald das Paket in das Produktionsprojekt aufgenommen wurde, wird es automatisch alle erforderlichen Abhängigkeiten von DoD herunterladen und den Build-Prozess reproduzieren.

Das folgende Beispiel zeigt das erfolgreich erstellte Paket im Produktions-Repository, nachdem der Build-Prozess abgeschlossen wurde:

Produktions-Repository: Build-Ergebnis in der WebUI anzeigen.

In YUM-bereitem Repository für Produktion veröffentlichen

Schließlich wird das Paket auch im YUM-fähigen Prod-Repository veröffentlicht, sobald das Paket erfolgreich erstellt wurde. Hier sehen Sie ein Beispiel für das YUM-fähige Prod-Repository, das OBS verwaltet.

(Hinweis: Sie können den Projekt- und Repository-Namen in Ihren Einstellungen festlegen):

Automatische Veröffentlichung im Prod-Repository nach dem Erstellen des Pakets

Extras

In diesem Kapitel möchten wir uns einige mögliche zusätzliche Anpassungen für OBS in Ihren Build-Infrastrukturen ansehen.

Automatisierte Build-Pipelines

OBS unterstützt automatisierte Builds, die sich nahtlos in Delivery-Pipelines integrieren lassen. Teams können CI-Trigger einrichten, um neue Pakete in OBS zu übertragen und automatisch Builds basierend auf vordefinierten Kriterien zu initiieren. Dies reduziert den manuellen Aufwand und gewährleistet die zeitnahe Bereitstellung aktualisierter Pakete.

Pakete erhalten automatisierte Builds, wenn sie in ein OBS-Projekt übertragen werden, wodurch Administratoren automatisierte Builds einfach über Pipelines integrieren können:

Paket erhielt automatisierte Builds, wenn es in ein OBS-Projekt übertragen wurde. Einfache Integration in Delivery-Pipelines.

OBS-Worker

Darüber hinaus ist der Open Build Service in der Lage, mehrere Worker (Builder) in verschiedenen Architekturen zu konfigurieren. Wenn Sie mit anderen Architekturen bauen möchten, installieren Sie einfach das Paket ‚obs-worker‘ auf einer anderen Hardware mit einer anderen Architektur und bearbeiten Sie dann einfach die Datei /etc/sysconfig/obs-server.

In dieser Datei müssen Sie mindestens die folgenden Parameter auf dem Worker-Knoten festlegen:

  • OBS_SRC_SERVER
  • OBS_REPO_SERVERS
  • OBS_WORKER_INSTANCES

Diese Methode kann auch nützlich sein, wenn Sie mehr Worker in derselben Architektur in OBS verfügbar haben möchten. Die öffentliche OBS-Instanz von OpenSUSE verfügt über ein groß angelegtes Setup, das ein gutes Beispiel dafür ist.

Emulierter Build

Im Open Build Service ist es auch möglich, emulierte Builds für Cross-Architektur-Build-Funktionen über QEMU zu konfigurieren. Durch die Verwendung dieser Funktion sind fast keine Änderungen in den Paketquellen erforderlich. Entwickler können auf Zielarchitekturen bauen, ohne dass andere Hardwarearchitekturen verfügbar sein müssen. Sie können jedoch auf Probleme mit Fehlern oder fehlender Unterstützung in QEMU stoßen, und die Emulation kann den Build-Prozess erheblich verlangsamen. Sehen Sie sich beispielsweise an, wie OpenSuSE dies auf RISC-V verwaltet hat, als sie in der anfänglichen Portierungsphase nicht über die echte Hardware verfügten.

Fazit

Die Integration einer privaten OBS-Instanz als Build-Infrastruktur bietet Teams eine robuste und umfassende Lösung, um sicherheitsgepatchte Pakete für die erweiterte Unterstützung von End-of-Life (EOL) Linux-Distributionen zu erstellen. Auf diese Weise können Teams den Paket-Build-Prozess vereinheitlichen, Inkonsistenzen reduzieren und Reproduzierbarkeit erreichen. Dieser Ansatz vereinfacht den Entwicklungs- und Test-Workflow und verbessert gleichzeitig die Gesamtqualität und Zuverlässigkeit der generierten Pakete. Durch die Replikation der semi-originalen Build-Umgebung werden die resultierenden Pakete in ähnlicher Qualität und Kompatibilität wie Upstream-Builds erstellt.

Wenn Sie an der Integration einer privaten Build-Infrastruktur interessiert sind oder erweiterte Unterstützung für eine EOL-Linux-Distribution benötigen, kontaktieren Sie uns bitte. Unser Team verfügt über das Fachwissen, um Sie bei der Integration einer privaten OBS-Instanz in Ihre bestehende Infrastruktur zu unterstützen.

Dieser Artikel wurde ursprünglich von Andrew Lee verfasst.

Mit Version 256 hat systemd run0 eingeführt. Lennart Poettering beschreibt run0 als Alternative zu sudo und erklärt auf Mastodon zugleich, was in seinen Augen das Problem mit sudo ist.

In diesem Blogeintrag wollen wir aber nicht auf die Stärken oder Schwächen von sudo eingehen, sondern uns run0 einmal etwas genauer anschauen und es als sudo-Alternative verwenden.

Anders als sudo verwendet run0 weder die Konfigurationsdatei /etc/sudoers, noch ein SUID-Bit zur Erweiterung der User-Berechtigungen. Im Hintergrund nutzt es systemd-run zum Start neuer Prozesse, welches bereits seit einigen Jahren in systemd zu finden ist.

PolKit kommt zum Einsatz, wenn es darum geht zu prüfen, ob ein User auch entsprechende Berechtigungen besitzt, run0 zu verwenden. Hierbei können alle Regeln verwendet werden, die die Konfiguration von PolKit hergeben. In unserem Beispiel werden wir uns auf eine einfache Variante konzentrieren.

Versuchsaufbau

Für unser Beispiel verwenden wir eine t2.micro EC2-Instanz mit Debian Bookworm. Da run0 erst in systemd Version 256 Einzug gehalten hat und Debian Bookworm zum aktuellen Zeitpunkt noch mit Version 252 ausgeliefert wird, müssen wir zunächst das Debian Testing Repository hinzufügen.

❯ ssh admin@2a05:d014:ac8:7e00:c4f4:af36:3938:206e
…

admin@ip-172-31-15-135:~$ sudo su -

root@ip-172-31-15-135:~# cat < /etc/apt/sources.list.d/testing.list
> deb https://deb.debian.org/debian testing main
> EOF

root@ip-172-31-15-135:~# apt update
Get:1 file:/etc/apt/mirrors/debian.list Mirrorlist [38 B]
Get:5 file:/etc/apt/mirrors/debian-security.list Mirrorlist [47 B]
Get:7 https://deb.debian.org/debian testing InRelease [169 kB]
Get:2 https://cdn-aws.deb.debian.org/debian bookworm InRelease [151 kB]
…
Fetched 41.3 MB in 6s (6791 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
299 packages can be upgraded. Run 'apt list --upgradable' to see them.

root@ip-172-31-15-135:~# apt-cache policy systemd
systemd:
Installed: 252.17-1~deb12u1
Candidate: 256.1-2
Version table:
256.1-2 500
500 https://deb.debian.org/debian testing/main amd64 Packages
254.5-1~bpo12+3 100
100 mirror+file:/etc/apt/mirrors/debian.list bookworm-backports/main amd64 Packages
252.22-1~deb12u1 500
500 mirror+file:/etc/apt/mirrors/debian.list bookworm/main amd64 Packages
*** 252.17-1~deb12u1 100
100 /var/lib/dpkg/status
root@ip-172-31-15-135:~# apt-get install systemd
…

root@ip-172-31-15-135:~# dpkg -l | grep systemd
ii libnss-resolve:amd64 256.1-2 amd64 nss module to resolve names via systemd-resolved
ii libpam-systemd:amd64 256.1-2 amd64 system and service manager - PAM module
ii libsystemd-shared:amd64 256.1-2 amd64 systemd shared private library
ii libsystemd0:amd64 256.1-2 amd64 systemd utility library
ii systemd 256.1-2 amd64 system and service manager
ii systemd-cryptsetup 256.1-2 amd64 Provides cryptsetup, integritysetup and veritysetup utilities
ii systemd-resolved 256.1-2 amd64 systemd DNS resolver
ii systemd-sysv 256.1-2 amd64 system and service manager - SysV compatibility symlinks
ii systemd-timesyncd 256.1-2 amd64 minimalistic service to synchronize local time with NTP servers

root@ip-172-31-15-135:~# reboot
…

Zum initialen Login wird der User admin verwendet. Dieser User wurde durch cloud-init bereits in der Datei /etc/sudoers.d/90-cloud-init-users hinterlegt und darf demnach ohne Passwortabfrage beliebige sudo-Kommandos ausführen.

sudo cat /etc/sudoers.d/90-cloud-init-users
# Created by cloud-init v. 22.4.2 on Thu, 27 Jun 2024 09:22:48 +0000

# User rules for admin
admin ALL=(ALL) NOPASSWD:ALL

Analog zu sudo wollen wir nun run0 für den User admin freischalten.

Ohne weitere Konfiguration erhält der User admin einen Login-Prompt, bei dem er nach dem root-Passwort gefragt wird. Dabei handelt es sich um das Standardverhalten von PolKit.

admin@ip-172-31-15-135:~$ run0 ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== Authentication is required to manage system services or other units. Authenticating as: Debian (admin) Password:

Da dies nicht dem von uns gewünschten Verhalten entspricht, müssen wir in Form einer PolKit-Regel ein wenig nachhelfen. Zusätzliche PolKit Regeln werden unter /etc/polkit-1/rules.d/ abgelegt.

root@ip-172-31-15-135:~# cat < /etc/polkit-1/rules.d/99-run0.rules
polkit.addRule(function(action, subject) {
  if (action.id = "org.freedesktop.systemd1.manage-units") {
    if (subject.user === "admin") {
      return polkit.Result.YES;
    }
  }
});
> EOF

Die verwendete Regel ist dabei wie folgt aufgebaut: Zunächst wird überprüft, ob es sich bei der aufgeführten action um org.freedesktop.systemd1.manage-units handelt. Ist das der Fall, wird geprüft, ob es sich bei dem ausführenden User um den User admin handelt. Sind beide Voraussetzungen erfüllt, gibt unsere Regel „YES“ zurück, was bedeutet, dass keine weitere Prüfung (z.B. Passwortabfrage) nötig ist.

Alternativ hierzu könnte auch geprüft werden, ob der ausführende User einer bestimmten Gruppe angehört, wie z.B. admin oder sudo (if (subject.isInGroup("admin")). Auch wäre es denkbar, den Benutzer nach seinem eigenen Passwort zu fragen anstelle des root-Passworts.

Die neue Regel wird von PolKit automatisch eingelesen und kann sofort verwendet werden. Via journalctl -u polkit kann geprüft werden, ob es etwaige Fehler beim Einlesen der neuen Regeln gab. Nach der Konfiguration von PolKit darf der User admin nun analog zu unserer initialen sudo-Konfiguration run0 ausführen.

Prozessaufbau

Im nachfolgenden Listing wird ersichtlich, worin der Unterschied des Call-Stacks zwischen sudo und run0 liegt. Während im Falle von sudo jeweils eigene Child-Prozesse gestartet werden, startet run0 einen neuen Prozess via systemd-run.

root@ip-172-31-15-135:~# sudo su -
root@ip-172-31-15-135:~# ps fo tty,ruser,ppid,pid,sess,cmd
TT       RUSER       PPID     PID    SESS CMD
pts/2    admin       1484    1514    1484 sudo su -
pts/0    admin       1514    1515    1515  \_ sudo su -
pts/0    root        1515    1516    1515      \_ su -
pts/0    root        1516    1517    1515          \_ -bash
pts/0    root        1517    1522    1515              \_ ps fo tty,ruser,ppid,pid,sess,cmd
admin@ip-172-31-15-135:~$ run0
root@ip-172-31-15-135:/home/admin# ps fo tty,ruser,ppid,pid,sess,cmd
TT       RUSER       PPID     PID    SESS CMD
pts/0    root           1    1562    1562 -/bin/bash
pts/0    root        1562    1567    1562  \_ ps fo tty,ruser,ppid,pid,sess,cmd

Fazit und Anmerkung

Wie das oben stehende Beispiel gezeigt hat, kann run0 generell als einfache sudo-Alternative verwendet werden und bietet dabei einige sicherheitsrelevante Vorteile. Falls run0 sich gegenüber sudo durchsetzt, wird dies allerdings nicht innerhalb des nächsten Jahres geschehen. Einigen Distributionen fehlt stand jetzt schlicht eine ausreichend aktuelle systemd-Version. Hinzukommt, dass die Konfiguration von PolKit für einige Admins nicht zu den täglichen Aufgaben gehört und hier erst Know-How aufgebaut werden muss, um etwaige vorhandene sudo-„Konstrukte“ zu überführen.

Zudem sollte ein entscheidender Vorteil von run0 nicht außen vor bleiben: Standardmäßig färbt es den Hintergrund rot! 😉

Nachdem die vorausgegangenen Artikel eine Einführung in AppArmor gegeben sowie das Vorgehen zum Erstellen eines AppArmor-Profils für nginx beschrieben haben, geht dieser Artikel noch einen Schritt weiter: neben statischen Inhalten soll der nginx-Webserver auch klassische CGI-Scripte ausführen und deren Ausgabe zurückliefern können.

Obwohl das Common Gateway Interface, kurz CGI, seine Hochzeit in den 1990er Jahren erlebte und vielerorts durch modernere Alternativen verdrängt wurde, finden sich auch heute noch Systeme und Prozesse bei denen jahrzehntelang gepflegte CGI-Scripte zum Einsatz kommen.

Unabhängig davon eignet sich die Thematik für diesen Artikel deshalb besonders, weil der Aufbau und die Einrichtung eines Szenarios einfach genug zu beschreiben ist, sodass das eigentliche Thema für diesen Artikel, nämlich AppArmor-Kindprofile, problemlos auf andere Konstrukte übertragen werden kann.

nginx und fcgiwrap

Eine weit verbreitete Methode, um den nginx-Webserver um die Fähigkeit CGI-Scripte ausführen zu können zu erweitern, ist die Verwendung von fcgiwrap.

Die Projektbeschreibung nennt fcgiwrap einen “einfachen FastCGI-Wrapper für CGI-Scripte”, also ein Programm, das herkömmliche CGI-Scripte ausführt und für diese die FastCGI-Kommunikation mit dem Webserver übernimmt, ohne dass die eigentlichen Scripte angepasst werden müssen.

Um den Rahmen dieses Artikels nicht zu sprengen sei auf die Anleitung aus dem nginx-Wiki zur Installation und Konfiguration von fcgiwrap verwiesen.

Beispielkonfiguration

Das folgende Listing zeigt die für unser Szenario verwendete Konfigurationsdatei /etc/nginx/conf.d/cgi-bin.conf, welche auf der von fcgiwrap mitgelieferten Beispielkonfiguration, zu finden unter /usr/share/doc/fcgiwrap/examples/nginx.conf, basiert.

location /cgi-bin/ { 
  gzip off;

  root /var/www;

  fastcgi_pass unix:/var/run/fcgiwrap.socket;

  include /etc/nginx/fastcgi_params;

  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Aus dem Listing ist leicht zu entnehmen, dass sich im Ordner /cgi-bin/ unterhalb des root-Verzeichnisses /var/www Scripte befinden, die ausgeführt werden sollen – in diesem Szenario handelt es sich dabei um einfache Python-Scripte.

Das Programm fcgiwrap läuft als eigenständiger Prozess auf dem System und öffnet ein Unix-Socket, über das der nginx-Webserver gemäß dem FastCGI-Protokoll mit fcgiwrap kommunizieren kann, um die Ausführung der Scripte zu veranlassen und die dabei erzeugte Ausgabe zurück zu erhalten.

Für die Überwachung und Absicherung des nginx-Webservers mittels AppArmor wird das im vorherigen Artikel erstellte Profil benutzt. Obwohl bislang ungenutzte Funktionen des nginx zum Einsatz kommen muss dieses nicht angepasst werden: auf den Ordner cgi-bin/ greift der Webserver selbst überhaupt nicht zu; stattdessen veranlasst er fcgiwrap über das Socket das angeforderte Script auszuführen.

Auch der Zugriff auf das Socket /var/run/fcgiwrap.socket muss nicht explizit erlaubt werden: hierfür existieren bereits Regeln in abstractions/base.

Kindprozesse

Spätestens hier wird klar: wenn nicht nginx, sondern fcgiwrap die Scripte ausführt, greift das verwendete AppArmor-Profil nicht, da dieses nur die nginx-Prozesse überwacht. Es muss also ein weiteres Profil angelegt werden!

Mit einem einzigen Profil für fcgiwrap ist es dabei jedoch nicht getan, denn ein Script, das von fcgiwrap ausgeführt wird, läuft als eigenständiger Kindprozess: für ein Python-Script startet fcgiwrap also beispielsweise den Python-Interpreter – und der sollte dann auch von AppArmor überwacht werden!

Anders gesagt: da ein Profil für fcgiwrap wiederum nur Prozesse der Programmdatei /usr/sbin/fcgiwrap berücksichtigt, liefe der Python-Interpreter ohne AppArmor-Kontrolle. Durch spezielle Execute-Permissions und Kindprofile lässt sich jedoch genau bestimmen ob und in welchem Rahmen Kindprozesse eines von AppArmor überwachten Prozesses ausgeführt werden dürfen.

Kindprofile

Das folgende Listing zeigt den Inhalt des AppArmor-Profils /etc/apparmor.d/usr.sbin.fcgiwrap. Anhand dieses Listings soll die Definition von Kindprofilen erläutert werden:

include <tunables/global>

@{CGIBIN}=/var/www/cgi-bin

profile fcgiwrap /usr/sbin/fcgiwrap {
    include <abstractions/base>

    /usr/sbin/fcgiwrap mr,

    @{CGIBIN}/* rcix,

    profile @{CGIBIN}/* {
        include <abstractions/base>
        include <abstractions/nameservice>
        include <abstractions/python>

        /usr/bin/python3.9 mr,

        @{CGIBIN}/* r,
    }
}

Auf den ersten Blick gleicht das gezeigte Profil den Profilen aus den vergangenen Artikeln. Es fällt jedoch auf, dass innerhalb des Profilteils ein weiteres, namenloses Profil definiert wird, dessen Pfadangabe auf alle Dateien in /var/www/cgi-bin/ passt.

Dieses sogenannte Kindprofil wird durch die Regel @{CGIBIN}/* rcix im äußeren Profil auf alle durch fcgiwrap ausgeführten Scripte im Ordner CGIBIN angewendet. Die Permission r steht dabei wie gehabt für Lesezugriff; interessanter ist die Permission cix: x erlaubt wie gehabt die Ausführung der Datei (execute), c gibt jedoch an, dass dabei ein entsprechendes Kindprofil angewendet werden soll. Wird das Kindprofil nicht gefunden sorgt i dafür, dass das aktuelle Profil vererbt (inherited) wird.

Würde die Permission c als Großbuchstabe, also C, angegeben, würden vor der Ausführung die Umgebungsvariablen gelöscht. Da CGI zur einwandfreien Funktion jedoch auf deren Erhalt angewiesen ist, kam diese Option hier nicht in Frage. Als Alternative zu i gibt es die Permission u, die dafür sorgt, dass der Kindprozess unconfined, also uneingeschränkt, läuft, falls das Kindprofil nicht gefunden wird. Auch dies wäre in diesem Falle nicht wünschenswert.

Im Quick guide to AppArmor profile Language findet sich im Abschnitt File permissions eine Übersicht an Möglichkeiten die Execute-Permission genauer zu spezifizieren. In der AppArmor Core Policy Reference werden sie im Abschnitt Execute rules noch einmal genauer gruppiert erläutert.

Da es sich bei Python um eine interpretierte Sprache handelt, werden die Scripte, wie oben erwähnt, nicht selbst ausgeführt, sondern (vereinfacht gesagt) von einem Interpreter geladen und von diesem ausgeführt. Der Aufbau des eigentlichen Kindprofils sollte daher leicht verständlich sein: der Python-Interpreter /usr/bin/python3.9 darf in den Speicher geladen und ausgeführt werden (mr) und alle Dateien im Ordner @{CGIBIN} lesen, eine Berechtigung zur Ausführung ist nicht nötig.

Zur Ausführung eines Python-Scripts werden in der Regel noch verschiedene Bibliotheken und Module benötigt. Diese, beziehungsweise ihre Pfade, wurden bereits in <abstractions/python> zusammengefasst, sodass AppArmor-Profile für Python-Scripte diese Regeln lediglich mittels include einbinden müssen.

Das obige Kindprofil enthält sonst keinerlei Regeln. Daher beschränken sich die erlaubten Zugriffe auf solche, die von den in den Abstractions enthaltenen Regeln definiert werden. Durch die Erfahrungen aus den letzten beiden Artikeln ist das Kindprofil jedoch schnell um entsprechende Regeln erweitert. Auch aa-logprof kann hierbei wieder zu Rate gezogen werden.

Sollen die Scripte beispielsweise Dateien im Verzeichnis /data lesend und schreibend verarbeiten dürfen, geht es von Hand immer noch am schnellsten: es muss lediglich die Zeile /data/* rw, in das Kindprofil eingetragen werden.

Der Anfang ist gemacht

Der Einsatz von Kindprofilen beschränkt sich nicht nur auf Webserver: jegliche Software, die andere Programme als Kindprozess startet lässt sich nach diesem Prinzip genauso gut mit AppArmor überwachen und absichern. Gleiches gilt für Scripte und Interpreter anderer Programmiersprachen.

Wir unterstützen Sie gerne

Ob AppArmor, Debian oder PostgreSQL: mit über 22+ Jahren an Entwicklungs- und Dienstleistungserfahrung im Open Source Bereich kann die credativ GmbH Sie mit einem beispiellosen und individuell konfigurierbaren Support professionell begleiten und Sie in allen Fragen bei Ihrer Open Source Infrastruktur voll und ganz unterstützen.

Sie haben Fragen zu unserem Artikel oder würden sich wünschen, dass die Spezialisten von credativ sich eine andere Software ihrer Wahl angucken? Dann schauen Sie doch vorbei und melden sich über unser Kontaktformular oder schreiben uns eine E-Mail an info@credativ.de.

Über credativ

Die credativ GmbH ist ein herstellerunabhängiges Beratungs- und Dienstleistungsunternehmen mit Standort in Mönchengladbach. Seit dem erfolgreichen Merger mit Instaclustr 2021 ist die credativ GmbH das europäische Hauptquartier der Instaclustr Gruppe.

Die Instaclustr Gruppe hilft Unternehmen bei der Realisierung eigener Applikationen im großen Umfang dank Managed-Plattform-Solutions für Open Source Technologien wie zum Beispiel Apache Cassandra®, Apache Kafka®, Apache Spark™, Redis™, OpenSearch™, Apache ZooKeeper™, PostgreSQL® und Cadence. Instaclustr kombiniert eine komplette Dateninfrastruktur-Umgebung mit praktischer Expertise, Support und Consulting um eine kontinuierliche Leistung und Optimierung zu gewährleisten. Durch Beseitigung der Komplexität der Infrastruktur wird es Unternehmen ermöglicht, ihre internen Entwicklungs- und Betriebsressourcen auf die Entwicklung innovativer kundenorientierter Anwendungen zu geringeren Kosten zu konzentrieren. Zu den Kunden von Instaclustr gehören einige der größten und innovativsten Fortune-500-Unternehmen.

Wie im vorausgegangenen Beitrag beschrieben erfolgt unter unixoiden Systemen die Rechtekontrolle traditionell nach dem Prinzip der Discretionary Access Control (DAC). Anwendungen und Dienste laufen unter einer bestimmten User- und Group-ID und erhalten die entsprechenden Zugriffsrechte auf Dateien und Ordner.

AppArmor implementiert für Linux, aufbauend auf den Linux Security Modules, eine sogenannte Mandatory Access Control: eine Zugriffskontrollstrategie mit der einzelnen Programmen bestimmte Rechte gewährt oder verweigert werden können. Diese Sicherheitsschicht existiert zusätzlich zur traditionellen DAC.

Seit Debian 10 buster ist AppArmor standardmäßig im Kernel enthalten und aktiviert. Die Pakete apparmor und apparmor-utils bringen Tools zur Erstellung und Pflege von AppArmor-Profilen mit.

Mitgelieferte Profile

Die beiden erwähnten Pakete bringen keine fertigen Profile, sondern lediglich die im vorigen Artikel erwähnten Abstractions mit: Sammlungen von Regeln, die in mehreren Profilen eingebunden werden können.

Manche Programme bringen ihre Profile in ihren Paketen selbst mit, andere enthalten Profile, wenn entsprechende Module nachinstalliert werden – zum Beispiel mod_apparmor beim Apache Webserver.

Die Pakete apparmor-profiles und apparmor-profiles-extra enthalten AppArmor-Profile, die nach der Installation in den Verzeichnissen /etc/apparmor.d (für erprobte Profile) beziehungsweise /usr/share/apparmor/extra-profiles (für experimentelle Profile) zu finden sind. Diese Profile können als Grundlage für eigene Profile herangezogen werden.

Profile selbst erstellen

Während für die meisten gängigen Serverdienste wie beispielsweise den Apache Webserver zumindest experimentelle Profile zur Verfügung stehen, ist für den nginx Webserver nichts zu finden. Dies ist jedoch nicht weiter tragisch, denn ein neues AppArmor-Profil ist mit Hilfe der apparmor-utils schnell erstellt.

Beispiel nginx

Im Folgenden wird eine einfache Basisinstallation des nginx, die lediglich HTML-Dateien unter /var/www/html über HTTP ausliefert, angenommen. Dabei liegt der Fokus vor allem auf der allgemeinen Herangehensweise, sich wiederholende Schritte werden also übersprungen.

Die geschilderte Herangehensweise lässt sich auf beliebige andere Programme übertragen. Um sich über die verwendeten Pfade und Dateien eines Programms zu informieren, kann dpkg mit der Option -L herangezogen werden, welche alle Pfade eines Pakets auflistet. Dabei ist zu beachten, dass hierfür möglicherweise mehrere Pakete befragt werden müssen, für nginx liefert das gleichnamige Paket kaum brauchbare Informationen; diese erhält man erst mit dem Paket nginx-common:

# dpkg -L nginx-common

Für die folgenden Schritte empfiehlt es sich zwei Terminals mit root-Rechten geöffnet zu haben.

Bevor der Webserver-Prozess zur Erstellung eines Profils beobachtet werden kann, müssen all seine laufenden Prozesse beendet werden:

# systemctl stop nginx

Sind alle Prozesse gestoppt, wird im zweiten Terminal aa-genprof mit dem Pfad der Programmdatei des Webservers aufgerufen:

# aa-genprof /usr/sbin/nginx

Es erscheinen einige Informationen zum aktuellen Aufruf von aa-genprof, darunter der Hinweis Profiling: /usr/sbin/nginx, gefolgt von Please start the application to be profiled in another window and exercise its functionality now.

Um dem Folge zu leisten wird im ersten Terminalfenster der Webserver-Prozess wieder gestartet:

# systemctl start nginx

Bevor im zweiten Fenster die Option S zum durchsuchen der Logdateien nach AppArmor-Events aufgerufen wird, sollte der Webserver einige Momente laufen, auch sollte er von einem Browser aus aufgerufen werden, damit möglichst alle üblichen Aktivitäten des Prozesses aufgezeichnet werden.

Ist dies geschehen, können mit einem Druck auf die Taste S die Logdateien nach Events durchsucht werden:

[(S)can system log for AppArmor events] / (F)inish
Reading log entries from /var/log/syslog.
Updating AppArmor profiles in /etc/apparmor.d.
Complain-mode changes:

Wurde ein Event gefunden, wird das betroffene Profil sowie die Aktion angezeigt, die von AppArmor aufgezeichnet wurde:

Profile:    /usr/sbin/nginx
Capability: dac_override
Severity:   9

 [1 - capability dac_override,]
(A)llow / [(D)eny] / (I)gnore / Audi(t) / Abo(r)t / (F)inish

Hier fordert das Programm /usr/sbin/nginx die Capability dac_override an, die bereits im letzten Artikel beschrieben wurde. Sie ist für den Betrieb des Webservers unabdingbar und wird mittels Druck auf A erlaubt. Alternativ kann die Anforderung mit D verweigert oder mit I ignoriert werden. Mit der Option Audit würde diese Anforderung auch weiterhin im laufenden Betrieb in der Logdatei aufgezeichnet.

Profile:    /usr/sbin/nginx
Capability: net_bind_service
Severity:   8

 [1 - #include <abstractions/nis>]
  2 - capability net_bind_service,

Das nächste Event zeigt, dass der Prozess die Capability net_bind_service anfordert, der benötigt wird, um einen Port mit einer Portnummer kleiner als 1024 zu öffnen.

Anders als bei der ersten Nachfrage gibt es hier zwei Möglichkeiten den Zugriff zukünftig zu erlauben: in der ersten Option werden Abstractions für NIS, den Network Information Service eingebunden. In dieser Abstraction, welche unter /etc/apparmor.d/abstractions/nis zu finden ist, ist neben einer Regel, die den Zugriff auf Regelwerke für NIS erlaubt auch die Capability net_bind_service aufgeführt.

Da der HTTP-Server jedoch keine NIS-Funktionalität mitbringt, reicht es lediglich die Capability zu erlauben. Mit einem Druck auf 2 und A wird diese in das Profil übernommen.

Gleiches gilt für die in den folgenden Schritten vorgeschlagenen Abstractions für dovecot und postfix: hier genügt es lediglich die Capabilities setgid und setuid zu erlauben.

Manchmal kann die Bezeichnung der Abstractions etwas irreführend sein: so enthält die Abstraction nameservice neben Regeln, welche neben dem lesenden Zugriff auf die üblichen Nameservice-Dateien wie passwd oder hosts erlauben auch solche Regeln, die den Netzwerkzugriff gestatten. Es lohnt also immer ein Blick in die jeweilige Datei unter /etc/apparmor.d/abstractions/, ob sich das Einbinden der Abstraction lohnt.

Nachdem der Webserver-Prozess alle nötigen Capabilities erhalten hat, versucht er offenbar seine Fehler-Logdatei /var/log/nginx/log mit Schreibrechten zu öffnen. Hier fällt auf, dass neben dem üblichen Allow, Deny und Ignore noch die Optionen Glob und Glob with Extension hinzugekommen sind.

Profile:  /usr/sbin/nginx
Path:     /var/log/nginx/error.log
New Mode: w
Severity: 8

 [1 - /var/log/nginx/error.log w,]
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish

Bei Eingabe von E wird der Liste ein weiterer Vorschlag hinzugefügt:

  1 - /var/log/nginx/error.log w, 
 [2 - /var/log/nginx/*.log w,]

Der Dateiname error.log wurde durch einen Wildcard und die Endung .log ersetzt. Diese Regel würde neben den Schreibrechten auf die Datei /var/log/nginx/error.log beispielsweise auch solche auf die Datei /var/log/nginx/access.log gewähren – das sind (mindestens) zwei Regeln zusammengefasst zu einer einzigen.

Diese Regeln würde für dieses Beispiel bereits ausreichen, möglicherweise sollen aber auch Dateien, welche nicht die Dateiendung .log besitzen, im Verzeichnis /var/log geschrieben werden dürfen. Durch Eingabe von G wird der Liste noch ein weiterer Vorschlag hinzugefügt:

  1 - /var/log/nginx/error.log w, 
  2 - /var/log/nginx/*.log w, 
 [3 - /var/log/nginx/* w,]

Der Dateiname wurde nun durch einen einzelnen Wildcard ersetzt, der Prozess dürfte also beliebige Dateien in /var/log/nginx mit Schreibrechten öffnen.

Wie bereits erwähnt werden durch die vorgeschlagenen Regeln ausschließlich Schreib-, jedoch keine Leserechte eingeräumt, selbst wenn die Zugriffsrechte der Datei mehr erlauben würden. Für Logdatei eines Webservers reichen die Schreibrechte jedoch völlig aus.

Im Folgenden verlangt nginx lesenden Zugriff auf verschiedene Konfigurationsdateien, zum Beispiel /etc/nginx/nginx.conf. Diese befindet sich im Konfigurationsverzeichnis des nginx Webservers, in dem noch weitere Dateien liegen, die ebenfalls gelesen werden können sollen.

Profile:  /usr/sbin/nginx
Path:     /etc/nginx/nginx.conf
New Mode: owner r
Severity: unknown

 [1 - owner /etc/nginx/nginx.conf r,]

Auch hier kann mit G die Regel auf alle Dateien des Verzeichnisses /etc/nginx erweitert werden.

  1 - owner /etc/nginx/nginx.conf r, 
 [2 - owner /etc/nginx/* r,]

Gleiches gilt für die Unterverzeichnisse des Konfigurationsverzeichnisses, diese können durch Globbing als /etc/nginx/*/ abgedeckt werden.

Einen Spezialfall für das Globbing stellen die in jenen Unterverzeichnissen enthaltenen Dateien dar:

Profile:  /usr/sbin/nginx
Path:     /etc/nginx/sites-available/default
New Mode: owner r
Severity: unknown

 [1 - owner /etc/nginx/sites-available/default r,]

Nach zweifacher Eingabe von G wird nach dem von oben bekannten Wildcard * das Wildcard ** vorgeschlagen, welches, wie im vorherigen Artikel beschrieben, alle in Unterverzeichnissen (und deren Unterverzeichnissen) befindlichen Dateien abdeckt.

  1 - owner /etc/nginx/sites-available/default r, 
  2 - owner /etc/nginx/sites-available/* r, 
 [3 - owner /etc/nginx/** r,]

Die letzten Schritte enthielten außerdem alle das Attribut owner: dieses bewirkt, dass eine Regel ausschließlich dann Anwendung findet, wenn der zugreifende Prozess auch Besitzer der Datei ist. Ist die Datei vorhanden, gehört jedoch jemand anderem, wird der Zugriff verweigert.

Es folgen noch einige weitere Pfade und Dateien wie /usr/share/nginx/modules-available/, /run/nginx.pid und /proc/sys/kernel/random/boot_id, welche der nginx ebenfalls zum ordnungsgemäßen Betrieb benötigt. Die Vorgehensweise bleibt jedoch unverändert.

Sind alle Events durchlaufen schließt das Programm mit der Meldung:

= Changed Local Profiles =

The following local profiles were changed. Would you like to save them?

 [1 - /usr/sbin/nginx]
(S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t

Die Optionen sind klar: mit S werden Änderungen gespeichert, mit V können diese vorher als Diff angeschaut werden. Das folgende Listing zeigt das im obigen Durchlauf erzeugte Profil.

include <tunables/global>

profile nginx /usr/sbin/nginx {
    include <abstractions/base>
    include <abstractions/nameservice>

    capability dac_override,
    capability dac_read_search,
    capability setgid,
    capability setuid,

    /usr/sbin/nginx mr,

    /var/log/nginx/*.log w,

    /var/www/html/** r,

    owner /etc/nginx/* r,
    owner /etc/nginx/** r,

    owner /run/nginx.pid rw,

    owner /usr/share/GeoIP/*.mmdb r,
    owner /usr/share/nginx/modules-available/*.conf r,

    owner /var/cache/nginx/** rw,
    owner /var/lib/nginx/** rw,
}

Nach dem Speichern der Änderungen kehrt aa-genprof zu seinem Startbildschirm zurück. Hier könnte nun erneut in Logdateien nach Events gesucht oder aber das Programm mit F beendet werden.

Das Programm endet mit der Meldung:

Setting /usr/sbin/nginx to enforce mode.

Reloaded AppArmor profiles in enforce mode.

Das soeben erstellte Profil wurde also geladen und in den enforce-Modus versetzt. Das bedeutet, dass dem Programm nur noch im Profil erlaubte Zugriffe möglich sind, alle anderen Zugriffsversuche werden von AppArmor blockiert und im Syslog festgehalten.

Für simple Programme ist die Erstellung eines Profils damit beendet und AppArmor kann seine Arbeit verrichten; komplexere Programme hingegen werden im weiteren Verlauf bislang unbekanntes Verhalten zeigen, das vom bislang erstellten Profil unterbunden würde. Hier hilft es, das Profil mittels aa-complain in den sogenannten complain-Modus zu schalten.

# aa-complain nginx

Zugriffe, welche über das bekannte Profil hinausgehen werden im complain-Modus grundsätzlich erlaubt, aber im Syslog festgehalten.

Feb 18 15:25:50 web01 kernel: [ 9908.611408] audit: type=1400 audit(1645197950.338:100): apparmor="ALLOWED" operation="open" profile="nginx" name="/srv/www/index.html" pid=4490 comm="nginx" requested_mask="r" denied_mask="r" fsuid=33 ouid=0

Im obigen Auszug aus dem Syslog wurde das Webroot-Verzeichnis auf dem Host web01 zu /srv/www geändert, das vormals erstellte AppArmor-Profil jedoch nicht angepasst. Da sich das Profil nun im complain-Modus befindet wurde der Zugriff dennoch erlaubt: apparmor="ALLOWED"; im enforce-Mode stünde dort ein DENIED und der Zugriff würde verweigert.

An den übrigen Informationen ist gut zu erkennen, was passiert ist: der Prozess mit der Prozess-ID (pid) 4190 hat versucht die Datei /srv/www/index.html (name) lesend (requested_mask) zu öffnen, was aufgrund des Profils (profile) nginx jedoch untersagt wäre (denied_mask).

Sollte eine mit AppArmor abgesicherte Software also einmal nicht so funktionieren wie erwartet, lohnt zu allererst ein Blick ins Syslog!

Nach einiger Zeit werden sich dort einige Einträge befinden, die dann in das AppArmor-Profil übernommen werden sollen. Dafür wird das Programm aa-logprof verwendet: es durchsucht das Syslog nach Einträgen und fragt in der Manier von aa-genprof nach, ob und wie darauf Einträge im Profil erstellt werden sollen. Dieser Vorgang kann beliebig oft wiederholt werden.

Finden sich im Syslog keine weiteren Einträge mehr, Wurde das Profil genügend angepasst und kann mit aa-enforce wieder in den enforce-Modus versetzt werden:

# aa-enforce nginx

Damit ist die grundlegende Erstellung eines einfachen AppArmor-Profils abgeschlossen und die Prozesse des nginx werden entsprechend der darin definierten Regeln kontrolliert und überwacht.

Wir unterstützen Sie gerne

Ob AppArmor, Debian oder PostgreSQL: mit über 22+ Jahren an Entwicklungs- und Dienstleistungserfahrung im Open Source Bereich, kann die credativ GmbH Sie mit einem beispiellosen und individuell konfigurierbaren Support professionell begleiten und Sie in allen Fragen bei Ihrer Open Source Infrastruktur voll und ganz unterstützen.

Sie haben Fragen zu unserem Artikel oder würden sich wünschen, dass die Spezialisten von credativ sich eine andere Software ihrer Wahl angucken?
Dann schauen Sie doch vorbei und melden sich über unser Kontaktformular oder schreiben uns eine E-mail an info@credativ.de.

Über credativ

Die credativ GmbH ist ein herstellerunabhängiges Beratungs- und Dienstleistungsunternehmen mit Standort in Mönchengladbach. Seit dem erfolgreichen Merger mit Instaclustr 2021 ist die credativ GmbH das europäische Hauptquartier der Instaclustr Gruppe.

Die Instaclustr Gruppe hilft Unternehmen bei der Realisierung eigener Applikationen im großen Umfang dank Managed-Plattform-Solutions für Open Source Technologien wie zum Beispiel Apache Cassandra®, Apache Kafka®, Apache Spark™, Redis™, OpenSearch™, Apache ZooKeeper™, PostgreSQL® und Cadence.
Instaclustr kombiniert eine komplette Dateninfrastruktur-Umgebung mit praktischer Expertise, Support und Consulting um eine kontinuierliche Leistung und Optimierung zu gewährleisten. Durch Beseitigung der Komplexität der Infrastruktur, wird es Unternehmen ermöglicht, ihre internen Entwicklungs- und Betriebsressourcen auf die Entwicklung innovativer kundenorientierter Anwendungen zu geringeren Kosten zu konzentrieren. Zu den Kunden von Instaclustr gehören einige der größten und innovativsten Fortune-500-Unternehmen.

Eigentlich ist Zugriffskontrolle unter Linux eine einfache Sache:

Dateien geben ihre Zugriffsrechte (Ausführen, Schreiben, Lesen) getrennt für ihren Besitzer, ihre Gruppe und zu guter Letzt sonstige Benutzer an. Jeder Prozess (egal ob die Shell eines Benutzers oder ein Systemdienst) auf dem System läuft unter einer Benutzer-ID sowie Gruppen-ID, welche für die Zugriffskontrolle herangezogen werden.

Einem Webserver, der mit den Rechten des Users www-data und der Gruppe www-data ausgeführt wird, kann so der Zugriff auf seine Konfigurationsdatei im Verzeichnis /etc, seine Logdatei unter /log und die auszuliefernden Dateien unter /var/www gestattet werden. Mehr Zugriffsrechte sollte der Webserver für seine Arbeit nicht brauchen.

Dennoch könnte er, sei es durch Fehlkonfiguration oder eine Sicherheitslücke, auch auf Dateien anderer Benutzer und Gruppen zugreifen und diese ausliefern, solange diese für jedermann lesbar sind, wie es zum Beispiel technisch bedingt bei /etc/passwd der Fall ist. Mit der traditionellen Discretionary Access Control (DAC), wie sie unter Linux und anderen unixoiden Systemen eingesetzt wird, ist dies leider nicht zu verhindern.

Schon seit Dezember 2003 bietet der Linux-Kernel jedoch mit den Linux Security Modules (LSM) ein Framework an, mit der sich eine Mandatory Access Control (MAC) implementieren lässt, bei der in Regelwerken genau spezifiziert werden kann, auf welche Ressourcen ein Prozess zugreifen darf. AppArmor implementiert eine solche MAC und ist bereits seit 2010 im Linux-Kernel enthalten. Während es ursprünglich nur bei SuSE und später Ubuntu Verwendung fand, ist es seit Buster (2019) auch in Debian standardmäßig aktiviert.

AppArmor

AppArmor überprüft und überwacht auf Basis eines Profils, welche Berechtigungen ein Programm oder Script auf einem System hat. Ein Profil enthält dabei im Normalfall das Regelwerk für ein einzelnes Programm. Darin ist zum Beispiel definiert wie (lesend, schreibend) auf welche Dateien und Verzeichnisse zugegriffen werden darf, ob ein Netzwerksocket erzeugt werden darf oder ob und in welchem Rahmen andere Anwendungen ausgeführt werden dürfen. Alle anderen, nicht im Profil definierten Aktionen, werden verweigert.

Aufbau eines Profils

Das folgende Listing (die Zeilennummern sind nicht Bestandteil der Datei und dienen lediglich der Orientierung) zeigt das Profil für einen einfachen Webserver, dessen Programmdatei unter /usr/sbin/httpd zu finden ist.

Standardmäßig liegen AppArmor-Profile im Verzeichnis /etc/apparmor.d und sind per Konvention nach dem Pfad der Programmdatei benannt. Der erste Schrägstrich wird dabei ausgelassen, alle folgenden Schrägstriche werden durch Punkte ersetzt. Das Profil des Webservers befindet sich demnach in der Datei /etc/apparmor.d/usr.sbin.httpd.

 1  include <tunables/global>
 2  
 3  @{WEBROOT}=/var/www/html
 4  
 5  profile httpd /usr/sbin/httpd {
 6      include <abstractions/base>
 7      include <abstractions/nameservice>
 8  
 9      capability dac_override,
10      capability dac_read_search,
11      capability setgid,
12      capability setuid,
13
14      /usr/sbin/httpd mr,
15
16      /etc/httpd/httpd.conf r,
17      /run/httpd.pid rw,
18  
19      @{WEBROOT}/** r,
20
21      /var/log/httpd/*.log w,
22  }

Präambel

Die Anweisung include in Zeile 1 fügt, wie die gleichnamige C-Präprozessordirektive, den Inhalt anderer Dateien an Ort und Stelle ein. Ist der Dateiname wie hier von spitzen Klammern umgeben, so bezieht sich der angegebene Pfad auf den Ordner /etc/apparmor.d, bei Anführungszeichen ist der Pfad relativ zur Profildatei.

Vereinzelt, wenn auch mittlerweile veraltet, findet sich noch die Schreibweise #include. Da Kommentare in AppArmor-Profilen jedoch mit einem # beginnen und der Rest der Zeile ignoriert wird, führt die alte Schreibweise zu einem Widerspruch: eine vermeintlich auskommentierte #include-Anweisung würde nämlich sehr wohl ausgeführt! Um eine include-Anweisung auszukommentieren empfiehlt sich also ein Leerzeichen nach dem #.

Die Dateien im Unterordner tunables enthalten in der Regel Variablen- und Alias-Definitionen, die von mehreren Profilen benutzt und gemäß dem don’t repeat yourself-Prinzip (DRY) nur an einer Stelle definiert werden.

In Zeile 2 wird mit @{WEBROOT} die Variable WEBROOT angelegt und ihr den Wert /var/www/html zugewiesen. Würden neben dem aktuellen Profil auch noch andere Profile Regeln für das Webroot-Verzeichnis definieren, könnte sie stattdessen auch in einer eigenen Datei tunables definiert und in den entsprechenden Profilen per include eingebunden werden.

Profilteil

Der Profilteil beginnt in Zeile 5 mit dem Schlüsselwort profile. Ihm folgt der Name des Profils, hier httpd, der Pfad der ausführbaren Datei, /usr/sbin/httpd, sowie gegebenenfalls Flags, welche das Verhalten des Profils beeinflussen. Von geschweiften Klammern eingeschlossen folgen dann die einzelnen Regeln des Profils.

Wie bereits zuvor fügt auch in den Zeilen 6 und 7 include den Inhalt der angeführten Datei an Ort und Stelle ein. Im Unterordner abstractions befinden sich gemäß dem DRY-Prinzip Dateien mit Regelsätzen, die in der gleichen Form immer wieder auftreten, da sie grundlegende aber auch spezifische Funktionalitäten behandeln.

So wird in der Datei base beispielsweise neben dem Zugriff auf verschiedene Dateisysteme wie /dev, /proc und /sys auch der auf Laufzeitbibliotheken oder einige systemweite Konfigurationsdateien geregelt. Die Datei nameservice enthält, entgegen ihrer Benennung, nicht nur Regeln, welche die Namensauflösung betreffen, sondern auch solche, die überhaupt erst einen Zugriff auf das Netzwerk erlauben. Diese beiden abstractions finden sich also in den meisten Profilen, vor allem von Netzwerkdiensten.

Beginnend mit Zeile 9 räumen Regeln mit dem Schlüsselwort capability einem Programm besondere Privilegien, sogenannte capabilities ein. Dabei gehören setuid und setgid sicherlich zu den bekannteren: sie erlauben dem Programm, seine eigene uid und gid zu ändern; beispielsweise kann ein Webserver so als root starten, den privilegierten Port 80 öffnen und dann seine root-Rechte ablegen. dac_override und dac_read_search erlauben es, die Überprüfung von Lese-, Schreib- und Ausführungsrechten zu umgehen. Ohne diese capability dürfte selbst ein Programm, welches unter der uid root läuft, anders als man es von der Shell gewöhnt ist, nicht ungeachtet der Dateiattribute die Dateien zugreifen.

Ab Zeile 14 befinden sich Regeln, welche über Zugriffsberechtigungen auf Pfade (also Ordner und Dateien) entscheiden. Der Aufbau ist denkbar einfach: zu Beginn wird der Pfad angegeben, gefolgt von einem Leerzeichen und den Kürzeln für die erteilten Berechtigungen.

Exkurs: Berechtigungen

Folgende Tabelle liefert einen kurzen Überblick über die gängigsten Berechtigungen:

KürzelBedeutungBeschreibung
rreadlesender Zugriff
wwriteschreibender Zugriff
aappendAnhängen von Daten
xexecuteAusführen
mmemory map executableMappen und Ausführen des Inhalts der Datei im Speicher
klockSetzen einer Sperre
llinkErstellen eines Links

Exkurs: Globbing

Pfade können sowohl einzeln voll ausgeschrieben oder mehrere Pfade durch Wildcards zu einem Pfad zusammengefasst werden. Dieses Globbing genannte Verfahren findet heutzutage auch bei den meisten Shells Anwendung, sodass diese Schreibweise keine Schwierigkeiten mit sich bringen sollte.

AusdruckBeschreibung
/dir/filebezeichnet genau eine Datei
/dir/*umfasst alle Dateien innerhalb von /dir/
/dir/**umfasst alle Dateien innerhalb von /dir/ samt Unterverzeichnisse
?steht für genau ein Zeichen
{}Geschweifte Klammern ermöglichen Alternationen
[]Eckige Klammern können für Zeichenklassen verwendet Werden

Beispiele:

AusdruckBeschreibung
/dir/???bezeichnet also alle Dateien in /dir, deren Dateiname genau 3 Zeichen lang ist
/dir/*.{png,jpg}bezeichnet alle Bilddateien in /dir, deren Dateiendung png oder jpg lautet
/dir/[abc]*bezeichnet alle Dateien in /dir, deren Name mit den Buchstaben a, b oder c beginnt

Für Zugriffe auf die Programmdatei /usr/sbin/httpd erhält der Webserver in Zeile 14 die Berechtigungen mr. Das Kürzel r steht für read und bedeutet, dass der Inhalt der Datei gelesen werden darf, m steht für memory map executable und erlaubt es, den Inhalt der Datei in den Arbeitsspeicher zu laden und auszuführen.

Wer einen Blick in die Datei /etc/apparmor.d/abstractions/base wagt, wird sehen, dass die Berechtigung m unter anderem auch für das Laden von Bibliotheken nötig ist.

Während des Startvorgangs wird der Webserver versuchen seine Konfiguration aus der Datei /etc/httpd.conf auszulesen. Da der Pfad die Berechtigung r zum lesen besitzt, wird AppArmor dies erlauben. Anschließend schreibt httpd seine PID in die Datei /run/httpd.pid. Das Kürzel w steht natürlich für write und erlaubt Schreiboperationen auf dem Pfad. (Zeilen 16, 17)

Der Webserver soll Dateien unterhalb des WEBROOT-Verzeichnisses ausliefern. Um nicht alle Dateien und Unterverzeichnisse nicht einzeln aufführen zu müssen kann der Wildcard ** benutzt werden. Der Ausdruck @{WEBROOT}/** in Zeile 19 steht also für alle Dateien innerhalb und unterhalb des Ordners /var/www/html – inklusive Unterordnern und versteckten Dateien. Da es sich um eine statische Webseite handelt und der Webserver die Dateien nicht zu verändern braucht, wird mit r nur eine Leseberechtigung erteilt.

Wie üblich werden alle Zugriffe auf den Webserver in den Logdateien access.log und error.log im Verzeichnis /var/log/httpd/ protokolliert. Diese werden vom Webserver nur geschrieben, es reicht also in Zeile 21 mit w lediglich eine Schreibberechtigung auf den Pfad /var/log/httpd/* zu setzen.

Damit ist das Profil auch schon komplett und bereit für den Einsatz. Neben den hier gezeigten gibt es noch eine ganze Reihe weiterer Regeltypen, mit denen das erlaubte Verhalten eines Prozesses genau definiert werden kann.

Weitere Informationen zum Aufbau von Profilen finden sich in der Manpage zu apparmor.d sowie im Wiki-Artikel zur AppArmor Quick Profile Language, eine ausführliche Beschreibung aller Regeln findet sich in der AppArmor Core Policy Reference.

Erstellung eines Profils

Einige Anwendungen und Pakete bringen bereits fertige AppArmor-Profile mit, andere müssen noch an die Gegebenheiten angepasst werden. Wiederum andere Pakete bringen überhaupt keine Profile mit – diese müssen vom Admin selbst erstellt werden.

Um ein neues AppArmor-Profil für eine Anwendung zu erstellen wird üblicherweise zuerst ein nur sehr grundlegendes Profil angelegt und AppArmor angewiesen dieses im sogenannten complain mode zu behandeln. Hier werden Zugriffe, welche noch nicht im Profil definiert sind, in den Logdateien des Systems festzuhalten.

Anhand dieser Log-Einträge kann das Profil dann nach einiger Zeit nachgebessert und, wenn keine Einträge mehr in den Logs auftauchen, AppArmor angewiesen werden die in dem Profil in den enforce mode zu übernehmen und die darin aufgeführten Regeln durchzusetzen sowie nicht definierte Zugriffe zu sperren.

Auch wenn es problemlos möglich ist ein AppArmor-Profil von Hand in einem Texteditor zu erstellen und anzupassen, finden sich im Paket apparmor-utils verschiedene Hilfsprogramme, die einem die Arbeit erleichtern können: so hilft aa-genprof beim Anlegen eines neuen Profils, aa-complain schaltet dieses in den complain mode, aa-logprof hilft dabei Logdateien zu durchsuchen und dem Profil entsprechende neue Regeln hinzuzufügen, aa-enforce schaltet das Profil letztendlich in den enforce mode.

Im nächsten Artikel dieser Serie werden wir auf den hier geschaffenen Grundlagen ein eigenes Profil für den Webserver nginx erstellen.

Wir unterstützen Sie gerne

Ob AppArmor, Debian oder PostgreSQL: mit über 22+ Jahren an Entwicklungs- und Dienstleistungserfahrung im Open Source Bereich kann die credativ GmbH Sie mit einem beispiellosen und individuell konfigurierbaren Support professionell begleiten und Sie in allen Fragen bei Ihrer Open Source Infrastruktur voll und ganz unterstützen.

Sie haben Fragen zu unserem Artikel oder würden sich wünschen, dass die Spezialisten von credativ sich eine andere Software ihrer Wahl angucken? Dann schauen Sie doch vorbei und melden sich über unser Kontaktformular oder schreiben uns eine E-Mail an info@credativ.de.

Über credativ

Die credativ GmbH ist ein herstellerunabhängiges Beratungs- und Dienstleistungsunternehmen mit Standort in Mönchengladbach. Seit dem erfolgreichen Merger mit Instaclustr 2021 ist die credativ GmbH das europäische Hauptquartier der Instaclustr Gruppe.

Die Instaclustr Gruppe hilft Unternehmen bei der Realisierung eigener Applikationen im großen Umfang dank Managed-Plattform-Solutions für Open Source Technologien wie zum Beispiel Apache Cassandra®, Apache Kafka®, Apache Spark™, Redis™, OpenSearch™, Apache ZooKeeper™, PostgreSQL® und Cadence. Instaclustr kombiniert eine komplette Dateninfrastruktur-Umgebung mit praktischer Expertise, Support und Consulting um eine kontinuierliche Leistung und Optimierung zu gewährleisten. Durch Beseitigung der Komplexität der Infrastruktur wird es Unternehmen ermöglicht, ihre internen Entwicklungs- und Betriebsressourcen auf die Entwicklung innovativer kundenorientierter Anwendungen zu geringeren Kosten zu konzentrieren. Zu den Kunden von Instaclustr gehören einige der größten und innovativsten Fortune-500-Unternehmen.

In der Software ’sudo‘, die auf fast jedem System installiert ist, um Nutzern begrenzte root-Nutzer-Privilegien zu gewähren, wurde eine schwerwiegende Sicherheitslücke entdeckt. Dieser Lücke (mit dem Namen ‚Baron Samedit‘) wurde die CVE-Nummer CVE-2021-3156 zugewiesen. Durch eine Änderung im Quellcode wurde dieser Fehler bereits 2011 in sudo eingebaut und betrifft damit auch ältere Betriebssystem-Versionen.

Details zur Sicherheitslücke

Dieser Heap-based buffer overflow erlaubt es selbst Nutzern, die nicht im sudoers-File stehen, unter bestimmten Bedingungen Administrator-Privilegien zu erlangen. Ausführlich beschrieben wird diese Lücke auf der OSS-Mailingliste.

Die Software-Entwickler von sudo haben am 26. Januar 2021 die Version 1.9.5p2 herausgegeben, die diesen Fehler behebt. Die meisten Software-Distributionen haben hierfür bereits Update-Pakete zur Verfügung gestellt.

Unterstützung

Wir empfehlen Ihnen, Ihre jeweiligen Betriebssysstem-Distributionen zu aktualisieren und entsprechende Updates einzuspielen, wenn Sie sudo auf Ihren Systemen installiert haben.

Wenn sie hierbei Unterstützung benötigen, steht Ihnen das Open Source Support Center der credativ GmbH gerne zur Verfügung. Kontaktieren sie uns noch heute.

Wichtige Updates – Erste Tests mit CentOS 7 und mögliche Auswirkungen

Das aktuelle Thema Meltdown und Spectre wurde in einem vorhergehenden Artikel bereits ausführlich besprochen. Dort gab es erste kleine Tests mit dem Auswirkungen von den relevanten Securitypatches auf die Geschwindigkeit des Systems. Kernelentwickler hatten bereits darauf hingewiesen, dass unter Umständen mit 5 bis 30 Prozent Geschwindigkeitseinbußen zu rechnen ist.

Es gab einige Fragen zu den Auswirkungen in CentOS 7, da einige der Securitypatches in den Kernel portiert wurden (und damit logischerweise auch in RHEL 7 zu finden sind). CentOS 7 bietet mit dem Kernel 3.10.0-693.11.6 einen aktuellen Patchlevel mit aktuellen Fixes für die beschriebenen Sicherheitsproblemen an, unter anderm auch die Backports für Kernel Page Tables Isolation (KPTI). Um vollständig alle Sicherheitsprobleme zu beheben, muss ferner die Aktualisierungen des Microcodes eingespielt werden (microcode_ctl-2.1-22.2). Red Hat hat hier einen entsprechenden Artikel online gestellt, der die Änderungen erläutert.

Ist das System nach aktuellem Kenntnisstand geschützt, kann dies über die jeweiligen debugfs Einträge abgefragt werden:

$ cat /sys/kernel/debug/x86/ibrs_enabled
0
 
$ cat /sys/kernel/debug/x86/ibpb_enabled
0
 
$ cat /sys/kernel/debug/x86/pti_enabled
1

Auf dem verwendeten virtuellen Testsystem waren IBRS und IBPB jeweils 0, da auf dem Host auch die relevanten Microcode Updates noch nicht zur Verfügung standen. Insbesondere die Variante 2 (Spectre, Branching Poisoning Attack) soll mit IBPB dann auch nicht mehr möglich sein. Ob und wie alle Einträge tatsächlich aktiviert sind, hängt von der verwendeten Plattform ab. Der Red Hat Artikel bietet hier ebenfalls eine Übersicht über die möglichen Konstellationen. Ein Folgeartikel wird sich mit den jeweiligen unterschiedlichen Einstellungen noch beschäftigen und die Performancemessungen hierfür ebenfalls wiederholen.

Mit den obigen Einstellungen für IBRS und IBPB ist das Testsystem gegen die Varianten 1 (Spectre mit Branch Bounds-checking exploit) und Variante 3 (Meltdown, Speculative Cache Loading) geschützt.

Die folgenden Messungen wurden im Vergleich mit dem vorhergehenden Patchlevel 3.10.0-693.11.1 durchgeführt, um die Auswirkungen zu testen.

Die Messungen

Wie alle Messungen sind die Zahlen im folgenden natürlich spezifisch für die verwendete Umgebung. Die Messungen sollen nicht prinzipiell die Leistungsfähigkeit demonstrieren, sondern die Auswirkungen von Änderungen im Kernel zeigen. Als Benchmark dient erneut der PostgreSQL®-eigene Benchmark pgbench mit PostgreSQL® HEAD vom 05.01.2018. Die Tests wurden auf einer CentOS 7 (7.4.1708) VM durchgeführt, ebenfalls jeweils mit 4 und 8 vCPUs. Anders als in den Tests auf der Fedora 27 Instanz standen jedoch 8 GByte RAM zur Verfügung, allerdings sollte das im wesentlichen keine Auswirkungen auf die Messung haben. pgbench wurde zunächst mit 16 Verbindungen und einem pgbench Thread gestartet. Der Testlauf hier ist SELECT-only.

1_CentOS7_KPTI_pgbench_16

Der ältere Kernel bietet bei dieser Teststellung bei 4 vCPUs ca. 10%, bei der Verwendung doppelt sovieler Prozessoren ca. 5% mehr Durchsatz (41045 zu 37050 respektive 53712 zu 51365 Transaktionen pro Sekunde). pgbench bietet auch die Möglichkeit, mehrere Threads für den Benchmark zu nutzen. Die folgende Teststellung verwendet für jede Datenbankverbindung einen eigenen Thread (Schalter -j für pgbench):

2_CentOS7_KPTI_pgbench_16_threads_16_prepared

Der Vergleich verwendet jeweils 8 vCPUs und stellt die Durchsatzraten mit Prepared Statements (Schalter -Mprepared für pgbench) und normale Statements gegenüber (letzteres ist der Standard für pgbench). Hier ergibt sich sogar ein winziger Performancevorteil für den neuen Kernel mit den sicherheitsrelevanten Patches, dieser liegt zwischen 1 und 2%. Damit liegen diese bei diesem Benchmark quasi gleichauf. pgbench unterstützt die Möglichkeit benutzerdefinierte Skripte auszuführen die es gestattet, eigene SQL Befehle für einen Testlauf zu benutzen. KPTI hat seinen größten Geschwindigkeitsnachteil theoretisch bei Workloads, wo sehr viele Context Switches und Systemaufrufe stattfinden. Im folgenden wird ein unrealistisches Szenario getestet, indem pgbench mit den identischen Laufzeitparametern zu den vorhergehenden Testfällen immer ein

SELECT 1;

verwendet. Das Ergebnis stellt sich wie folgt dar:

3_CentOS_KPTI_SELECT_One_prepared_0

Der Einbruch gegenüber dem Kernel mit KPTI ist hier deutlich. Er verliert knapp 16% gegenüber dem Kernel ohne die entsprechenden Fixes, in Zahlen sind es 167352 (3.10.0-693.11.1) gegenüber 147947 (Kernel 3.10.0-693.11.6) Transaktionen pro Sekunden.

Das ganze lässt sich noch weitertreiben, in dem gar keine Abfrage mehr ausgeführt und man daher Parser und alles nachgelagerte der Datenbank außen vor lässt. Man erreicht dies, indem pgbench lediglich ein „;“ als SQL-Skript vorgesetzt bekommt. Dies stresst hauptsächlich nur noch das System selbst, da die Datenbank im Endeffekt nichts mehr zu tun hat und lediglich Prozesse forked. Das Resultat gestaltet sich dann wie folgt:

4_CentOS_KPTI_pgbench_noop

In absoluten Zahlen stehen hier im Schnitt 254574 zu 230021 (Kernel 3.10.0-693.11.1 sowie 3.10.0-693.11.6) gegenüber. Dies entspricht 10% weniger Durchsatz im Falle des neuen Kernels. Interessant ist auch der Vergleich, falls die CPU und der verwendete Kernel kein PCID (Process-Context Identifiers) bieten. Dies bedeutet in den meisten hier getesteten Fällen einen deutlichen Einbruch der Durchsätze. Der Testfall mit pgbench und 16 Datenbankverbindungen SELECT-only (ohne Prepared Statements) mit jeweils einem eigenem pgbench Thread ergibt im Vergleich:

5_CentOS7_KPTI_pgbench_16_nopcid

Im Vergleich zum Kernel ohne KPTI hat das Fehlen der Unterstützung von PCID einen Einbruch um ca. 7% zur Folge (56232 im Vergleich zu 52757 Transaktionen pro Sekunde). Noch deutlicher fällt das Resultat im Vergleich des „noop“-Benchmarks aus. Hier beträgt der Unterschied zwischen dem Kernel ohne KPTI, mit KPTI und dem Fehlen von PCID jeweils 10% und 16%:

6_CentOS7_KPTI_pgbench_16_noop_nopcid

Interessant bei diesen Vergleichen ist noch der Einfluss von schreibenden Transaktionen. Hierzu wurde pgbench anstatt im SELECT-only Modus (Schalter -S) im TPC-B Workload getestet. Der beobachtete Trend der Geschwindigkeitseinbußen von moderat beim Kernel mit KPTI Unterstützung hin zu bemerkenswert lässt sich auch hier beobachten:

7_CentOS7_KPTI_pgbench_16_tpcb_prepared_nopcid

Fazit

In den getesteten Fällen mit pgbench zeigt sich, dass die von den Kernelentwickler pauschal angegebenen Einbußen beim Kernelupgrade durchaus realistisch sind. Allerdings kann nicht pauschal eine spezifische Zahl genannt werden, da die Einbrüche sehr stark abhängig vom Workload sind. pgbench an sich ist bereits ein sehr synthetischer Workload, der zwar die Datenbank stark stressen kann, allerdings mit der realen Welt sehr wenig gemein hat. Selbst hier bedarf es stark unrealistischer Benchmarkkonfigurationen, um die Einbrüche in den genannten Bereichen tatsächlich aufzuzeigen, nie wurde aber der Worstcase von 30 % tatsächlich erreicht. Dies bedeutet nicht, dass es den Workload mit diesen Geschwindigkeitseinbußen tatsächlich nicht gibt. Eher unterstreichen die von uns ermittelten Ergebnisse die Bedeutung von eigenen, möglichst Anwendungsnahen Tests, die spezifisch den eigenen Workload und etwaige zu erwartende Einbußen überprüfen. In der Mehrzahl pendeln sich die Unterschiede zu Ungunsten des CentOS7 Kernels mit Sicherheitspatches in etwa bei 5% ein, es können jedoch durchaus mehr sein. Besonders wenn die Unterstützung von PCID von der Plattform nicht gewährleistet wird, so kann dies deutlich höhere Einbußen nach sich ziehen, bis zu 16% in den hier gezeigten Teststellungen.

Mit Beginn des Jahres 2018 wurden Probleme mit dem Speichermanagement und Intel-Prozessoren öffentlich. Laut diesen Reports ist es offensichtlich möglich, beliebige Bereiche des Kernel- und Userspace Speicherbereiches auszulesen und damit unter Umständen auf sensitive Bereiche zugreifen zu können.

Im Laufe der letzten Tage gab es einige Gerüchte und Vermutungen, in welche Richtung dies alles geht, mittlerweile gibt es ein offizielles Statement der Hacker von Project Zero (Google), die die Erkenntnisse zusammenfassen.

Was ist genau passiert?

Im wesentlichen wurden Angriffsvektoren identifiziert, die über die nicht erfolgreiche, spekulative Ausführung von Code auf einem Prozessor und geschicktes Timing privilegierte Informationen trotz fehlender Berechtigung aus dem Cache der CPU auslesen kann. Hierbei ist es möglich, trotz der fehlenden Berechtigung (sei es aus dem User- oder innerhalb des Kernelspace) Speicherbereiche zu lesen und deren Inhalte zu interpretieren. Dies ermöglich theoretisch breitflächige Einfallstore für Schadsoftware, wie etwa das Ausspionieren sensibler Daten oder der Mißbrauch von Berechtigungen. Man spricht hier von sogenannten Side Channel Attacks. Betroffen sind nach aktuellem Kenntnisstand nicht nur Intel CPUs (von denen man ausschließlich ursprünglich ausging), sondern auch AMD CPUs, ARM sowie POWER8 und 9 Prozessoren.

Was genau geschieht aktuell?

Project Zero fasst im Report die wesentlichen Probleme zusammen. Es existieren mehrere Exploits die mit unterschiedlichen Ansätzen privilegierte Speicherbereiche lesen und damit unberechtigterweise an Informationen in sensiblen Bereichen des Kernels oder anderer Prozesse gelangen können. Da fast alle modernen CPUs die spekulative Ausführung von Anweisungen unterstützen, um ein Leerlaufen ihrer Ausführungseinheiten und damit verbundene hohe Latenzen zu verhindern, sind eine Vielzahl von Systemen theoretisch betroffen. Ein weiterer Ansatzpunkt dieses Angriffsszenarios ist die in aktuellen Systemen vorhandene Art und Weise, wie die Speicherbereiche von User- und Kernelspace interagieren. Tatsächlich war es bisher so, dass diese Speicherbereiche nicht wirklich voneinander getrennt sind, sondern der Zugriff auf diese Bereiche mit einem speziellen Bit abgesichert sind. Der Vorteil dieser nicht vorhandenen Trennung sind insbesondere dann tragend, wenn beispielsweise häufig von User- auf Kernelspace umgeschaltet werden muss.

Die Angriffsszenarien im Einzelnen sind:

Dieses Angriffsszenario nutzt die in modernen CPUs vorhandene Branch Prediction, d.h. Vorabanalyse über die Wahrscheinlichkeit dass bestimmter Code bzw. -zweige erfolgreich ausgeführt werden können. Hierbei wird die CPU dazu verleitet eigentlich von der Vorhersage nicht berücksichtigten Code spekulativ auszuführen. Diese Attacke kann dann benutzt werden, um schadhaften Code auszuführen. Diese Attacke funktioniert theoretisch auf allen CPUs mit entsprechender Sprungvorhersage, allerdings ist es laut Project Zero hier schwer zusammenfassend zu sagen welche Prozessoren in welcher Art betroffen sind. Spectre hat vor allem Anwendungen im Userspace im Fokus. Da Spectre hauptsächlich dann funktioniert, wenn bereits fehlerhafter Code in entsprechenden Anwendungen vorhanden ist, sollte besonders auf entsprechende Updates geachtet werden.

Bei Meltdown wird nun die spekulative Ausführung genutzt, um Code auszuführen, der eigentlich gar nicht final erreicht werden kann. Dies sind hier Exception-Anweisungen mit nachfolgenden Instruktionen, die nie ausgeführt würden. Durch die spekulative Ausführung der CPU werden diese Instruktionen dennoch von der CPU berücksichtigt. Zwar gibt es keine Seiteneffekte durch diese Art der Ausführung, dennoch verbleiben die von der Instruktion belegten Speicheradressen im Cache der CPU und können von dort genutzt werden, um alle Speicheradressen zu testen. Da aktuell der Speicherbereich des Kernels und des Userspaces gleichermaßen zusammenhängend organisiert sind, kann so nicht nur der gesamte Speicherbereich des Kernels, sondern auch aller auf dem System laufenden Prozesse ausgelesen werden. Eine detaillierte Beschreibung der Funktionsweise des Angriffs ist hier zu finden. Meltdown funktioniert nur auf Intel Prozessoren, da nur hier die Privilegien auf den adressierten Speicherbereich bei Out-Of-Order Ausführung nicht mehr geprüft werden.

Beide Szenarios machen sich auf unterschiedliche Art und Weise die entsprechenden Sicherheitslücken zunutze. Die CVEs zu den Lücken sind im einzelnen:

Wie geht es weiter?

Um Meltdown-Attacken vorzubeugen gibt es für Linux, Windows und OSX bereits entsprechende Updates (Letzteres enthält entsprechende Änderungen bereits seit geraumer Zeit. Im wesentlichen trennen diese Updates die Speicherverwaltung für den Kernel- und Userspace völlig auf (unter Linux bekannt als KPTI-Patches, Kernel Page Table Isolation, formals auch KAISER). Hierdurch ist es nicht mehr möglich, durch die Privilege Escalation auf Intelprozessoren auf Speicherbereiche des Kernels ausgehend eines unprivilegierten Kontextes zuzugreifen. RedHat stellt diese ebenso wie CentOS und Fedora bereits mit aktualisierten Kerneln zur Verfügung.

Insbesondere die Meltdown-Attacken werden hierbei wirkungsvoll unterdrückt, für Spectre-Attacken selbst gibt es ausgehend der aktuellen Sachlage keine belastbaren, wirkungsvollen Maßnahmen. Wichtig ist jedoch, dass im Linuxkernel eBPF und die entsprechende Ausführung vom BPF-Code im Kernel deaktiviert wird.

sysctl -a | grep net.core.bpf_jit_enable
sysctl net.core.bpf_jit_enable=0

Die Änderung setzt „root“-Berechtigungen voraus.

Geschwindigkeit aktualisierter Kernel

Durch die Trennung der Speicherverwaltung für Kernel- und Userspace werden Kontextwechsel und Systemaufrufe teurer. Dies sorgt für deutlich höhere Latenzen, insbesondere wenn die Anwendung sehr viele Kontextwechsel (beispielsweise Netzwerkommunikation) verursacht. Die Einbußen hierbei sind schwierig zu bemessen, da nicht jeder Workload wirklich identische Zugriffsmuster zugrunde legt. Für kritische Systeme empfehlen sich daher Lasttests auf identischen Testsystemen falls möglich. Falls nicht möglich sollten die Lastparameter nach der Aktualisierung des Systems sorgfältig beobachtet werden. Man nimmt pauschal Geschwindigkeitseinbußen im Rahmen 5% an, letztendlich wurden jedoch auch Tests der Kernelentwickler mit bis zu 30% Einbußen beobachtet. Da die Page Table Isolation (bis jetzt) nur für x86_64 Architekturen verfügbar ist, treffen diese Zahlen tatsächlich nur auf Maschinen mit Intel Prozessoren zu. Tatsächlich wird KPTI vom Kernel Upstream standardmässig nicht für AMD aktiviert.

Ob KPTI aktiviert ist, lässt sich per Kernel-Log ermitteln:

dmesg -T | grep "page tables isolation"
[Fr Jan  5 10:10:16 2018] Kernel/User page tables isolation: enabled

Insbesondere Anwender von Datenbanken sind hier sensitiv, da zum Beispiel Systeme wie PostgreSQL® in der Regel eine hohe Anzahl Context Switches bei hoher Last verursachen. Bei credativ haben wir den Impact auf einem kleinen virtualisierten System klassifiziert. Grundlage ist ein Fedora 27 System als KVM-Gast mit 4 GByte RAM und schnellem NVMe-Speicher als Grundlage. Dies spielt bei diesem Test jedoch eher ein unbedeutende Rolle, da die mit pgbench durchgeführten Datenbanktests lediglich eine Größe von knapp 750 MByte hat. Der Shared Buffer Pool der PostgreSQL® Instanz war mit 1 GByte so konfiguriert, dass die komplette Datenbank in den Datenbankcache passt. Die Tests wurden jeweils mit 4 und 8 virtuellen Prozessoren durchgeführt. Das Hostsystem verfügt über einen Intel Core i7-6770HQ Prozessor.

Die größte Auswirkung ist zu beobachten, wenn PCID im Kernel nicht vorhanden bzw, deaktiviert ist. PCID ist eine Optimierung, die einen Flush des Translation Lookaside Buffers (TLB) verhindert, wenn ein Context Switch erfolgt. Virtuelle Speicheradressen werden nur dann erfolgreich per TLB aufgelöst, wenn die PCID zum aktuellen Thread auf der jeweiligen CPU passt. PCID steht ab kernel 4.14 zur Verfügung. Der Test vergleicht einen Entwicklerkernel mit Page Table Isolation (PTI) 4.15.0-rc6, aktuellen Fedora Upstreamkernel mit und ohne Sicherheitspatches. PTI kann deaktiviert werden, indem man dem Kernel mittels pti=off beim Booten ein entsprechendes Argument definiert.

Das Fedora Testsystem hat bereits einen sehr aktuellen Kernel (4.14). Der Unterschied zum alten Upstreamkernel 4.14.8 ohne Sicherheitsrelevante Patches beträgt zum neuen Kernel 4.14.11 ca. 6%. pgbench liefert dann folgende Durchsatzraten (Transaction per second):

TPS_KPTI

Legt man den ehemaligen Standardkernel 4.14.8 von Fedora 27 mit 100% zugrunde, ergibt sich folgendes Bild:

Percent_Kernels_KPTI

Die PostgreSQL® Community hat ebenfalls bereits kleinere Tests zum Messen der Auswirkungen durchgeführt. Die Ergebnisse decken sich mit unseren Erkenntnissen. Der neue Kernel 4.14.11 mit den relevanten Patches bietet in etwa diesselbe Performance wie der Entwicklerkernel 4.15.0-rc6 auf dieser Plattform. 4.14.11 ist in diesen Testcases sogar teilweise vor dem alten Upstreamkernel (8 vCPU, Vergleich 4.14.8, grün und 4.14.11, braun). Allerdings liegt der Vorteil hier bei knapp über 1%, so dass man hier eher davon ausgehen kann, dass bei dieser Teststellung keine großen Geschwindigkeitsunterschiede bestehen.

Für Interessierte gibt es zudem eine eigene Seite zu dem Thema. Diese fasst alle wesentliche Informationen zusammen. Empfehlenswert ist auch die Zusammenfassung des Journalisten Hanno Böck auf github, die eine sehr gute Liste aller Patches für Meltdown und Spectre bietet.

Dieser Artikel wurde ursprünglich von Bernd Helmle geschrieben.

Das Kernel-Subsystem Inotify überwacht das Dateisystem und berichtet alle „Events“ genannten Aktionen auf dem Dateisystem wie Zugriffe, Änderungen, Löschungen, neue Dateien und ähnliches an den Userspace. Dies ist vor allen Dingen nützlich für Desktop-Suchprogramme, einige Arten von Virenscannern und jede andere Form von Programm das instantan reagieren muss, sobald sich entsprechende Dateien geändert haben.

Für die Shell besteht die Möglichkeit, mit Hilfe der inotify-tools Inotify zu integrieren. Dafür stehen zwei Programme zur Verfügung: inotifywatch sammelt mehrheitlich statistische Daten und eignet sich für das Reporting der Zahl von Änderungen in einem gegebenen Datenbestand. inotifywatch hingegen kann beliebige Dateien und Verzeichnisse (auch rekursiv) überwachen, dabei verschiedene Typen von Events unterscheiden und auf diesen beruhend andere Programme oder Aktionen aufrufen. Der alltägliche Umgang mit dem Skript wird durch optionale Schalter für das Formatieren der Ausgabe, das Ignorieren von spezifischen Dateien und Verzeichnissen und andere vereinfacht.

Als Beispiel soll im folgenden ein Verzeichnis mit einem LaTeX-Dokument überwacht werden. Der Admin muss kontinuierlich viele kleine Änderungen darin vornehmen, zwischendurch immer wieder die PDF dazu prüfen, und will nicht jedes Mal den notwendigen „make“-Befehl von Hand ausführen. Dabei ist zu beachten, dass es lediglich um den Event-Typus Änderung, also „modify“ geht, und dass die Änderungen in Vim stattfinden, entsprechende temporäre Dateien müssen also ignoriert werden:

 

$ while true; do inotifywait -r -e modify --exclude=".swp" . && make; done
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
./mainmatter/ MODIFY File15.tex
latexmk -pdf -r ./pdflatexmkrc
Latexmk: This is Latexmk, John Collins, 11 Nov. 2012, version: 4.35.
**** Report bugs etc to John Collins . ****
Latexmk: applying rule 'pdflatex'...
Rule 'pdflatex': File changes, etc:
   Changed files, or newly in use since previous run(s):
      'mainmatter/File15.tex'
------------
Run number 1 of rule 'pdflatex'
------------

 

Inotify meldet korrekt eine Änderung in der Datei „File15.tex“ und ruft make auf. Die While-Schleife stellt sicher, dass nach einem Durchlauf das Programm weiter läuft und auf die nächste Änderung wartet.

 

Dieser Artikel wurde ursprünglich geschrieben von Roland Wolters.

Der OOM-Killer kann auf stark ausgelasteten Maschinen für böse Überraschungen sorgen: Prozesse werden plötzlich und unerwartet beendet. Dieses Verhalten lässt sich aber mit Kernel-Bord-Mitteln sehr genau beeinflussen. Administratoren auf Linuxmaschinen mit hoher RAM-Nutzung erleben oft eine Begegnung der unheimlichen Art: den Linux OOM-Killer (OOM = Out Of Memory). Der Administrator findet in diesem Szenario eine „abgestürzte“ PostgreSQL®-Instanz vor, im Serverlog finden sich dann einer oder meist mehrere Einträge der Form

Out of Memory: Killed process PID (Prozessname)

Doch was genau steckt dahinter?

Virtueller Speicher und Overcommit

Virtueller Speicher in Linuxsystemen wird auf vielfältige Weise adressiert: RAM, mmap(), Swap oder Shared Memory, um ein paar Beispiele zu nennen. Es ist möglich, durch das sogenannte Overcommit-Verhalten bei Allokieren von Speicher mehr Ressourcen anzufordern, als tatsächlich im System aktuell vorhanden ist. In solchen Situationen spricht man von einer OOM-Situation, das System hat alle Ressourcen aufgebraucht und ist nicht mehr in der Lage, mehr virtuellen Speicher zu adressieren. Hier wird der OOM-Killer aktiv, der Prozesse nach festgelegten Kriterien auswählt und diese terminiert, um dem System ein wenig Luft zu verschaffen. Dieses Verhalten ist insbesondere für Datenbanksysteme zu berücksichtigen, die nicht auf dedizierter Hardware laufen. Der OOM-Killer bevorzugt in solchen Umgebungen häufig PostgreSQL®, da als Kandidaten zum Terminieren solche Prozesse ausgewählt werden, die mit aggressiver Speichernutzung auffallen. Da der OOM-Killer den gesamten Adressraum aller Kinder inklusive Shared Memory in Summe sieht, erkennt man recht schnell, dass PostgreSQL® auf jeden Fall weit oben in der Liste der Kandidaten auftauchen wird. Wie stark der zur Verfügung stehende Speicher genutzt wird, findet man am schnellsten über das /proc-Filesystem heraus:

$ grep Commit /proc/meminfo
CommitLimit:    376176 kB
Committed_AS:   265476 kB

In diesem Beispiel sind aktuell als Obergrenze 376176 kB(CommitLimit) an Speichernutzung möglich, zugewiesen wurden 265476 kB (Committed_AS). Nähert sich CommitLimit sehr stark an Committed_AS an oder übersteigt diesen sogar, dann ist der Einsatz des OOM-Killers wahrscheinlich. Der Linux-Kernel stellt einige Schnittstellen zur Verfügung, die das Verhalten des OOM-Killers gegenüber PostgreSQL® beeinflusst.

Overcommit abschalten

Die radikalste Methode ist, Overcommit generell im Kernel abzuschalten. Allerdings kommt dies nur für dedizierte Datenbanksysteme in Frage, auf denen PostgreSQL® exklusiv läuft. Das Overcommit-Verhalten lässt sich in modernen 2.6ern Kernel in drei Kategorien mit dem Parameter

vm.overcommit_memory = 0

konfigurieren. Die einzelnen Kategorien hierbei sind:

Der Anteil des physikalischen RAM bei Modus 2 wird über den zusätzlichen Parameter

vm.overcommit_ratio = 50

kontrolliert. Während vm.overcommit_memory=1 für Spezialanwendungen interessant sein könnte, wird es im Praxiseinsatz eher zum Einsatz für die Parameterwerte 0 oder 2 kommen. Wird Overcommit über vm.overcommit_memory=2 abgeschaltet, so wird ein Prozess (in Abhängigkeit von vm_overcommit_ratio) sofort eine „Out Of Memory“-Bedingung beim Allokieren von Speicher erhalten. Abhängig von der Distribution sollte man die Einstellungen permanent in die Datei /etc/sysctl.conf speichern, so dass diese auch nach einem Neustart des Systems aktiv sind:

$ echo "vm.overcommit_memory=2 >> /etc/sysctl.conf
$ echo "vm.overcommit_ratio=60 >> /etc/sysctl.conf
$ sysctl -p /etc/sysctl.conf

Die Änderungen wirken sich sofort auf den virtuellen Speicher aus, man kann dies erneut durch Abrufen von /proc/meminfo überprüfen:

$ grep Commit /proc/meminfo
CommitLimit:    401440 kB
Committed_AS:   266456 kB

Die Maschine verfügt über 249848 kB Swap und 252656 kB physikalischen RAM. Nach der Formel Swap + vm.overcommit_ratio * RAM ergibt dies ein CommitLimit von 401440 kB.

OOM-Killer auf Prozessebene konfigurieren

Ist PostgreSQL® nicht auf einem dedizierten Server installiert und wird mit einer speicherhungrigen Middleware (bspw. JBoss- oder Tomcat-Installation) auf demselben System betrieben, so ist es wünschenswert, Overcommit-Verhalten zwar zu erlauben, im Falle einer „Out Of Memory“-Situation aber PostgreSQL® vom OOM-Killer auszunehmen. Seit Kernel 2.6.11 bietet Linux daher ein Interface an, um den OOM-Score eines Prozesses zu tunen, so dass dieser vom OOM-Killer weniger oder stärker berücksichtigt wird. Dies erlaubt ein sehr feinfühliges Einstellen des Systems auf die Speicherbedürfnisse einzelner Prozesse. Die Konfiguration wird über eine Datei im /proc-Filesystem des Kernel vorgenommen, beispielsweise hier für den PostgreSQL®-Hauptprozess unter Debian (0 ist die Standardeinstellung für Prozesse):

$ cat /proc/$(cat /var/run/postgresql/8.4-main.pid)/oom_adj
0

Die erlaubten Werte sind von -17 bis +15, negative Werte verringern die Affinität des Prozesses gegenüber den OOM-Killer, positive Werte erhöhen diese. -17 schaltet den OOM-Killer für den jeweiligen Prozess komplett ab. Die Einstellung wird vom Parent an etwaige Kindprozesse weitervererbt. Da PostgreSQL® sich für eine Datenbankverbindung forked, reicht es, diese Einstellung dem PostgreSQL®-Hauptprozess mitzugeben:

$ echo -17 >> /proc/$(cat /var/run/postgresql/8.4-main.pid)/oom_adj
$ psql -q postgres
test=# SELECT pg_backend_pid();
 pg_backend_pid
----------------
           3429
(1 Zeile)
 
test=#
[1]+  Stopped                 psql -q test
$ cat /proc/3429/oom_adj
-17

Der Nachteil dieser Methode ist, dass dies nun für alle Kindprozesse des PostgreSQL®-Hauptprozesses gilt, was eventuell vom DBA nicht mehr gewünscht ist. Beispielsweise möchte man zwar gerne die PostgreSQl-Systemprozesse wie Background Writer oder Autovacuum vor dem OOM-Killer schützen, nicht jedoch normale Datenbankverbindungen. Das Setzen von /proc/PID/oom_adj erfordert jedoch einen privilegierten Benutzer, so dass man am Besten die Einstellung direkt im Startskript der PostgreSQL®-Datenbank vornimmt.

Erweiterungen in PostgreSQL® 9.0

PostgreSQL® 9.0 wird hinsichtlich der Zusammenarbeit mit dem /proc-Interface ebenfalls einige Neuerungen mitbringen. Zum einen wurde das im Quelltext mitgelieferte Linux-Startskript dahingehend erweitert, zum anderen bietet das Backend nun auch Unterstützung, falls man die /proc-Einstellungen eben nicht an normale Datenbankverbindungen weitervererben möchte. Hierzu kann der PostgreSQL®-Server mit dem Makro LINUX_OOM_ADJ=0 kompiliert werden, beispielsweise:

$ ./configure CC="ccache gcc" CFLAGS="-DLINUX_OOM_ADJ=0"

Diese Methode schützt dann die PostgreSQL®-Systemprozesse effektiv, erlaubt aber dem OOM-Killer etwaige Amoklaufende Backends trotzdem zu terminieren.

Alternativen

Eine alternative Lösung gibt es auch in Form eines Kernelpatches. Dies ergänzt das /proc-Filesystem um eine Liste an Prozessnamen, die explizit vom OOM-Killer nicht berücksichtigt werden dürfen. Da dies jedoch eine inoffizielle Erweiterung des Kernels ist, muss man seinen eigenen Kernel damit pflegen, auch ist diese Erweiterung bei weitem nicht so flexibel wie das Interface über oom_adj. Des weiteren sind Prozessnamen relativ ungeeignet, um spezifische Prozesse eindeutig zu identifizieren (z.B. Java- oder Perlbasierte Prozesse).

Zusammenfassung

Der Linuxkernel bietet mittlerweile umfassende Möglichkeiten, die Speichernutzung von Prozessen an das Memory Management des Kernels anzupassen. Die flexibelste Lösung stellt das /proc-Filesystem mit dem oom_adj-Interface dar. PostgreSQL® 9.0 ergänzt dies durch weitere Maßnahmen. Dedizierte Datenbanksysteme können vom Administrator dahingehend angepasst werden, gar kein Overcommit des virtuellen Speichers zuzulassen, hier muss jedoch sorgfältig abgewogen werden, welche Anforderungen die PostgreSQL®-Instanz an die VM des Kernels stellt. Alle Blog-Artikel zum Thema PostgreSQL® werden auch als Kategorie PostgreSQL® samt eigenem Feed angeboten – und falls ihr nach Support und Services für PostgreSQL® sucht, seit ihr bei uns ebenfalls richtig.

Der Werkzeugkasten eines System-Administrators sollte immer mit effektiven Werkzeugen gefüllt sein. Heute stellen wir das Paket sysstat vor.

Das Paket sysstat ist eine Sammlung von Kommandozeilen-Programmen, die dem Systemadministrator einen schnellen Überblick über die Leistungsfähigkeit des Systems verschaffen. In ihrer Arbeitsweise sind sie ein Frontend zu den Daten des Linux-Kernels, und können dementsprechend nur Daten ausgeben, die der Kernel selbst kennt – sie sammeln keine Daten darüber hinaus!

iostat

iostat liefert vor allen Dingen Status-Informationen über den Datendurchsatz der Festplatten, angeschlossener NFS-Laufwerke und aller CPUs. Ist ein System im Leistungsverhalten auffällig, kann iostat verwendet werden, um z.B. IO-Waits zu identifizieren.

Linux 2.6.31-19-generic (mymachine)         04.03.2010      _x86_64_        (2 CPU)
 
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          11,82    0,29    3,44    1,25    0,00   83,20
 
Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               9,39       161,19       168,44    4264806    4456696

Die zur Verfügung stehenden Optionen sind dabei umfangreich, die Wichtigsten dienen vor allen Dingen der Feststellung für spezialisierte Ausgaben:

-d
Anzeige nur der Festplatten-Daten.
-c
Anzeige der reinen CPU-Daten.
-p
Anzeige der IO-Daten je Partition.
-n
Anzeige der NFS-IO-Daten.
-x
Erweiterte Anzeige der Festplatten-Daten.
-t $NUM1
Gibt an, nach wie vielen Sekunden die Anzeige aktualisiert werden soll.

mpstat

Das Werkzeug mpstat dient der genaueren Analyse der Prozessor-Auslastung – ohne weitere Option wird eine Standard-Übersicht gezeigt, die an die Ausgabe von iostat erinnert.

Linux 2.6.31-19-generic (mymachine)         04.03.2010      _x86_64_        (2 CPU)
 
17:01:52     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
17:01:52     all   11,96    0,29    3,26    1,23    0,10    0,11    0,00    0,00   83,06

Im Gegensatz zu iostat wird hier aber direkt schon die Arbeit mit aufgelistet, die der Prozessor an Soft- und Hardware-Interrupts verrichtet. Mit der Option -A wird die Ausgabe erweitert: die Statistiken werden pro Prozessor angezeigt, außerdem werden die Interrupts pro Sekunde pro Prozessor angezeigt.Eine Zahl $NUM hinter dem Befehl lässt diesen als Prozess laufen, und aktualisiert die Anzeige alle $NUM Sekunden.

pidstat

pidstat hilft bei der Analyse einzelner Prozesse – während der Aufruf ohne Optionen noch eine Liste aller Prozesse anzeigt, hilft die Option -C bei der Spezifizierung der zu untersuchenden Tasks:

Linux 2.6.31-19-generic (mymachine)         04.03.2010      _x86_64_        (2 CPU)
 
17:02:32          PID    %usr %system  %guest    %CPU   CPU  Command
17:02:32            1    0,00    0,00    0,00    0,00     1  init
17:02:32         2888    0,00    0,00    0,00    0,00     0  start_kdeinit
17:02:32         2889    0,00    0,00    0,00    0,00     0  kdeinit4

Mit der zusätzlichen Option -d werden I/O-Statistiken zu den Prozessen angezeigt. Mit -p kann die PID definiert werden statt des Prozess-Namens, -r verschafft einen Überblick über die Speicherauslastung. Eine Zahl $NUM hinter dem Befehl lässt diesen als Prozess laufen, und aktualisiert die Anzeige alle $NUM Sekunden.

sar

Die bisher vorgestellten Informationen geben jeweils nur einen Schnappschuss der Systemleistung wieder, echtes Status-Logging findet hier aber nicht statt. Dafür ist das Programm sar mit seinen Helfer-Programmen verantwortlich: es nimmt via Cron-Job alle 10 Minuten verschiedene Leistungsdaten des Systems auf, und sichert diese. Ein einfacher Aufruf gibt eine erste Idee des Performance-Verhaltens:

Linux 2.6.31-19-generic (mymachine)         04.03.2010      _x86_64_        (2 CPU)
 
09:30:30          LINUX RESTART
 
09:35:02        CPU     %user     %nice   %system   %iowait    %steal     %idle
09:45:01        all     17,38      1,02      5,10      3,87      0,00     72,63
09:55:01        all     11,90      0,27      2,86      0,75      0,00     84,23
10:05:01        all     10,20      3,52      3,46      2,55      0,00     80,27
10:15:02        all     12,96      0,32      3,18      0,65      0,00     82,89
10:25:01        all      7,94      0,18      3,17      2,42      0,00     86,30
10:35:01        all     12,41      0,89      4,55      0,56      0,00     81,60
10:45:02        all      8,97      0,09      3,51      0,89      0,00     86,55

Der Abruf aller Informationen mit sar -A sprengt aber leicht jede Bildschirmgröße. Die unterschiedlichen Einzel-Optionen sind zu zahlreich, um sie hier detailliert aufzulisten, einen Überblick gibt die Man-Page.

 

Dieser Artikel wurde ursprünglich geschrieben von Roland Wolters.