Skip to content

Commit f0154a7

Browse files
Merge pull request #52223 from nextcloud/feat/add-configurable-ipv6-subnet
feat(security): add configurable IPv6 subnet for BFP and throttling
2 parents f34466c + 9f666c2 commit f0154a7

File tree

3 files changed

+25
-8
lines changed

3 files changed

+25
-8
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 & 4 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 security.ipv6_normalized_subnet_size, defaults to 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,14 @@ private function getIPv6Subnet(string $ip): string {
3538
$ip = substr($ip, 0, $pos - 1);
3639
}
3740

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

41-
return inet_ntop($binary & $mask) . '/48';
48+
return inet_ntop($binary & $mask) . '/' . $maskSize;
4249
}
4350

4451
/**
@@ -63,7 +70,7 @@ private function getEmbeddedIpv4(string $ipv6): ?string {
6370

6471

6572
/**
66-
* Gets either the /32 (IPv4) or the /48 (IPv6) subnet of an IP address
73+
* Gets either the /32 (IPv4) or the /56 (default for IPv6) subnet of an IP address
6774
*/
6875
public function getSubnet(): string {
6976
if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {

tests/lib/Security/Normalizer/IpAddressTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@ public function subnetDataProvider() {
3737
],
3838
[
3939
'2001:0db8:0000:0000:0000:8a2e:0370:7334',
40-
'2001:db8::/48',
40+
'2001:db8::/56',
4141
],
4242
[
4343
'2001:db8:3333:4444:5555:6666:7777:8888',
44-
'2001:db8:3333::/48',
44+
'2001:db8:3333:4400::/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)