@anonympins/fingerprint 0.6.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +128 -119
- package/composer.json +2 -1
- package/package.json +1 -1
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +6802 -6411
- package/src/js/library.js +18 -11
- package/src/js/problem-manager.js +6 -2
- package/src/js/tests/cross_parity.test.js +17 -11
- package/src/js/tests/fingerprint.test.js +47 -10
- package/src/js/tests/metrics.test.js +157 -103
- package/src/php/Challenge/ChallengeUtils.php +69 -6
- package/src/php/Config/SecurityProfiles.php +3 -2
- package/src/php/FingerprintEngine.php +154 -21
- package/src/php/Optimization/OptimizationOperators.php +21 -21
- package/src/php/RequestContext.php +5 -0
- package/src/php/Tests/ChallengeUtilsTest.php +6 -4
- package/src/php/Tests/FingerprintEngineTest.php +186 -0
- package/src/php/Tests/MetricsTest.php +97 -45
- package/src/php/Tests/RequestUtilsTest.php +14 -14
- package/src/php/Tests/config/ed25519_key.json +4 -0
- package/src/php/Utils/Env.php +50 -0
- package/src/php/Utils/MetricsManager.php +209 -166
- package/src/php/Utils/RequestUtils.php +70 -14
|
@@ -1,46 +1,98 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
-
use Anonympins\Fingerprint\DirectFingerprint;
|
|
9
|
-
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
10
|
-
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
-
use PHPUnit\Framework\TestCase;
|
|
13
|
-
|
|
14
|
-
class MetricsTest extends TestCase
|
|
15
|
-
{
|
|
16
|
-
protected function setUp(): void
|
|
17
|
-
{
|
|
18
|
-
parent::setUp();
|
|
19
|
-
StoreManager::configureStore(new InMemoryStore());
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public function testIncrementCounterAndGetPrometheusMetrics(): void
|
|
23
|
-
{
|
|
24
|
-
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
25
|
-
$metrics = MetricsManager::getPrometheusMetrics();
|
|
26
|
-
|
|
27
|
-
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
28
|
-
$this->assertStringContainsString('status="passed"', $metrics);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public function testGetPrometheusMetricsViaDirectFingerprint(): void
|
|
32
|
-
{
|
|
33
|
-
$config = [
|
|
34
|
-
'metricsAuthorizationCallback' => function (RequestContext $context) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
$direct = new DirectFingerprint($config);
|
|
40
|
-
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
41
|
-
$metrics = $direct->getPrometheusMetrics();
|
|
42
|
-
|
|
43
|
-
$this->assertNotNull($metrics);
|
|
44
|
-
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
45
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
9
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
10
|
+
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
|
+
|
|
14
|
+
class MetricsTest extends TestCase
|
|
15
|
+
{
|
|
16
|
+
protected function setUp(): void
|
|
17
|
+
{
|
|
18
|
+
parent::setUp();
|
|
19
|
+
StoreManager::configureStore(new InMemoryStore());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function testIncrementCounterAndGetPrometheusMetrics(): void
|
|
23
|
+
{
|
|
24
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
25
|
+
$metrics = MetricsManager::getPrometheusMetrics();
|
|
26
|
+
|
|
27
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
28
|
+
$this->assertStringContainsString('status="passed"', $metrics);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public function testGetPrometheusMetricsViaDirectFingerprint(): void
|
|
32
|
+
{
|
|
33
|
+
$config = [
|
|
34
|
+
'metricsAuthorizationCallback' => function (RequestContext $context) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
$direct = new DirectFingerprint($config);
|
|
40
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
41
|
+
$metrics = $direct->getPrometheusMetrics();
|
|
42
|
+
|
|
43
|
+
$this->assertNotNull($metrics);
|
|
44
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public function testGetMetricSecurityWeights(): void
|
|
48
|
+
{
|
|
49
|
+
$config = [
|
|
50
|
+
'weights' => [
|
|
51
|
+
'historyScore' => 0.3,
|
|
52
|
+
'rotationScore' => 0.5,
|
|
53
|
+
'invalidWeight' => 'not-a-number'
|
|
54
|
+
]
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
$weights = MetricsManager::getMetric('security_weight', $config);
|
|
58
|
+
$this->assertArrayHasKey('historyScore', $weights);
|
|
59
|
+
$this->assertEquals(0.3, $weights['historyScore']);
|
|
60
|
+
$this->assertArrayHasKey('rotationScore', $weights);
|
|
61
|
+
$this->assertEquals(0.5, $weights['rotationScore']);
|
|
62
|
+
$this->assertArrayNotHasKey('invalidWeight', $weights);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public function testGetMetricSecurityThresholds(): void
|
|
66
|
+
{
|
|
67
|
+
$config = [
|
|
68
|
+
'thresholds' => [
|
|
69
|
+
'low' => 20,
|
|
70
|
+
'medium' => 45,
|
|
71
|
+
'high' => 75,
|
|
72
|
+
'block' => 95
|
|
73
|
+
]
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
$thresholds = MetricsManager::getMetric('security_threshold', $config);
|
|
77
|
+
$this->assertEquals(20.0, $thresholds['low']);
|
|
78
|
+
$this->assertEquals(45.0, $thresholds['medium']);
|
|
79
|
+
$this->assertEquals(75.0, $thresholds['high']);
|
|
80
|
+
$this->assertEquals(95.0, $thresholds['block']);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public function testGetMetricDynamicCountersAndObservations(): void
|
|
84
|
+
{
|
|
85
|
+
MetricsManager::clearMetrics();
|
|
86
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
87
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
88
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'blocked']);
|
|
89
|
+
|
|
90
|
+
$counters = MetricsManager::getMetric('requests_total');
|
|
91
|
+
$this->assertEquals(2, $counters['status="passed"']);
|
|
92
|
+
$this->assertEquals(1, $counters['status="blocked"']);
|
|
93
|
+
|
|
94
|
+
MetricsManager::observeValue('suspicion_score', 85.5, ['action' => 'blocked']);
|
|
95
|
+
$observations = MetricsManager::getMetric('suspicion_score');
|
|
96
|
+
$this->assertEquals(85.5, $observations['action="blocked"']);
|
|
97
|
+
}
|
|
46
98
|
}
|
|
@@ -418,16 +418,16 @@ class RequestUtilsTest extends TestCase
|
|
|
418
418
|
|
|
419
419
|
RequestUtils::getBotnetClusterScore($this->createRequestContext(['clientIp' => '192.168.1.2']), $stableFpHash);
|
|
420
420
|
$score = RequestUtils::getBotnetClusterScore($this->createRequestContext(['clientIp' => '192.168.1.3']), $stableFpHash);
|
|
421
|
-
$this->assertEquals(
|
|
421
|
+
$this->assertEquals(30.2, $score['botnetClusterScore']);
|
|
422
422
|
|
|
423
423
|
RequestUtils::getBotnetClusterScore($this->createRequestContext(['clientIp' => '192.168.1.4']), $stableFpHash);
|
|
424
424
|
$score = RequestUtils::getBotnetClusterScore($this->createRequestContext(['clientIp' => '192.168.1.5']), $stableFpHash);
|
|
425
|
-
$this->assertEquals(
|
|
425
|
+
$this->assertEquals(45.2, $score['botnetClusterScore']);
|
|
426
426
|
|
|
427
427
|
for ($i = 6; $i <= 10; $i++) {
|
|
428
428
|
$score = RequestUtils::getBotnetClusterScore($this->createRequestContext(['clientIp' => "192.168.1.{$i}"]), $stableFpHash);
|
|
429
429
|
}
|
|
430
|
-
$this->assertEquals(
|
|
430
|
+
$this->assertEquals(57.4, $score['botnetClusterScore']);
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
public function testRealWorldConsoleBotnetClustering(): void
|
|
@@ -455,17 +455,17 @@ class RequestUtilsTest extends TestCase
|
|
|
455
455
|
$score = RequestUtils::getBotnetClusterScore($context, $stableFpHash);
|
|
456
456
|
|
|
457
457
|
if ($i === 1) {
|
|
458
|
-
$this->assertEquals(0.0, $score['botnetClusterScore']);
|
|
459
|
-
}
|
|
460
|
-
$this->assertEquals(
|
|
461
|
-
}
|
|
462
|
-
$this->assertEquals(
|
|
463
|
-
}
|
|
464
|
-
$this->assertEquals(
|
|
465
|
-
}
|
|
466
|
-
$this->assertEquals(
|
|
467
|
-
}
|
|
468
|
-
$this->assertEquals(
|
|
458
|
+
$this->assertEquals(0.0, $score['botnetClusterScore'], "Expected score for 1 IP");
|
|
459
|
+
} else if ($i === 2) {
|
|
460
|
+
$this->assertEquals(17.7, $score['botnetClusterScore'], "Expected score for 2 IPs");
|
|
461
|
+
} else if ($i === 3) {
|
|
462
|
+
$this->assertEquals(30.2, $score['botnetClusterScore'], "Expected score for 3 IPs");
|
|
463
|
+
} else if ($i === 4) {
|
|
464
|
+
$this->assertEquals(39.0, $score['botnetClusterScore'], "Expected score for 4 IPs");
|
|
465
|
+
} else if ($i === 5) {
|
|
466
|
+
$this->assertEquals(45.2, $score['botnetClusterScore'], "Expected score for 5 IPs");
|
|
467
|
+
} else if ($i === 10) {
|
|
468
|
+
$this->assertEquals(57.4, $score['botnetClusterScore'], "Expected score for 10 IPs");
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
class Env
|
|
8
|
+
{
|
|
9
|
+
/** @var array<string, mixed> */
|
|
10
|
+
private static array $localCache = [];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param string $key
|
|
14
|
+
* @param mixed $default
|
|
15
|
+
* @return mixed
|
|
16
|
+
*/
|
|
17
|
+
public static function get(string $key, $default = null)
|
|
18
|
+
{
|
|
19
|
+
if (array_key_exists($key, self::$localCache)) {
|
|
20
|
+
return self::$localCache[$key];
|
|
21
|
+
}
|
|
22
|
+
if (isset($_ENV[$key]) && $_ENV[$key] !== '') {
|
|
23
|
+
return $_ENV[$key];
|
|
24
|
+
}
|
|
25
|
+
if (isset($_SERVER[$key]) && $_SERVER[$key] !== '') {
|
|
26
|
+
return $_SERVER[$key];
|
|
27
|
+
}
|
|
28
|
+
$val = getenv($key);
|
|
29
|
+
if ($val !== false && $val !== '') {
|
|
30
|
+
return $val;
|
|
31
|
+
}
|
|
32
|
+
return $default;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static function set(string $key, $value): void
|
|
36
|
+
{
|
|
37
|
+
self::$localCache[$key] = $value;
|
|
38
|
+
$_ENV[$key] = $value;
|
|
39
|
+
$_SERVER[$key] = $value;
|
|
40
|
+
@putenv("{$key}={$value}");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static function clear(string $key): void
|
|
44
|
+
{
|
|
45
|
+
unset(self::$localCache[$key]);
|
|
46
|
+
unset($_ENV[$key]);
|
|
47
|
+
unset($_SERVER[$key]);
|
|
48
|
+
@putenv($key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1,167 +1,210 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
-
|
|
7
|
-
class MetricsManager
|
|
8
|
-
{
|
|
9
|
-
/** @var array Stockage temporaire des compteurs Prometheus */
|
|
10
|
-
private static array $counters = [];
|
|
11
|
-
|
|
12
|
-
/** @var array Stockage temporaire des observations Prometheus */
|
|
13
|
-
private static array $observations = [];
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Incrémente un compteur Prometheus.
|
|
17
|
-
*
|
|
18
|
-
* @param string $name Nom du compteur.
|
|
19
|
-
* @param array $labels Libellés/Labels associés.
|
|
20
|
-
*/
|
|
21
|
-
public static function incrementCounter(string $name, array $labels = []): void
|
|
22
|
-
{
|
|
23
|
-
if (strpos($name, 'fingerprint_') !== 0) {
|
|
24
|
-
$name = 'fingerprint_' . $name;
|
|
25
|
-
}
|
|
26
|
-
ksort($labels);
|
|
27
|
-
$labelPairs = [];
|
|
28
|
-
foreach ($labels as $k => $v) {
|
|
29
|
-
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
30
|
-
}
|
|
31
|
-
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
32
|
-
$key = $name . $labelsStr;
|
|
33
|
-
|
|
34
|
-
if (!isset(self::$counters[$key])) {
|
|
35
|
-
self::$counters[$key] = [
|
|
36
|
-
'name' => $name,
|
|
37
|
-
'labelsStr' => $labelsStr,
|
|
38
|
-
'value' => 0
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
self::$counters[$key]['value']++;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Enregistre une observation de valeur (ex: temps d'exécution, score).
|
|
46
|
-
*
|
|
47
|
-
* @param string $name Nom de la métrique.
|
|
48
|
-
* @param float $value Valeur observée.
|
|
49
|
-
* @param array $labels Libellés/Labels associés.
|
|
50
|
-
*/
|
|
51
|
-
public static function observeValue(string $name, float $value, array $labels = []): void
|
|
52
|
-
{
|
|
53
|
-
if (strpos($name, 'fingerprint_') !== 0) {
|
|
54
|
-
$name = 'fingerprint_' . $name;
|
|
55
|
-
}
|
|
56
|
-
ksort($labels);
|
|
57
|
-
$labelPairs = [];
|
|
58
|
-
foreach ($labels as $k => $v) {
|
|
59
|
-
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
60
|
-
}
|
|
61
|
-
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
62
|
-
$key = $name . $labelsStr;
|
|
63
|
-
|
|
64
|
-
self::$observations[$key] = [
|
|
65
|
-
'name' => $name,
|
|
66
|
-
'labelsStr' => $labelsStr,
|
|
67
|
-
'value' => $value
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
$
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
$metrics .= "\
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
class MetricsManager
|
|
8
|
+
{
|
|
9
|
+
/** @var array Stockage temporaire des compteurs Prometheus */
|
|
10
|
+
private static array $counters = [];
|
|
11
|
+
|
|
12
|
+
/** @var array Stockage temporaire des observations Prometheus */
|
|
13
|
+
private static array $observations = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Incrémente un compteur Prometheus.
|
|
17
|
+
*
|
|
18
|
+
* @param string $name Nom du compteur.
|
|
19
|
+
* @param array $labels Libellés/Labels associés.
|
|
20
|
+
*/
|
|
21
|
+
public static function incrementCounter(string $name, array $labels = []): void
|
|
22
|
+
{
|
|
23
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
24
|
+
$name = 'fingerprint_' . $name;
|
|
25
|
+
}
|
|
26
|
+
ksort($labels);
|
|
27
|
+
$labelPairs = [];
|
|
28
|
+
foreach ($labels as $k => $v) {
|
|
29
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
30
|
+
}
|
|
31
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
32
|
+
$key = $name . $labelsStr;
|
|
33
|
+
|
|
34
|
+
if (!isset(self::$counters[$key])) {
|
|
35
|
+
self::$counters[$key] = [
|
|
36
|
+
'name' => $name,
|
|
37
|
+
'labelsStr' => $labelsStr,
|
|
38
|
+
'value' => 0
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
self::$counters[$key]['value']++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Enregistre une observation de valeur (ex: temps d'exécution, score).
|
|
46
|
+
*
|
|
47
|
+
* @param string $name Nom de la métrique.
|
|
48
|
+
* @param float $value Valeur observée.
|
|
49
|
+
* @param array $labels Libellés/Labels associés.
|
|
50
|
+
*/
|
|
51
|
+
public static function observeValue(string $name, float $value, array $labels = []): void
|
|
52
|
+
{
|
|
53
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
54
|
+
$name = 'fingerprint_' . $name;
|
|
55
|
+
}
|
|
56
|
+
ksort($labels);
|
|
57
|
+
$labelPairs = [];
|
|
58
|
+
foreach ($labels as $k => $v) {
|
|
59
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
60
|
+
}
|
|
61
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
62
|
+
$key = $name . $labelsStr;
|
|
63
|
+
|
|
64
|
+
self::$observations[$key] = [
|
|
65
|
+
'name' => $name,
|
|
66
|
+
'labelsStr' => $labelsStr,
|
|
67
|
+
'value' => $value
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Helper method to retrieve a specific metric's values by name.
|
|
73
|
+
*
|
|
74
|
+
* @param string $name Name of the metric.
|
|
75
|
+
* @param array $securityConfig The security configuration.
|
|
76
|
+
* @return array Associative array of labels to values.
|
|
77
|
+
*/
|
|
78
|
+
public static function getMetric(string $name, array $securityConfig = []): array
|
|
79
|
+
{
|
|
80
|
+
$result = [];
|
|
81
|
+
$queryName = str_starts_with($name, 'fingerprint_') ? $name : 'fingerprint_' . $name;
|
|
82
|
+
|
|
83
|
+
if ($queryName === 'fingerprint_security_weight') {
|
|
84
|
+
$weights = $securityConfig['weights'] ?? [];
|
|
85
|
+
foreach ($weights as $indicator => $weight) {
|
|
86
|
+
if (is_numeric($weight)) {
|
|
87
|
+
$result[$indicator] = (float)$weight;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} elseif ($queryName === 'fingerprint_security_threshold') {
|
|
91
|
+
$thresholds = $securityConfig['thresholds'] ?? [];
|
|
92
|
+
foreach ($thresholds as $level => $threshold) {
|
|
93
|
+
if (is_numeric($threshold)) {
|
|
94
|
+
$result[$level] = (float)$threshold;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
foreach (self::$counters as $counter) {
|
|
99
|
+
if ($counter['name'] === $queryName || $counter['name'] === $name) {
|
|
100
|
+
$labelStr = trim($counter['labelsStr'], '{}');
|
|
101
|
+
$result[$labelStr !== '' ? $labelStr : 'value'] = $counter['value'];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
foreach (self::$observations as $obs) {
|
|
105
|
+
if ($obs['name'] === $queryName || $obs['name'] === $name) {
|
|
106
|
+
$labelStr = trim($obs['labelsStr'], '{}');
|
|
107
|
+
$result[$labelStr !== '' ? $labelStr : 'value'] = $obs['value'];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return $result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
|
|
116
|
+
*/
|
|
117
|
+
public static function clearMetrics(): void
|
|
118
|
+
{
|
|
119
|
+
self::$counters = [];
|
|
120
|
+
self::$observations = [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Génère les métriques au format Prometheus text/plain.
|
|
125
|
+
*
|
|
126
|
+
* @param array $securityConfig La configuration de sécurité active.
|
|
127
|
+
* @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
|
|
128
|
+
* @return string
|
|
129
|
+
*/
|
|
130
|
+
public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
|
|
131
|
+
{
|
|
132
|
+
$metrics = "";
|
|
133
|
+
|
|
134
|
+
if (empty(self::$counters)) {
|
|
135
|
+
$metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
|
|
136
|
+
$metrics .= "# TYPE fingerprint_requests_total counter\n";
|
|
137
|
+
$metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
|
|
138
|
+
} else {
|
|
139
|
+
$grouped = [];
|
|
140
|
+
foreach (self::$counters as $c) {
|
|
141
|
+
$grouped[$c['name']][] = $c;
|
|
142
|
+
}
|
|
143
|
+
foreach ($grouped as $name => $instances) {
|
|
144
|
+
$metrics .= "# HELP {$name} Total requests processed.\n";
|
|
145
|
+
$metrics .= "# TYPE {$name} counter\n";
|
|
146
|
+
foreach ($instances as $instance) {
|
|
147
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Export des observations (Gauges)
|
|
153
|
+
if (!empty(self::$observations)) {
|
|
154
|
+
$groupedObs = [];
|
|
155
|
+
foreach (self::$observations as $obs) {
|
|
156
|
+
$groupedObs[$obs['name']][] = $obs;
|
|
157
|
+
}
|
|
158
|
+
foreach ($groupedObs as $name => $instances) {
|
|
159
|
+
$metrics .= "\n# HELP {$name} Value observation.\n";
|
|
160
|
+
$metrics .= "# TYPE {$name} gauge\n";
|
|
161
|
+
foreach ($instances as $instance) {
|
|
162
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 1. Export des poids actifs (Weights)
|
|
168
|
+
if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
|
|
169
|
+
$metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
|
|
170
|
+
$metrics .= "# TYPE fingerprint_security_weight gauge\n";
|
|
171
|
+
foreach ($securityConfig['weights'] as $indicator => $weight) {
|
|
172
|
+
if (is_numeric($weight)) {
|
|
173
|
+
$metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 2. Export des seuils actifs (Thresholds)
|
|
179
|
+
if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
|
|
180
|
+
$metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
|
|
181
|
+
$metrics .= "# TYPE fingerprint_security_threshold gauge\n";
|
|
182
|
+
foreach ($securityConfig['thresholds'] as $level => $threshold) {
|
|
183
|
+
if (is_numeric($threshold)) {
|
|
184
|
+
$metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
|
|
190
|
+
if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
|
|
191
|
+
$savePath = $securityConfig['autotuning']['savePath'];
|
|
192
|
+
if (file_exists($savePath)) {
|
|
193
|
+
$savedData = json_decode(file_get_contents($savePath), true);
|
|
194
|
+
if (is_array($savedData) && isset($savedData['objectives'])) {
|
|
195
|
+
$lastBestSolution = $savedData;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
|
|
201
|
+
if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
|
|
202
|
+
$fpr = $lastBestSolution['objectives'][0] ?? 0.0;
|
|
203
|
+
$fnr = $lastBestSolution['objectives'][1] ?? 0.0;
|
|
204
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
|
|
205
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return $metrics;
|
|
209
|
+
}
|
|
167
210
|
}
|