5
5
namespace CrowdSecBouncer ;
6
6
7
7
use DateTime ;
8
+ use Exception ;
8
9
use IPLib \Address \AddressInterface ;
9
10
use IPLib \Address \Type ;
10
11
use IPLib \Factory ;
11
12
use IPLib \Range \Subnet ;
13
+ use Psr \Cache \InvalidArgumentException ;
12
14
use Psr \Log \LoggerInterface ;
13
15
use Symfony \Component \Cache \Adapter \AbstractAdapter ;
14
16
use Symfony \Component \Cache \Adapter \FilesystemAdapter ;
15
17
use Symfony \Component \Cache \Adapter \MemcachedAdapter ;
16
18
use Symfony \Component \Cache \PruneableInterface ;
17
19
18
20
/**
19
- * The cache mecanism to store every decisions from LAPI/CAPI. Symfony Cache component powered.
21
+ * The cache mechanism to store every decision from LAPI/CAPI. Symfony Cache component powered.
20
22
*
21
23
* @author CrowdSec team
22
24
*
@@ -47,11 +49,15 @@ class ApiCache
47
49
48
50
/** @var bool */
49
51
private $ warmedUp = null ;
52
+ /**
53
+ * @var string
54
+ */
55
+ private $ fallbackRemediation ;
50
56
51
57
/**
52
- * @param LoggerInterface $logger The logger to use
53
- * @param ApiClient $apiClient The APIClient instance to use
54
- * @param AbstractAdapter $adapter The AbstractAdapter adapter to use
58
+ * @param LoggerInterface $logger The logger to use
59
+ * @param ApiClient|null $apiClient The APIClient instance to use
60
+ * @param AbstractAdapter|null $adapter The AbstractAdapter adapter to use
55
61
*/
56
62
public function __construct (LoggerInterface $ logger , ApiClient $ apiClient = null , AbstractAdapter $ adapter = null )
57
63
{
@@ -63,14 +69,16 @@ public function __construct(LoggerInterface $logger, ApiClient $apiClient = null
63
69
/**
64
70
* Configure this instance.
65
71
*
66
- * @param bool $liveMode If we use the live mode (else we use the stream mode)
67
- * @param string $apiUrl The URL of the LAPI
68
- * @param int $timeout The timeout well calling LAPI
69
- * @param string $userAgent The user agent to use when calling LAPI
70
- * @param string $apiKey The Bouncer API Key to use to connect LAPI
71
- * @param int $cacheExpirationForCleanIp The duration to cache an IP considered as clean by LAPI
72
- * @param int $cacheExpirationForBadIp The duration to cache an IP considered as bad by LAPI
73
- * @param string $fallbackRemediation The remediation to use when the remediation sent by LAPI is not supported by this library
72
+ * @param bool $liveMode If we use the live mode (else we use the stream mode)
73
+ * @param string $apiUrl The URL of the LAPI
74
+ * @param int $timeout The timeout well calling LAPI
75
+ * @param string $userAgent The user agent to use when calling LAPI
76
+ * @param string $apiKey The Bouncer API Key to use to connect LAPI
77
+ * @param int $cacheExpirationForCleanIp The duration to cache an IP considered as clean by LAPI
78
+ * @param int $cacheExpirationForBadIp The duration to cache an IP considered as bad by LAPI
79
+ * @param string $fallbackRemediation The remediation to use when the remediation sent by LAPI is not supported by
80
+ * this library
81
+ * @throws InvalidArgumentException
74
82
*/
75
83
public function configure (
76
84
bool $ liveMode ,
@@ -103,6 +111,8 @@ public function configure(
103
111
104
112
/**
105
113
* Add remediation to a Symfony Cache Item identified by IP.
114
+ * @throws InvalidArgumentException
115
+ * @throws Exception
106
116
*/
107
117
private function addRemediationToCacheItem (string $ ip , string $ type , int $ expiration , int $ decisionId ): string
108
118
{
@@ -145,6 +155,8 @@ private function addRemediationToCacheItem(string $ip, string $type, int $expira
145
155
146
156
/**
147
157
* Remove a decision from a Symfony Cache Item identified by ip.
158
+ * @throws InvalidArgumentException
159
+ * @throws Exception
148
160
*/
149
161
private function removeDecisionFromRemediationItem (string $ ip , int $ decisionId ): bool
150
162
{
@@ -177,7 +189,7 @@ private function removeDecisionFromRemediationItem(string $ip, int $decisionId):
177
189
$ item ->expiresAt (new DateTime ('@ ' .$ maxLifetime ));
178
190
$ item ->set ($ cacheContent );
179
191
180
- // Save the cache without commiting it to the cache system.
192
+ // Save the cache without committing it to the cache system.
181
193
// Useful to improve performance when updating the cache.
182
194
if (!$ this ->adapter ->saveDeferred ($ item )) {
183
195
throw new BouncerException ("cache# $ ip: Unable to save item " );
@@ -230,7 +242,7 @@ private function formatRemediationFromDecision(?array $decision): array
230
242
if (!$ decision ) {
231
243
$ duration = time () + $ this ->cacheExpirationForCleanIp ;
232
244
if (!$ this ->liveMode ) {
233
- // In stream mode we considere an clean IP forever... until the next resync.
245
+ // In stream mode we consider a clean IP forever... until the next resync.
234
246
$ duration = 315360000 ; // in this case, forever is 10 years as PHP_INT_MAX will cause trouble with the Memcached Adapter (int to float unwanted conversion)
235
247
}
236
248
@@ -251,6 +263,9 @@ private function formatRemediationFromDecision(?array $decision): array
251
263
];
252
264
}
253
265
266
+ /**
267
+ * @throws InvalidArgumentException
268
+ */
254
269
private function defferUpdateCacheConfig (array $ config ): void
255
270
{
256
271
$ cacheConfigItem = $ this ->adapter ->getItem ('cacheConfig ' );
@@ -262,6 +277,7 @@ private function defferUpdateCacheConfig(array $config): void
262
277
263
278
/**
264
279
* Update the cached remediation of the specified IP from these new decisions.
280
+ * @throws InvalidArgumentException
265
281
*/
266
282
private function saveRemediationsForIp (array $ decisions , string $ ip ): string
267
283
{
@@ -292,6 +308,7 @@ private function saveRemediationsForIp(array $decisions, string $ip): string
292
308
293
309
/**
294
310
* Update the cached remediations from these new decisions.
311
+ * @throws InvalidArgumentException
295
312
*/
296
313
private function saveRemediations (array $ decisions ): array
297
314
{
@@ -347,6 +364,9 @@ private function saveRemediations(array $decisions): array
347
364
return ['success ' => $ this ->commit (), 'errors ' => $ errors ];
348
365
}
349
366
367
+ /**
368
+ * @throws InvalidArgumentException
369
+ */
350
370
private function removeRemediations (array $ decisions ): array
351
371
{
352
372
$ errors = [];
@@ -420,6 +440,9 @@ private function removeRemediations(array $decisions): array
420
440
return ['count ' => $ count , 'errors ' => $ errors ];
421
441
}
422
442
443
+ /**
444
+ * @throws InvalidArgumentException
445
+ */
423
446
public function clear (): bool
424
447
{
425
448
$ this ->setCustomErrorHandler ();
@@ -442,6 +465,7 @@ public function clear(): bool
442
465
* Used when the stream mode has just been activated.
443
466
*
444
467
* @return array "count": number of decisions added, "errors": decisions not added
468
+ * @throws InvalidArgumentException
445
469
*/
446
470
public function warmUp (): array
447
471
{
@@ -451,8 +475,7 @@ public function warmUp(): array
451
475
$ this ->clear ();
452
476
}
453
477
$ this ->logger ->debug ('' , ['type ' => 'START_CACHE_WARMUP ' ]);
454
- $ startup = true ;
455
- $ decisionsDiff = $ this ->apiClient ->getStreamedDecisions ($ startup );
478
+ $ decisionsDiff = $ this ->apiClient ->getStreamedDecisions (true );
456
479
$ newDecisions = $ decisionsDiff ['new ' ];
457
480
458
481
$ nbNew = 0 ;
@@ -483,6 +506,7 @@ public function warmUp(): array
483
506
* Used for the stream mode when we have to update the remediations list.
484
507
*
485
508
* @return array number of deleted and new decisions, and errors when processing decisions
509
+ * @throws InvalidArgumentException
486
510
*/
487
511
public function pullUpdates (): array
488
512
{
@@ -522,8 +546,9 @@ public function pullUpdates(): array
522
546
/**
523
547
* This method is called when nothing has been found in cache for the requested IP.
524
548
* In live mode is enabled, calls the API for decisions concerning the specified IP
525
- * In stream mode, as we considere cache is the single source of truth, the IP is considered clean.
526
- * Finally the result is stored in caches for further calls.
549
+ * In stream mode, as we consider cache is the single source of truth, the IP is considered clean.
550
+ * Finally, the result is stored in caches for further calls.
551
+ * @throws InvalidArgumentException
527
552
*/
528
553
private function miss (string $ ipToQuery , string $ cacheKey ): string
529
554
{
@@ -538,9 +563,10 @@ private function miss(string $ipToQuery, string $cacheKey): string
538
563
}
539
564
540
565
/**
541
- * Used in both mode (stream and ruptue ).
566
+ * Used in both mode (stream and rupture ).
542
567
* This method formats the cached item as a remediation.
543
568
* It returns the highest remediation level found.
569
+ * @throws InvalidArgumentException
544
570
*/
545
571
private function hit (string $ ip ): string
546
572
{
@@ -557,6 +583,7 @@ private function hit(string $ip): string
557
583
* Request the cache for the specified IP.
558
584
*
559
585
* @return string the computed remediation string, or null if no decision was found
586
+ * @throws InvalidArgumentException
560
587
*/
561
588
public function get (AddressInterface $ address ): string
562
589
{
@@ -604,8 +631,8 @@ public function prune(): bool
604
631
}
605
632
606
633
/**
607
- * When Memcached connection fail, it throw an unhandled warning.
608
- * To catch this warning as a clean execption we have to temporarily change the error handler.
634
+ * When Memcached connection fail, it throws an unhandled warning.
635
+ * To catch this warning as a clean exception we have to temporarily change the error handler.
609
636
*/
610
637
private function setCustomErrorHandler (): void
611
638
{
@@ -646,7 +673,7 @@ private function commit(): bool
646
673
/**
647
674
* Test the connection to the cache system (Redis or Memcached).
648
675
*
649
- * @throws BouncerException if the connection was not successful
676
+ * @throws BouncerException|InvalidArgumentException if the connection was not successful
650
677
* */
651
678
public function testConnection (): void
652
679
{
0 commit comments