Skip to content

Commit f647073

Browse files
committed
feat(ip): add configurable IPv6 subnet for BFP and throttling
Signed-off-by: Benjamin Gaussorgues <[email protected]>
1 parent 2be6f6f commit f647073

4 files changed

Lines changed: 26 additions & 9 deletions

File tree

3rdparty

Submodule 3rdparty updated 140 files

config/config.sample.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,16 @@
405405
*/
406406
'ratelimit.protection.enabled' => true,
407407

408+
/**
409+
* Size of subnet used to normalize IPv6
410+
*
411+
* For Brute Force Protection and Rate Limiting, IPv6 are truncated using subnet size.
412+
* It defaults to /56 but you can set it between /32 and /64
413+
*
414+
* Defaults to ``56``
415+
*/
416+
'security.ipv6_normalized_subnet_size' => 56,
417+
408418
/**
409419
* By default, WebAuthn is available, but it can be explicitly disabled by admins
410420
*/

lib/private/Security/Normalizer/IpAddress.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
*/
3131
namespace OC\Security\Normalizer;
3232

33+
use OCP\IConfig;
34+
3335
/**
3436
* Class IpAddress is used for normalizing IPv4 and IPv6 addresses in security
3537
* relevant contexts in Nextcloud.
@@ -46,7 +48,8 @@ public function __construct(
4648
}
4749

4850
/**
49-
* Return the given subnet for an IPv6 address (48 first bits)
51+
* Return the given subnet for an IPv6 address
52+
* Rely on security.ipv6_normalized_subnet_size, defaults to 56
5053
*/
5154
private function getIPv6Subnet(string $ip): string {
5255
if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1]
@@ -57,10 +60,14 @@ private function getIPv6Subnet(string $ip): string {
5760
$ip = substr($ip, 0, $pos - 1);
5861
}
5962

63+
$config = \OCP\Server::get(IConfig::class);
64+
$maskSize = min(64, $config->getSystemValueInt('security.ipv6_normalized_subnet_size', 56));
65+
$maskSize = max(32, $maskSize);
66+
$mask = pack('VVP', (1 << 32) - 1, (1 << $maskSize - 32) - 1, 0);
67+
6068
$binary = \inet_pton($ip);
61-
$mask = inet_pton('FFFF:FFFF:FFFF::');
6269

63-
return inet_ntop($binary & $mask).'/48';
70+
return inet_ntop($binary & $mask) . '/' . $maskSize;
6471
}
6572

6673
/**
@@ -85,7 +92,7 @@ private function getEmbeddedIpv4(string $ipv6): ?string {
8592

8693

8794
/**
88-
* Gets either the /32 (IPv4) or the /48 (IPv6) subnet of an IP address
95+
* Gets either the /32 (IPv4) or the /56 (default for IPv6) subnet of an IP address
8996
*/
9097
public function getSubnet(): string {
9198
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
@@ -52,19 +52,19 @@ public function subnetDataProvider() {
5252
],
5353
[
5454
'2001:0db8:0000:0000:0000:8a2e:0370:7334',
55-
'2001:db8::/48',
55+
'2001:db8::/56',
5656
],
5757
[
5858
'2001:db8:3333:4444:5555:6666:7777:8888',
59-
'2001:db8:3333::/48',
59+
'2001:db8:3333:4400::/56',
6060
],
6161
[
6262
'::1234:5678',
63-
'::/48',
63+
'::/56',
6464
],
6565
[
6666
'[::1]',
67-
'::/48',
67+
'::/56',
6868
],
6969
];
7070
}

0 commit comments

Comments
 (0)