Skip to content

Commit d8d38db

Browse files
committed
update cache key system
1 parent ea04070 commit d8d38db

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

scripts/setup-local-crowdsec.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
#!/bin/sh
22

33
# Delete existing LAPI database.
4-
[ -e ../var/docker-data/crowdsec.db ] && rm ../var/docker-data/crowdsec.db
4+
[ -e ./var/docker-data/crowdsec.db ] && rm ./var/docker-data/crowdsec.db
55

66
# Start containers.
77
docker-compose up --force-recreate -d crowdsec
88
docker-compose up --remove-orphans -d redis memcached
99

1010
# Create a bouncer with cscli and copy expose generated key.
11-
docker-compose exec crowdsec /usr/local/bin/cscli bouncers add bouncer-php-library -o raw > ../.bouncer-key
11+
docker-compose exec crowdsec /usr/local/bin/cscli bouncers add bouncer-php-library -o raw > ./.bouncer-key
1212

1313
# Create a watcher with cscli.
14-
docker-compose exec crowdsec cscli machines add PhpUnitTestMachine --password PhpUnitTestMachinePassword > /dev/null 2>&1
14+
docker-compose exec crowdsec cscli machines add PhpUnitTestMachine --password PhpUnitTestMachinePassword > /dev/null 2>&1
15+
16+
# Ensure composer deps are presents
17+
docker-compose run app composer install

src/ApiCache.php

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace CrowdSecBouncer;
66

77
use DateTime;
8+
use IPLib\Address\AddressInterface;
89
use IPLib\Factory;
910
use IPLib\Range\Subnet;
1011
use Psr\Log\LoggerInterface;
@@ -257,19 +258,19 @@ private function saveRemediations(array $decisions): bool
257258

258259
if ('Ip' === $decision['scope']) {
259260
$address = Factory::addressFromString($decision['value']);
260-
$this->addRemediationToCacheItem($address->toString(), $type, $exp, $id);
261+
$this->addRemediationToCacheItem(Bouncer::formatIpAsCacheKey($address), $type, $exp, $id);
261262
} elseif ('Range' === $decision['scope']) {
262263
$range = Subnet::fromString($decision['value']);
263264

264265
$comparableEndAddress = $range->getEndAddress()->getComparableString();
265266
$address = $range->getStartAddress();
266-
$this->addRemediationToCacheItem($address->toString(), $type, $exp, $id);
267+
$this->addRemediationToCacheItem(Bouncer::formatIpAsCacheKey($address), $type, $exp, $id);
267268
$ipCount = 1;
268269
do {
269270
$address = $address->getNextAddress();
270-
$this->addRemediationToCacheItem($address->toString(), $type, $exp, $id);
271+
$this->addRemediationToCacheItem(Bouncer::formatIpAsCacheKey($address), $type, $exp, $id);
271272
++$ipCount;
272-
if ($ipCount >= Constants::MAX_ALLOWED_IP_RANGE_WIDTH) {
273+
if ($ipCount >= 1000) {
273274
throw new BouncerException("Unable to store the decision ${$decision['id']}, the IP range: ${$decision['value']} is too large and can cause storage problem. Decision ignored.");
274275
}
275276
} while (0 !== strcmp($address->getComparableString(), $comparableEndAddress));
@@ -285,7 +286,7 @@ private function removeRemediations(array $decisions): int
285286
foreach ($decisions as $decision) {
286287
if ('Ip' === $decision['scope']) {
287288
$address = Factory::addressFromString($decision['value']);
288-
if (!$this->removeDecisionFromRemediationItem($address->toString(), $decision['id'])) {
289+
if (!$this->removeDecisionFromRemediationItem(Bouncer::formatIpAsCacheKey($address), $decision['id'])) {
289290
$this->logger->debug('', ['type' => 'DECISION_TO_REMOVE_NOT_FOUND_IN_CACHE', 'decision' => $decision['id']]);
290291
} else {
291292
$this->logger->debug('', [
@@ -299,18 +300,18 @@ private function removeRemediations(array $decisions): int
299300

300301
$comparableEndAddress = $range->getEndAddress()->getComparableString();
301302
$address = $range->getStartAddress();
302-
if (!$this->removeDecisionFromRemediationItem($address->toString(), $decision['id'])) {
303+
if (!$this->removeDecisionFromRemediationItem(Bouncer::formatIpAsCacheKey($address), $decision['id'])) {
303304
$this->logger->debug('', ['type' => 'DECISION_TO_REMOVE_NOT_FOUND_IN_CACHE', 'decision' => $decision['id']]);
304305
}
305306
$ipCount = 1;
306307
$success = true;
307308
do {
308309
$address = $address->getNextAddress();
309-
if (!$this->removeDecisionFromRemediationItem($address->toString(), $decision['id'])) {
310+
if (!$this->removeDecisionFromRemediationItem(Bouncer::formatIpAsCacheKey($address), $decision['id'])) {
310311
$success = false;
311312
}
312313
++$ipCount;
313-
if ($ipCount >= Constants::MAX_ALLOWED_IP_RANGE_WIDTH) {
314+
if ($ipCount >= 1000) {
314315
throw new BouncerException("Unable to store the decision ${$decision['id']}, the IP range: ${$decision['value']} is too large and can cause storage problem. Decision ignored.");
315316
}
316317
} while (0 !== strcmp($address->getComparableString(), $comparableEndAddress));
@@ -458,16 +459,17 @@ public function pullUpdates(): array
458459
* In stream mode, as we considere cache is the single source of truth, the IP is considered clean.
459460
* Finally the result is stored in caches for further calls.
460461
*/
461-
private function miss(string $ip): string
462+
private function miss(string $ipToQuery, string $cacheKey): string
462463
{
463464
$decisions = [];
464465

465466
if ($this->liveMode) {
466-
$this->logger->debug('', ['type' => 'DIRECT_API_CALL', 'ip' => $ip]);
467-
$decisions = $this->apiClient->getFilteredDecisions(['ip' => $ip]);
467+
$this->logger->debug('', ['type' => 'DIRECT_API_CALL', 'ip' => $ipToQuery]);
468+
$decisions = $this->apiClient->getFilteredDecisions(['ip' => $ipToQuery]);
468469
}
469470

470-
return $this->saveRemediationsForIp($decisions, $ip);
471+
472+
return $this->saveRemediationsForIp($decisions, $cacheKey);
471473
}
472474

473475
/**
@@ -491,27 +493,28 @@ private function hit(string $ip): string
491493
*
492494
* @return string the computed remediation string, or null if no decision was found
493495
*/
494-
public function get(string $ip): string
496+
public function get(AddressInterface $address): string
495497
{
496-
$this->logger->debug('', ['type' => 'START_IP_CHECK', 'ip' => $ip]);
498+
$cacheKey = Bouncer::formatIpAsCacheKey($address);
499+
$this->logger->debug('', ['type' => 'START_IP_CHECK', 'ip' => $cacheKey]);
497500
if (!$this->liveMode && !$this->warmedUp) {
498501
throw new BouncerException('CrowdSec Bouncer configured in "stream" mode. Please warm the cache up before trying to access it.');
499502
}
500503

501-
if ($this->adapter->hasItem(base64_encode($ip))) {
502-
$remediation = $this->hit($ip);
504+
if ($this->adapter->hasItem(base64_encode($cacheKey))) {
505+
$remediation = $this->hit($cacheKey);
503506
$cache = 'hit';
504507
} else {
505-
$remediation = $this->miss($ip);
508+
$remediation = $this->miss($address->toString(), $cacheKey);
506509
$cache = 'miss';
507510
}
508511

509512
if (Constants::REMEDIATION_BYPASS === $remediation) {
510-
$this->logger->info('', ['type' => 'CLEAN_IP', 'ip' => $ip, 'cache' => $cache]);
513+
$this->logger->info('', ['type' => 'CLEAN_IP', 'ip' => $cacheKey, 'cache' => $cache]);
511514
} else {
512515
$this->logger->warning('', [
513516
'type' => 'BAD_IP',
514-
'ip' => $ip,
517+
'ip' => $cacheKey,
515518
'remediation' => $remediation,
516519
'cache' => $cache,
517520
]);

src/Bouncer.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
use Gregwar\Captcha\CaptchaBuilder;
99
use Gregwar\Captcha\PhraseBuilder;
10+
use IPLib\Address\AddressInterface;
11+
use IPLib\Address\Type;
1012
use IPLib\Factory;
1113
use Monolog\Handler\NullHandler;
1214
use Monolog\Logger;
@@ -91,6 +93,18 @@ private function capRemediationLevel(string $remediation): string
9193
return $remediation;
9294
}
9395

96+
/**
97+
* Format the input IP address as a cache key.
98+
*/
99+
public static function formatIpAsCacheKey(AddressInterface $ip): string
100+
{
101+
if (Type::T_IPv6 === $ip->getAddressType) {
102+
return implode(':', \array_slice($ip->toString(true), 0, 4));
103+
}
104+
105+
return $ip->toString();
106+
}
107+
94108
/**
95109
* Get the remediation for the specified IP. This method use the cache layer.
96110
* In live mode, when no remediation was found in cache,
@@ -100,11 +114,11 @@ private function capRemediationLevel(string $remediation): string
100114
*/
101115
public function getRemediationForIp(string $ip): string
102116
{
103-
$ip = Factory::addressFromString($ip, false);
104-
if (null === $ip) {
117+
$address = Factory::addressFromString($ip, false);
118+
if (null === $address) {
105119
throw new BouncerException("IP $ip format is invalid.");
106120
}
107-
$remediation = $this->apiCache->get($ip->toString());
121+
$remediation = $this->apiCache->get($address);
108122

109123
return $this->capRemediationLevel($remediation);
110124
}

src/Constants.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,4 @@ class Constants
4545

4646
/** @var array<string> The list of each known remediation, sorted by priority */
4747
public const ORDERED_REMEDIATIONS = [self::REMEDIATION_BAN, self::REMEDIATION_CAPTCHA, self::REMEDIATION_BYPASS];
48-
49-
/** @var int The maximum allowed range width */
50-
public const MAX_ALLOWED_IP_RANGE_WIDTH = 1000000;
5148
}

0 commit comments

Comments
 (0)