[SecurityBundle] Validate the IPs configured in access_control
This commit is contained in:
parent
91c5b14d8b
commit
857ac9519e
@ -143,6 +143,15 @@ class MainConfiguration implements ConfigurationInterface
|
|||||||
->integerNode('port')->defaultNull()->end()
|
->integerNode('port')->defaultNull()->end()
|
||||||
->arrayNode('ips')
|
->arrayNode('ips')
|
||||||
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
|
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
|
||||||
|
->beforeNormalization()->always()->then(function ($v) {
|
||||||
|
foreach ($v as $ip) {
|
||||||
|
if (false === $this->isValidIp($ip)) {
|
||||||
|
throw new \LogicException(sprintf('The given "%s" value in the "access_control" config option is not a valid IP address.', $ip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $v;
|
||||||
|
})->end()
|
||||||
->prototype('scalar')->end()
|
->prototype('scalar')->end()
|
||||||
->end()
|
->end()
|
||||||
->arrayNode('methods')
|
->arrayNode('methods')
|
||||||
@ -419,4 +428,30 @@ class MainConfiguration implements ConfigurationInterface
|
|||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isValidIp(string $cidr): bool
|
||||||
|
{
|
||||||
|
$cidrParts = explode('/', $cidr);
|
||||||
|
|
||||||
|
if (1 === \count($cidrParts)) {
|
||||||
|
return false !== filter_var($cidrParts[0], FILTER_VALIDATE_IP);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $cidrParts[0];
|
||||||
|
$netmask = $cidrParts[1];
|
||||||
|
|
||||||
|
if (!ctype_digit($netmask)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
return $netmask <= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
return $netmask <= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,15 @@ secured-by-one-ip:
|
|||||||
secured-by-two-ips:
|
secured-by-two-ips:
|
||||||
path: /secured-by-two-ips
|
path: /secured-by-two-ips
|
||||||
|
|
||||||
|
secured-by-one-real-ip:
|
||||||
|
path: /secured-by-one-real-ip
|
||||||
|
|
||||||
|
secured-by-one-real-ip-with-mask:
|
||||||
|
path: /secured-by-one-real-ip-with-mask
|
||||||
|
|
||||||
|
secured-by-one-real-ipv6:
|
||||||
|
path: /secured-by-one-real-ipv6
|
||||||
|
|
||||||
form_logout:
|
form_logout:
|
||||||
path: /logout_path
|
path: /logout_path
|
||||||
|
|
||||||
|
@ -71,10 +71,16 @@ class SecurityRoutingIntegrationTest extends WebTestCase
|
|||||||
{
|
{
|
||||||
$allowedClientA = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '1.1.1.1']);
|
$allowedClientA = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '1.1.1.1']);
|
||||||
$allowedClientB = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '2.2.2.2']);
|
$allowedClientB = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '2.2.2.2']);
|
||||||
|
$allowedClientC = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '203.0.113.0']);
|
||||||
$barredClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '192.168.1.1']);
|
$barredClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '192.168.1.1']);
|
||||||
|
|
||||||
$this->assertAllowed($allowedClientA, '/secured-by-two-ips');
|
$this->assertAllowed($allowedClientA, '/secured-by-two-ips');
|
||||||
$this->assertAllowed($allowedClientB, '/secured-by-two-ips');
|
$this->assertAllowed($allowedClientB, '/secured-by-two-ips');
|
||||||
|
|
||||||
|
$this->assertRestricted($allowedClientA, '/secured-by-one-real-ip');
|
||||||
|
$this->assertRestricted($allowedClientA, '/secured-by-one-real-ipv6');
|
||||||
|
$this->assertAllowed($allowedClientC, '/secured-by-one-real-ip-with-mask');
|
||||||
|
|
||||||
$this->assertRestricted($barredClient, '/secured-by-two-ips');
|
$this->assertRestricted($barredClient, '/secured-by-two-ips');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +106,15 @@ class SecurityRoutingIntegrationTest extends WebTestCase
|
|||||||
$this->assertAllowed($allowedClient, '/protected-via-expression');
|
$this->assertAllowed($allowedClient, '/protected-via-expression');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInvalidIpsInAccessControl()
|
||||||
|
{
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
$this->expectExceptionMessage('The given "256.357.458.559" value in the "access_control" config option is not a valid IP address.');
|
||||||
|
|
||||||
|
$client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'invalid_ip_access_control.yml']);
|
||||||
|
$client->request('GET', '/unprotected_resource');
|
||||||
|
}
|
||||||
|
|
||||||
private function assertAllowed($client, $path)
|
private function assertAllowed($client, $path)
|
||||||
{
|
{
|
||||||
$client->request('GET', $path);
|
$client->request('GET', $path);
|
||||||
|
@ -39,6 +39,10 @@ security:
|
|||||||
- { path: ^/secure-but-not-covered-by-access-control$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/secure-but-not-covered-by-access-control$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
# these real IP addresses are reserved for docs/examples (https://tools.ietf.org/search/rfc5737)
|
||||||
|
- { path: ^/secured-by-one-real-ip$, ips: 198.51.100.0, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
- { path: ^/secured-by-one-real-ip-with-mask$, ips: '203.0.113.0/24', roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
- { path: ^/secured-by-one-real-ipv6$, ips: 0:0:0:0:0:ffff:c633:6400, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/highly_protected_resource$, roles: IS_ADMIN }
|
- { path: ^/highly_protected_resource$, roles: IS_ADMIN }
|
||||||
- { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or is_granted('ROLE_USER')" }
|
- { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or is_granted('ROLE_USER')" }
|
||||||
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
|
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
imports:
|
||||||
|
- { resource: ./../config/default.yml }
|
||||||
|
|
||||||
|
security:
|
||||||
|
encoders:
|
||||||
|
Symfony\Component\Security\Core\User\User: plaintext
|
||||||
|
|
||||||
|
providers:
|
||||||
|
in_memory:
|
||||||
|
memory:
|
||||||
|
users:
|
||||||
|
johannes: { password: test, roles: [ROLE_USER] }
|
||||||
|
|
||||||
|
firewalls:
|
||||||
|
default:
|
||||||
|
form_login: ~
|
||||||
|
logout: ~
|
||||||
|
anonymous: ~
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
# the '256.357.458.559' IP is wrong on purpose, to check invalid IP errors
|
||||||
|
- { path: ^/unprotected_resource$, ips: [1.1.1.1, 256.357.458.559], roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
Reference in New Issue
Block a user