Backend support for organization IP ranges.

We can now store IP ranges with Organizations. The aim is to have any user
logging in with a remote IP address within such a race will get the
organization roles assigned to the user object stored in the Flask session.

This commit just contains the MongoDB storage and querying, and not yet the
updates to the user.
This commit is contained in:
2018-01-23 18:08:53 +01:00
parent 9bd41ed5d7
commit c44f0489bc
8 changed files with 355 additions and 10 deletions

View File

@@ -7,6 +7,14 @@ from pillar.tests import AbstractPillarTest
from pillar.api.utils import remove_private_keys
class AbstractOrgTest(AbstractPillarTest):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.enter_app_context()
self.om = self.app.org_manager
class OrganizationCruTest(AbstractPillarTest):
"""Test creating and updating organizations."""
@@ -470,15 +478,12 @@ class OrganizationPatchTest(AbstractPillarTest):
self.assertEqual(admin_uid, db_org['admin_uid'])
class OrganizationResourceEveTest(AbstractPillarTest):
class OrganizationResourceEveTest(AbstractOrgTest):
"""Test GET/POST/PUT access to Organization resource"""
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.enter_app_context()
self.om = self.app.org_manager
# Pillar admin
self.create_user(24 * '1', roles={'admin'}, token='uberadmin')
@@ -764,3 +769,179 @@ class UserCreationTest(AbstractPillarTest):
db_org = self._from_db()
self.assertEqual([], db_org['unknown_members'])
self.assertEqual([my_id], db_org['members'])
class IPRangeTest(AbstractOrgTest):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.uid = self.create_user(24 * 'a', token='token')
self.org_roles = {'org-subscriber', 'org-phabricator'}
self.org = self.app.org_manager.create_new_org('Хакеры', self.uid, 25,
org_roles=self.org_roles)
self.org_id = self.org['_id']
def _patch(self, payload: dict, expected_status=204) -> dict:
self.patch(f'/api/organizations/{self.org_id}',
json={
'op': 'edit-from-web',
'name': self.org['name'],
**payload,
},
auth_token='token',
expected_status=expected_status)
db_org = self.om._get_org(self.org_id)
return db_org
def test_ipranges_doc(self):
from pillar.api.organizations import ip_ranges
doc = ip_ranges.doc('2a03:b0c0:0:1010::8fe:6ef1/120')
self.assertEqual(0x2a03b0c0000010100000000008fe6e00.to_bytes(16, 'big'), doc['start'])
self.assertEqual(0x2a03b0c0000010100000000008fe6eff.to_bytes(16, 'big'), doc['end'])
self.assertEqual('2a03:b0c0:0:1010::8fe:6e00/120', doc['human'])
self.assertEqual(120, doc['prefix'])
self.assertEqual({'prefix', 'human', 'start', 'end'}, set(doc.keys()))
def test_ipranges_query(self):
from pillar.api.organizations import ip_ranges
doc = ip_ranges.query('2a03:b0c0:0:1010::8fe:6ef1')
addr = 0x2a03b0c0000010100000000008fe6ef1.to_bytes(16, 'big')
self.assertEqual(addr, doc['$elemMatch']['start']['$lte'])
self.assertEqual(addr, doc['$elemMatch']['end']['$gte'])
def test_patch_set_ip_ranges_happy(self):
from pillar.api.organizations import ip_ranges
# IP ranges should be saved as integers for fast matching.
db_org = self._patch({'ip_ranges': [
'192.168.3.0/24',
'192.168.3.1/32',
'2a03:b0c0:0:1010::8fe:6ef1/120',
]})
self.assertEqual([
ip_ranges.doc('192.168.3.0/24'),
ip_ranges.doc('192.168.3.1/32'),
ip_ranges.doc('2a03:b0c0:0:1010::8fe:6ef1/120'),
], db_org['ip_ranges'])
def test_patch_unset_ip_ranges_happy(self):
"""Setting to empty list should just delete the entire key."""
ipranges = [
'192.168.3.0/24',
'48.44.12.35/32',
'2a01:7c8:aab9:3b::0/64',
]
self._patch({'ip_ranges': ipranges})
db_org = self._patch({'ip_ranges': []})
self.assertNotIn('ip_ranges', db_org)
def test_single_host(self):
from pillar.api.organizations import ip_ranges
# A host is a valid range by itself.
db_org = self._patch({'ip_ranges': ['192.168.3.5', '3abe:0412:0000::f00d']})
self.assertEqual([
ip_ranges.doc('192.168.3.5/32'),
ip_ranges.doc('3abe:0412:0000::f00d/128'),
], db_org['ip_ranges'])
def test_patch_set_ip_ranges_invalid(self):
db_org = self._patch({'ip_ranges': ['www.example.com']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
db_org = self._patch({'ip_ranges': ['127,0,0,0/16']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
def test_patch_set_ip_ranges_invalid_ipv4(self):
# zero range should not be allowed
db_org = self._patch({'ip_ranges': ['0.0.0.0/0']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
# range too large should not be allowed
db_org = self._patch({'ip_ranges': ['192.168.3.5/64']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
# Hollywood-style IP address
db_org = self._patch({'ip_ranges': ['555.168.3.5/24']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
def test_patch_set_ip_ranges_invalid_ipv6(self):
# small range should not be allowed
db_org = self._patch({'ip_ranges': ['::/0']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
db_org = self._patch({'ip_ranges': ['123::/16']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
# range too large should not be allowed
db_org = self._patch({'ip_ranges': ['2a03:b0c0:0:1010::8fe:6ef1/192']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
# Hollywood-style IP address
db_org = self._patch({'ip_ranges': ['555:555:555:555:b0c0:0:1010:fe:6ef1/64']},
expected_status=422)
self.assertNotIn('ip_ranges', db_org)
# Non-hex IP address
db_org = self._patch({'ip_ranges': ['boo:foo::1010::8fe:6ef1/64']}, expected_status=422)
self.assertNotIn('ip_ranges', db_org)
class IPRangeQueryTest(AbstractOrgTest):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.uid = self.create_user(24 * 'a', token='token')
def _patch(self, org_id: bson.ObjectId, payload: dict, expected_status=204) -> dict:
self.patch(f'/api/organizations/{org_id}',
json={
'op': 'edit-from-web',
**payload,
},
auth_token='token',
expected_status=expected_status)
db_org = self.om._get_org(org_id)
return db_org
def test_happy(self):
# Set up a few organisations. A and B have overlapping IPv4 ranges, B and C on IPv6.
org_roles_a = {'org-roleA1', 'org-roleA2'}
org_a = self.app.org_manager.create_new_org('Хакеры', self.uid, 25, org_roles=org_roles_a)
self._patch(org_a['_id'], {
'name': 'Хакеры',
'ip_ranges': [
'192.168.0.0/16',
'2a03:b0c0:0:1010::8fe:6ef1/120',
]})
org_roles_b = {'org-roleB'}
org_b = self.app.org_manager.create_new_org('ヤクザ', self.uid, 25, org_roles=org_roles_b)
self._patch(org_b['_id'], {
'name': 'ヤクザ',
'ip_ranges': [
'192.168.9.0/24',
'2a03:b0c0:beef:1010::/64',
]})
org_roles_c = {'org-roleC'}
org_c = self.app.org_manager.create_new_org('ਘਰ ਵਿਚ ਨ', self.uid, 25, org_roles=org_roles_c)
self._patch(org_c['_id'], {
'name': 'ਘਰ ਵਿਚ ਨ',
'ip_ranges': [
'172.168.9.0/24',
'2a03:b0c0:beef::/48',
]})
self.assertEqual(org_roles_a, self.om.roles_for_ip_address('192.168.3.255'))
self.assertEqual(org_roles_a.union(org_roles_b),
self.om.roles_for_ip_address('192.168.9.16'))
self.assertEqual(org_roles_a, self.om.roles_for_ip_address('2a03:b0c0:0:1010::8fe:6e47'))
self.assertEqual(org_roles_b.union(org_roles_c),
self.om.roles_for_ip_address('2a03:b0c0:beef:1010::8fe:6e47'))
self.assertEqual(org_roles_c, self.om.roles_for_ip_address('2a03:b0c0:beef:d00d::8fe:6e47'))
self.assertEqual(set(), self.om.roles_for_ip_address('1111:ffff::1'))
self.assertEqual(set(), self.om.roles_for_ip_address('::1'))