Kai Ole Hartwig
6 Min. Lesezeit
Niedrig
Von

TYPO3 auf Kubernetes unter IT-Grundschutz, Teil 2: Das gehärtete TYPO3-Image

Das Image ist die erste Verteidigungslinie. Was nicht darin steckt, kann weder angegriffen noch falsch konfiguriert werden, und was zur Laufzeit nicht schreiben darf, lässt sich auch nicht dauerhaft verändern.

Dieser Teil zeigt, wie ein TYPO3-Image aussieht, das den Container-Baustein SYS.1.6 trägt: mit einer minimalen Basis, einem einzigen Dienst und Laufzeitrechten, die kaum noch etwas erlauben.

Teil 2 von 14 der Serie „TYPO3 auf Kubernetes unter IT-Grundschutz“. Die Übersicht aller Teile steht in Teil 1.

01 — Eine minimale Basis statt eines Betriebssystems

Ein klassisches PHP-Image bringt ein halbes Betriebssystem mit, von der Shell über den Paketmanager bis zu Compiler-Resten und Dutzenden Bibliotheken, die TYPO3 nie braucht. Jede davon kann im nächsten Scan als Schwachstelle auftauchen.

Die Alternative ist eine minimale Basis nach dem Distroless-Prinzip. Gut eignen sich Basen wie Wolfi, weil sie Pakete in kleinen, häufig aktualisierten Einheiten liefern. Das Image enthält dann nur noch PHP, die benötigten Extensions, FrankenPHP und die Laufzeitbibliotheken.

So entsteht das Image

Damit ist SYS.1.6.A6 Verwendung sicherer Images erfüllt. Wer die Basis außerdem selbst baut und regelmäßig neu erzeugt, deckt auch SYS.1.6.A14 Aktualisierung von Images ab.

Die Frage nach der Shell

Ganz ohne Shell zu arbeiten, ist konsequent, aber unbequem, vor allem wenn im Fehlerfall debuggt werden muss. Kubernetes löst das mit ephemeren Debug-Containern über kubectl debug, die sich vorübergehend an einen Pod hängen, ohne dass das Image selbst Werkzeuge mitbringen muss. Im Profil restricted braucht es dafür kubectl debug --profile=restricted.

02 — FrankenPHP im Worker-Modus: ein Dienst pro Container

Die klassische Kombination aus Webserver und PHP-FPM braucht zwei Prozesse und meist noch einen Supervisor dazwischen. Das widerspricht SYS.1.6.A11 Nur ein Dienst pro Container.

FrankenPHP löst das elegant, weil es ein Webserver mit eingebautem PHP ist: Ein einziger Prozess nimmt die HTTP-Anfragen entgegen und führt TYPO3 aus. Im Worker-Modus bleibt TYPO3 zwischen den Anfragen im Speicher geladen, sodass der Bootstrap pro Anfrage entfällt und die Antwortzeiten deutlich sinken.

Worauf man im Worker-Modus achten muss

Ein Worker lebt länger als eine Anfrage. Globaler Zustand, statische Caches oder offene Verbindungen können deshalb in die nächste Anfrage durchsickern, und in der Praxis zeigt sich das genau dort, wo Extensions mit statischen Variablen arbeiten. Eine Obergrenze für die Anfragen pro Worker begrenzt das Risiko, weil der Worker danach neu startet. Die typischen Fallen beschreibt TYPO3 auf FrankenPHP: operative Stolperfallen.

Scheduler als eigener Prozess

Der TYPO3-Scheduler gehört nicht in denselben Container. Er läuft als eigener CronJob mit demselben Image und einem anderen Startbefehl, damit auch hier ein Dienst pro Container bleibt.

03 — Laufzeitrechte: kein Root, nur lesen, keine Capabilities

Das beste Image hilft wenig, wenn der Container mit vollen Rechten läuft. Entscheidend ist deshalb der securityContext des Pods:

 

securityContext:
  runAsNonRoot: true
  runAsUser: 65532
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

 

Das deckt SYS.1.6.A17 Ausführung von Containern ohne Privilegien ab. Weil FrankenPHP auf einem Port oberhalb von 1024 lauscht, braucht der Container nicht einmal eine Capability, um seinen Port zu öffnen.

Nur lesen, wo nur gelesen wird

Mit readOnlyRootFilesystem ist das gesamte Image zur Laufzeit schreibgeschützt. TYPO3 braucht aber einige beschreibbare Pfade, und die bekommen gezielt eigene Volumes:

Damit ist SYS.1.6.A23 Unveränderlichkeit der Container weitgehend umgesetzt, denn Image und Code lassen sich im laufenden Container nicht mehr verändern. Umso wichtiger ist es, die wenigen beschreibbaren Pfade eng zu begrenzen und zu überwachen.

Durchsetzen statt hoffen

Einzelne Pods richtig zu konfigurieren, reicht allerdings nicht. Deshalb erzwingt der Namespace die Regeln über Pod Security Admission mit dem Profil restricted:

 

metadata:
  labels:
    pod-security.kubernetes.io/enforce: restricted

 

Ein Pod, der die Regeln verletzt, wird so gar nicht erst angelegt. Das ist ein wichtiger Baustein für SYS.1.6.A17 und SYS.1.6.A21 Erweiterte Sicherheitsrichtlinien.

04 — Ressourcen begrenzen und Code vom Image trennen

SYS.1.6.A15 verlangt, die Ressourcen pro Container zu begrenzen. Jeder TYPO3-Pod bekommt deshalb Requests für CPU und Speicher sowie ein Speicherlimit, und eine LimitRange im Namespace setzt Vorgaben für Pods, die keine eigenen Werte mitbringen.

 

resources:
  requests:
    cpu: 250m
    memory: 384Mi
  limits:
    memory: 768Mi

 

PHP und Container sprechen dieselbe Sprache

Ein typischer Stolperstein ist das PHP-Speicherlimit. Liegt memory_limit mal Anzahl der Worker über dem Container-Limit, beendet der Kernel den Container ohne Vorwarnung (OOMKilled). Rechne deshalb beide Werte zusammen und lass Luft für FrankenPHP selbst.

Warum der Code nicht ins Image gehört

Dieses Image enthält nur die Laufzeit, während der TYPO3-Code getrennt als eigenes Artefakt dazukommt. So lässt sich ein PHP-Sicherheitsupdate ausrollen, ohne jede Website neu zu bauen, und umgekehrt ändert ein Release der Website nichts an der geprüften Laufzeit. Den Hintergrund beschreibt Laufzeit und Anwendung trennen, und wie der Code sicher in den Pod kommt, zeigt Teil 3.

Häufige Fragen

Warum nicht einfach das offizielle PHP-Image verwenden?+

Das offizielle Image ist für breite Verwendung gebaut und bringt viel mit, was TYPO3 nicht braucht. Jede zusätzliche Bibliothek erzeugt Befunde im Scan und Aufwand bei Updates. Eine minimale Basis hält die Angriffsfläche und die Zahl der Befunde klein.

Wie debugge ich einen Container ohne Shell?+

Mit einem ephemeren Debug-Container: kubectl debug hängt einen temporären Container mit Werkzeugen an den laufenden Pod. Er teilt dessen Prozesse und Netz und verschwindet danach wieder, sodass das Image selbst schlank bleibt. Im Profil restricted braucht es dazu kubectl debug --profile=restricted.

Läuft TYPO3 mit readOnlyRootFilesystem überhaupt?+

Ja, wenn die beschreibbaren Pfade gezielt als Volumes eingehängt werden. /tmp und var/ bekommen ein emptyDir, fileadmin und typo3temp liegen auf gemeinsamem Speicher. Alles andere bleibt schreibgeschützt.

Wie viele FrankenPHP-Worker sind sinnvoll?+

Das hängt von CPU und Speicher des Pods ab. Als Faustregel startest du mit zwei Workern pro CPU-Kern. Entscheidend ist, dass Worker mal PHP-Speicherlimit unter dem Container-Limit bleibt. Skaliert wird dann über zusätzliche Pods, nicht über immer mehr Worker.

Fazit

Ein gehärtetes TYPO3-Image ist klein, hat keinen Paketmanager und führt genau einen Dienst aus. Es läuft ohne Root-Rechte und ohne Capabilities auf einem schreibgeschützten Dateisystem, und Pod Security Admission sorgt dafür, dass das nicht nur für einzelne Pods gilt. Damit sind die zentralen Anforderungen aus SYS.1.6 abgedeckt, bis hin zu A21 und A23 für hohen Schutzbedarf.

Weiter mit Teil 3: Code als signiertes Artefakt. Zurück zu Teil 1: Welche Bausteine wirklich greifen.

Ich baue dir ein gehärtetes TYPO3-Image, das Pod Security im Profil restricted besteht.

Minimale Basis, FrankenPHP im Worker-Modus, schreibgeschütztes Dateisystem und ein Build, der die Basis regelmäßig neu erzeugt.

Plattform-Betrieb statt Beratung auf Papier: Auf Wunsch pflege ich das Image dauerhaft und halte es frei von bekannten Schwachstellen.

Termin buchen →

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.