Mattschwarze Kombinationsschloss-Scheibe mit gebürstetem Aluminium-Ring auf Eichentisch, drei kleine Indikator-Markierungen, kühles gerichtetes Tageslicht.
Extension · moselwal/secret-resolver

secret-resolver — Secrets in Site-Configs, zur Laufzeit.

Schreibt %secret(API_KEY)% direkt in Ihre TYPO3-Site-Configs — wird zur Laufzeit cascading aufgelöst: erst KEY_FILE-Env, dann /run/secrets/, dann Env-Fallback. Erweiterbar über SecretProviderInterface. Container-Deployments brauchen genau das.

Das Problem

Secrets in Site-Configs sind plain text — oder ungeladen.

Mit secret-resolver

  • %secret(API_KEY)%-Syntax direkt in Site-Config-YAMLs
  • Cascading Lookup: API_KEY_FILE/run/secrets/api_keyAPI_KEY Env
  • Out-of-the-box mit Docker und Kubernetes Secrets kompatibel
  • Eigene Provider via SecretProviderInterface (z.B. Vault, AWS SM)
  • Cache-bewusst — keine Re-Lookups bei jedem Request

Bisher

  • API-Keys hardgecodet in YAML-Configs (im Git-Repo)
  • Oder: über ENV-Variablen in TypoScript referenziert (klobig)
  • Docker-/K8s-Secrets via /run/secrets/ nicht out-of-the-box nutzbar
  • Multi-Provider-Lookup (Vault, AWS Secrets Manager) als Custom-Boilerplate

Vier Bausteine

Erweiterbarkeit

SecretProviderInterface implementieren — schon ist Vault, AWS Secrets Manager oder Bitwarden Secrets als zusätzlicher Lookup-Step nutzbar.

Container-tauglich

Funktioniert nahtlos mit Docker Secrets und Kubernetes Mounted Secrets — keine Anpassung am Image-Bau erforderlich.

Cascading Lookup

Default-Reihenfolge: {KEY}_FILE-Env (Pfad), /run/secrets/{key}, dann {KEY}-Env als Fallback. Konfigurierbar pro Provider.

%secret()%-Syntax

Direkt in YAML-Site-Configs einsetzbar — keine Custom-Bootstrap-Logik nötig.

Verfügbarkeit: Coming soon — öffentliche Veröffentlichung in Vorbereitung

Die öffentliche Bereitstellung als Composer-Paket wird derzeit vorbereitet. Wenn Sie den Baustein bereits in Ihrer TYPO3-Plattform einsetzen möchten, sprechen Sie uns über das Kontaktformular an — wir liefern aktuell im Rahmen von Plattform-Engagements aus.

Verwendung

Einfache Keys (Cascade-Resolution)

 

# config/sites/main/config.yaml
apiKey: '%secret(API_KEY)%'
dbPassword: '%secret(DB_PASSWORD)%'

# Inline in Strings:
dsn: 'mysql://user:%secret(DB_PASSWORD)%@db:3306/app'

 

Der Key wird durch alle registrierten Provider in Prioritätsreihenfolge aufgelöst — First Match Wins.

Erweiterte Keys (Provider-targeted)

 

# Direkter Vault-Lookup — umgeht Cascade, geht an den "vault"-Provider
dbPassword: '%secret(vault:kv-v2/database.password)%'
apiToken: '%secret(vault:transit/api_token)%'

# AWS Secrets Manager
dbPassword: '%secret(aws-sm:prod/database.password)%'

 

Format: %secret(provider:path/to/secret.subKey)%

TeilPflichtBeschreibung
providerJaProvider-Name (vault, aws-sm …) — routet direkt
pathNeinSecret-Pfad mit /-Trennern
subKeyNeinJSON-Sub-Key nach letztem . im finalen Pfadsegment

Sub-Key-Extraktion: Liefert ein Provider z. B. {"password":"s3cret","username":"admin"}, extrahiert der Sub-Key password automatisch "s3cret". Einfache Keys (ohne :) funktionieren weiter wie bisher — vollständig rückwärtskompatibel.

Quellcode & Doku

TYPO3 Extension Repository

Nicht im offiziellen TER — die öffentliche Distribution über Composer wird vorbereitet (coming soon).

Composer-Package

Veröffentlichung als moselwal/secret-resolver in Vorbereitung. Coming soon.

Repository

Quellcode und Issue-Tracker werden mit der öffentlichen Veröffentlichung freigeschaltet. Coming soon.

Mirror

Öffentlicher Mirror und Pull-Request-Workflow folgen mit der Veröffentlichung. Coming soon.

Resolution-Cascade & Priorities

Bei einfachen Schlüsseln wie %secret(DB_PASSWORD)% wird der Schlüssel durch alle registrierten Provider in absteigender Priority-Reihenfolge gereicht. Der erste Treffer gewinnt; leere Werte und Dateien, die nur Whitespace enthalten, werden übersprungen.

Built-in Cascade

PriorityProviderQuelleBeispiel
30FileEnvSecretProviderDB_PASSWORD_FILE-Env → Datei lesenDB_PASSWORD_FILE=/vault/secrets/db-pass
20RunSecretsSecretProvider/run/secrets/db_passwordDocker- oder K8s-Secret-Mount

Erweiterte Schlüssel im Format %secret(provider:path/to/secret.subKey)% umgehen die Cascade und werden direkt an den genannten Provider geroutet. Rückgabewerte in JSON-Form lassen sich über den Sub-Key extrahieren: aus {"password":"s3cret","username":"admin"} liefert der Sub-Key password automatisch s3cret.

Priority-Guidelines

PriorityAnwendungsfall
50+Override für alles (z. B. lokaler Dev-Mock-Provider)
40Primäres Backend (Vault, AWS Secrets Manager, Azure Key Vault)
30Datei-basierte Env-Variablen (built-in)
20Docker- oder K8s-Secret-Mounts (built-in)
10Fallback / Last Resort

Aufgelöste Werte werden in cache.core abgelegt — identisch zu %env()%. Nach einer Secret-Rotation räumt vendor/bin/typo3 cache:flush den Cache.

Eigene SecretProvider entwickeln

Die Erweiterung ist auf Erweiterbarkeit ausgelegt. Jede TYPO3-Extension kann einen eigenen Provider beisteuern, ohne das Core-Paket zu ändern. Drei Schritte genügen.

Step 1: SecretProviderInterface implementieren

Der Provider liefert einen eindeutigen Namen für erweiterte Schlüssel, entscheidet über supports(), ob er die Auflösung versucht, und gibt in resolve() den Wert oder null zurück. Eine statische priority() bestimmt die Position in der Cascade.

 

<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Infrastructure\Provider;

use Moselwal\SecretResolver\Domain\Contract\SecretProviderInterface;
use Moselwal\SecretResolver\Domain\ValueObject\SecretKey;

final readonly class VaultSecretProvider implements SecretProviderInterface
{
    public function __construct(
        private VaultClient $client,
    ) {}

    public function getName(): string
    {
        // Return a unique provider name for extended key format targeting.
        // Users can then write %secret(vault:kv-v2/db.password)%
        // Return '' to participate only in the cascade (simple keys).
        return 'vault';
    }

    public function supports(SecretKey $key): bool
    {
        if ($key->isExtended()) {
            $path = $key->getSecretPath() ?? $key->getKeyName();
            return $this->client->secretExists($path);
        }

        return $this->client->secretExists($key->lowerCase);
    }

    public function resolve(SecretKey $key): ?string
    {
        $path = $key->isExtended()
            ? ($key->getSecretPath() ?? $key->getKeyName())
            : $key->lowerCase;

        try {
            $value = $this->client->readSecret($path);
        } catch (\Throwable) {
            return null; // Fallback to next provider in cascade
        }

        return $value !== '' ? $value : null;
    }

    public static function priority(): int
    {
        // Higher priority = checked first.
        // Built-in: FileEnv=30, RunSecrets=20
        return 40;
    }
}

 

Step 2: Über Services.yaml registrieren

Manuelle Registrierung entfällt — TYPO3s DI-Container erkennt alle SecretProviderInterface-Implementierungen über das von dieser Extension konfigurierte _instanceof-Auto-Tagging. Voraussetzung ist, dass Ihre Extension das übliche Autowiring nutzt.

 

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  MyVendor\MyExtension\:
    resource: '../Classes/*'

 

Abhängigkeiten wie VaultClient werden automatisch injiziert, wenn sie im DI-Container Ihrer Extension verfügbar sind.

Step 3: Provider verwenden

Einfache Schlüssel laufen durch die Cascade; ein Provider mit Priority 40 wird vor den built-in-Providern befragt. Erweiterte Schlüssel routen direkt zum benannten Provider und unterstützen optionale Sub-Key-Extraktion aus JSON-Antworten.

 

# Simple key - goes through cascade (Vault at priority 40 is checked first)
apiKey: '%secret(API_KEY)%'

# Extended key - routes directly to Vault, extracts "password" from JSON response
dbPassword: '%secret(vault:kv-v2/database.password)%'

# Extended key - full secret path, no sub-key extraction
certificate: '%secret(vault:pki/issue/my-cert)%'

 

SecretKey-Properties

Die folgenden Properties stehen Providern auf dem übergebenen SecretKey-Value-Object zur Verfügung:

PropertyTypBeschreibung
$key->rawstringOriginal-Eingabe (DB_PASSWORD oder vault:kv-v2/db.password)
$key->upperCasestringSchlüsselname in Großbuchstaben (ohne Provider-Präfix)
$key->lowerCasestringSchlüsselname in Kleinbuchstaben (ohne Provider-Präfix)
$key->provider?stringProvider-Name (vault) oder null bei einfachen Schlüsseln
$key->path?stringPfad-Segment (kv-v2/db) oder null
$key->subKey?stringSub-Key für JSON-Extraktion (password) oder null
$key->isExtended()booltrue, wenn Provider-Präfix vorhanden ist
$key->getKeyName()stringSchlüssel ohne Provider-Präfix (kv-v2/db.password)
$key->getSecretPath()?stringPfad ohne Sub-Key (kv-v2/db) oder null
Nächster Schritt

Container-Deployment aufräumen?

secret-resolver ist Open Source und kompakt. Für Vault- oder AWS-Secrets-Manager-Anbindung, Cluster-Setups oder Migration weg von Plain-Text-Secrets unterstützen wir gerne.

Secret-Setup besprechen

Oder direkt schreiben: kontakt@moselwal.de

Setzen wir ein bei …

Dieses Paket trägt das Secret-Handling in TYPO3 Kubernetes und ist Pflicht-Bestandteil eines Setups, das Open Source & Digitale Souveränität ernst nimmt — Schlüssel bleiben bei Ihnen, nicht im Image. In der betreuten Variante: AI-Ready CMS as a Service.