Zum Inhalt springen

13. Sicherheitsaspekte

Trennung von Berechtigungen, Angriffsfläche, Linux-Capabilities und sicherer Betrieb

HAProxy ist für den Betrieb mit stark eingeschränkten Berechtigungen ausgelegt. Üblicherweise wird er in einer chroot-Umgebung isoliert und wechselt zu einem Benutzer ohne root-Rechte, der innerhalb dieser Umgebung keinerlei Berechtigungen hat. Sollte künftig eine Schwachstelle entdeckt werden, würde eine Kompromittierung damit nicht den Rest des Systems betreffen.

Für chroot muss der Prozess zunächst als root gestartet werden. Eine von Hand aufgebaute chroot-Umgebung, in der der Prozess gestartet wird, ist nicht sinnvoll: Solche Umgebungen sind aufwendig einzurichten, werden nie ausreichend gepflegt und enthalten stets wesentlich mehr Fehler als das eigentliche Dateisystem. Bei einer Kompromittierung könnte ein Angreifer zudem das eigens eingerichtete Dateisystem verwenden. Leider verwechseln viele Administratoren den Start als root mit dem dauerhaften Betrieb als root. Sie ändern die UID deshalb bereits vor dem Start von haproxy und verringern dadurch die wirksamen Sicherheitsbeschränkungen.

HAProxy muss als root gestartet werden, um:

  • die Grenzen für Dateideskriptoren anzupassen
  • sich an privilegierte Portnummern zu binden
  • sich an eine bestimmte Netzwerkschnittstelle zu binden
  • transparent auf einer fremden Adresse zu lauschen
  • sich in der chroot-Umgebung zu isolieren
  • zu einer anderen, nicht privilegierten UID zu wechseln

HAProxy muss möglicherweise dauerhaft als root laufen, um:

  • sich für ausgehende Verbindungen an eine Schnittstelle zu binden
  • sich für ausgehende Verbindungen an privilegierte Quellports zu binden
  • sich für ausgehende Verbindungen transparent an eine fremde Adresse zu binden

Die meisten Nutzer benötigen den dauerhaften Betrieb als root nie. Der Start als root deckt dagegen die meisten Einsatzzwecke ab.

Eine sichere Konfiguration enthält:

  • eine chroot-Anweisung, die auf ein leeres Verzeichnis ohne Zugriffsberechtigungen zeigt. Dieses lässt sich in der UNIX-Befehlszeile wie folgt vorbereiten:
# mkdir /var/empty && chmod 0 /var/empty || echo "Failed"

Im global-Abschnitt der HAProxy-Konfiguration wird es so angegeben:

chroot /var/empty
  • sowohl eine uid/user- als auch eine gid/group-Anweisung im global-Abschnitt:
user haproxy
group haproxy
  • einen stats-Socket, dessen mode, uid und gid zum Benutzer beziehungsweise zur Gruppe passen, die auf die CLI zugreifen dürfen, sodass niemand sonst Zugriff erhält:
stats socket /var/run/haproxy.stat uid hatop gid hatop mode 600

13.1. Unterstützung für Linux-Capabilities

Seit Version v2.9 unterstützt haproxy Linux-Capabilities. Wird die Binärdatei mit USE_LINUX_CAP=1 kompiliert, kann sie beim Wechsel von root zu einem Benutzer ohne root-Rechte die mit dem Schlüsselwort ‘setcap’ angegebenen Capabilities beibehalten.

Seit Version v3.1 prüft haproxy außerdem mit dem Systemaufruf capget, ob die Administration die mit ‘setcap’ angegebenen Capabilities in der Permitted-Menge der Binärdatei gesetzt hat. Ist dies der Fall, übernimmt er diese mit capset in die Effective-Menge seines Prozesses, während er ohne root-Rechte läuft.

Damit sollen alle potenziellen Einsatzfälle vermieden werden, in denen haproxy als root startet und weiterläuft, etwa der transparente Proxy-Modus oder die Bindung an privilegierte Ports.

Das Schlüsselwort ‘setcap’ unterstützt folgende Netzwerk-Capabilities:

  • cap_net_admin: transparentes Proxying, Bindung eines Sockets an eine bestimmte Netzwerkschnittstelle und Verwendung der Aktion set-mark;
  • cap_net_raw (Teilmenge von cap_net_admin): transparentes Proxying;
  • cap_net_bind_service: Bindung eines Sockets an einen privilegierten Port;
  • cap_sys_admin: Erstellen eines Sockets in einem bestimmten Netzwerk-Namespace.

HAProxy übernimmt diese Capabilities niemals aus seiner Permitted-Menge in die Effective-Menge, wenn sie nicht als Argument von ‘setcap’ aufgeführt sind. Weitere Informationen zu ‘setcap’ und den unterstützten Capabilities enthält Kapitel 3.1 zur Prozessverwaltung und Sicherheit im Konfigurationshandbuch.

Die Administration kann die benötigten Capabilities mit folgendem Befehl zur Permitted-Menge der haproxy-Binärdatei hinzufügen:

Beispiel:

# setcap cap_net_admin,cap_net_bind_service=p /usr/local/sbin/haproxy

Nach dem Start sind die hinzugefügten Capabilities in der Permitted-Menge des Prozesses sichtbar. Sind dieselben Capabilities als Argumente von ‘setcap’ angegeben, können sie auch in der Effective-Menge des Prozesses erscheinen. Dies lässt sich mit folgendem Befehl prüfen:

Beispiel:

# grep Cap /proc/<haproxy PID>/status
CapInh: 0000000000000000
CapPrm: 0000000000001400
CapEff: 0000000000001400
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

Weitere Einzelheiten zu setcap und den Capability-Mengen stehen in den Linux-Handbuchseiten unter capabilities(7).

Bei manchen Einsatzzwecken, etwa transparentem Proxying oder dem Erstellen eines Sockets in einem bestimmten Netzwerk-Namespace, erkennt der Konfigurationsparser, dass cap_net_raw, cap_sys_admin oder andere unterstützte Capabilities benötigt werden. Während der Initialisierung prüft der haproxy-Prozess dann, ob diese in seine Effective-Menge übernommen werden können. Scheitert dies an einem Fehler bei capget oder capset, beispielsweise aufgrund von Systemaufrufbeschränkungen durch Sicherheitsmodule wie SELinux oder Seccomp, gibt der Prozess diagnostische Warnungen aus; dazu mit -dD starten.

Da zahlreiche Plattformen mit unterschiedlichen Systemeinstellungen unterstützt werden, kann der Parser aus der Konfigurationsdatei nicht ableiten, ob eine Bindung an privilegierte Ports erfolgen wird. Bei unzureichenden Berechtigungen im Betrieb ohne root-Rechte beendet sich der Prozess deshalb lediglich mit einer ALERT-Meldung wie der folgenden. Es ist dann Aufgabe des Benutzers, seine Konfiguration und die Capability-Menge der haproxy-Binärdatei erneut zu prüfen.

Beispiel:

$ haproxy -dD -f haproxy.cfg
...
[ALERT]    (96797): Binding [haproxy.cfg:36] for frontend fe: cannot bind socket (Permission denied) for [0.0.0.0:80]
[ALERT]    (96797): [haproxy.main()] Some protocols failed to start their listeners! Exiting.