mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Add security diagnoser with meltdown checks
This commit is contained in:
parent
cee3b4de27
commit
b81cd4fc68
2 changed files with 102 additions and 0 deletions
98
data/hooks/diagnosis/90-security.py
Normal file
98
data/hooks/diagnosis/90-security.py
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from yunohost.diagnosis import Diagnoser
|
||||||
|
from moulinette.utils.filesystem import read_json, write_to_json
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityDiagnoser(Diagnoser):
|
||||||
|
|
||||||
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
|
cache_duration = 3600
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
"CVE-2017-5754"
|
||||||
|
|
||||||
|
if self.is_vulnerable_to_meltdown():
|
||||||
|
yield dict(meta={"test": "meltdown"},
|
||||||
|
status="ERROR",
|
||||||
|
summary=("diagnosis_security_vulnerable_to_meltdown", {}),
|
||||||
|
details=[("diagnosis_security_vulnerable_to_meltdown_details", ())]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
yield dict(meta={},
|
||||||
|
status="SUCCESS",
|
||||||
|
summary=("diagnosis_security_all_good", {})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_vulnerable_to_meltdown(self):
|
||||||
|
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
||||||
|
|
||||||
|
# We use a cache file to avoid re-running the script so many times,
|
||||||
|
# which can be expensive (up to around 5 seconds on ARM)
|
||||||
|
# and make the admin appear to be slow (c.f. the calls to diagnosis
|
||||||
|
# from the webadmin)
|
||||||
|
#
|
||||||
|
# The cache is in /tmp and shall disappear upon reboot
|
||||||
|
# *or* we compare it to dpkg.log modification time
|
||||||
|
# such that it's re-ran if there was package upgrades
|
||||||
|
# (e.g. from yunohost)
|
||||||
|
cache_file = "/tmp/yunohost-meltdown-diagnosis"
|
||||||
|
dpkg_log = "/var/log/dpkg.log"
|
||||||
|
if os.path.exists(cache_file):
|
||||||
|
if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log):
|
||||||
|
self.logger_debug("Using cached results for meltdown checker, from %s" % cache_file)
|
||||||
|
return read_json(cache_file)[0]["VULNERABLE"]
|
||||||
|
|
||||||
|
# script taken from https://github.com/speed47/spectre-meltdown-checker
|
||||||
|
# script commit id is store directly in the script
|
||||||
|
SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh"
|
||||||
|
|
||||||
|
# '--variant 3' corresponds to Meltdown
|
||||||
|
# example output from the script:
|
||||||
|
# [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
|
||||||
|
try:
|
||||||
|
self.logger_debug("Running meltdown vulnerability checker")
|
||||||
|
call = subprocess.Popen("bash %s --batch json --variant 3" %
|
||||||
|
SCRIPT_PATH, shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
# TODO / FIXME : here we are ignoring error messages ...
|
||||||
|
# in particular on RPi2 and other hardware, the script complains about
|
||||||
|
# "missing some kernel info (see -v), accuracy might be reduced"
|
||||||
|
# Dunno what to do about that but we probably don't want to harass
|
||||||
|
# users with this warning ...
|
||||||
|
output, err = call.communicate()
|
||||||
|
assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode
|
||||||
|
|
||||||
|
# If there are multiple lines, sounds like there was some messages
|
||||||
|
# in stdout that are not json >.> ... Try to get the actual json
|
||||||
|
# stuff which should be the last line
|
||||||
|
output = output.strip()
|
||||||
|
if "\n" in output:
|
||||||
|
self.logger_debug("Original meltdown checker output : %s" % output)
|
||||||
|
output = output.split("\n")[-1]
|
||||||
|
|
||||||
|
CVEs = json.loads(output)
|
||||||
|
assert len(CVEs) == 1
|
||||||
|
assert CVEs[0]["NAME"] == "MELTDOWN"
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
|
||||||
|
raise Exception("Command output for failed meltdown check: '%s'" % output)
|
||||||
|
|
||||||
|
self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file)
|
||||||
|
write_to_json(cache_file, CVEs)
|
||||||
|
return CVEs[0]["VULNERABLE"]
|
||||||
|
|
||||||
|
|
||||||
|
def main(args, env, loggers):
|
||||||
|
return SecurityDiagnoser(args, env, loggers).diagnose()
|
|
@ -180,6 +180,9 @@
|
||||||
"diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.",
|
"diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.",
|
||||||
"diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...",
|
"diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...",
|
||||||
"diagnosis_regenconf_nginx_conf_broken": "The nginx configuration appears to be broken!",
|
"diagnosis_regenconf_nginx_conf_broken": "The nginx configuration appears to be broken!",
|
||||||
|
"diagnosis_security_all_good": "No critical security vulnerability was found.",
|
||||||
|
"diagnosis_security_vulnerable_to_meltdown": "You appear vulnerable to the Meltdown criticial security vulnerability",
|
||||||
|
"diagnosis_security_vulnerable_to_meltdown_details": "To fix this, you should upgrade your system and reboot to load the new linux kernel (or contact your server provider if this doesn't work). See https://meltdownattack.com/ for more infos.",
|
||||||
"diagnosis_description_ip": "Internet connectivity",
|
"diagnosis_description_ip": "Internet connectivity",
|
||||||
"diagnosis_description_dnsrecords": "DNS records",
|
"diagnosis_description_dnsrecords": "DNS records",
|
||||||
"diagnosis_description_services": "Services status check",
|
"diagnosis_description_services": "Services status check",
|
||||||
|
@ -187,6 +190,7 @@
|
||||||
"diagnosis_description_ports": "Ports exposure",
|
"diagnosis_description_ports": "Ports exposure",
|
||||||
"diagnosis_description_http": "HTTP exposure",
|
"diagnosis_description_http": "HTTP exposure",
|
||||||
"diagnosis_description_regenconf": "System configurations",
|
"diagnosis_description_regenconf": "System configurations",
|
||||||
|
"diagnosis_description_security": "Security checks",
|
||||||
"diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}",
|
"diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}",
|
||||||
"diagnosis_ports_unreachable": "Port {port} is not reachable from outside.",
|
"diagnosis_ports_unreachable": "Port {port} is not reachable from outside.",
|
||||||
"diagnosis_ports_ok": "Port {port} is reachable from outside.",
|
"diagnosis_ports_ok": "Port {port} is reachable from outside.",
|
||||||
|
|
Loading…
Add table
Reference in a new issue