- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
301 lines
8.0 KiB
PHP
301 lines
8.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Waf\Rules;
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Core\ValueObjects\Percentage;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\Waf\Rules\ValueObjects\RuleMatch;
|
|
|
|
/**
|
|
* Result of rule engine evaluation
|
|
*/
|
|
final readonly class RuleEvaluationResult
|
|
{
|
|
public function __construct(
|
|
public array $matches,
|
|
public int $evaluatedRules,
|
|
public int $skippedRules,
|
|
public int $errorCount,
|
|
public array $errors,
|
|
public Duration $evaluationTime,
|
|
public Timestamp $timestamp
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Check if any rules matched
|
|
*/
|
|
public function hasMatches(): bool
|
|
{
|
|
return ! empty($this->matches);
|
|
}
|
|
|
|
/**
|
|
* Get number of matches
|
|
*/
|
|
public function getMatchCount(): int
|
|
{
|
|
return count($this->matches);
|
|
}
|
|
|
|
/**
|
|
* Get matches by severity
|
|
*/
|
|
public function getMatchesBySeverity(string $severity): array
|
|
{
|
|
return array_filter(
|
|
$this->matches,
|
|
fn (RuleMatch $match) => $match->severity->value === $severity
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get critical matches
|
|
*/
|
|
public function getCriticalMatches(): array
|
|
{
|
|
return $this->getMatchesBySeverity('critical');
|
|
}
|
|
|
|
/**
|
|
* Get high severity matches
|
|
*/
|
|
public function getHighSeverityMatches(): array
|
|
{
|
|
return $this->getMatchesBySeverity('high');
|
|
}
|
|
|
|
/**
|
|
* Get blocking matches (matches that should block the request)
|
|
*/
|
|
public function getBlockingMatches(): array
|
|
{
|
|
return array_filter(
|
|
$this->matches,
|
|
fn (RuleMatch $match) => $match->shouldBlock()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get matches that should trigger alerts
|
|
*/
|
|
public function getAlertingMatches(): array
|
|
{
|
|
return array_filter(
|
|
$this->matches,
|
|
fn (RuleMatch $match) => $match->shouldAlert()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get OWASP Top 10 matches
|
|
*/
|
|
public function getOwaspTop10Matches(): array
|
|
{
|
|
return array_filter(
|
|
$this->matches,
|
|
fn (RuleMatch $match) => $match->category->isOwaspTop10()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get matches by category
|
|
*/
|
|
public function getMatchesByCategory(string $category): array
|
|
{
|
|
return array_filter(
|
|
$this->matches,
|
|
fn (RuleMatch $match) => $match->category->value === $category
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if any blocking matches exist
|
|
*/
|
|
public function shouldBlock(): bool
|
|
{
|
|
return ! empty($this->getBlockingMatches());
|
|
}
|
|
|
|
/**
|
|
* Check if any alerting matches exist
|
|
*/
|
|
public function shouldAlert(): bool
|
|
{
|
|
return ! empty($this->getAlertingMatches());
|
|
}
|
|
|
|
/**
|
|
* Check if evaluation had errors
|
|
*/
|
|
public function hasErrors(): bool
|
|
{
|
|
return $this->errorCount > 0;
|
|
}
|
|
|
|
/**
|
|
* Get highest severity level from matches
|
|
*/
|
|
public function getHighestSeverity(): ?string
|
|
{
|
|
if (empty($this->matches)) {
|
|
return null;
|
|
}
|
|
|
|
$severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
|
|
|
|
foreach ($severityOrder as $severity) {
|
|
if (! empty($this->getMatchesBySeverity($severity))) {
|
|
return $severity;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get categories of all matches
|
|
*/
|
|
public function getMatchedCategories(): array
|
|
{
|
|
$categories = [];
|
|
|
|
foreach ($this->matches as $match) {
|
|
$categories[$match->category->value] = $match->category;
|
|
}
|
|
|
|
return array_values($categories);
|
|
}
|
|
|
|
/**
|
|
* Get rule IDs of all matches
|
|
*/
|
|
public function getMatchedRuleIds(): array
|
|
{
|
|
return array_map(
|
|
fn (RuleMatch $match) => $match->ruleId->value,
|
|
$this->matches
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calculate overall confidence score
|
|
*/
|
|
public function getOverallConfidence(): Percentage
|
|
{
|
|
if (empty($this->matches)) {
|
|
return Percentage::from(0.0);
|
|
}
|
|
|
|
$totalConfidence = 0.0;
|
|
$confidenceCount = 0;
|
|
|
|
foreach ($this->matches as $match) {
|
|
if ($match->confidence !== null) {
|
|
$totalConfidence += $match->confidence->getValue();
|
|
$confidenceCount++;
|
|
}
|
|
}
|
|
|
|
if ($confidenceCount === 0) {
|
|
return Percentage::from(0.0);
|
|
}
|
|
|
|
return Percentage::from($totalConfidence / $confidenceCount);
|
|
}
|
|
|
|
/**
|
|
* Calculate threat score based on matches
|
|
*/
|
|
public function getThreatScore(): Percentage
|
|
{
|
|
if (empty($this->matches)) {
|
|
return Percentage::from(0.0);
|
|
}
|
|
|
|
$score = 0.0;
|
|
$maxScore = 0.0;
|
|
|
|
foreach ($this->matches as $match) {
|
|
$matchScore = match ($match->severity->value) {
|
|
'critical' => 25.0,
|
|
'high' => 20.0,
|
|
'medium' => 10.0,
|
|
'low' => 5.0,
|
|
'info' => 1.0,
|
|
default => 0.0
|
|
};
|
|
|
|
// Apply confidence modifier
|
|
if ($match->confidence !== null) {
|
|
$matchScore *= ($match->confidence->getValue() / 100.0);
|
|
}
|
|
|
|
$score += $matchScore;
|
|
$maxScore = max($maxScore, $matchScore);
|
|
}
|
|
|
|
// Use weighted combination: 70% cumulative + 30% maximum
|
|
$finalScore = ($score * 0.7) + ($maxScore * 0.3);
|
|
|
|
return Percentage::from(min(100.0, $finalScore));
|
|
}
|
|
|
|
/**
|
|
* Get evaluation performance summary
|
|
*/
|
|
public function getPerformanceSummary(): array
|
|
{
|
|
$efficiency = 0.0;
|
|
if ($this->evaluatedRules > 0) {
|
|
$rulesPerMs = $this->evaluatedRules / max(1, $this->evaluationTime->toMilliseconds());
|
|
$efficiency = $rulesPerMs * 1000; // Rules per second
|
|
}
|
|
|
|
return [
|
|
'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(),
|
|
'evaluated_rules' => $this->evaluatedRules,
|
|
'skipped_rules' => $this->skippedRules,
|
|
'error_count' => $this->errorCount,
|
|
'rules_per_second' => round($efficiency, 2),
|
|
'error_rate' => $this->evaluatedRules > 0
|
|
? round(($this->errorCount / $this->evaluatedRules) * 100, 2)
|
|
: 0.0,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Convert to array for serialization
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'timestamp' => $this->timestamp->toIsoString(),
|
|
'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(),
|
|
'evaluated_rules' => $this->evaluatedRules,
|
|
'skipped_rules' => $this->skippedRules,
|
|
'error_count' => $this->errorCount,
|
|
'match_count' => $this->getMatchCount(),
|
|
'has_matches' => $this->hasMatches(),
|
|
'should_block' => $this->shouldBlock(),
|
|
'should_alert' => $this->shouldAlert(),
|
|
'highest_severity' => $this->getHighestSeverity(),
|
|
'threat_score' => $this->getThreatScore()->getValue(),
|
|
'overall_confidence' => $this->getOverallConfidence()->getValue(),
|
|
'matched_categories' => array_map(fn ($cat) => $cat->value, $this->getMatchedCategories()),
|
|
'matched_rule_ids' => $this->getMatchedRuleIds(),
|
|
'owasp_top10_matches' => count($this->getOwaspTop10Matches()),
|
|
'critical_matches' => count($this->getCriticalMatches()),
|
|
'high_severity_matches' => count($this->getHighSeverityMatches()),
|
|
'blocking_matches' => count($this->getBlockingMatches()),
|
|
'alerting_matches' => count($this->getAlertingMatches()),
|
|
'performance_summary' => $this->getPerformanceSummary(),
|
|
'matches' => array_map(fn (RuleMatch $match) => $match->toArray(), $this->matches),
|
|
'errors' => $this->errors,
|
|
];
|
|
}
|
|
}
|