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

Store deb packages list for a device. #395

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/backend/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'PORT': os.getenv('DB_PORT', '5432'),
'OPTIONS': {
'connect_timeout': 3,
},
}
}
}
COMMON_NAME_PREFIX = 'd.wott-dev.local'
Expand Down
15 changes: 11 additions & 4 deletions backend/device_registry/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,22 @@ def get(self, request, *args, **kwargs):
firewallstate_object, _ = FirewallState.objects.get_or_create(device=device)
block_networks = portscan_object.block_networks.copy()
block_networks.extend(settings.SPAM_NETWORKS)
return Response({'policy': firewallstate_object.policy_string,
firewallstate_object.ports_field_name: portscan_object.block_ports,
'block_networks': block_networks})
return Response({
'firewall': {
a-martynovich marked this conversation as resolved.
Show resolved Hide resolved
'policy': firewallstate_object.policy_string,
firewallstate_object.ports_field_name: portscan_object.block_ports,
'block_networks': block_networks
},
'deb_packages_hash': device.deb_packages.get('hash')
})

def post(self, request, *args, **kwargs):
data = request.data
device = Device.objects.get(device_id=request.device_id)
device.last_ping = timezone.now()
device.agent_version = data.get('agent_version')
if 'deb_packages' in data:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way you'll keep old and outdated packages list if the agent was reverted to some old version that doesn't pass deb_packages. I recommend sending (from agent) None as the deb_packages payload element, which means it was not changed. So you'll do nothing if deb_packages is None and you'll clear device.deb_packages content in case of no deb_packages in data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we clear deb_packages? Isn't outdated data better than no data at all?
@vpetersson need your input on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a timestamp on the actual report, right? If so, then yes, old data is better than no data.

device.deb_packages = data['deb_packages']

device_info_object, _ = DeviceInfo.objects.get_or_create(device=device)
device_info_object.device__last_ping = timezone.now()
Expand Down Expand Up @@ -102,7 +109,7 @@ def post(self, request, *args, **kwargs):
firewall_state.rules = firewall_rules
firewall_state.save()

device.save(update_fields=['last_ping', 'agent_version', 'trust_score'])
device.save(update_fields=['last_ping', 'agent_version', 'deb_packages', 'trust_score'])

if datastore_client:
task_key = datastore_client.key('Ping')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Generated by Django 2.1.10 on 2019-08-06 05:10

from django.db import migrations, models
from device_registry.models import Device


def save_trust_score(apps, schema_editor):
Device = apps.get_model('device_registry', 'Device')
for d in Device.objects.all():
d.save(update_fields=['trust_score'])

Expand Down
19 changes: 19 additions & 0 deletions backend/device_registry/migrations/0056_device_deb_packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.1.10 on 2019-08-30 10:48

import django.contrib.postgres.fields.jsonb
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('device_registry', '0055_remove_deviceinfo_trust_score'),
]

operations = [
migrations.AddField(
model_name='device',
name='deb_packages',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
),
]
19 changes: 19 additions & 0 deletions backend/device_registry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,30 @@ class Device(models.Model):
agent_version = models.CharField(max_length=36, blank=True, null=True)
tags = tagulous.models.TagField(to=Tag, blank=True)
trust_score = models.FloatField(null=True)
deb_packages = JSONField(blank=True, default=dict)

@property
def certificate_expired(self):
return self.certificate_expires < timezone.now()

INSECURE_SERVICES = [
'fingerd',
'tftpd',
'telnetd',
'snmpd',
'xinetd',
'nis',
'atftpd',
'tftpd-hpa',
'rsh-server',
'rsh-redone-server'
]
@property
def insecure_services(self):
if 'packages' in self.deb_packages:
packages = set([p['name'] for p in self.deb_packages['packages']])
return set(self.INSECURE_SERVICES) & packages

def get_name(self):
if self.name:
return self.name
Expand Down
16 changes: 15 additions & 1 deletion backend/device_registry/templates/device_info_security.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,21 @@ <h4 class="tab-title">Security</h4>
<th scope="row">
Insecure Services
</th>
<td>Coming soon!</td>
<td>
{% with object.insecure_services as services %}
{% if services %}
<ul>
{% for service_name in services %}
<li>{{ service_name }}</p>
a-martynovich marked this conversation as resolved.
Show resolved Hide resolved
{% endfor %}
</ul>
{% elif services is not None %}
No insecure services detected.
{% else %}
N/A
{% endif %}
{% endwith %}
</td>
</tr>
<th scope="row">Logins</th>
<td>
Expand Down
35 changes: 35 additions & 0 deletions backend/device_registry/tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,41 @@ def test_logins(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<pre>pi:')
self.assertContains(response, 'success: 1')

def test_insecure_services(self):
self.client.login(username='test', password='123')
url = reverse('device-detail-security', kwargs={'pk': self.device.pk})

response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, '>telnetd<')
self.assertNotContains(response, '>fingerd<')
self.assertNotContains(response, 'No insecure services detected')

self.device.deb_packages = {
'packages': [
{'name': 'python2', 'version': 'VERSION'},
{'name': 'python3', 'version': 'VERSION'}
]
}
self.device.save()
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, '>telnetd<')
self.assertNotContains(response, '>fingerd<')
self.assertContains(response, 'No insecure services detected')

self.device.deb_packages = {
'packages': [
{'name': 'telnetd', 'version': 'VERSION'},
{'name': 'fingerd', 'version': 'VERSION'}
]
}
self.device.save()
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '>telnetd<')
self.assertContains(response, '>fingerd<')


class PairingKeysView(TestCase):
Expand Down
51 changes: 44 additions & 7 deletions backend/device_registry/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,15 +717,26 @@ def setUp(self):
def test_ping_get_success(self):
response = self.client.get(self.url, **self.headers)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, {'policy': self.device.firewallstate.policy_string,
'block_ports': [], 'block_networks': settings.SPAM_NETWORKS})
self.assertDictEqual(response.data, {
'firewall': {
'policy': self.device.firewallstate.policy_string,
'block_ports': [], 'block_networks': settings.SPAM_NETWORKS
},
'deb_packages_hash': None
})

def test_pong_data(self):
# 1st request
response = self.client.get(self.url, **self.headers)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, {'block_ports': [], 'block_networks': settings.SPAM_NETWORKS,
'policy': self.device.firewallstate.policy_string})
self.assertDictEqual(response.data, {
'firewall': {
'block_ports': [],
'block_networks': settings.SPAM_NETWORKS,
'policy': self.device.firewallstate.policy_string
},
'deb_packages_hash': None
})
# 2nd request
self.device.portscan.block_ports = [['192.168.1.178', 'tcp', 22, False]]
self.device.portscan.block_networks = [['192.168.1.177', False]]
Expand All @@ -735,9 +746,14 @@ def test_pong_data(self):

response = self.client.get(self.url, **self.headers)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, {'policy': self.device.firewallstate.policy_string,
'block_ports': [['192.168.1.178', 'tcp', 22, False]],
'block_networks': [['192.168.1.177', False]] + settings.SPAM_NETWORKS})
self.assertDictEqual(response.data, {
'firewall': {
'policy': self.device.firewallstate.policy_string,
'block_ports': [['192.168.1.178', 'tcp', 22, False]],
'block_networks': [['192.168.1.177', False]] + settings.SPAM_NETWORKS
},
'deb_packages_hash': None
})

def test_ping_creates_models(self):
devinfo_obj_count_before = DeviceInfo.objects.count()
Expand Down Expand Up @@ -833,6 +849,27 @@ def test_ping_writes_trust_score(self):
self.device.refresh_from_db()
self.assertGreater(self.device.trust_score, 0.42)

def test_ping_writes_packages(self):
packages = [{'name': 'PACKAGE', 'version': 'VERSION'}]
self.ping_payload['deb_packages'] = {
'hash': 'abcdef',
'packages': packages
}
self.client.post(self.url, self.ping_payload, **self.headers)

response = self.client.get(self.url, **self.headers)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, {
'firewall': {
'block_ports': [],
'block_networks': settings.SPAM_NETWORKS,
'policy': self.device.firewallstate.policy_string
},
'deb_packages_hash': 'abcdef'
})
self.device.refresh_from_db()
self.assertListEqual(self.device.deb_packages['packages'], packages)


class DeviceEnrollView(APITestCase):
def setUp(self):
Expand Down