Zum Inhalt springen

Node-Hardening

Für nicht vertrauenswürdige Tenants (das Hosting-Profil). Auch ohne die folgenden Schritte laufen Gameserver-Container bereits:

  • ohne alle Capabilities und mit no-new-privileges
  • mit dem Seccomp-Profil von Docker
  • mit Limits für Arbeitsspeicher (ohne Swap), CPU und Prozesse
  • mit einem Netzwerk pro Tenant: Tenants erreichen sich gegenseitig nicht
Die Isolationseinstellungen eines NodesDie Isolationseinstellungen eines Nodes

Installations- und Update-Jobs laufen als root (Installer schreiben jede Datei des Servers), behalten aber nur die Capabilities dafür: Besitzer und Rechte von Dateien ändern und den Benutzer wechseln. Keine Raw-Sockets, keine Gerätedateien, kein chroot.

Die Seite des Nodes zeigt unter Isolation, was sein Docker bietet; der Agent meldet es bei jeder Verbindung. Der Agent selbst braucht nie mehr als den Docker-Socket: Alles Folgende richtet der Operator einmal ein. Änderungen gelten für jeden Server ab seinem nächsten Start.

Root in einem Container ist dann nicht Root auf dem Node. In /etc/docker/daemon.json:

{ "userns-remap": "default" }

Danach systemctl restart docker und den Agent neu erstellen. Der Agent muss Dateien als Root des Nodes verwalten und läuft deshalb mit userns_mode: host (deploy/compose.agent.yml und der Befehl unter Add node setzen das bereits).

Was sich ändert:

  • Docker hält Images, Container und Volumes pro Remapping getrennt (/var/lib/docker/100000.100000): Images werden neu heruntergeladen, und Steam-Anmeldungen müssen neu gemacht werden.
  • Server-Dateien gehören auf dem Node dem verschobenen Benutzer (988 → 100988). Der Agent erkennt das beim Start und übergibt bestehende Server einmal dem neuen Besitzer; er merkt sich den Besitzer in /var/lib/panel-agent/file-owner.

Ein Schalter auf der Seite des Nodes; bei neuen Nodes mit dem Hosting-Profil eingeschaltet.

Gameserver-Container bekommen ein schreibgeschütztes Root-Dateisystem. Ihre Dateien, /tmp (256 MB) und /var/tmp bleiben beschreibbar. Images, die anderswo schreiben, starten so nicht: Schalte es für diesen Node aus, oder korrigiere das Image.

Optional, ein Schalter auf der Seite des Nodes. Gameserver laufen dann auf dem eigenen Kernel von gVisor: die stärkste Isolation, mit etwas Mehraufwand bei CPU und Netzwerk. Installiere es auf dem Node (Debian, Ubuntu):

Terminal-Fenster
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt-get update && sudo apt-get install -y runsc
sudo runsc install # trägt die Runtime "runsc" in /etc/docker/daemon.json ein
sudo systemctl restart docker

Starte den Agent neu, damit er die neue Runtime meldet; solange der Node keine hat, wird der Schalter abgelehnt.

gVisor kann in den Netzwerken der Tenants den Nameserver von Docker nicht nutzen; der Agent gibt gVisor-Containern deshalb eigene: 1.1.1.1 und 9.9.9.9, oder was AGENT_SANDBOX_DNS festlegt (z. B. deinen Router).

Gameserver-Container sollen weder die Services des Nodes selbst erreichen (SSH, Datenbanken, andere veröffentlichte Ports) noch die Netzwerke um ihn herum (LAN, VPN, den Metadaten-Service der Cloud).

Tenant-Netzwerke verwenden Bridge-Interfaces mit Namen pnl…. deploy/node-isolation.nft sperrt diesen Verkehr und lässt Spieler und Antworten durch:

Terminal-Fenster
sudo install -m 644 deploy/node-isolation.nft /etc/nftables.d/panel-isolation.nft
sudo nft -f /etc/nftables.d/panel-isolation.nft # und für Neustarts in /etc/nftables.conf einbinden
  • Steht der DNS-Resolver des Nodes im LAN (z. B. der Router), erlaube ihn zuerst in der Datei (die auskommentierte Zeile), sonst können Gameserver keine Namen auflösen.
  • Vorher erstellte Netzwerke behalten die Docker-Namen br-…, bis kein Server sie mehr nutzt; entferne sie dann mit docker network rm.