- 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.
365 lines
10 KiB
Markdown
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
|