refactor(deployment): Remove WireGuard VPN dependency and restore public service access

Remove WireGuard integration from production deployment to simplify infrastructure:
- Remove docker-compose-direct-access.yml (VPN-bound services)
- Remove VPN-only middlewares from Grafana, Prometheus, Portainer
- Remove WireGuard middleware definitions from Traefik
- Remove WireGuard IPs (10.8.0.0/24) from Traefik forwarded headers

All monitoring services now publicly accessible via subdomains:
- grafana.michaelschiemer.de (with Grafana native auth)
- prometheus.michaelschiemer.de (with Basic Auth)
- portainer.michaelschiemer.de (with Portainer native auth)

All services use Let's Encrypt SSL certificates via Traefik.
This commit is contained in:
2025-11-05 12:48:25 +01:00
parent 7c52065aae
commit 95147ff23e
215 changed files with 29490 additions and 368 deletions

View File

@@ -0,0 +1,798 @@
# ErrorHandling → ExceptionHandling Migration Strategy
**Status:** Task 13 Phase 5 - Migration Planning
**Date:** 2025-11-05
**Phase 4 Completion:** All legacy files examined, incompatibilities documented
## Executive Summary
The legacy `ErrorHandling` module cannot be removed until **5 critical incompatibilities** are resolved. This document provides implementation strategies for each blocker.
## Critical Blockers
| # | Blocker | Severity | Location | Impact |
|---|---------|----------|----------|---------|
| 1 | ErrorAggregator signature mismatch | 🔴 CRITICAL | ErrorHandler.php:128 | Prevents error aggregation |
| 2 | ExceptionHandlingMiddleware unreachable code | 🔴 URGENT | ExceptionHandlingMiddleware.php:32-37 | Broken error recovery |
| 3 | SecurityEventLogger old types | 🔴 HIGH | SecurityEventLogger.php:28-52 | Breaks DDoS logging |
| 4 | Missing CLI error rendering | 🔴 HIGH | AppBootstrapper.php:155-163 | No CLI error handling |
| 5 | Missing HTTP Response generation | 🔴 HIGH | Multiple locations | No middleware recovery |
---
## Strategy 1: Fix ErrorAggregator Signature Mismatch
### Current State (BROKEN)
**Location:** `src/Framework/ErrorHandling/ErrorHandler.php:127-128`
```php
// BROKEN: OLD signature call
$this->errorAggregator->processError($errorHandlerContext);
```
**NEW signature requires:**
```php
public function processError(
\Throwable $exception,
ExceptionContextProvider $contextProvider,
bool $isDebug = false
): void
```
### Migration Strategy
**Option A: Minimal Change (Recommended)**
Create adapter method in ErrorHandler that converts ErrorHandlerContext to ExceptionContextProvider:
```php
// Add to ErrorHandler.php
private function dispatchToErrorAggregator(
\Throwable $exception,
ErrorHandlerContext $errorHandlerContext
): void {
// Create ExceptionContextProvider instance
$contextProvider = $this->container->get(ExceptionContextProvider::class);
// Convert ErrorHandlerContext to ExceptionContextData
$contextData = ExceptionContextData::create(
operation: $errorHandlerContext->exception->operation ?? null,
component: $errorHandlerContext->exception->component ?? null,
userId: $errorHandlerContext->request->userId,
sessionId: $errorHandlerContext->request->sessionId,
requestId: $errorHandlerContext->request->requestId,
clientIp: $errorHandlerContext->request->clientIp,
userAgent: $errorHandlerContext->request->userAgent,
occurredAt: new \DateTimeImmutable(),
tags: $errorHandlerContext->exception->tags ?? [],
metadata: $errorHandlerContext->exception->metadata ?? [],
data: $errorHandlerContext->metadata
);
// Store in WeakMap
$contextProvider->set($exception, $contextData);
// Call ErrorAggregator with NEW signature
$this->errorAggregator->processError(
$exception,
$contextProvider,
$this->isDebugMode()
);
}
```
**Change at line 127:**
```php
// BEFORE (BROKEN)
$this->errorAggregator->processError($errorHandlerContext);
// AFTER (FIXED)
$this->dispatchToErrorAggregator($exception, $errorHandlerContext);
```
**Files to modify:**
- ✏️ `src/Framework/ErrorHandling/ErrorHandler.php` (add adapter method)
**Testing:**
- Trigger error that calls ErrorAggregator
- Verify context data preserved in WeakMap
- Check error aggregation dashboard shows correct context
---
## Strategy 2: Fix ExceptionHandlingMiddleware Unreachable Code
### Current State (BROKEN)
**Location:** `src/Framework/Http/Middlewares/ExceptionHandlingMiddleware.php:26-39`
```php
public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
{
try {
return $next($context);
} catch (\Throwable $e) {
$error = new ErrorKernel();
$error->handle($e); // ← Calls exit() - terminates PHP
// UNREACHABLE CODE - execution never reaches here
$response = $this->errorHandler->createHttpResponse($e, $context);
return $context->withResponse($response);
}
}
```
**Problem:** ErrorKernel.handle() calls exit(), making recovery impossible.
### Migration Strategy
**Solution: Add non-terminal mode to ErrorKernel**
**Step 1: Add createHttpResponse() to ErrorKernel**
```php
// Add to src/Framework/ExceptionHandling/ErrorKernel.php
/**
* Create HTTP Response without terminating execution
* (for middleware recovery pattern)
*/
public function createHttpResponse(\Throwable $exception): Response
{
// Initialize context if not already done
if ($this->contextProvider === null) {
$this->initializeContext($exception);
}
// Enrich context from request globals
$this->enrichContextFromRequest($exception);
// Create Response using renderer chain
$response = $this->createResponseFromException($exception);
// Log error (without terminating)
$this->logError($exception);
// Dispatch to aggregator
$this->dispatchToErrorAggregator($exception);
return $response;
}
/**
* Extract response creation from handle()
*/
private function createResponseFromException(\Throwable $exception): Response
{
// Try framework exception handler
if ($exception instanceof FrameworkException) {
return $this->handleFrameworkException($exception);
}
// Try specialized handlers
if ($this->exceptionHandlerManager !== null) {
$response = $this->exceptionHandlerManager->handle($exception);
if ($response !== null) {
return $response;
}
}
// Fallback to renderer chain
return $this->rendererChain->render($exception, $this->contextProvider);
}
```
**Step 2: Update ExceptionHandlingMiddleware**
```php
// Update src/Framework/Http/Middlewares/ExceptionHandlingMiddleware.php
use App\Framework\ExceptionHandling\ErrorKernel;
final readonly class ExceptionHandlingMiddleware
{
public function __construct(
private ErrorKernel $errorKernel, // ← Inject ErrorKernel
private Logger $logger
) {}
public function __invoke(
MiddlewareContext $context,
Next $next,
RequestStateManager $stateManager
): MiddlewareContext {
try {
return $next($context);
} catch (\Throwable $e) {
// Log error
$this->logger->error('[Middleware] Exception caught', [
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
// Create recovery response (non-terminal)
$response = $this->errorKernel->createHttpResponse($e);
// Return context with error response
return $context->withResponse($response);
}
}
}
```
**Files to modify:**
- ✏️ `src/Framework/ExceptionHandling/ErrorKernel.php` (add createHttpResponse() method)
- ✏️ `src/Framework/Http/Middlewares/ExceptionHandlingMiddleware.php` (fix catch block)
**Testing:**
- Throw exception in middleware chain
- Verify Response returned (no exit())
- Check error logged and aggregated
- Verify subsequent middleware not executed
---
## Strategy 3: Migrate SecurityEventLogger to ExceptionContextProvider
### Current State (OLD Architecture)
**Location:** `src/Framework/ErrorHandling/SecurityEventLogger.php:28-52`
```php
public function logSecurityEvent(
SecurityException $exception,
ErrorHandlerContext $context // ← OLD architecture
): void
```
**Dependencies:** Used by DDoS system (AdaptiveResponseSystem.php:244-250, 371-379)
### Migration Strategy
**Solution: Create bridge adapter that converts ExceptionContextProvider to old format**
**Step 1: Add WeakMap support to SecurityEventLogger**
```php
// Update src/Framework/ErrorHandling/SecurityEventLogger.php
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
final readonly class SecurityEventLogger
{
public function __construct(
private Logger $logger,
private AppConfig $appConfig,
private ?ExceptionContextProvider $contextProvider = null // ← NEW
) {}
/**
* NEW signature - preferred for new code
*/
public function logSecurityEventFromException(
SecurityException $exception
): void {
if ($this->contextProvider === null) {
throw new \RuntimeException('ExceptionContextProvider required for new logging');
}
// Retrieve context from WeakMap
$exceptionContext = $this->contextProvider->get($exception);
if ($exceptionContext === null) {
// Fallback: Create minimal context
$exceptionContext = ExceptionContextData::create();
}
// Convert to OWASP format
$owaspLog = $this->createOWASPLogFromWeakMap($exception, $exceptionContext);
// Log via framework logger
$this->logToFramework($exception, $owaspLog);
}
/**
* LEGACY signature - kept for backward compatibility
* @deprecated Use logSecurityEventFromException() instead
*/
public function logSecurityEvent(
SecurityException $exception,
ErrorHandlerContext $context
): void {
// Keep existing implementation for backward compatibility
$owaspLog = $this->createOWASPLog($exception, $context);
$this->logToFramework($exception, $owaspLog);
}
private function createOWASPLogFromWeakMap(
SecurityException $exception,
ExceptionContextData $context
): array {
$securityEvent = $exception->getSecurityEvent();
return [
'datetime' => date('c'),
'appid' => $this->appConfig->name,
'event' => $securityEvent->getEventIdentifier(),
'level' => $securityEvent->getLogLevel()->value,
'description' => $securityEvent->getDescription(),
'useragent' => $context->userAgent,
'source_ip' => $context->clientIp,
'host_ip' => $_SERVER['SERVER_ADDR'] ?? 'unknown',
'hostname' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'protocol' => $_SERVER['SERVER_PROTOCOL'] ?? 'unknown',
'port' => $_SERVER['SERVER_PORT'] ?? 'unknown',
'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'request_method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
'category' => $securityEvent->getCategory(),
'requires_alert' => $securityEvent->requiresAlert(),
];
}
private function logToFramework(
SecurityException $exception,
array $owaspLog
): void {
$securityEvent = $exception->getSecurityEvent();
$frameworkLogLevel = $this->mapSecurityLevelToFrameworkLevel(
$securityEvent->getLogLevel()
);
$this->logger->log(
$frameworkLogLevel,
$securityEvent->getDescription(),
[
'security_event' => $securityEvent->getEventIdentifier(),
'security_category' => $securityEvent->getCategory(),
'requires_alert' => $securityEvent->requiresAlert(),
'owasp_format' => $owaspLog,
]
);
}
}
```
**Step 2: Update DDoS system to use array logging (no SecurityException needed)**
```php
// Update src/Framework/DDoS/Response/AdaptiveResponseSystem.php
// CURRENT (line 244-250):
$this->securityLogger->logSecurityEvent([
'event_type' => 'ddos_enhanced_monitoring',
'client_ip' => $assessment->clientIp->value,
// ...
]);
// AFTER: Keep as-is - this is array-based logging, not SecurityException
// No changes needed here
```
**Files to modify:**
- ✏️ `src/Framework/ErrorHandling/SecurityEventLogger.php` (add WeakMap support)
**Files unchanged:**
-`src/Framework/DDoS/Response/AdaptiveResponseSystem.php` (already uses array logging)
**Testing:**
- Trigger DDoS detection
- Verify OWASP logs generated
- Check both old and new signatures work
---
## Strategy 4: Create CLI Error Rendering for ErrorKernel
### Current State
**Location:** `src/Framework/Core/AppBootstrapper.php:155-163`
```php
private function registerCliErrorHandler(): void
{
$output = $this->container->has(ConsoleOutput::class)
? $this->container->get(ConsoleOutput::class)
: new ConsoleOutput();
$cliErrorHandler = new CliErrorHandler($output); // ← Legacy
$cliErrorHandler->register();
}
```
**Legacy CliErrorHandler features:**
- Colored console output (ConsoleColor enum)
- Exit(1) on fatal errors
- Stack trace formatting
### Migration Strategy
**Solution: Create CliErrorRenderer for ErrorKernel renderer chain**
**Step 1: Create CliErrorRenderer**
```php
// Create src/Framework/ExceptionHandling/Renderers/CliErrorRenderer.php
namespace App\Framework\ExceptionHandling\Renderers;
use App\Framework\Console\ConsoleOutput;
use App\Framework\Console\ConsoleColor;
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
final readonly class CliErrorRenderer implements ErrorRenderer
{
public function __construct(
private ConsoleOutput $output
) {}
public function canRender(\Throwable $exception): bool
{
// Render in CLI context only
return PHP_SAPI === 'cli';
}
public function render(
\Throwable $exception,
?ExceptionContextProvider $contextProvider = null
): void {
$this->output->writeLine(
"❌ Uncaught " . get_class($exception) . ": " . $exception->getMessage(),
ConsoleColor::BRIGHT_RED
);
$this->output->writeLine(
" File: " . $exception->getFile() . ":" . $exception->getLine(),
ConsoleColor::RED
);
if ($exception->getPrevious()) {
$this->output->writeLine(
" Caused by: " . $exception->getPrevious()->getMessage(),
ConsoleColor::YELLOW
);
}
$this->output->writeLine(" Stack trace:", ConsoleColor::GRAY);
foreach (explode("\n", $exception->getTraceAsString()) as $line) {
$this->output->writeLine(" " . $line, ConsoleColor::GRAY);
}
// Context information if available
if ($contextProvider !== null) {
$context = $contextProvider->get($exception);
if ($context !== null && $context->operation !== null) {
$this->output->writeLine(
" Operation: " . $context->operation,
ConsoleColor::CYAN
);
}
}
}
}
```
**Step 2: Register CLI renderer in ErrorKernel**
```php
// Update src/Framework/ExceptionHandling/ErrorKernel.php initialization
private function initializeRendererChain(): void
{
$renderers = [];
// CLI renderer (highest priority in CLI context)
if (PHP_SAPI === 'cli' && $this->container->has(ConsoleOutput::class)) {
$renderers[] = new CliErrorRenderer(
$this->container->get(ConsoleOutput::class)
);
}
// HTTP renderers
$renderers[] = new HtmlErrorRenderer($this->container);
$renderers[] = new JsonErrorRenderer();
$this->rendererChain = new ErrorRendererChain($renderers);
}
```
**Step 3: Update AppBootstrapper to use ErrorKernel in CLI**
```php
// Update src/Framework/Core/AppBootstrapper.php
private function registerCliErrorHandler(): void
{
// NEW: Use ErrorKernel for CLI (unified architecture)
new ExceptionHandlerManager();
// ErrorKernel will detect CLI context and use CliErrorRenderer
// via its renderer chain
}
```
**Files to modify:**
- ✏️ Create `src/Framework/ExceptionHandling/Renderers/CliErrorRenderer.php`
- ✏️ `src/Framework/ExceptionHandling/ErrorKernel.php` (register CLI renderer)
- ✏️ `src/Framework/Core/AppBootstrapper.php` (use ErrorKernel in CLI)
**Files to delete (after migration):**
- 🗑️ `src/Framework/ErrorHandling/CliErrorHandler.php` (replaced by CliErrorRenderer)
**Testing:**
- Run console command that throws exception
- Verify colored output in terminal
- Check stack trace formatting
- Verify exit(1) called
---
## Strategy 5: Create HTTP Response Generation for ErrorKernel
### Current State
Legacy ErrorHandler.createHttpResponse() pattern (lines 71-86, 115-145) provides:
- Response generation without terminating
- ErrorResponseFactory for API/HTML rendering
- Middleware recovery pattern support
### Migration Strategy
**Solution: Extract ErrorResponseFactory pattern into ErrorKernel**
**Step 1: Create ResponseErrorRenderer**
```php
// Create src/Framework/ExceptionHandling/Renderers/ResponseErrorRenderer.php
namespace App\Framework\ExceptionHandling\Renderers;
use App\Framework\Http\Response;
use App\Framework\Http\Status;
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
use App\Framework\Template\TemplateRenderer;
final readonly class ResponseErrorRenderer
{
public function __construct(
private ?TemplateRenderer $templateRenderer = null
) {}
public function createResponse(
\Throwable $exception,
?ExceptionContextProvider $contextProvider = null
): Response {
// Determine if API or HTML response needed
$isApiRequest = $this->isApiRequest();
if ($isApiRequest) {
return $this->createApiResponse($exception, $contextProvider);
}
return $this->createHtmlResponse($exception, $contextProvider);
}
private function createApiResponse(
\Throwable $exception,
?ExceptionContextProvider $contextProvider
): Response {
$statusCode = $this->getHttpStatusCode($exception);
$body = json_encode([
'error' => [
'message' => $exception->getMessage(),
'type' => get_class($exception),
'code' => $exception->getCode(),
]
]);
return new Response(
status: Status::from($statusCode),
body: $body,
headers: ['Content-Type' => 'application/json']
);
}
private function createHtmlResponse(
\Throwable $exception,
?ExceptionContextProvider $contextProvider
): Response {
$statusCode = $this->getHttpStatusCode($exception);
if ($this->templateRenderer !== null) {
$body = $this->templateRenderer->render('errors/exception', [
'exception' => $exception,
'context' => $contextProvider?->get($exception),
'statusCode' => $statusCode
]);
} else {
$body = $this->createFallbackHtml($exception, $statusCode);
}
return new Response(
status: Status::from($statusCode),
body: $body,
headers: ['Content-Type' => 'text/html']
);
}
private function isApiRequest(): bool
{
// Check Accept header or URL prefix
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
$uri = $_SERVER['REQUEST_URI'] ?? '';
return str_contains($accept, 'application/json')
|| str_starts_with($uri, '/api/');
}
private function getHttpStatusCode(\Throwable $exception): int
{
// Map exception types to HTTP status codes
return match (true) {
$exception instanceof \InvalidArgumentException => 400,
$exception instanceof \UnauthorizedException => 401,
$exception instanceof \ForbiddenException => 403,
$exception instanceof \NotFoundException => 404,
default => 500
};
}
private function createFallbackHtml(\Throwable $exception, int $statusCode): string
{
return <<<HTML
<!DOCTYPE html>
<html>
<head>
<title>Error {$statusCode}</title>
</head>
<body>
<h1>Error {$statusCode}</h1>
<p>{$exception->getMessage()}</p>
</body>
</html>
HTML;
}
}
```
**Step 2: Integrate into ErrorKernel.createHttpResponse()**
```php
// Update src/Framework/ExceptionHandling/ErrorKernel.php
private ResponseErrorRenderer $responseRenderer;
private function initializeResponseRenderer(): void
{
$templateRenderer = $this->container->has(TemplateRenderer::class)
? $this->container->get(TemplateRenderer::class)
: null;
$this->responseRenderer = new ResponseErrorRenderer($templateRenderer);
}
public function createHttpResponse(\Throwable $exception): Response
{
// Initialize context
if ($this->contextProvider === null) {
$this->initializeContext($exception);
}
// Enrich from request
$this->enrichContextFromRequest($exception);
// Create Response
$response = $this->responseRenderer->createResponse(
$exception,
$this->contextProvider
);
// Log error (without terminating)
$this->logError($exception);
// Dispatch to aggregator
$this->dispatchToErrorAggregator($exception);
return $response;
}
```
**Files to modify:**
- ✏️ Create `src/Framework/ExceptionHandling/Renderers/ResponseErrorRenderer.php`
- ✏️ `src/Framework/ExceptionHandling/ErrorKernel.php` (add createHttpResponse())
**Testing:**
- Throw exception in middleware
- Verify JSON response for /api/* routes
- Verify HTML response for web routes
- Check status codes correct
---
## Migration Execution Plan
### Phase 5a: Preparation (Current Phase)
- ✅ Document all 5 strategies
- ⏳ Review strategies with team
- ⏳ Create feature branch: `feature/migrate-errorhandling-module`
### Phase 5b: Implementation Order
**Week 1: Foundation**
1. Strategy 5: HTTP Response generation (enables middleware recovery)
2. Strategy 2: Fix ExceptionHandlingMiddleware (depends on Strategy 5)
**Week 2: Compatibility**
3. Strategy 1: ErrorAggregator signature fix (critical for logging)
4. Strategy 3: SecurityEventLogger migration (preserves DDoS logging)
**Week 3: CLI Support**
5. Strategy 4: CLI error rendering (replaces CliErrorHandler)
**Week 4: Cleanup**
6. Remove legacy ErrorHandling module
7. Update all import statements
8. Run full test suite
### Testing Strategy
**Per-Strategy Testing:**
- Unit tests for new components
- Integration tests for error flows
- Manual testing in development environment
**Final Integration Testing:**
- Trigger errors in web context → verify HTTP Response
- Trigger errors in CLI context → verify colored output
- Trigger security events → verify OWASP logs
- Trigger DDoS detection → verify adaptive response
- Check ErrorAggregator dashboard → verify context preserved
### Rollback Plan
Each strategy is independent and can be rolled back:
- Strategy 1: Remove adapter method
- Strategy 2: Revert middleware catch block
- Strategy 3: Remove WeakMap support from SecurityEventLogger
- Strategy 4: Keep CliErrorHandler active
- Strategy 5: Don't use createHttpResponse()
---
## Success Criteria
- ✅ All 5 blockers resolved
- ✅ Zero breaking changes to public APIs
- ✅ DDoS system continues functioning
- ✅ CLI error handling preserved
- ✅ Middleware recovery pattern works
- ✅ ErrorAggregator receives correct context
- ✅ All tests passing
- ✅ Legacy ErrorHandling module deleted
---
## Next Actions
**Immediate (Phase 5b start):**
1. Create feature branch: `git checkout -b feature/migrate-errorhandling-module`
2. Implement Strategy 5 (HTTP Response generation)
3. Implement Strategy 2 (Fix middleware)
4. Run tests and verify middleware recovery
**This Week:**
- Complete Strategies 1-2
- Manual testing in development
**Next Week:**
- Complete Strategies 3-5
- Integration testing
- Code review
**Final Week:**
- Remove legacy module
- Documentation updates
- Production deployment