- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
342 lines
9.8 KiB
PHP
342 lines
9.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Waf\MachineLearning\Detectors;
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\DateTime\DateTime;
|
|
use App\Framework\Waf\MachineLearning\AnomalyType;
|
|
use App\Framework\Waf\MachineLearning\BehaviorType;
|
|
use App\Framework\Waf\MachineLearning\Detectors\ClusteringAnomalyDetector;
|
|
use App\Framework\Waf\MachineLearning\ValueObjects\BehaviorBaseline;
|
|
use App\Framework\Waf\MachineLearning\ValueObjects\BehaviorFeature;
|
|
|
|
// Hilfsfunktion zum Erstellen einer Baseline für Tests
|
|
function createTestBaseline(?BehaviorType $type = null): BehaviorBaseline
|
|
{
|
|
$type = $type ?? BehaviorType::PATH_PATTERNS;
|
|
$now = Timestamp::fromDateTime(DateTime::fromTimestamp(time()));
|
|
|
|
return new BehaviorBaseline(
|
|
type: $type,
|
|
identifier: 'test-client',
|
|
mean: 10.0,
|
|
standardDeviation: 5.0,
|
|
median: 10.0,
|
|
minimum: 5.0,
|
|
maximum: 25.0,
|
|
percentiles: [
|
|
25 => 7.5,
|
|
75 => 15.0,
|
|
90 => 18.0,
|
|
95 => 20.0,
|
|
99 => 22.0,
|
|
],
|
|
sampleCount: 20,
|
|
createdAt: $now,
|
|
lastUpdated: $now,
|
|
windowSize: Duration::fromMinutes(30),
|
|
confidence: 0.8
|
|
);
|
|
}
|
|
|
|
// Hilfsfunktion zum Erstellen von Testfeatures
|
|
function createTestFeatures(): array
|
|
{
|
|
return [
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_depth',
|
|
value: 3.0,
|
|
unit: 'count'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_segments',
|
|
value: 4.0,
|
|
unit: 'count'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 25.0,
|
|
unit: 'characters'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PARAMETER_PATTERNS,
|
|
name: 'param_count',
|
|
value: 2.0,
|
|
unit: 'count'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PARAMETER_PATTERNS,
|
|
name: 'param_length_avg',
|
|
value: 8.0,
|
|
unit: 'characters'
|
|
),
|
|
];
|
|
}
|
|
|
|
test('erkennt Cluster-Abweichungen', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: true,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
// Normale Features
|
|
$normalFeatures = createTestFeatures();
|
|
|
|
// Anomales Feature mit deutlich abweichenden Werten
|
|
$anomalousFeature = new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 150.0, // Deutlich höher als normal
|
|
unit: 'characters'
|
|
);
|
|
|
|
$features = array_merge($normalFeatures, [$anomalousFeature]);
|
|
|
|
// Act
|
|
$anomalies = $detector->detectAnomalies($features, null);
|
|
|
|
// Assert
|
|
expect($anomalies)->not->toBeEmpty();
|
|
expect($anomalies[0]->type)->toBe(AnomalyType::CLUSTERING_DEVIATION);
|
|
});
|
|
|
|
test('gruppiert Features nach Typ', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: true,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
// Features mit verschiedenen Typen
|
|
$features = [
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_feature',
|
|
value: 10.0,
|
|
unit: 'count'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PARAMETER_PATTERNS,
|
|
name: 'param_feature',
|
|
value: 5.0,
|
|
unit: 'count'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::REQUEST_FREQUENCY,
|
|
name: 'freq_feature',
|
|
value: 2.0,
|
|
unit: 'requests/second'
|
|
),
|
|
];
|
|
|
|
// Wir können die private Methode nicht direkt testen, aber wir können testen,
|
|
// dass der Detektor die Features analysieren kann
|
|
|
|
// Act & Assert
|
|
expect($detector->canAnalyze($features))->toBeTrue();
|
|
});
|
|
|
|
test('unterstützt verschiedene Verhaltenstypen', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: true,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
// Act
|
|
$supportedTypes = $detector->getSupportedBehaviorTypes();
|
|
|
|
// Assert
|
|
expect($supportedTypes)->toBeArray();
|
|
expect($supportedTypes)->toContain(BehaviorType::REQUEST_FREQUENCY);
|
|
expect($supportedTypes)->toContain(BehaviorType::PATH_PATTERNS);
|
|
expect($supportedTypes)->toContain(BehaviorType::PARAMETER_PATTERNS);
|
|
expect($supportedTypes)->toContain(BehaviorType::USER_AGENT_PATTERNS);
|
|
});
|
|
|
|
test('erkennt Dichte-Anomalien wenn aktiviert', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: false,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
// Normale Features mit ähnlichen Werten
|
|
$normalFeatures = [
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 20.0,
|
|
unit: 'characters'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 22.0,
|
|
unit: 'characters'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 19.0,
|
|
unit: 'characters'
|
|
),
|
|
new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 21.0,
|
|
unit: 'characters'
|
|
),
|
|
];
|
|
|
|
// Isoliertes Feature
|
|
$isolatedFeature = new BehaviorFeature(
|
|
type: BehaviorType::PATH_PATTERNS,
|
|
name: 'path_length',
|
|
value: 100.0, // Deutlich abseits der anderen
|
|
unit: 'characters'
|
|
);
|
|
|
|
$features = array_merge($normalFeatures, [$isolatedFeature]);
|
|
|
|
// Act
|
|
$anomalies = $detector->detectAnomalies($features, null);
|
|
|
|
// Assert
|
|
expect($anomalies)->not->toBeEmpty();
|
|
// Je nach Implementierung könnte es verschiedene Anomalietypen sein
|
|
expect($anomalies[0]->type)->toBe(AnomalyType::CLUSTERING_DEVIATION);
|
|
});
|
|
|
|
test('aktualisiert Modell mit neuen Daten', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: true,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
$features = createTestFeatures();
|
|
|
|
// Act - Keine Assertion möglich, da interne Daten private sind
|
|
// Wir testen nur, dass keine Exception geworfen wird
|
|
$detector->updateModel($features);
|
|
|
|
// Assert
|
|
expect(true)->toBeTrue(); // Dummy assertion
|
|
});
|
|
|
|
test('gibt Konfiguration korrekt zurück', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.75,
|
|
maxClusters: 5,
|
|
minClusterSize: 3,
|
|
outlierThreshold: 0.9,
|
|
maxIterations: 20,
|
|
convergenceThreshold: 0.005,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: false,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
// Act
|
|
$config = $detector->getConfiguration();
|
|
|
|
// Assert
|
|
expect($config)->toBeArray();
|
|
expect($config['enabled'])->toBeTrue();
|
|
expect($config['confidence_threshold'])->toBe(0.75);
|
|
expect($config['max_clusters'])->toBe(5);
|
|
expect($config['min_cluster_size'])->toBe(3);
|
|
expect($config['outlier_threshold'])->toBe(0.9);
|
|
expect($config['max_iterations'])->toBe(20);
|
|
expect($config['enable_density_analysis'])->toBeTrue();
|
|
expect($config['enable_group_anomaly_detection'])->toBeFalse();
|
|
});
|
|
|
|
test('gibt leere Ergebnisse zurück wenn deaktiviert', function () {
|
|
// Arrange
|
|
$detector = new ClusteringAnomalyDetector(
|
|
enabled: false,
|
|
confidenceThreshold: 0.5,
|
|
maxClusters: 3,
|
|
minClusterSize: 2,
|
|
outlierThreshold: 0.8,
|
|
maxIterations: 10,
|
|
convergenceThreshold: 0.01,
|
|
enableDensityAnalysis: true,
|
|
enableGroupAnomalyDetection: true,
|
|
clusterCenters: [],
|
|
clusterAssignments: [],
|
|
featureVectors: []
|
|
);
|
|
|
|
$features = createTestFeatures();
|
|
|
|
// Act
|
|
$anomalies = $detector->detectAnomalies($features, null);
|
|
|
|
// Assert
|
|
expect($anomalies)->toBeEmpty();
|
|
});
|