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.
This commit is contained in:
364
docs/REDIS_AUTHENTICATION_ANALYSIS.md
Normal file
364
docs/REDIS_AUTHENTICATION_ANALYSIS.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user