Kai Ole Hartwig
7 Min. Lesezeit
Niedrig
Von

TYPO3 auf Kubernetes unter IT-Grundschutz, Teil 7: Secrets ohne Git

Ein TYPO3 im Cluster braucht Zugangsdaten: Datenbank, Cache, Mailserver, Encryption Key, API-Tokens. Wo diese Werte liegen, entscheidet mehr über die Sicherheit als fast jede andere Einstellung. Dieser Teil zeigt, wie Secrets aus dem Git-Repository verschwinden, wie sie trotzdem reproduzierbar in den Pod kommen und warum Dateien besser sind als Umgebungsvariablen.

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

01 — Warum Secrets nicht ins Git gehören, auch nicht verschlüsselt

GitOps verspricht: Alles, was im Cluster läuft, steht im Repository. Für Secrets ist das eine Falle. Ein Klartext-Passwort im Repository ist offensichtlich falsch. Weniger offensichtlich ist das verschlüsselte Secret, etwa mit SOPS. Es funktioniert, verlagert das Problem aber nur. Der Schlüssel zum Entschlüsseln muss irgendwo liegen, oft im Cluster selbst. Wer ihn hat, liest jedes Secret der gesamten Git-Historie, auch längst rotierte Werte.

In der Praxis zeigt sich noch ein zweiter Nachteil. Rotation heißt bei SOPS: neu verschlüsseln, committen, mergen, ausrollen. Das dauert. Genau deshalb wird selten rotiert.

Die robustere Trennung sieht so aus: Im Git steht nur, welches Secret ein Mandant braucht und wo es liegt. Der Wert selbst liegt in einem Secret-Store außerhalb des Clusters, etwa AWS Secrets Manager, HashiCorp Vault oder ein vergleichbarer Dienst. Das Repository bleibt vollständig und prüfbar, enthält aber nichts, was sich missbrauchen lässt.

02 — External Secrets Operator und Workload Identity

Der External Secrets Operator (ESO) schließt die Lücke. Er liest ein ExternalSecret aus dem Git, holt die Werte aus dem Store und legt daraus ein normales Kubernetes-Secret an. Ändert sich der Wert im Store, zieht ESO ihn im eingestellten Intervall nach.

 

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: typo3-db
  namespace: tenant-a
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: tenant-a-store
    kind: SecretStore
  target:
    name: typo3-db
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: tenant-a/typo3/db
        property: password

 

Entscheidend ist, womit sich ESO am Store anmeldet. Ein statischer Access Key im Cluster wäre wieder ein Secret im Cluster. Besser ist Workload Identity: Der Service-Account des Mandanten bekommt über OIDC eine Cloud-Rolle, bei AWS heißt das IRSA. Diese Rolle darf genau die Secrets unter dem Präfix ihres Mandanten lesen, sonst nichts. Ein kompromittierter Namespace kommt so nicht an die Zugangsdaten eines anderen.

Deshalb ein SecretStore pro Namespace statt eines einzigen ClusterSecretStore. Ein zentraler Store mit breiten Rechten wäre bequemer, hebt die Mandantentrennung aus Teil 6 aber an genau dieser Stelle wieder auf.

03 — Dateien statt Umgebungsvariablen

Ein Kubernetes-Secret lässt sich als Umgebungsvariable oder als Datei in den Container geben. Für Zugangsdaten sind Dateien die bessere Wahl. Umgebungsvariablen erben alle Kindprozesse. Sie landen in Crash-Reports, Debug-Ausgaben und im Prozess-Listing. Eine gemountete Datei unter /run/secrets liest dagegen nur, wer sie gezielt öffnet. Sie lässt sich per Dateirechten auf einen Nutzer beschränken. Und Kubernetes aktualisiert die Datei bei einer Rotation, solange sie nicht per subPath eingebunden ist.

TYPO3 liest seine Konfiguration aus settings.php und additional.php, in vielen Setups aus Umgebungsvariablen. Die Brücke ist ein kleiner Resolver. Die Konfiguration verweist auf einen Namen, der Resolver liest beim Bootstrap die passende Datei. So bleibt die Konfiguration im Repository frei von Werten, und die Anwendung sieht trotzdem die richtigen Zugangsdaten.

 

// config/system/additional.php
$read = static function (string $name): string {
    $file = '/run/secrets/' . $name;
    if (!is_readable($file)) {
        throw new \RuntimeException('Missing secret: ' . $name);
    }
    return trim((string) file_get_contents($file));
};

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] = $read('db-password');

 

Wichtig ist, dass ein fehlendes Secret laut scheitert. Ein Resolver, der bei fehlender Datei still einen leeren String zurückgibt, erzeugt Fehler, die erst viel später und an ganz anderer Stelle auffallen.

Worker-Modus: Die Datei ist neu, der Wert noch nicht

Im Worker-Modus liest TYPO3 additional.php einmal beim Start des Workers. Danach hält es die Werte im Speicher. Eine aktualisierte Datei wirkt deshalb erst, wenn der Worker neu startet. Für die Rotation heißt das: Nach dem Wechsel im Store starten die Pods rollierend neu, etwa ausgelöst durch einen Reloader, der auf das geänderte Secret reagiert. Die Obergrenze an Anfragen pro Worker aus Teil 2 reicht dafür nicht. Sie sagt nichts darüber, wann der letzte Worker den neuen Wert liest.

04 — Rotation, Alarme und Verschlüsselung im Cluster

Mit einem externen Store wird Rotation zu einem Vorgang an einer Stelle. Der Wert ändert sich im Store, ESO zieht nach, die Datei im Pod aktualisiert sich, und ein rollierender Neustart bringt den Wert in die Anwendung.

Datenbank-Passwörter brauchen einen Übergang, damit keine Verbindung abreißt. MariaDB hat anders als MySQL keine Dual-Password-Funktion (RETAIN CURRENT PASSWORD). Bewährt haben sich deshalb zwei Benutzer im Wechsel. Der gerade ungenutzte bekommt ein neues Passwort, die Anwendung wird rollierend auf ihn umgestellt, danach ist der andere an der Reihe. So gibt es zu jedem Zeitpunkt gültige Zugangsdaten.

Genauso wichtig ist ein Alarm, wenn die Synchronisation scheitert. Ein ExternalSecret mit Status SecretSyncedError fällt sonst erst auf, wenn ein Pod neu startet und sein Secret fehlt. ESO stellt dafür Metriken bereit, die sich direkt in eine Alarmregel übernehmen lassen.

Im Cluster selbst liegen die Secrets in etcd. Dort gehören sie verschlüsselt hin. k3s bringt das mit secrets-encryption mit, bei anderen Distributionen ist es eine EncryptionConfiguration für den API-Server. Und schließlich braucht es einen Notfallzugang. Ein Break-Glass-Verfahren regelt, wer wann und wie an Zugangsdaten kommt, wenn der Store nicht erreichbar ist. Jede Nutzung wird protokolliert.

Bezug zum IT-Grundschutz

Häufige Fragen

Reicht SOPS nicht aus, wenn der Schlüssel gut geschützt ist?+

Für kleine Setups kann das genügen. Der Schlüssel entschlüsselt aber die gesamte Git-Historie, und jede Rotation braucht einen Commit. Ein externer Store trennt Wert und Repository vollständig und macht Rotation zu einem Vorgang an einer Stelle.

Warum keine Umgebungsvariablen für Zugangsdaten?+

Umgebungsvariablen werden an Kindprozesse vererbt und tauchen in Fehlerberichten, Debug-Ausgaben und Prozesslisten auf. Gemountete Dateien liest nur, wer sie gezielt öffnet. Kubernetes aktualisiert sie außerdem bei einer Rotation. Im Worker-Modus braucht es danach einen rollierenden Neustart, damit die Anwendung den neuen Wert liest.

Was passiert, wenn der Secret-Store ausfällt?+

Bereits synchronisierte Kubernetes-Secrets bleiben erhalten, laufende Pods arbeiten weiter. Neue oder geänderte Werte kommen erst wieder, wenn der Store erreichbar ist. Für diesen Fall braucht es ein dokumentiertes Break-Glass-Verfahren mit Protokollierung.

Muss etcd zusätzlich verschlüsselt werden?+

Ja. Kubernetes-Secrets liegen in etcd ohne weitere Konfiguration unverschlüsselt. Die Verschlüsselung im Ruhezustand schützt etcd-Daten und Snapshots, solange der Schlüssel getrennt davon liegt. In k3s schaltet die Option secrets-encryption sie ein. Gegen den Verlust des Datenträgers hilft erst ein KMS-Provider oder eine Datenträgerverschlüsselung.

Fazit

Secrets gehören weder ins Image noch ins Git. Ein externer Store, eine Workload Identity pro Mandant und Dateien statt Umgebungsvariablen reichen, um das sauber zu lösen. Der eigentliche Gewinn zeigt sich bei der Rotation: Sie wird vom Projekt zum Handgriff.

Im nächsten Teil geht es darum, TYPO3 zustandslos zu machen. Sessions, fileadmin, Caches und Locks wandern aus dem Pod. Zurück zu Teil 6: Netz und Mandantentrennung.

Ich bringe die Secrets deines TYPO3-Clusters aus dem Git in einen externen Store. Mit einer Rotation, die im Alltag wirklich stattfindet.

Bestandsaufnahme der Zugangsdaten, External Secrets mit Workload Identity pro Mandant, Umstellung von Umgebungsvariablen auf Dateien und ein Alarm, wenn die Synchronisation scheitert.

Plattform-Betrieb statt Beratung auf Papier: Auf Wunsch betreibe ich Store, Synchronisation und Rotation dauerhaft mit.

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.