diff --git a/plugin-setuptools/.gitignore b/plugin-setuptools/.gitignore new file mode 100644 index 000000000..0bc32fbc9 --- /dev/null +++ b/plugin-setuptools/.gitignore @@ -0,0 +1,92 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# PyCharm +.idea/ diff --git a/plugin-setuptools/README.md b/plugin-setuptools/README.md new file mode 100644 index 000000000..f8dd94dc5 --- /dev/null +++ b/plugin-setuptools/README.md @@ -0,0 +1,35 @@ +# plugin-setuptools + +The vulnerability-assessment-tool plugin for Python allows to scan a Python application developed with setuptools. + +Notes: + +* The plugin is in beta, use with care and provide us feedback + +## Install the vulnerability-assessment-tool plugin + +Until the plugin is available in PyPI, it has to be installed from the sources. Clone this repo and run the following: + +``` +cd plugin-setuptools +python setup.py install +``` + +## Scan your application + +Until now, only the `app`` goal is supported, the other vulnerability-assessment-tool goals will be added step-by-step. +Feel free to volunteer :) + +### Create a method-level BOM + +Create a file `vulas-python.cfg` in the project's root folder. It must contain the following information, further configuration settings can be added if necessary. + +```ini +vulas.shared.backend.serviceUrl = http:/localhost:8033/backend +``` + +Then run the following command: + +```sh +python setup.py app +``` diff --git a/plugin-setuptools/setup.cfg b/plugin-setuptools/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/plugin-setuptools/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/plugin-setuptools/setup.py b/plugin-setuptools/setup.py new file mode 100644 index 000000000..292fb2999 --- /dev/null +++ b/plugin-setuptools/setup.py @@ -0,0 +1,40 @@ +from setuptools import setup, find_packages +import os +import sys + +# 'setup.py publish' shortcut. +if sys.argv[-1] == 'publish': + os.system('rm dist/*') + os.system('python setup.py bdist_wheel') + os.system('twine upload -r pypi dist/*') + sys.exit() + +setup( + name="vulnerability-assessment-tool-plugin-setuptools", + version="3.1.7", + packages=find_packages(), + + # Make sure to include the vulnerability-assessment-tool java CLI + package_data={ + # If any package contains *.txt or *.rst files, include them: + '': ['*.jar', '*.rst', '*.txt'] + }, + + # Starts the wrapper + entry_points={ + 'console_scripts': [ + 'vulas = vulas.wrapper:main' + ], + "distutils.commands": [ + "clean = vulas.clean_command:clean", + "cleanSpace = vulas.cleanSpace_command:cleanSpace", + "app = vulas.bom_command:bom", + "report = vulas.report_command:report" + ], + "distutils.setup_keywords": [ + "debug = vulas.bom_command:assert_bool" + ], + }, + + test_suite="vulas.tests.test_all" +) diff --git a/plugin-setuptools/vulas/__init__.py b/plugin-setuptools/vulas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugin-setuptools/vulas/bom_command.py b/plugin-setuptools/vulas/bom_command.py new file mode 100644 index 000000000..4c3be72f9 --- /dev/null +++ b/plugin-setuptools/vulas/bom_command.py @@ -0,0 +1,75 @@ +from distutils.core import Command +from vulas import cli_wrapper +import os + +class bom(Command): + + description = 'Runs the vulnerability-assessment-tool goal APP' + + user_options = [('debug', 'd', "call the Java CLI in debug mode")] + + boolean_options = ['debug'] + + app = {} + file = {} + + + def initialize_options(self): + self.debug = False + + def finalize_options(self): + assert self.debug in (None, True, False), 'True/False' + + def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + raise DistutilsSetupError( + "%r must be a boolean value (got %r)" % (attr, value) + ) + + def run(self): + print("Starting vulnerability-assessment-tool goal: APP") + + # Collect all arguments + args = {} + + # App identifier + for key in self.get_vulas_app().keys(): + args[key] = self.get_vulas_app()[key] + + # vulas-python.cfg + path = cli_wrapper.read_vulas_configuration_path() + conf = cli_wrapper.read_vulas_configuration(path) + for key in conf.keys(): + args[key] = conf[key] + + # Other + # src_dir = '' + # for p in self.distribution.packages: + # if not src_dir == '': + # src_dir += ',' + # src_dir += os.path.join(os.getcwd(), p) + + args['vulas.core.app.sourceDir'] = os.getcwd() + + # To prevent an exception in the JavaBomTask + args['vulas.core.app.appPrefixes'] = 'com.sap' + + print("Arguments:") + for key in args: + print(" " + key + " = " + args[key]) + + # Run the CLI + rc = cli_wrapper.run(args, "app", self.debug) + + if rc != True: + raise RuntimeError("Command line interface returned status code 1") + + def get_vulas_app(self): + if not self.app: + self.app = { + 'vulas.core.appContext.group':self.distribution.get_name(), + 'vulas.core.appContext.artifact':self.distribution.get_name(), + 'vulas.core.appContext.version':self.distribution.get_version() + } + return self.app diff --git a/plugin-setuptools/vulas/cleanSpace_command.py b/plugin-setuptools/vulas/cleanSpace_command.py new file mode 100644 index 000000000..c8a7b206d --- /dev/null +++ b/plugin-setuptools/vulas/cleanSpace_command.py @@ -0,0 +1,71 @@ +from distutils.core import Command +from vulas import cli_wrapper +import os + +class cleanSpace(Command): + + description = 'Runs the vulnerability-assessment-tool goal CLEANSPACE' + + user_options = [('debug', 'd', "call the Java CLI in debug mode")] + + boolean_options = ['debug'] + + app = {} + file = {} + + def initialize_options(self): + self.debug = False + + def finalize_options(self): + assert self.debug in (None, True, False), 'True/False' + + def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + raise DistutilsSetupError( + "%r must be a boolean value (got %r)" % (attr, value) + ) + + def run(self): + print("Starting vulnerability-assessment-tool goal: CLEANSPACE") + + # Collect all arguments + args = {} + + # App identifier + for key in self.get_vulas_app().keys(): + args[key] = self.get_vulas_app()[key] + + # vulas-python.cfg + path = cli_wrapper.read_vulas_configuration_path() + conf = cli_wrapper.read_vulas_configuration(path) + for key in conf.keys(): + args[key] = conf[key] + + # Other + # src_dir = '' + # for p in self.distribution.packages: + # if not src_dir == '': + # src_dir += ',' + # src_dir += os.path.join(os.getcwd(), p) + + args['vulas.core.app.sourceDir'] = os.getcwd() + + print("Arguments:") + for key in args: + print(" " + key + " = " + args[key]) + + # Run the CLI + rc = cli_wrapper.run(args, "cleanSpace", self.debug) + + if rc != True: + raise RuntimeError("Command line interface returned status code 1") + + def get_vulas_app(self): + if not self.app: + self.app = { + 'vulas.core.appContext.group':self.distribution.get_name(), + 'vulas.core.appContext.artifact':self.distribution.get_name(), + 'vulas.core.appContext.version':self.distribution.get_version() + } + return self.app diff --git a/plugin-setuptools/vulas/clean_command.py b/plugin-setuptools/vulas/clean_command.py new file mode 100644 index 000000000..1da3fb79e --- /dev/null +++ b/plugin-setuptools/vulas/clean_command.py @@ -0,0 +1,71 @@ +from distutils.core import Command +from vulas import cli_wrapper +import os + +class clean(Command): + + description = 'Runs the vulnerability-assessment-tool goal CLEAN' + + user_options = [('debug', 'd', "call the Java CLI in debug mode")] + + boolean_options = ['debug'] + + app = {} + file = {} + + def initialize_options(self): + self.debug = False + + def finalize_options(self): + assert self.debug in (None, True, False), 'True/False' + + def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + raise DistutilsSetupError( + "%r must be a boolean value (got %r)" % (attr, value) + ) + + def run(self): + print("Starting vulnerability-assessment-tool goal: CLEAN") + + # Collect all arguments + args = {} + + # App identifier + for key in self.get_vulas_app().keys(): + args[key] = self.get_vulas_app()[key] + + # vulas-python.cfg + path = cli_wrapper.read_vulas_configuration_path() + conf = cli_wrapper.read_vulas_configuration(path) + for key in conf.keys(): + args[key] = conf[key] + + # Other + # src_dir = '' + # for p in self.distribution.packages: + # if not src_dir == '': + # src_dir += ',' + # src_dir += os.path.join(os.getcwd(), p) + + args['vulas.core.app.sourceDir'] = os.getcwd() + + print("Arguments:") + for key in args: + print(" " + key + " = " + args[key]) + + # Run the CLI + rc = cli_wrapper.run(args, "clean", self.debug) + + if rc != True: + raise RuntimeError("Command line interface returned status code 1") + + def get_vulas_app(self): + if not self.app: + self.app = { + 'vulas.core.appContext.group':self.distribution.get_name(), + 'vulas.core.appContext.artifact':self.distribution.get_name(), + 'vulas.core.appContext.version':self.distribution.get_version() + } + return self.app diff --git a/plugin-setuptools/vulas/cli-scanner-latest-jar-with-dependencies.jar b/plugin-setuptools/vulas/cli-scanner-latest-jar-with-dependencies.jar new file mode 100644 index 000000000..0d0a526ea Binary files /dev/null and b/plugin-setuptools/vulas/cli-scanner-latest-jar-with-dependencies.jar differ diff --git a/plugin-setuptools/vulas/cli_wrapper.py b/plugin-setuptools/vulas/cli_wrapper.py new file mode 100644 index 000000000..3879d2fc9 --- /dev/null +++ b/plugin-setuptools/vulas/cli_wrapper.py @@ -0,0 +1,63 @@ +""" +Wraps the vulnerability-assessment-tool Java CLI. +""" + +import sys +import os + +CONFIG_FILE = "vulas-python.cfg" +wrapper_path = os.path.dirname(os.path.realpath(__file__)) +jar_path = os.path.join(wrapper_path, "cli-scanner-latest-jar-with-dependencies.jar") + +""" Checks whether the JAR of the Java CLI is available and executes. """ +def is_available(): + exists = os.path.isfile(jar_path) + exit_code = os.system(" ".join(("java", "-jar", jar_path))) + return exists and exit_code==0 + +""" Transforms the given dict into a list of Java system properties""" +def dict_to_java_sys_props(dict): + props = list() + for key in dict.keys(): + props.append('-D' + key + '="' + dict.get(key) + '"') + return props + +""" Runs the Java CLI, whereby the entries of the given dictionary are used as Java system properties. """ +def run(dict, goal, debug='off'): + cmd = list() + cmd.append("java") + + # Uncomment to debug the Java CLI + if(debug): + cmd.append("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000") + + for v in dict_to_java_sys_props(dict): + cmd.append(v) + for v in ["-jar", jar_path, "-goal", goal]: + cmd.append(v) + exit_code = os.system(" ".join(cmd)) + return exit_code==0 + +def read_vulas_configuration_path(): + cwd = os.getcwd() + config_path = os.path.join(cwd, CONFIG_FILE) + exists = os.path.isfile(config_path) + if not exists: + raise ValueError('Configuration file [' + CONFIG_FILE + "] not found in working directory [" + cwd + "]") + return config_path + +def read_vulas_configuration(path): + file = {} + with open(path) as f: + for line in f: + if not line.startswith('#') and not line.strip()== '': + (key, val) = line.split('=') + if not val.strip() == '': + file[key.strip()] = val.strip() + return file + +def main(): # needed for console script + os.system(run({})) + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/plugin-setuptools/vulas/report_command.py b/plugin-setuptools/vulas/report_command.py new file mode 100644 index 000000000..277d9f829 --- /dev/null +++ b/plugin-setuptools/vulas/report_command.py @@ -0,0 +1,71 @@ +from distutils.core import Command +from vulas import cli_wrapper +import os + +class report(Command): + + description = 'Runs the vulnerability-assessment-tool goal REPORT' + + user_options = [('debug', 'd', "call the Java CLI in debug mode")] + + boolean_options = ['debug'] + + app = {} + file = {} + + def initialize_options(self): + self.debug = False + + def finalize_options(self): + assert self.debug in (None, True, False), 'True/False' + + def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + raise DistutilsSetupError( + "%r must be a boolean value (got %r)" % (attr, value) + ) + + def run(self): + print("Starting vulnerability-assessment-tool goal: REPORT") + + # Collect all arguments + args = {} + + # App identifier + for key in self.get_vulas_app().keys(): + args[key] = self.get_vulas_app()[key] + + # vulas-python.cfg + path = cli_wrapper.read_vulas_configuration_path() + conf = cli_wrapper.read_vulas_configuration(path) + for key in conf.keys(): + args[key] = conf[key] + + # Other + # src_dir = '' + # for p in self.distribution.packages: + # if not src_dir == '': + # src_dir += ',' + # src_dir += os.path.join(os.getcwd(), p) + + args['vulas.core.app.sourceDir'] = os.getcwd() + + print("Arguments:") + for key in args: + print(" " + key + " = " + args[key]) + + # Run the CLI + rc = cli_wrapper.run(args, "report", self.debug) + + if rc != True: + raise RuntimeError("Command line interface returned status code 1") + + def get_vulas_app(self): + if not self.app: + self.app = { + 'vulas.core.appContext.group':self.distribution.get_name(), + 'vulas.core.appContext.artifact':self.distribution.get_name(), + 'vulas.core.appContext.version':self.distribution.get_version() + } + return self.app diff --git a/plugin-setuptools/vulas/tests/__init__.py b/plugin-setuptools/vulas/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugin-setuptools/vulas/tests/test_all.py b/plugin-setuptools/vulas/tests/test_all.py new file mode 100644 index 000000000..810217ff4 --- /dev/null +++ b/plugin-setuptools/vulas/tests/test_all.py @@ -0,0 +1,25 @@ +import unittest +import os +from vulas import cli_wrapper +from vulas import bom_command + +class MyTestCase(unittest.TestCase): + + def test_jar_works(self): + self.assertEqual(True, cli_wrapper.is_available()) + + def test_sys_props(self): + dict = { + 'abc':'123', + 'xyz':'789' + } + props = cli_wrapper.dict_to_java_sys_props(dict) + self.assertEqual(set(['-Dabc=123', '-Dxyz=789']), set(props)) + + def test_cfg_file(self): + config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vulas-python-test.cfg") + file = cli_wrapper.read_vulas_configuration(config_path) + self.assertTrue(file) + +if __name__ == '__main__': + unittest.main() diff --git a/plugin-setuptools/vulas/tests/vulas-python-test.cfg b/plugin-setuptools/vulas/tests/vulas-python-test.cfg new file mode 100644 index 000000000..207be116d --- /dev/null +++ b/plugin-setuptools/vulas/tests/vulas-python-test.cfg @@ -0,0 +1,4 @@ +vulas.core.tenant.token = default +vulas.core.space.token = public +vulas.core.uploadEnabled = true +vulas.shared.backend.serviceUrl = http://localhost:8033/backend \ No newline at end of file