security: Show vulnerability counts

Show the number of vulnerabilities reported by debsecan for freedombox
package and for managed_packages of each installed app.

Essential apps are not included in the list. Also note that
dependencies of the managed_packages are not included yet.

The purpose of this information is to help users decide which apps to
use, and what level of personal information to store in each app.

Closes #1609.

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
[sunil@medhas.org Show essential apps too]
[sunil@medhas.org Fix HTML tags]
[sunil@medhas.org Use setup_helper to get the installed state of an app]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
James Valleroy 2019-07-27 20:47:41 -04:00 committed by Sunil Mohan Adapa
parent a0837be410
commit 22c00d5cd4
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 96 additions and 23 deletions

View File

@ -18,13 +18,16 @@
FreedomBox app for security configuration.
"""
import subprocess
from collections import defaultdict
from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth import app as app_module
from plinth import menu
from plinth import menu, module_loader
from .manifest import backup # noqa, pylint: disable=unused-import
from .manifest import backup # noqa, pylint: disable=unused-import
version = 6
@ -32,7 +35,7 @@ is_essential = True
name = _('Security')
managed_packages = ['fail2ban']
managed_packages = ['fail2ban', 'debsecan']
managed_services = ['fail2ban']
@ -107,3 +110,43 @@ def set_restricted_access(enabled):
action = 'enable-restricted-access'
actions.superuser_run('security', [action])
def get_vulnerability_counts():
"""Return number of security vulnerabilities for each app"""
lines = subprocess.check_output(['debsecan']).decode().split('\n')
cves = defaultdict(set)
for line in lines:
if line:
(label, package, *_) = line.split()
cves[label].add(package)
apps = {
'freedombox': {
'name': 'freedombox',
'packages': {'freedombox'},
'count': 0,
}
}
for module_name, module in module_loader.loaded_modules.items():
try:
packages = module.managed_packages
except AttributeError:
continue # app has no managed packages
# filter out apps not setup yet
if module.setup_helper.get_state() == 'needs-setup':
continue
apps[module_name] = {
'name': module_name,
'packages': set(packages),
'count': 0,
}
for cve_packages in cves.values():
for app_ in apps.values():
if cve_packages & app_['packages']:
app['count'] += 1
return apps

View File

@ -1,4 +1,4 @@
{% extends "simple_app.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -21,15 +21,34 @@
{% load bootstrap %}
{% load i18n %}
{% block configuration %}
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary"
value="{% trans "Submit" %}"/>
</form>
{% block status %}
<h3>{% trans "Status" %}</h3>
<p>
{% blocktrans trimmed with count=freedombox_vulns.count %}
The installed version of FreedomBox has {{ count }} reported security
vulnerabilities.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
The following table lists the reported number of security vulnerabilities
for each installed app.
{% endblocktrans %}
</p>
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "App Name" %}</th>
<th>{% trans "Vulnerabilities Reported" %}</th>
</tr>
</thead>
<tbody>
{% for app in apps_vulns %}
<tr>
<td>{{ app.name }}</td>
<td>{{ app.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -43,11 +43,21 @@ def index(request):
else:
form = SecurityForm(initial=status, prefix='security')
return TemplateResponse(request, 'security.html', {
'title': _('Security'),
'manual_page': security.manual_page,
'form': form
})
vulnerability_counts = security.get_vulnerability_counts()
return TemplateResponse(
request, 'security.html', {
'name':
_('Security'),
'manual_page':
security.manual_page,
'form':
form,
'freedombox_vulns':
vulnerability_counts.pop('freedombox'),
'apps_vulns':
sorted(vulnerability_counts.values(),
key=lambda app: app['name']),
})
def get_status(request):
@ -64,9 +74,10 @@ def _apply_changes(request, old_status, new_status):
try:
security.set_restricted_access(new_status['restricted_access'])
except Exception as exception:
messages.error(request,
_('Error setting restricted access: {exception}')
.format(exception=exception))
messages.error(
request,
_('Error setting restricted access: {exception}').format(
exception=exception))
else:
messages.success(request, _('Updated security configuration'))