# Redis Authentication Problem Analysis & Refactoring Plan ## Problem Description **Fehlermeldung:** ``` Direct Redis connection failed: Failed to connect to Redis (cache): WRONGPASS invalid username-password pair or user is disabled. with Host: staging-redis and Password: gRUFGtFFthCGFHYjZ7o9yuiDRJ0JdG4Syiv1fVPb544= ``` ## Root Cause Analysis ### Identified Issues 1. **Password Whitespace/Newline Problem** - Das Passwort wird aus Docker Secrets geladen (`/run/secrets/redis_password`) - `DockerSecretsResolver` verwendet `trim()`, aber m?glicherweise gibt es unsichtbare Zeichen (z.B. `\r\n`, `\n`, Tabs) - Redis ist sehr strikt bei Passwort-?bereinstimmungen 2. **Fehlende Passwort-Validierung** - Keine Validierung des Passworts vor der Verwendung - Keine Sanitization au?erhalb von `trim()` 3. **Unzureichende Fehlerbehandlung** - Fehlermeldung enth?lt das vollst?ndige Passwort (Security Risk!) - Keine Unterscheidung zwischen verschiedenen Auth-Fehlertypen - Keine detaillierte Diagnose-Informationen 4. **Keine Redis ACL Unterst?tzung** - Aktuell nur Legacy Passwort-Auth (`requirepass`) - Redis 6+ unterst?tzt ACL mit Username/Password - Code ist nicht f?r zuk?nftige ACL-Nutzung vorbereitet 5. **Fehlende Passwort-Normalisierung** - Verschiedene Whitespace-Zeichen werden nicht normalisiert - Keine Behandlung von UTF-8 BOM oder anderen Steuerzeichen ## Current Implementation Analysis ### Code Flow ``` Environment::get(REDIS_PASSWORD) ? DockerSecretsResolver::resolve() ? file_get_contents() + trim() ? RedisConfig::fromEnvironment() ? RedisConnection::connect() ? $client->auth($password) ? WRONGPASS Fehler hier ``` ### Problematic Code Sections **1. DockerSecretsResolver.php (Zeile 104)** ```php $result = trim($content); ``` - Problem: Entfernt nur Whitespace am Anfang/Ende - Fehlt: Normalisierung von `\r\n` zu `\n`, Entfernung von unsichtbaren Zeichen **2. RedisConnection.php (Zeile 92-95)** ```php if ($this->config->password) { if (! $this->client->auth($this->config->password)) { throw new RedisConnectionException("Redis authentication failed"); } } ``` - Problem: Generische Fehlermeldung ohne Details - Fehlt: Unterscheidung zwischen verschiedenen Auth-Fehlern - Fehlt: Passwort-Validierung vor Auth-Versuch **3. RedisConnection.php (Zeile 115)** ```php throw new RedisConnectionException( "Failed to connect to Redis ({$this->name}): " . $e->getMessage() . " with Host: {$this->config->host} and Password: {$this->config->password}", previous: $e ); ``` - **KRITISCH**: Passwort wird in Exception-Message ausgegeben (Security Risk!) - Passwort sollte niemals in Logs/Exceptions erscheinen ## Refactoring Vorschl?ge ### 1. Passwort-Normalisierung verbessern **Problem:** `trim()` ist unzureichend f?r Docker Secrets **L?sung:** Erweiterte Normalisierung in `DockerSecretsResolver` ```php private function normalizeSecret(string $content): string { // Entferne alle Whitespace-Zeichen am Anfang und Ende $normalized = trim($content); // Entferne unsichtbare Steuerzeichen (au?er normale Zeichen) $normalized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $normalized); // Normalisiere Line Endings (falls vorhanden) $normalized = str_replace(["\r\n", "\r"], "", $normalized); // Entferne UTF-8 BOM falls vorhanden if (str_starts_with($normalized, "\xEF\xBB\xBF")) { $normalized = substr($normalized, 3); } return $normalized; } ``` ### 2. Passwort-Validierung hinzuf?gen **Problem:** Keine Validierung vor Verwendung **L?sung:** Validierung in `RedisConfig` ```php private function validatePassword(?string $password): ?string { if ($password === null || $password === '') { return null; } // Warnung f?r sehr kurze Passw?rter if (strlen($password) < 8) { // Log warning, aber nicht blockieren (kann gewollt sein) } // Warnung f?r Whitespace in der Mitte (nur trimmen, nicht blockieren) $trimmed = trim($password); if ($trimmed !== $password) { // Log warning: Password hatte Whitespace am Anfang/Ende } return $trimmed !== '' ? $trimmed : null; } ``` ### 3. Sicherheit: Passwort aus Exceptions entfernen **Problem:** Passwort erscheint in Exception-Messages **L?sung:** Maskiertes Passwort in Fehlermeldungen ```php private function maskPassword(?string $password): string { if ($password === null || $password === '') { return '(not set)'; } $length = strlen($password); if ($length <= 4) { return str_repeat('*', $length); } return substr($password, 0, 2) . str_repeat('*', $length - 4) . substr($password, -2); } ``` ### 4. Verbesserte Fehlerbehandlung **Problem:** Generische Fehlermeldungen ohne Kontext **L?sung:** Detaillierte Exception mit diagnostischen Informationen ```php private function authenticate(): void { if (!$this->config->password) { return; // Keine Authentifizierung erforderlich } try { $authResult = $this->client->auth($this->config->password); if (!$authResult) { throw new RedisConnectionException( "Redis authentication failed for connection '{$this->name}'", context: $this->createAuthDiagnostics() ); } } catch (RedisException $e) { $message = $e->getMessage(); // Spezifische Fehlermeldungen basierend auf Redis-Fehlercode if (str_contains($message, 'WRONGPASS')) { throw new RedisConnectionException( "Redis authentication failed: Invalid password for connection '{$this->name}'", context: $this->createAuthDiagnostics(), previous: $e ); } // Andere Auth-Fehler throw new RedisConnectionException( "Redis authentication error for connection '{$this->name}': {$message}", context: $this->createAuthDiagnostics(), previous: $e ); } } private function createAuthDiagnostics(): ExceptionContext { $context = ExceptionContext::forOperation('Redis Authentication'); $context->add('connection_name', $this->name); $context->add('host', $this->config->host); $context->add('port', $this->config->port); $context->add('database', $this->config->database); $context->add('password_set', $this->config->password !== null); $context->add('password_length', $this->config->password ? strlen($this->config->password) : 0); // KEIN Passwort in Context! return $context; } ``` ### 5. Redis ACL Unterst?tzung (Optional, f?r Zukunft) **Problem:** Keine Unterst?tzung f?r Redis 6+ ACL **L?sung:** Optionale Username/Password Auth ```php final readonly class RedisConfig { private function __construct( public string $host = 'redis', public int $port = 6379, public ?string $password = null, public ?string $username = null, // NEU f?r ACL // ... rest ) {} public static function fromEnvironment(Environment $env): self { return new self( // ... password: $env->get(EnvKey::REDIS_PASSWORD, null), username: $env->get(EnvKey::REDIS_USERNAME, null), // NEU // ... ); } } // In RedisConnection.php private function authenticate(): void { if (!$this->config->password) { return; } // Redis 6+ ACL: auth(username, password) if ($this->config->username) { $authResult = $this->client->auth($this->config->username, $this->config->password); } else { // Legacy: auth(password) $authResult = $this->client->auth($this->config->password); } if (!$authResult) { throw new RedisConnectionException(/* ... */); } } ``` ### 6. EnvKey Enum erweitern **L?sung:** Optional `REDIS_USERNAME` f?r zuk?nftige ACL-Nutzung ```php enum EnvKey: string { // ... existing ... case REDIS_HOST = 'REDIS_HOST'; case REDIS_PORT = 'REDIS_PORT'; case REDIS_PASSWORD = 'REDIS_PASSWORD'; case REDIS_USERNAME = 'REDIS_USERNAME'; // NEU (optional) case REDIS_PREFIX = 'REDIS_PREFIX'; } ``` ## Implementierungsplan ### Phase 1: Kritische Sicherheitsfixes (SOFORT) 1. ? **Passwort aus Exception-Messages entfernen** - `RedisConnection.php` Zeile 115: Maskiertes Passwort verwenden - Security Hotfix 2. ? **Verbesserte Passwort-Normalisierung** - `DockerSecretsResolver.php`: Erweiterte `normalizeSecret()` Methode - Behebt wahrscheinlich das Hauptproblem ### Phase 2: Robustheit (HOCH) 3. ? **Passwort-Validierung** - `RedisConfig`: Validierung und Sanitization - Warnungen f?r problematische Passw?rter 4. ? **Verbesserte Fehlerbehandlung** - Spezifische Exception-Messages - Diagnostische Informationen (ohne Passwort!) ### Phase 3: Zukunftssicherheit (MITTEL) 5. ?? **Redis ACL Unterst?tzung** (Optional) - Nur wenn Redis ACL verwendet werden soll - Backward-kompatibel mit Legacy Auth ## Priorisierung | Priorit?t | Task | Impact | Effort | |-----------|------|--------|--------| | ?? KRITISCH | Passwort aus Exceptions entfernen | Security | Low | | ?? KRITISCH | Passwort-Normalisierung verbessern | Fixes Bug | Low | | ?? HOCH | Passwort-Validierung | Robustheit | Low | | ?? HOCH | Verbesserte Fehlerbehandlung | Debugging | Medium | | ?? MITTEL | Redis ACL Unterst?tzung | Zukunft | Medium | ## Testing Recommendations 1. **Unit Tests f?r Passwort-Normalisierung** ```php test('normalizes password with newlines', function () { $resolver = new DockerSecretsResolver(); // Test with \r\n, \n, \r, etc. }); ``` 2. **Integration Tests f?r Redis Auth** ```php test('connects with password from Docker Secret', function () { // Test with actual Redis instance }); ``` 3. **Security Tests** ```php test('does not expose password in exception messages', function () { // Verify password is masked }); ``` ## Expected Outcomes Nach Implementierung: - ? Passwort wird korrekt normalisiert (Whitespace entfernt) - ? Keine Passw?rter in Logs/Exceptions - ? Bessere Fehlermeldungen f?r Debugging - ? Robustere Passwort-Behandlung - ? Vorbereitung f?r Redis ACL (optional) ## Notes - Das Hauptproblem ist wahrscheinlich **Whitespace/Newlines im Passwort** - Die Security-Probleme (Passwort in Exceptions) sollten sofort behoben werden - ACL-Unterst?tzung ist optional, aber gut f?r Zukunftssicherheit