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

Commit

Permalink
Merge pull request #401 from GreatFruitOmsk/agent-22
Browse files Browse the repository at this point in the history
Store the list of deb packages.
  • Loading branch information
vpetersson committed Sep 11, 2019
2 parents 26e029a + 7d0c78a commit 0847b76
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 17 deletions.
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({
'policy': firewallstate_object.policy_string,
firewallstate_object.ports_field_name: portscan_object.block_ports,
'block_networks': block_networks,
'deb_packages_hash': device.deb_packages_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:
deb_packages = data['deb_packages']
device.deb_packages_hash = deb_packages['hash']
device.set_deb_packages(deb_packages['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_hash', '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
37 changes: 37 additions & 0 deletions backend/device_registry/migrations/0056_auto_20190909_1128.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 2.1.10 on 2019-09-09 11:28

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


class Migration(migrations.Migration):

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

operations = [
migrations.CreateModel(
name='DebPackage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('version', models.CharField(max_length=128)),
('arch', models.CharField(choices=[(device_registry.models.DebPackage.Arch('i386'), 'i386'), (device_registry.models.DebPackage.Arch('amd64'), 'amd64'), (device_registry.models.DebPackage.Arch('armhf'), 'armhf'), (device_registry.models.DebPackage.Arch('all'), 'all')], max_length=16)),
],
),
migrations.AddField(
model_name='device',
name='deb_packages_hash',
field=models.CharField(blank=True, max_length=32),
),
migrations.AlterUniqueTogether(
name='debpackage',
unique_together={('name', 'version', 'arch')},
),
migrations.AddField(
model_name='device',
name='deb_packages',
field=models.ManyToManyField(to='device_registry.DebPackage'),
),
]
66 changes: 64 additions & 2 deletions backend/device_registry/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from enum import Enum
import datetime
from statistics import mean
import json
import uuid

from django.conf import settings
from django.db import models
from django.db import models, transaction
from django.utils import timezone
from django.contrib.postgres.fields import JSONField
from django.db import transaction

import yaml
import tagulous.models
Expand Down Expand Up @@ -39,6 +39,26 @@ def from_db_value(self, value, expression, connection, context):
return value


class DebPackage(models.Model):
class Distro(Enum):
DEBIAN = 'debian'
RASPBIAN = 'raspbian'
UBUNTU = 'ubuntu'

class Arch(Enum):
i386 = 'i386'
AMD64 = 'amd64'
ARMHF = 'armhf'
ALL = 'all'

name = models.CharField(max_length=128)
version = models.CharField(max_length=128)
arch = models.CharField(max_length=16, choices=[(tag, tag.value) for tag in Arch])

class Meta:
unique_together = ['name', 'version', 'arch']


class Device(models.Model):
device_id = models.CharField(
max_length=128,
Expand All @@ -65,11 +85,53 @@ 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 = models.ManyToManyField(DebPackage)
deb_packages_hash = models.CharField(max_length=32, blank=True)

@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):
"""
Get a list of deb packages which are marked "insecure", i.e. their names are in INSECURE_SERVICES list.
:return: list of DebPackage or None if set_deb_packages() wasn't called before.
"""
if not self.deb_packages_hash:
return None
return self.deb_packages.filter(name__in=self.INSECURE_SERVICES)

def set_deb_packages(self, packages):
"""
Assign the list of installed deb packages to this device.
:param packages: list of dicts with the following values: 'name': str, 'version': str, 'arch': DebPackage.Arch.
"""
# Save new packages to DB.
DebPackage.objects.bulk_create([DebPackage(name=package['name'], version=package['version'],
arch=package['arch']) for package in packages],
ignore_conflicts=True)
# Get packages qs.
q_objects = models.Q()
for package in packages:
q_objects.add(models.Q(name=package['name'], version=package['version'], arch=package['arch']), models.Q.OR)

# Set deb_packages.
self.deb_packages.set(DebPackage.objects.filter(q_objects).only('pk'))

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 in services %}
<li>{{ service.name }}</li>
{% 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
43 changes: 42 additions & 1 deletion backend/device_registry/tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,47 @@ def test_logins(self):
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.set_deb_packages([
{'name': 'python2', 'version': 'VERSION', 'arch': 'i386'},
{'name': 'python3', 'version': 'VERSION', 'arch': 'i386'}
])
self.device.deb_packages_hash = 'abcdef'
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.assertListEqual(list(self.device.deb_packages.values('name', 'version', 'arch')), [
{'name': 'python2', 'version': 'VERSION', 'arch': 'i386'},
{'name': 'python3', 'version': 'VERSION', 'arch': 'i386'}
])

self.device.set_deb_packages([
{'name': 'telnetd', 'version': 'VERSION', 'arch': 'i386'},
{'name': 'fingerd', 'version': 'VERSION', 'arch': 'i386'}
])
self.device.save()
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '>telnetd<')
self.assertContains(response, '>fingerd<')
self.assertNotContains(response, 'No insecure services detected')
self.assertListEqual(list(self.device.deb_packages.values('name', 'version', 'arch')), [
{'name': 'telnetd', 'version': 'VERSION', 'arch': 'i386'},
{'name': 'fingerd', 'version': 'VERSION', 'arch': 'i386'}
])


class PairingKeysView(TestCase):
def setUp(self):
Expand Down Expand Up @@ -572,7 +613,7 @@ def setUp(self):
owner=self.user,
certificate=TEST_CERT,
name='First',
last_ping=timezone.now()-datetime.timedelta(days=1, hours=1)
last_ping=timezone.now() - datetime.timedelta(days=1, hours=1)
)
self.deviceinfo0 = DeviceInfo.objects.create(
device=self.device0,
Expand Down
44 changes: 37 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,22 @@ 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, {
'policy': self.device.firewallstate.policy_string,
'block_ports': [], 'block_networks': settings.SPAM_NETWORKS,
'deb_packages_hash': ''
})

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, {
'block_ports': [],
'block_networks': settings.SPAM_NETWORKS,
'policy': self.device.firewallstate.policy_string,
'deb_packages_hash': ''
})
# 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 +742,12 @@ 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, {
'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': ''
})

def test_ping_creates_models(self):
devinfo_obj_count_before = DeviceInfo.objects.count()
Expand Down Expand Up @@ -833,6 +843,26 @@ 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', 'arch': 'all'}]
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, {
'block_ports': [],
'block_networks': settings.SPAM_NETWORKS,
'policy': self.device.firewallstate.policy_string,
'deb_packages_hash': 'abcdef'
})
self.device.refresh_from_db()
self.assertQuerysetEqual(self.device.deb_packages.all(), packages,
transform=lambda p: {'name': p.name, 'version': p.version, 'arch': p.arch})


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

0 comments on commit 0847b76

Please sign in to comment.