Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
247
src/Framework/Http/Middlewares/WafMiddleware.php
Normal file
247
src/Framework/Http/Middlewares/WafMiddleware.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Http\Middlewares;
|
||||
|
||||
use App\Framework\Config\WafConfig;
|
||||
use App\Framework\Http\HttpMiddleware;
|
||||
use App\Framework\Http\HttpResponse;
|
||||
use App\Framework\Http\MiddlewareContext;
|
||||
use App\Framework\Http\MiddlewarePriority;
|
||||
use App\Framework\Http\MiddlewarePriorityAttribute;
|
||||
use App\Framework\Http\Next;
|
||||
use App\Framework\Http\RequestStateManager;
|
||||
use App\Framework\Http\Responses\JsonResponse;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Waf\LayerResult;
|
||||
use App\Framework\Waf\WafEngine;
|
||||
|
||||
/**
|
||||
* WAF (Web Application Firewall) Middleware
|
||||
*
|
||||
* Integrates the WAF engine into the HTTP request pipeline for security analysis
|
||||
* and threat detection. Blocks or flags requests based on WAF layer results.
|
||||
*/
|
||||
#[MiddlewarePriorityAttribute(MiddlewarePriority::SECURITY)]
|
||||
final readonly class WafMiddleware implements HttpMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private WafEngine $wafEngine,
|
||||
private Logger $logger,
|
||||
private WafConfig $config
|
||||
) {
|
||||
// Ensure security layers are registered
|
||||
$this->initializeSecurityLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize security layers if not already registered
|
||||
*/
|
||||
private function initializeSecurityLayers(): void
|
||||
{
|
||||
// Check if layers are already registered (avoid double registration)
|
||||
$healthStatus = $this->wafEngine->getHealthStatus();
|
||||
if ($healthStatus['total_layers'] > 0) {
|
||||
return; // Already initialized
|
||||
}
|
||||
|
||||
try {
|
||||
// Register security layers directly
|
||||
$this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\SqlInjectionLayer());
|
||||
$this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\CommandInjectionLayer());
|
||||
$this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\PathTraversalLayer());
|
||||
$this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\XssLayer());
|
||||
$this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\SuspiciousUserAgentLayer());
|
||||
|
||||
$this->logger->info('WAF security layers initialized', [
|
||||
'layers_count' => 5,
|
||||
'health_status' => $this->wafEngine->getHealthStatus(),
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Failed to initialize WAF security layers', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
|
||||
{
|
||||
if (! $this->config->enabled) {
|
||||
$this->logger->debug('WAF disabled, skipping analysis');
|
||||
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
try {
|
||||
$request = $context->request;
|
||||
|
||||
// Debug log request details
|
||||
$this->logger->debug('WAF analyzing request', [
|
||||
'path' => $request->path ?? '/',
|
||||
'method' => $request->method->value ?? 'UNKNOWN',
|
||||
'query_params' => $request->queryParams,
|
||||
'post_data' => $request->parsedBody->data ?? null,
|
||||
'user_agent' => $request->headers->get('User-Agent', ''),
|
||||
'client_ip' => $request->server->getClientIp()?->value ?? 'unknown',
|
||||
'waf_config' => [
|
||||
'enabled' => $this->config->enabled,
|
||||
'blocking_mode' => $this->config->blockingMode,
|
||||
'enabled_layers' => $this->config->enabledLayers,
|
||||
],
|
||||
]);
|
||||
|
||||
// Analyze request with WAF engine
|
||||
$wafResult = $this->wafEngine->analyze($request);
|
||||
|
||||
// Debug log analysis result
|
||||
$this->logger->debug('WAF analysis complete', [
|
||||
'result_status' => $wafResult->getStatus()->value ?? 'unknown',
|
||||
'result_action' => $wafResult->getAction(),
|
||||
'layer_name' => $wafResult->getLayerName(),
|
||||
'message' => $wafResult->getMessage(),
|
||||
'has_detections' => $wafResult->hasDetections(),
|
||||
'detections_count' => $wafResult->hasDetections() ? count($wafResult->getDetections()->getAll()) : 0,
|
||||
]);
|
||||
|
||||
// Handle based on result action
|
||||
switch ($wafResult->getAction()) {
|
||||
case LayerResult::ACTION_BLOCK:
|
||||
$this->logger->info('WAF blocking request', ['reason' => $wafResult->getMessage()]);
|
||||
|
||||
return $this->handleBlocked($context, $wafResult);
|
||||
|
||||
case LayerResult::ACTION_SUSPICIOUS:
|
||||
$this->logger->info('WAF flagging suspicious request', ['reason' => $wafResult->getMessage()]);
|
||||
|
||||
return $this->handleSuspicious($context, $wafResult, $next);
|
||||
|
||||
case LayerResult::ACTION_PASS:
|
||||
default:
|
||||
$this->logger->debug('WAF allowing request', ['reason' => $wafResult->getMessage()]);
|
||||
|
||||
// Continue to next middleware
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
// Log WAF error but don't block request on WAF failure
|
||||
$this->logger->error('WAF middleware error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'request_path' => $context->request->path ?? '/',
|
||||
'client_ip' => $context->request->server->getClientIp()?->value ?? 'unknown',
|
||||
]);
|
||||
|
||||
// Continue processing on WAF error
|
||||
return $next($context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle blocked requests
|
||||
*/
|
||||
private function handleBlocked(MiddlewareContext $context, LayerResult $wafResult): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
$clientIp = $request->server->getClientIp()?->value ?? 'unknown';
|
||||
|
||||
// Log security event
|
||||
$this->logger->warning('WAF blocked request', [
|
||||
'reason' => $wafResult->getMessage(),
|
||||
'client_ip' => $clientIp,
|
||||
'path' => $request->path ?? '/',
|
||||
'user_agent' => $request->headers->get('User-Agent', ''),
|
||||
'detections' => $this->formatDetections($wafResult),
|
||||
'layer' => $wafResult->getLayerName(),
|
||||
]);
|
||||
|
||||
if ($this->config->blockingMode) {
|
||||
// Return 403 Forbidden response
|
||||
$response = new JsonResponse([
|
||||
'error' => 'Request blocked by security policy',
|
||||
'code' => 'WAF_BLOCKED',
|
||||
'request_id' => $request->id->value(),
|
||||
], 403);
|
||||
} else {
|
||||
// Log-only mode - continue processing but log the threat
|
||||
$this->logger->warning('WAF would block request (log-only mode)', [
|
||||
'reason' => $wafResult->getMessage(),
|
||||
'client_ip' => $clientIp,
|
||||
'path' => $request->path ?? '/',
|
||||
]);
|
||||
|
||||
$response = new JsonResponse([
|
||||
'warning' => 'Request flagged by security policy',
|
||||
'code' => 'WAF_FLAGGED',
|
||||
], 200);
|
||||
}
|
||||
|
||||
return $context->withResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle suspicious requests
|
||||
*/
|
||||
private function handleSuspicious(MiddlewareContext $context, LayerResult $wafResult, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
$clientIp = $request->server->getClientIp()?->value ?? 'unknown';
|
||||
|
||||
// Log suspicious activity
|
||||
$this->logger->info('WAF flagged suspicious request', [
|
||||
'reason' => $wafResult->getMessage(),
|
||||
'client_ip' => $clientIp,
|
||||
'path' => $request->path ?? '/',
|
||||
'user_agent' => $request->headers->get('User-Agent', ''),
|
||||
'detections' => $this->formatDetections($wafResult),
|
||||
'layer' => $wafResult->getLayerName(),
|
||||
]);
|
||||
|
||||
// Continue to next middleware
|
||||
$resultContext = $next($context);
|
||||
|
||||
// Add WAF detection info to response headers for monitoring
|
||||
if ($resultContext->hasResponse()) {
|
||||
$response = $resultContext->response;
|
||||
$updatedHeaders = $response->headers
|
||||
->with('X-Waf-Status', 'suspicious')
|
||||
->with('X-Waf-Layer', $wafResult->getLayerName());
|
||||
|
||||
$updatedResponse = new HttpResponse(
|
||||
$response->body,
|
||||
$response->statusCode,
|
||||
$updatedHeaders
|
||||
);
|
||||
|
||||
return $resultContext->withResponse($updatedResponse);
|
||||
}
|
||||
|
||||
return $resultContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format detection information for logging
|
||||
*/
|
||||
private function formatDetections(LayerResult $wafResult): array
|
||||
{
|
||||
if (! $wafResult->hasDetections()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$detections = [];
|
||||
foreach ($wafResult->getDetections()->getAll() as $detection) {
|
||||
$detections[] = [
|
||||
'category' => $detection->category->value,
|
||||
'severity' => $detection->severity->value,
|
||||
'message' => $detection->message,
|
||||
'confidence' => $detection->confidence,
|
||||
'evidence' => $detection->evidence,
|
||||
];
|
||||
}
|
||||
|
||||
return $detections;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user