Commit 3cb8a899 authored by Jérome Perrin's avatar Jérome Perrin

check_software: check egg vulnerabilities for every used python versions

pkg_resources.Environment only scans packages for the current python
version, but the checked software might be using a different python
version than the python version used to check software (the slapos
python). The checked software might also include packages for
multiples python versions.

Compute the list used python packages from the eggs directory and use
an Environment for each python version, this way we check all python
versions and also when slapos python and software python are different.
parent f72f61db
Pipeline #19448 passed with stage
in 0 seconds
...@@ -253,22 +253,37 @@ def checkSoftware(slap, software_url): ...@@ -253,22 +253,37 @@ def checkSoftware(slap, software_url):
# type: (List[str], Dict) -> Iterable[str] # type: (List[str], Dict) -> Iterable[str]
"""Check eggs against known vulnerabilities database from https://github.com/pyupio/safety-db """Check eggs against known vulnerabilities database from https://github.com/pyupio/safety-db
""" """
env = pkg_resources.Environment(egg_directories) def get_python_versions():
for egg in env: # () -> Iterable[str]
known_vulnerabilities = safety_db.get(egg) """Returns all python versions used in egg_directories
if known_vulnerabilities: """
for distribution in env[egg]: python_versions = set()
for known_vulnerability in known_vulnerabilities: for egg_dir in egg_directories:
for vulnerable_spec in known_vulnerability['specs']: basename, _ = os.path.splitext(os.path.basename(egg_dir))
for req in pkg_resources.parse_requirements(egg + match = pkg_resources.EGG_NAME(basename)
vulnerable_spec): if match:
vulnerability_description = "\n".join( pyver = match.group('pyver')
u"{}: {}".format(*item) if pyver:
for item in known_vulnerability.items()) python_versions.add(pyver)
if distribution in req: return python_versions
yield (
u"{egg} use vulnerable version {distribution.version} because {vulnerable_spec}.\n" for python in get_python_versions():
"{vulnerability_description}\n".format(**locals())) env = pkg_resources.Environment(egg_directories, python=python)
for egg in env:
known_vulnerabilities = safety_db.get(egg)
if known_vulnerabilities:
for distribution in env[egg]:
for known_vulnerability in known_vulnerabilities:
for vulnerable_spec in known_vulnerability['specs']:
for req in pkg_resources.parse_requirements(egg +
vulnerable_spec):
vulnerability_description = "\n".join(
u"{}: {}".format(*item)
for item in known_vulnerability.items())
if distribution in req:
yield (
u"{egg} use vulnerable version {distribution.version} because {vulnerable_spec}.\n"
"{vulnerability_description}\n".format(**locals()))
warning_list.extend( warning_list.extend(
checkEggsVersionsKnownVulnerabilities( checkEggsVersionsKnownVulnerabilities(
......
...@@ -32,13 +32,14 @@ import os ...@@ -32,13 +32,14 @@ import os
import glob import glob
import tempfile import tempfile
import textwrap import textwrap
import warnings
from slapos.testing.check_software import checkSoftware from slapos.testing.check_software import checkSoftware
from slapos.grid.utils import md5digest
from .test_standalone import SlapOSStandaloneTestCase from .test_standalone import SlapOSStandaloneTestCase
class TestCheckSoftware(SlapOSStandaloneTestCase): class TestCheckSoftwareLDD(SlapOSStandaloneTestCase):
# BBB python2 # BBB python2
assertRaisesRegex = getattr( assertRaisesRegex = getattr(
unittest.TestCase, unittest.TestCase,
...@@ -138,3 +139,77 @@ class TestCheckSoftware(SlapOSStandaloneTestCase): ...@@ -138,3 +139,77 @@ class TestCheckSoftware(SlapOSStandaloneTestCase):
# parts # parts
self.test_shared_part_using_system_libraries() self.test_shared_part_using_system_libraries()
self.test_ok() self.test_ok()
class TestCheckSoftwareEggVulnerability(SlapOSStandaloneTestCase):
def _install_software(self):
"""install a fake software with vulnerable packages, installed as
eggs and develop-eggs, and for different python versions.
"""
software_url = '/fake/path/software.cfg'
software_hash = md5digest(software_url)
self.standalone.supply(software_url)
software_dir = os.path.join(
self.standalone.software_directory,
software_hash,
)
eggs_dir = os.path.join(software_dir, 'eggs')
os.makedirs(os.path.join(eggs_dir, 'Werkzeug-1.0.1-py3.7.egg', 'EGG-INFO'))
with open(
os.path.join(
eggs_dir,
'Werkzeug-1.0.1-py3.7.egg',
'EGG-INFO',
'PKG-INFO',
), 'w') as f:
f.write(
textwrap.dedent('''\
Metadata-Version: 2.1
Name: Werkzeug
Version: 1.0.1
'''))
develop_eggs_dir = os.path.join(software_dir, 'develop-eggs')
os.makedirs(
os.path.join(
develop_eggs_dir,
'lxml-4.6.3-py2.7-linux-x86_64.egg',
'EGG-INFO',
))
with open(
os.path.join(
develop_eggs_dir,
'lxml-4.6.3-py2.7-linux-x86_64.egg',
'EGG-INFO',
'PKG-INFO',
), 'w') as f:
f.write(
textwrap.dedent('''\
Metadata-Version: 2.1
Name: lxml
Version: 4.6.3
'''))
with open(os.path.join(software_dir, '.completed'), 'w') as f:
f.write('Thu Dec 2 01:35:02 2021')
with open(os.path.join(software_dir, '.installed.cfg'), 'w') as f:
f.write('[buildout]')
return software_url
def test_egg_known_vulnerability(self):
software_url = self._install_software()
with warnings.catch_warnings(record=True) as warning_context:
warnings.simplefilter("always")
checkSoftware(self.standalone, software_url)
warning, = [w for w in warning_context if 'vulnerable' in str(w.message)]
self.assertIn(
'Werkzeug version 2.0.2 improves the security of the debugger cookies',
str(warning.message),
)
self.assertIn(
'Lxml 4.6.5 includes a fix for CVE-2021-43818',
str(warning.message),
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment