diff --git a/backend/backend/settings/base.py b/backend/backend/settings/base.py index 85edd79f7..d213b4da6 100644 --- a/backend/backend/settings/base.py +++ b/backend/backend/settings/base.py @@ -35,7 +35,7 @@ def check_ip_range(ipr): with open(spam_networks_list_path) as f: read_data = f.read() spam_networks_list = re.findall(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.0/\d{1,2}).*', read_data, re.MULTILINE) -SPAM_NETWORKS = list(filter(check_ip_range, spam_networks_list)) +SPAM_NETWORKS = [[addr, False] for addr in filter(check_ip_range, spam_networks_list)] # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ diff --git a/backend/device_registry/api_views.py b/backend/device_registry/api_views.py index f09f56b61..8cdfb7b20 100644 --- a/backend/device_registry/api_views.py +++ b/backend/device_registry/api_views.py @@ -14,6 +14,7 @@ from rest_framework.decorators import api_view, renderer_classes, permission_classes from rest_framework.renderers import JSONRenderer from rest_framework.response import Response +from netaddr import IPAddress from device_registry import ca_helper from device_registry.serializers import DeviceInfoSerializer @@ -265,6 +266,11 @@ def mtls_ping_view(request, format=None): scan_info = request.data.get('scan_info', []) if isinstance(scan_info, str): scan_info = json.loads(scan_info) + # Add missing IP protocol version info. + for record in scan_info: + if 'ip_version' not in record: + ipaddr = IPAddress(record['host']) + record['ip_version'] = ipaddr.version portscan_object.scan_info = scan_info portscan_object.netstat = request.data.get('netstat', []) block_networks = portscan_object.block_networks.copy() diff --git a/backend/device_registry/migrations/0035_fix_missing_protocol_version.py b/backend/device_registry/migrations/0035_fix_missing_protocol_version.py new file mode 100644 index 000000000..785ac457e --- /dev/null +++ b/backend/device_registry/migrations/0035_fix_missing_protocol_version.py @@ -0,0 +1,46 @@ +# Generated by Django 2.1.7 on 2019-05-27 10:29 + +from django.db import migrations + +from netaddr import IPAddress + + +def fix_missing_protocol_version(apps, schema_editor): + PortScan = apps.get_model('device_registry', 'PortScan') + for instance in PortScan.objects.all(): + # Fix `block_ports`. + block_ports = instance.block_ports + for record in block_ports: + ipaddr = IPAddress(record[0]) + # Check if it's IPv6 address or not. + is_v6 = ipaddr.version == 6 + if len(record) == 3: + record.append(is_v6) + # Fix `scan_info`. + scan_info = instance.scan_info + for record in scan_info: + if 'ip_version' not in record: + ipaddr = IPAddress(record['host']) + record['ip_version'] = ipaddr.version + # Fix `block_networks`. + block_networks = instance.block_networks + block_networks_list = [] + for record in block_networks: + if not isinstance(record, list): + ipaddr = IPAddress(record) + block_networks_list.append([record, ipaddr.version == 6]) + else: + block_networks_list.append(record) + instance.block_networks = block_networks_list + + instance.save(update_fields=['block_ports', 'scan_info', 'block_networks']) + + +class Migration(migrations.Migration): + dependencies = [ + ('device_registry', '0034_device_name'), + ] + + operations = [ + migrations.RunPython(fix_missing_protocol_version), + ] diff --git a/backend/device_registry/models.py b/backend/device_registry/models.py index 46bb9bbb6..53f3a3473 100644 --- a/backend/device_registry/models.py +++ b/backend/device_registry/models.py @@ -273,11 +273,11 @@ def ports_form_data(self): Build 3 lists: 1) list of choices for the ports form (gonna be split in a template by '/' separator): - [[0, '192.168.1.178/22/TCP'], [0, '192.168.1.178/33/UDP']] + [[0, '::ffff:192.168.1.178/22/TCP/6'], [0, '192.168.1.178/33/UDP/4']] 2) list of initial values for the ports form: [0, 1] 3) list of choices for saving to the block list: - [['192.168.1.178', 22, 'tcp'], ['192.168.1.178', 33, 'udp']] + [['::ffff:192.168.1.178', 22, 'tcp', True], ['192.168.1.178', 33, 'udp', False]] """ initial_data = [] choices_data = [] @@ -285,17 +285,20 @@ def ports_form_data(self): port_record_index = 0 # 1st - take ports from the block list. for port_record in self.block_ports: - choices_data.append((port_record_index, '%s/%s/%s' % ( - port_record[0], port_record[2], port_record[1].upper()))) + choices_data.append((port_record_index, '%s/%s/%s/%d' % ( + port_record[0], port_record[2], port_record[1].upper(), 6 if port_record[3] else 4))) ports_data.append(port_record) initial_data.append(port_record_index) port_record_index += 1 # 2nd - take ports from the open ports list (only the ones missing in the block list). for port_record in self.scan_info: - if [port_record['host'], port_record['proto'], port_record['port']] not in self.block_ports: - choices_data.append((port_record_index, '%s/%s/%s' % ( - port_record['host'], port_record['port'], port_record['proto'].upper()))) - ports_data.append([port_record['host'], port_record['proto'], port_record['port']]) + if [port_record['host'], port_record['proto'], port_record['port'], port_record['ip_version'] == 6] \ + not in self.block_ports: + choices_data.append((port_record_index, '%s/%s/%s/%d' % ( + port_record['host'], port_record['port'], port_record['proto'].upper(), + port_record['ip_version']))) + ports_data.append([port_record['host'], port_record['proto'], port_record['port'], + port_record['ip_version'] == 6]) port_record_index += 1 return choices_data, initial_data, ports_data @@ -304,11 +307,11 @@ def connections_form_data(self): Build 3 lists: 1) list of choices for the open connections form (gonna be split in a template by '/' separator):: - [[0, '192.168.1.20/4567/192.168.1.178/80/v4/TCP/open/3425']] + [[0, '192.168.1.20/4567/192.168.1.178/80/4/TCP/open/3425']] 2) list of initial values for the open connections form: [0] 3) list of choices for saving to the block list: - ['192.168.1.20'] + [['192.168.1.20', False], ['::ffff:192.168.1.25', True]] """ initial_data = [] choices_data = [] @@ -318,25 +321,28 @@ def connections_form_data(self): # 1st - take addresses from the block list. for connection_record in self.block_networks: - if connection_record not in unique_addresses: - unique_addresses.add(connection_record) - choices_data.append((connection_record_index, '%s////v4///' % connection_record)) + if tuple(connection_record) not in unique_addresses: + unique_addresses.add(tuple(connection_record)) + choices_data.append((connection_record_index, '%s////%d///' % (connection_record[0], + 6 if connection_record[1] else 4))) connections_data.append(connection_record) initial_data.append(connection_record_index) connection_record_index += 1 # 2nd - take addresses from the open connections list (only the ones missing in the block list). for connection_record in self.netstat: - if connection_record['remote_address'] and connection_record['remote_address'][0] not in unique_addresses: - unique_addresses.add(connection_record['remote_address'][0]) + if connection_record['remote_address'] and (connection_record['remote_address'][0], + connection_record['ip_version'] == 6) not in unique_addresses: + unique_addresses.add((connection_record['remote_address'][0], connection_record['ip_version'] == 6)) choices_data.append(( - connection_record_index, '%s/%s/%s/%s/v%s/%s/%s/%s' % + connection_record_index, '%s/%s/%s/%s/%d/%s/%s/%s' % (connection_record['remote_address'][0], connection_record['remote_address'][1], connection_record['local_address'][0] if connection_record['local_address'] else '', connection_record['local_address'][1] if connection_record['local_address'] else '', connection_record['ip_version'], connection_record['type'].upper(), connection_record['status'], connection_record['pid']))) - connections_data.append(connection_record['remote_address'][0]) + connections_data.append([connection_record['remote_address'][0], + connection_record['ip_version'] == 6]) connection_record_index += 1 return choices_data, initial_data, connections_data diff --git a/backend/device_registry/templates/device_info_security.html b/backend/device_registry/templates/device_info_security.html index ceac954c3..150adb463 100644 --- a/backend/device_registry/templates/device_info_security.html +++ b/backend/device_registry/templates/device_info_security.html @@ -139,6 +139,7 @@

Security

Blocked + IP Address Port Proto @@ -147,6 +148,7 @@

Security

{% for checkbox in ports_form.open_ports %} {{ checkbox.tag }} + v{{ checkbox.choice_label|split_index:"/,3" }} {{ checkbox.choice_label|split_index:"/,0" }} {{ checkbox.choice_label|split_index:"/,1" }} {{ checkbox.choice_label|split_index:"/,2" }} @@ -173,11 +175,11 @@

Security

Blocked + IP Rem addr Rem port Loc addr Loc port - IP Type Status PID @@ -186,11 +188,11 @@

Security

{% for checkbox in connections_form.open_connections %} {{ checkbox.tag }} + v{{ checkbox.choice_label|split_index:"/,4" }} {{ checkbox.choice_label|split_index:"/,0" }} {{ checkbox.choice_label|split_index:"/,1" }} {{ checkbox.choice_label|split_index:"/,2" }} {{ checkbox.choice_label|split_index:"/,3" }} - {{ checkbox.choice_label|split_index:"/,4" }} {{ checkbox.choice_label|split_index:"/,5" }} {{ checkbox.choice_label|split_index:"/,6" }} {{ checkbox.choice_label|split_index:"/,7" }} diff --git a/backend/device_registry/tests.py b/backend/device_registry/tests.py index 92bd51744..9ca4aa16e 100644 --- a/backend/device_registry/tests.py +++ b/backend/device_registry/tests.py @@ -114,8 +114,8 @@ def test_common_name_does_not_match_valid_domain(self): TEST_RULES = {'INPUT': [{'src': '15.15.15.50/32', 'target': 'DROP'}, {'src': '15.15.15.51/32', 'target': 'DROP'}], 'OUTPUT': [], 'FORWARD': []} -OPEN_PORTS_INFO = [{"host": "192.168.1.178", "port": 22, "proto": "tcp", "state": "open"}] -OPEN_PORTS_INFO_TELNET = [{"host": "192.168.1.178", "port": 23, "proto": "tcp", "state": "open"}] +OPEN_PORTS_INFO = [{"host": "192.168.1.178", "port": 22, "proto": "tcp", "state": "open", "ip_version": 4}] +OPEN_PORTS_INFO_TELNET = [{"host": "192.168.1.178", "port": 23, "proto": "tcp", "state": "open", "ip_version": 4}] OPEN_CONNECTIONS_INFO = [ {'ip_version': 4, 'type': 'tcp', 'local_address': ['192.168.1.178', 4567], @@ -167,8 +167,8 @@ def test_pong_data(self): self.assertEqual(response.status_code, 200) self.assertDictEqual(response.data, {'block_ports': [], 'block_networks': settings.SPAM_NETWORKS}) # 2nd request - self.device0.portscan.block_ports = [['192.168.1.178', 'tcp', 22]] - self.device0.portscan.block_networks = ['192.168.1.177'] + self.device0.portscan.block_ports = [['192.168.1.178', 'tcp', 22, False]] + self.device0.portscan.block_networks = [['192.168.1.177', False]] self.device0.portscan.save(update_fields=['block_ports', 'block_networks']) request = self.api.post( '/v0.2/ping/', @@ -178,8 +178,8 @@ def test_pong_data(self): ) response = mtls_ping_view(request) self.assertEqual(response.status_code, 200) - self.assertDictEqual(response.data, {'block_ports': [['192.168.1.178', 'tcp', 22]], - 'block_networks': ['192.168.1.177'] + settings.SPAM_NETWORKS}) + self.assertDictEqual(response.data, {'block_ports': [['192.168.1.178', 'tcp', 22, False]], + 'block_networks': [['192.168.1.177', False]] + settings.SPAM_NETWORKS}) def test_ping_creates_models(self): request = self.api.post( @@ -272,7 +272,8 @@ def test_ping_converts_json(self): "host": "localhost", "port": 22, "proto": "tcp", - "state": "open" + "state": "open", + "ip_version": 4 }] firewall_rules = {'INPUT': [], 'OUTPUT': [], 'FORWARD': []} ping_payload = { @@ -363,12 +364,12 @@ def setUp(self): logins={'pi': {'failed': 1, 'success': 1}} ) portscan0 = [ - {"host": "192.168.1.178", "port": 22, "proto": "tcp", "state": "open"}, - {"host": "192.168.1.178", "port": 25, "proto": "tcp", "state": "open"} + {"host": "192.168.1.178", "port": 22, "proto": "tcp", "state": "open", "ip_version": 4}, + {"host": "192.168.1.178", "port": 25, "proto": "tcp", "state": "open", "ip_version": 4} ] portscan1 = [ - {"host": "192.168.1.178", "port": 80, "proto": "tcp", "state": "open"}, - {"host": "192.168.1.178", "port": 110, "proto": "tcp", "state": "open"} + {"host": "192.168.1.178", "port": 80, "proto": "tcp", "state": "open", "ip_version": 4}, + {"host": "192.168.1.178", "port": 110, "proto": "tcp", "state": "open", "ip_version": 4} ] self.portscan0 = PortScan.objects.create(device=self.device0, scan_info=portscan0) self.portscan1 = PortScan.objects.create(device=self.device1, scan_info=portscan1) @@ -707,14 +708,14 @@ def test_open_ports(self): form_data = {'is_ports_form': 'true', 'open_ports': ['0']} self.client.post(self.url2, form_data) portscan = PortScan.objects.get(pk=self.portscan.pk) - self.assertListEqual(portscan.block_ports, [['192.168.1.178', 'tcp', 22]]) + self.assertListEqual(portscan.block_ports, [['192.168.1.178', 'tcp', 22, False]]) def test_open_connections(self): self.client.login(username='test', password='123') form_data = {'is_connections_form': 'true', 'open_connections': ['0']} self.client.post(self.url2, form_data) portscan = PortScan.objects.get(pk=self.portscan.pk) - self.assertListEqual(portscan.block_networks, ['192.168.1.177']) + self.assertListEqual(portscan.block_networks, [['192.168.1.177', False]]) def test_no_logins(self): self.client.login(username='test', password='123')