Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

Fix issue with IPv6 and blocking #214

Merged
merged 1 commit into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/backend/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
6 changes: 6 additions & 0 deletions backend/device_registry/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
]
40 changes: 23 additions & 17 deletions backend/device_registry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,29 +273,32 @@ 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 = []
ports_data = []
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

Expand All @@ -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 = []
Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions backend/device_registry/templates/device_info_security.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ <h4 class="tab-title">Security</h4>
<thead class="thead-light">
<tr>
<th scope="col" width="5%">Blocked</th>
<th scope="col">IP</th>
<th scope="col">Address</th>
<th scope="col">Port</th>
<th scope="col">Proto</th>
Expand All @@ -147,6 +148,7 @@ <h4 class="tab-title">Security</h4>
{% for checkbox in ports_form.open_ports %}
<tr>
<td>{{ checkbox.tag }}</td>
<td>v{{ checkbox.choice_label|split_index:"/,3" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,0" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,1" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,2" }}</td>
Expand All @@ -173,11 +175,11 @@ <h4 class="tab-title">Security</h4>
<thead class="thead-light">
<tr>
<th scope="col" width="5%">Blocked</th>
<th scope="col">IP</th>
<th scope="col">Rem addr</th>
<th scope="col">Rem port</th>
<th scope="col">Loc addr</th>
<th scope="col">Loc port</th>
<th scope="col">IP</th>
<th scope="col">Type</th>
<th scope="col">Status</th>
<th scope="col">PID</th>
Expand All @@ -186,11 +188,11 @@ <h4 class="tab-title">Security</h4>
{% for checkbox in connections_form.open_connections %}
<tr>
<td>{{ checkbox.tag }}</td>
<td>v{{ checkbox.choice_label|split_index:"/,4" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,0" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,1" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,2" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,3" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,4" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,5" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,6" }}</td>
<td>{{ checkbox.choice_label|split_index:"/,7" }}</td>
Expand Down
27 changes: 14 additions & 13 deletions backend/device_registry/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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/',
Expand All @@ -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(
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand Down