-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1652 from wazuh/feature/1612-package-vuln-scanner
Add test to scan all python packages
- Loading branch information
Showing
10 changed files
with
246 additions
and
9 deletions.
There are no files selected for viewing
100 changes: 100 additions & 0 deletions
100
deps/wazuh_testing/wazuh_testing/tools/scans/dependencies.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Copyright (C) 2015-2021, Wazuh Inc. | ||
# Created by Wazuh, Inc. <[email protected]>. | ||
# This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 | ||
import re | ||
import subprocess | ||
from collections import namedtuple | ||
from datetime import datetime | ||
from json import dumps, loads | ||
from os import environ | ||
|
||
from safety.formatter import report | ||
from safety.safety import check | ||
|
||
python_bin = environ['_'] | ||
package_list = [] | ||
package_tuple = namedtuple('Package', ['key', 'version']) | ||
|
||
|
||
def run_report(): | ||
"""Perform vulnerability scan using Safety to check all packages listed. | ||
Returns: | ||
str: information about packages and vulnerabilities. | ||
""" | ||
json_report = [] | ||
vulns = check(packages=package_list, key='', db_mirror='', cached=False, ignore_ids=(), proxy={}) | ||
output_report = report(vulns=vulns, full=True, json_report=True, bare_report=False, | ||
checked_packages=len(package_list), db='', key='') | ||
for package_information in loads(output_report): | ||
json_report.append({ | ||
'package_name': package_information[0], | ||
'package_version': package_information[2], | ||
'package_affected_version': package_information[1], | ||
'vuln_description': package_information[3], | ||
'safety_id': package_information[4] | ||
}) | ||
json_data = { | ||
'report_date': datetime.now().isoformat(), | ||
'vulnerabilities_found': len(json_report), | ||
'packages': json_report | ||
} | ||
return dumps(json_data, indent=4) | ||
|
||
|
||
def prepare_input(pip_mode, input_file_path): | ||
"""Create temp input file with all packages listed and prepared to be scanned later on. | ||
Args: | ||
pip_mode (bool): enable/disable pip freeze to retrieve package information. | ||
input_file_path (str): path to the input file (used if pip_mode is disabled). | ||
""" | ||
python_process = subprocess.run([python_bin, '--version'], stdout=subprocess.PIPE, universal_newlines=True) | ||
pkg = python_process.stdout.strip().split() | ||
package_list.append(package_tuple(pkg[0], pkg[1])) | ||
if pip_mode: | ||
pip_mode_process = subprocess.run([python_bin, '-m', 'pip', 'freeze'], stdout=subprocess.PIPE, | ||
universal_newlines=True) | ||
for package_line in pip_mode_process.stdout.strip().split('\n'): | ||
pkg = package_line.strip().split('==') | ||
package_list.append(package_tuple(pkg[0], pkg[1])) | ||
else: | ||
with open(input_file_path, mode='r') as input_file: | ||
lines = input_file.readlines() | ||
for line in lines: | ||
line = re.sub('[<>~]', '=', line) | ||
if ',' in line: | ||
package_version = max(re.findall('\d+\.+\d*\.*\d', line)) | ||
package_name = re.findall('([a-z]+)', line)[0] | ||
line = f'{package_name}=={package_version}\n' | ||
if ';' in line: | ||
line = line.split(';')[0] + '\n' | ||
pkg = line.strip().split('==') | ||
package_list.append(package_tuple(pkg[0], pkg[1])) | ||
|
||
|
||
def export_report(output, output_file_path): | ||
"""Export report to a file or console as a message. | ||
Args: | ||
output (str): information about packages and vulnerabilities. | ||
output_file_path (str): path to file. | ||
""" | ||
if output_file_path: | ||
with open(output_file_path, mode='w') as output_file: | ||
output_file.write(output) | ||
else: | ||
print(output) | ||
|
||
|
||
def report_for_pytest(requirements_file): | ||
"""Method used by pytest to generate a report. | ||
Args: | ||
requirements_file (str): path to the input file. | ||
Returns: | ||
str: information about packages and vulnerabilities. | ||
""" | ||
prepare_input(False, requirements_file) | ||
return run_report() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Dependencies Scanner | ||
|
||
## Description | ||
It's a tool used to scan for vulnerabilities in a requirements.txt file.\ | ||
It can generate reports via console output or json file. Can be run with `pytest` and manage to handle remote files under github repositories. Requirements file can be specified with `repo`, `branch`, `requirements-path` parameters giving flexibility on file location. | ||
Output file in which the report will be generated can be specified with `report-path` parameter. | ||
|
||
## How to use - Pytest | ||
``` | ||
Parameters: | ||
--repo: repository name. Default: 'wazuh'. | ||
--branch: branch name of specified repository. Default: 'master'. | ||
--requirements-path: requirements file path. Default: 'framework/requirements.txt'. | ||
--report-path: output file path. Default: 'dependencies/report_file.json'. | ||
``` | ||
### Scanning wazuh-qa requirements file: | ||
``` | ||
↪ ~/git/wazuh-qa/tests/scans ⊶ feature/1612-package-vuln-scanner ⨘ python3 -m pytest -vv -x --disable-warnings dependencies/ --repo wazuh-qa --branch master --requirements-path requirements.txt | ||
==================================================================================== test session starts ===================================================================================== | ||
platform linux -- Python 3.9.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /home/kondent/pythonEnv/qa-env/bin/python3 | ||
cachedir: .pytest_cache | ||
metadata: {'Python': '3.9.5', 'Platform': 'Linux-5.11.0-34-generic-x86_64-with-glibc2.31', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'testinfra': '5.0.0'}} | ||
rootdir: /home/kondent/git/wazuh-qa/tests/scans | ||
plugins: html-3.1.1, metadata-1.11.0, testinfra-5.0.0 | ||
collected 1 item | ||
dependencies/test_dependencies.py::test_python_dependencies_vuln_scan FAILED [100%] | ||
========================================================================================== FAILURES ========================================================================================== | ||
_______________________________________________________________________________ test_python_dependencies_vuln_scan _______________________________________________________________________________ | ||
pytestconfig = <_pytest.config.Config object at 0x7f721b4c4eb0> | ||
def test_python_dependencies_vuln_scan(pytestconfig): | ||
branch = pytestconfig.getoption('--branch') | ||
repo = pytestconfig.getoption('--repo') | ||
requirements_path = pytestconfig.getoption('--requirements-path') | ||
report_path = pytestconfig.getoption('--report-path') | ||
requirements_url = f'https://raw.githubusercontent.com/wazuh/{repo}/{branch}/{requirements_path}' | ||
urlretrieve(requirements_url, REQUIREMENTS_TEMP_FILE.name) | ||
result = report_for_pytest(REQUIREMENTS_TEMP_FILE.name) | ||
REQUIREMENTS_TEMP_FILE.close() | ||
export_report(result, report_path) | ||
> assert loads(result)['vulnerabilities_found'] == 0, f'Vulnerables packages were found, full report at: ' \ | ||
f'{report_path}' | ||
E AssertionError: Vulnerables packages were found, full report at: /home/kondent/git/wazuh-qa/tests/scans/dependencies/report_file.json | ||
E assert 28 == 0 | ||
E +28 | ||
E -0 | ||
dependencies/test_dependencies.py:23: AssertionError | ||
================================================================================== short test summary info =================================================================================== | ||
FAILED dependencies/test_dependencies.py::test_python_dependencies_vuln_scan - AssertionError: Vulnerables packages were found, full report at: /home/kondent/git/wazuh-qa/tests/scans/dependen... | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
===================================================================================== 1 failed in 1.87s ====================================================================================== | ||
↪ ~/git/wazuh-qa/tests/scans ⊶ feature/1612-package-vuln-scanner ⨘ cat dependencies/report_file.json | ||
{ | ||
"report_date": "2021-09-10T09:49:43.471148", | ||
"vulnerabilities_found": 28, | ||
"packages": [ | ||
{ | ||
"package_name": "pillow", | ||
"package_version": "6.2.0", | ||
"package_affected_version": "<6.2.2", | ||
"vuln_description": "libImaging/TiffDecode.c in Pillow before 6.2.2 has a TIFF decoding integer overflow, related to realloc. See: CVE-2020-5310.", | ||
"safety_id": "37779" | ||
}, | ||
... | ||
... | ||
... | ||
] | ||
} | ||
``` | ||
|
||
### Scanning wazuh requirements file with a specific output path: | ||
``` | ||
↪ ~/git/wazuh-qa/tests/scans ⊶ feature/1612-package-vuln-scanner ⨘ python3 -m pytest -vv -x --disable-warnings dependencies/ --repo wazuh --branch master --requirements-path framework/requirements.txt --report-path ~/Desktop/report_file.json | ||
==================================================================================== test session starts ===================================================================================== | ||
platform linux -- Python 3.9.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /home/kondent/pythonEnv/qa-env/bin/python3 | ||
cachedir: .pytest_cache | ||
metadata: {'Python': '3.9.5', 'Platform': 'Linux-5.11.0-34-generic-x86_64-with-glibc2.31', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'testinfra': '5.0.0'}} | ||
rootdir: /home/kondent/git/wazuh-qa/tests/scans | ||
plugins: html-3.1.1, metadata-1.11.0, testinfra-5.0.0 | ||
collected 1 item | ||
dependencies/test_dependencies.py::test_python_dependencies_vuln_scan PASSED [100%] | ||
===================================================================================== 1 passed in 0.68s ====================================================================================== | ||
↪ ~/git/wazuh-qa/tests/scans ⊶ feature/1612-package-vuln-scanner ⨘ cat ~/Desktop/report_file.json | ||
{ | ||
"report_date": "2021-09-10T09:53:39.284082", | ||
"vulnerabilities_found": 0, | ||
"packages": [] | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import os | ||
|
||
DEFAULT_REQUIREMENTS_PATH = 'framework/requirements.txt' | ||
DEFAULT_REPORT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'report_file.json') | ||
|
||
|
||
def pytest_addoption(parser): | ||
parser.addoption('--requirements-path', action='store', default=DEFAULT_REQUIREMENTS_PATH) | ||
parser.addoption('--report-path', action='store', default=DEFAULT_REPORT_PATH) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## Code documentation | ||
|
||
::: tests.scans.dependencies.test_dependencies |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Copyright (C) 2015-2021, Wazuh Inc. | ||
# Created by Wazuh, Inc. <[email protected]>. | ||
# This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 | ||
import tempfile | ||
from json import loads | ||
from urllib.request import urlretrieve | ||
|
||
from wazuh_testing.tools.scans.dependencies import export_report, report_for_pytest | ||
|
||
REQUIREMENTS_TEMP_FILE = tempfile.NamedTemporaryFile() | ||
|
||
|
||
def test_python_dependencies_vuln_scan(pytestconfig): | ||
"""Check that the specified dependencies do not have any known vulnerabilities. | ||
Args: | ||
pytestconfig (fixture): Fixture that returns the :class:`_pytest.config.Config` object. | ||
""" | ||
branch = pytestconfig.getoption('--branch') | ||
repo = pytestconfig.getoption('--repo') | ||
requirements_path = pytestconfig.getoption('--requirements-path') | ||
report_path = pytestconfig.getoption('--report-path') | ||
requirements_url = f"https://raw.githubusercontent.com/wazuh/{repo}/{branch}/{requirements_path}" | ||
urlretrieve(requirements_url, REQUIREMENTS_TEMP_FILE.name) | ||
result = report_for_pytest(REQUIREMENTS_TEMP_FILE.name) | ||
REQUIREMENTS_TEMP_FILE.close() | ||
export_report(result, report_path) | ||
assert loads(result)['vulnerabilities_found'] == 0, f'Vulnerables packages were found, full report at: ' \ | ||
f"{report_path}" |