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

365 lines
10 KiB
Markdown

# 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