Files
michaelschiemer/docs/REDIS_AUTHENTICATION_ANALYSIS.md
Michael Schiemer 3085739e34 feat(filesystem): introduce FileOwnership and ProcessUser value objects
- Add `FileOwnership` to encapsulate file owner and group information.
- Add `ProcessUser` to represent and manage system process user details.
- Enhance ownership matching and debugging with structured data objects.
- Include new documentation on file ownership handling and permission improvements.
- Prepare infrastructure for enriched error handling in filesystem operations.
2025-11-04 00:56:49 +01:00

10 KiB

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)

$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)

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)

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

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

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

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

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

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

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)

  1. ? Passwort-Validierung

    • RedisConfig: Validierung und Sanitization
    • Warnungen f?r problematische Passw?rter
  2. ? Verbesserte Fehlerbehandlung

    • Spezifische Exception-Messages
    • Diagnostische Informationen (ohne Passwort!)

Phase 3: Zukunftssicherheit (MITTEL)

  1. ?? 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

    test('normalizes password with newlines', function () {
        $resolver = new DockerSecretsResolver();
        // Test with \r\n, \n, \r, etc.
    });
    
  2. Integration Tests f?r Redis Auth

    test('connects with password from Docker Secret', function () {
        // Test with actual Redis instance
    });
    
  3. Security Tests

    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