security: List whether each app is sandboxed

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
[sunil: i18n for yes, no, N/A strings, avoid changing an i18ned string]
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-12-27 10:48:00 -05:00 committed by Sunil Mohan Adapa
parent 1be1af9b18
commit 89aefc00cf
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 74 additions and 20 deletions

View File

@ -113,8 +113,8 @@ def set_restricted_access(enabled):
actions.superuser_run('security', [action])
def get_vulnerability_counts():
"""Return number of security vulnerabilities for each app"""
def get_apps_report():
"""Return a security report for each app"""
lines = subprocess.check_output(['debsecan']).decode().split('\n')
cves = defaultdict(set)
for line in lines:
@ -132,12 +132,12 @@ def get_vulnerability_counts():
'freedombox': {
'name': 'freedombox',
'packages': {'freedombox'},
'count': 0,
'past_count': 0 if past_cves else None,
'vulns': 0,
'past_vulns': 0 if past_cves else None,
}
}
if past_cves and 'freedombox' in past_cves:
apps['freedombox']['past_count'] = len(past_cves['freedombox'])
apps['freedombox']['past_vulns'] = len(past_cves['freedombox'])
for module_name, module in module_loader.loaded_modules.items():
try:
@ -145,6 +145,11 @@ def get_vulnerability_counts():
except AttributeError:
continue # app has no managed packages
try:
services = module.managed_services
except AttributeError:
services = None
# filter out apps not setup yet
if module.setup_helper.get_state() == 'needs-setup':
continue
@ -152,17 +157,56 @@ def get_vulnerability_counts():
apps[module_name] = {
'name': module_name,
'packages': set(packages),
'count': 0,
'past_count': 0 if past_cves else None,
'vulns': 0,
'past_vulns': 0 if past_cves else None,
'sandboxed': None,
}
for package in packages:
if past_cves and package in past_cves:
apps[module_name]['past_count'] += len(past_cves[package])
apps[module_name]['past_vulns'] += len(past_cves[package])
if services:
apps[module_name]['sandboxed'] = False
for service in services:
if _get_service_is_sandboxed(service):
apps[module_name]['sandboxed'] = True
for cve_packages in cves.values():
for app_ in apps.values():
if cve_packages & app_['packages']:
app_['count'] += 1
app_['vulns'] += 1
return apps
def _get_service_is_sandboxed(service):
"""Return whether service is sandboxed."""
lines = subprocess.check_output([
'systemctl',
'show',
service,
'--property=ProtectSystem',
'--property=ProtectHome',
'--property=PrivateTmp',
'--property=PrivateDevices',
'--property=PrivateNetwork',
'--property=PrivateUsers',
'--property=PrivateMounts',
]).decode().strip().split('\n')
pairs = [line.partition('=')[::2] for line in lines]
properties = {name: value for name, value in pairs}
if properties['ProtectSystem'] in ['yes', 'full', 'strict']:
return True
if properties['ProtectHome'] in ['yes', 'read-only', 'tmpfs']:
return True
for name in [
'PrivateTmp', 'PrivateDevices', 'PrivateNetwork', 'PrivateUsers',
'PrivateMounts'
]:
if properties[name] == 'yes':
return True
return False

View File

@ -24,7 +24,7 @@
{% block content %}
<h3>{% trans "Security Report" %}</h3>
<p>
{% blocktrans trimmed with count=freedombox_vulns.count %}
{% blocktrans trimmed with count=freedombox_report.vulns %}
The installed version of FreedomBox has {{ count }} reported security
vulnerabilities.
{% endblocktrans %}
@ -32,7 +32,8 @@
<p>
{% blocktrans trimmed %}
The following table lists the current reported number, and historical
count, of security vulnerabilities for each installed app.
count, of security vulnerabilities for each installed app. It also lists
whether each service is using sandboxing features.
{% endblocktrans %}
</p>
<table class="table table-bordered table-condensed table-striped">
@ -41,14 +42,24 @@
<th>{% trans "App Name" %}</th>
<th>{% trans "Current Vulnerabilities" %}</th>
<th>{% trans "Past Vulnerabilities" %}</th>
<th>{% trans "Sandboxed" %}</th>
</tr>
</thead>
<tbody>
{% for app in apps_vulns %}
{% for app in apps_report %}
<tr>
<td>{{ app.name }}</td>
<td>{{ app.count }}</td>
<td>{{ app.past_count|default_if_none:"❗"}}</td>
<td>{{ app.vulns }}</td>
<td>{{ app.past_vulns|default_if_none:"❗"}}</td>
<td>
{% if app.sandboxed is None %}
{% trans "N/A" %}
{% elif app.sandboxed %}
{% trans "Yes" %}
{% else %}
{% trans "No" %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

View File

@ -80,14 +80,13 @@ def _apply_changes(request, old_status, new_status):
def report(request):
"""Serve the security report page"""
vulnerability_counts = security.get_vulnerability_counts()
apps_report = security.get_apps_report()
return TemplateResponse(
request, 'security_report.html', {
'title':
_('Security Report'),
'freedombox_vulns':
vulnerability_counts.pop('freedombox'),
'apps_vulns':
sorted(vulnerability_counts.values(),
key=lambda app: app['name']),
'freedombox_report':
apps_report.pop('freedombox'),
'apps_report':
sorted(apps_report.values(), key=lambda app: app['name']),
})