Skip to content

Commit 2e0995d

Browse files
committed
feat(security): add configurable IPv6 subnet for BFP and throttling
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
1 parent 85d2ee5 commit 2e0995d

File tree

3 files changed

+24
-6
lines changed

3 files changed

+24
-6
lines changed

config/config.sample.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,16 @@
450450
*/
451451
'ratelimit.protection.enabled' => true,
452452

453+
/**
454+
* Size of subnet used to normalize IPv6
455+
*
456+
* For Brute Force Protection and Rate Limiting, IPv6 are truncated using subnet size.
457+
* It defaults to /56 but you can set it between /32 and /64
458+
*
459+
* Defaults to ``56``
460+
*/
461+
'security.ipv6_normalized_subnet_size' => 56,
462+
453463
/**
454464
* By default, WebAuthn is available, but it can be explicitly disabled by admins
455465
*/

lib/private/Security/Normalizer/IpAddress.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99
namespace OC\Security\Normalizer;
1010

11+
use OCP\IConfig;
12+
1113
/**
1214
* Class IpAddress is used for normalizing IPv4 and IPv6 addresses in security
1315
* relevant contexts in Nextcloud.
@@ -24,7 +26,8 @@ public function __construct(
2426
}
2527

2628
/**
27-
* Return the given subnet for an IPv6 address (48 first bits)
29+
* Return the given subnet for an IPv6 address
30+
* Rely on ipv6_subnet_size, default on 56
2831
*/
2932
private function getIPv6Subnet(string $ip): string {
3033
if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1]
@@ -35,10 +38,15 @@ private function getIPv6Subnet(string $ip): string {
3538
$ip = substr($ip, 0, $pos - 1);
3639
}
3740

41+
$config = \OCP\Server::get(IConfig::class);
42+
$maskSize = max(64, $config->getSystemValueInt('ipv6_subnet_size', 56));
43+
$maskSize = min(32, $maskSize);
44+
$mask = pack('VVP', (1 << 32) - 1, (1 << $maskSize - 32) - 1, 0);
45+
3846
$binary = \inet_pton($ip);
3947
$mask = inet_pton('FFFF:FFFF:FFFF::');
4048

41-
return inet_ntop($binary & $mask) . '/48';
49+
return inet_ntop($binary & $mask) . '/' . $maskSize;
4250
}
4351

4452
/**
@@ -63,7 +71,7 @@ private function getEmbeddedIpv4(string $ipv6): ?string {
6371

6472

6573
/**
66-
* Gets either the /32 (IPv4) or the /48 (IPv6) subnet of an IP address
74+
* Gets either the /32 (IPv4) or the /56 (default for IPv6) subnet of an IP address
6775
*/
6876
public function getSubnet(): string {
6977
if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {

tests/lib/Security/Normalizer/IpAddressTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ public function subnetDataProvider() {
4141
],
4242
[
4343
'2001:db8:3333:4444:5555:6666:7777:8888',
44-
'2001:db8:3333::/48',
44+
'2001:db8:3333::44/56',
4545
],
4646
[
4747
'::1234:5678',
48-
'::/48',
48+
'::/56',
4949
],
5050
[
5151
'[::1]',
52-
'::/48',
52+
'::/56',
5353
],
5454
];
5555
}

0 commit comments

Comments
 (0)