
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.
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_key→API_KEYEnv - 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.
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)%
| Teil | Pflicht | Beschreibung |
|---|---|---|
provider | Ja | Provider-Name (vault, aws-sm …) — routet direkt |
path | Nein | Secret-Pfad mit /-Trennern |
subKey | Nein | JSON-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
| Priority | Provider | Quelle | Beispiel |
|---|---|---|---|
| 30 | FileEnvSecretProvider | DB_PASSWORD_FILE-Env → Datei lesen | DB_PASSWORD_FILE=/vault/secrets/db-pass |
| 20 | RunSecretsSecretProvider | /run/secrets/db_password | Docker- 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
| Priority | Anwendungsfall |
|---|---|
| 50+ | Override für alles (z. B. lokaler Dev-Mock-Provider) |
| 40 | Primäres Backend (Vault, AWS Secrets Manager, Azure Key Vault) |
| 30 | Datei-basierte Env-Variablen (built-in) |
| 20 | Docker- oder K8s-Secret-Mounts (built-in) |
| 10 | Fallback / 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:
| Property | Typ | Beschreibung |
|---|---|---|
$key->raw | string | Original-Eingabe (DB_PASSWORD oder vault:kv-v2/db.password) |
$key->upperCase | string | Schlüsselname in Großbuchstaben (ohne Provider-Präfix) |
$key->lowerCase | string | Schlüsselname in Kleinbuchstaben (ohne Provider-Präfix) |
$key->provider | ?string | Provider-Name (vault) oder null bei einfachen Schlüsseln |
$key->path | ?string | Pfad-Segment (kv-v2/db) oder null |
$key->subKey | ?string | Sub-Key für JSON-Extraktion (password) oder null |
$key->isExtended() | bool | true, wenn Provider-Präfix vorhanden ist |
$key->getKeyName() | string | Schlüssel ohne Provider-Präfix (kv-v2/db.password) |
$key->getSecretPath() | ?string | Pfad ohne Sub-Key (kv-v2/db) oder null |
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.
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.