diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 00000000..a1a26468 --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,393 @@ +# Generated using bandit_conf_generator with the following configuration: +# profile_name: gate +# +profiles: + gate: + include: + - any_other_function_with_shell_equals_true + - assert_used + - blacklist_calls + - blacklist_import_func + - blacklist_imports + - exec_used + - execute_with_run_as_root_equals_true + - hardcoded_bind_all_interfaces + - hardcoded_password_string + - hardcoded_password_funcarg + - hardcoded_password_default + - hardcoded_sql_expressions + - hardcoded_tmp_directory + - jinja2_autoescape_false + - linux_commands_wildcard_injection + - paramiko_calls + - password_config_option_not_marked_secret + - request_with_no_cert_validation + - set_bad_file_permissions + - subprocess_popen_with_shell_equals_true + - subprocess_without_shell_equals_true + - start_process_with_a_shell + - start_process_with_no_shell + - start_process_with_partial_path + - ssl_with_bad_defaults + - ssl_with_bad_version + - ssl_with_no_version + - try_except_pass + - use_of_mako_templates + - weak_cryptographic_key + +exclude_dirs: +- /tests/ + +shell_injection: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - utils.execute + - utils.execute_with_timeout + +ssl_with_bad_version: + bad_protocol_versions: + - PROTOCOL_SSLv2 + - SSLv2_METHOD + - SSLv23_METHOD + - PROTOCOL_SSLv3 + - PROTOCOL_TLSv1 + - SSLv3_METHOD + - TLSv1_METHOD + +try_except_pass: + check_typed_exception: true + +plugin_name_pattern: '*.py' + +profiles: + gate: + include: + - any_other_function_with_shell_equals_true + - assert_used + - blacklist_calls + - blacklist_import_func + - blacklist_imports + - exec_used + - execute_with_run_as_root_equals_true + - hardcoded_bind_all_interfaces + - hardcoded_password_string + - hardcoded_password_funcarg + - hardcoded_password_default + - hardcoded_sql_expressions + - hardcoded_tmp_directory + - jinja2_autoescape_false + - linux_commands_wildcard_injection + - paramiko_calls + - password_config_option_not_marked_secret + - request_with_no_cert_validation + - set_bad_file_permissions + - subprocess_popen_with_shell_equals_true + - subprocess_without_shell_equals_true + - start_process_with_a_shell + - start_process_with_no_shell + - start_process_with_partial_path + - ssl_with_bad_defaults + - ssl_with_bad_version + - ssl_with_no_version + - try_except_pass + - use_of_mako_templates + - weak_cryptographic_key + +blacklist_calls: + bad_name_sets: + - pickle: + message: 'Pickle library appears to be in use, possible security issue. + + ' + qualnames: + - pickle.loads + - pickle.load + - pickle.Unpickler + - cPickle.loads + - cPickle.load + - cPickle.Unpickler + - marshal: + message: 'Deserialization with the marshal module is possibly dangerous. + + ' + qualnames: + - marshal.load + - marshal.loads + - md5: + message: Use of insecure MD2, MD4, or MD5 hash function. + qualnames: + - hashlib.md5 + - Crypto.Hash.MD2.new + - Crypto.Hash.MD4.new + - Crypto.Hash.MD5.new + - cryptography.hazmat.primitives.hashes.MD5 + - ciphers: + level: HIGH + message: 'Use of insecure cipher {func}. Replace with a known secure cipher + such as AES. + + ' + qualnames: + - Crypto.Cipher.ARC2.new + - Crypto.Cipher.ARC4.new + - Crypto.Cipher.Blowfish.new + - Crypto.Cipher.DES.new + - Crypto.Cipher.XOR.new + - cryptography.hazmat.primitives.ciphers.algorithms.ARC4 + - cryptography.hazmat.primitives.ciphers.algorithms.Blowfish + - cryptography.hazmat.primitives.ciphers.algorithms.IDEA + - cipher_modes: + message: Use of insecure cipher mode {func}. + qualnames: + - cryptography.hazmat.primitives.ciphers.modes.ECB + - mktemp_q: + message: Use of insecure and deprecated function (mktemp). + qualnames: + - tempfile.mktemp + - eval: + message: 'Use of possibly insecure function - consider using safer ast.literal_eval. + + ' + qualnames: + - eval + - mark_safe: + message: 'Use of mark_safe() may expose cross-site scripting vulnerabilities + and should be reviewed. + + ' + names: + - mark_safe + - httpsconnection: + message: 'Use of HTTPSConnection does not provide security, see https://wiki.openstack.org/wiki/OSSN/OSSN-0033 + + ' + qualnames: + - httplib.HTTPSConnection + - http.client.HTTPSConnection + - six.moves.http_client.HTTPSConnection + - yaml_load: + message: 'Use of unsafe yaml load. Allows instantiation of arbitrary objects. + Consider yaml.safe_load(). + + ' + qualnames: + - yaml.load + - urllib_urlopen: + message: 'Audit url open for permitted schemes. Allowing use of file:/ or custom + schemes is often unexpected. + + ' + qualnames: + - urllib.urlopen + - urllib.request.urlopen + - urllib.urlretrieve + - urllib.request.urlretrieve + - urllib.URLopener + - urllib.request.URLopener + - urllib.FancyURLopener + - urllib.request.FancyURLopener + - urllib2.urlopen + - urllib2.Request + - six.moves.urllib.request.urlopen + - six.moves.urllib.request.urlretrieve + - six.moves.urllib.request.URLopener + - six.moves.urllib.request.FancyURLopener + - random: + level: LOW + message: 'Standard pseudo-random generators are not suitable for security/cryptographic + purposes. + + ' + qualnames: + - random.random + - random.randrange + - random.randint + - random.choice + - random.uniform + - random.triangular + - telnetlib: + level: HIGH + message: 'Telnet-related funtions are being called. Telnet is considered insecure. + Use SSH or some other encrypted protocol. + + ' + qualnames: + - telnetlib.* + - xml_bad_cElementTree: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.etree.cElementTree.parse + - xml.etree.cElementTree.iterparse + - xml.etree.cElementTree.fromstring + - xml.etree.cElementTree.XMLParser + - xml_bad_ElementTree: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.etree.ElementTree.parse + - xml.etree.ElementTree.iterparse + - xml.etree.ElementTree.fromstring + - xml.etree.ElementTree.XMLParser + - xml_bad_expatreader: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.sax.expatreader.create_parser + - xml_bad_expatbuilder: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.dom.expatbuilder.parse + - xml.dom.expatbuilder.parseString + - xml_bad_sax: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.sax.parse + - xml.sax.parseString + - xml.sax.make_parser + - xml_bad_minidom: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.dom.minidom.parse + - xml.dom.minidom.parseString + - xml_bad_pulldom: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - xml.dom.pulldom.parse + - xml.dom.pulldom.parseString + - xml_bad_etree: + message: 'Using {func} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {func} with its defusedxml equivalent function. + + ' + qualnames: + - lxml.etree.parse + - lxml.etree.fromstring + - lxml.etree.RestrictedElement + - lxml.etree.GlobalParserTLS + - lxml.etree.getDefaultParser + - lxml.etree.check_docinfo + +hardcoded_tmp_directory: + tmp_dirs: + - /tmp + - /var/tmp + - /dev/shm + +blacklist_imports: + bad_import_sets: + - telnet: + imports: + - telnetlib + level: HIGH + message: 'A telnet-related module is being imported. Telnet is considered insecure. + Use SSH or some other encrypted protocol. + + ' + - info_libs: + imports: + - pickle + - cPickle + - subprocess + - Crypto + level: LOW + message: 'Consider possible security implications associated with {module} module. + + ' + - xml_libs: + imports: + - xml.etree.cElementTree + - xml.etree.ElementTree + - xml.sax.expatreader + - xml.sax + - xml.dom.expatbuilder + - xml.dom.minidom + - xml.dom.pulldom + - lxml.etree + - lxml + level: LOW + message: 'Using {module} to parse untrusted XML data is known to be vulnerable + to XML attacks. Replace {module} with the equivalent defusedxml package. + + ' + - xml_libs_high: + imports: + - xmlrpclib + level: HIGH + message: 'Using {module} to parse untrusted XML data is known to be vulnerable + to XML attacks. Use defused.xmlrpc.monkey_patch() function to monkey-patch + xmlrpclib and mitigate XML vulnerabilities. + + ' + +include: +- '*.py' +- '*.pyw' + +password_config_option_not_marked_secret: + function_names: + - oslo.config.cfg.StrOpt + - oslo_config.cfg.StrOpt + +hardcoded_password: + word_list: '%(site_data_dir)s/wordlist/default-passwords' + +execute_with_run_as_root_equals_true: + function_names: + - ceilometer.utils.execute + - cinder.utils.execute + - neutron.agent.linux.utils.execute + - nova.utils.execute + - nova.utils.trycmd diff --git a/oslo_utils/encodeutils.py b/oslo_utils/encodeutils.py index 82ce6af7..7b87226d 100644 --- a/oslo_utils/encodeutils.py +++ b/oslo_utils/encodeutils.py @@ -124,7 +124,7 @@ def exception_to_unicode(exc): # the implicit decoding from the default encoding try: msg = exc.__unicode__() - except UnicodeError: + except UnicodeError: # nosec pass if msg is None: @@ -145,7 +145,7 @@ def exception_to_unicode(exc): # if the string is not a valid UTF-8 string: the UTF-8 codec includes # a validation algorithm to ensure the consistency of the codec. return msg.decode('utf-8') - except UnicodeDecodeError: + except UnicodeDecodeError: # nosec pass # Try the locale encoding, most error messages are encoded to this encoding @@ -153,7 +153,7 @@ def exception_to_unicode(exc): encoding = sys.getfilesystemencoding() try: return msg.decode(encoding) - except UnicodeDecodeError: + except UnicodeDecodeError: # nosec pass # The encoding is not ASCII, not UTF-8, nor the locale encoding. Fallback diff --git a/oslo_utils/reflection.py b/oslo_utils/reflection.py index 8b9b535e..330a537b 100644 --- a/oslo_utils/reflection.py +++ b/oslo_utils/reflection.py @@ -64,7 +64,7 @@ def get_class_name(obj, fully_qualified=True): obj = type(obj) try: built_in = obj.__module__ in _BUILTIN_MODULES - except AttributeError: + except AttributeError: # nosec pass else: if built_in: @@ -158,7 +158,7 @@ def is_same_callback(callback1, callback2, strict=True): self1 = six.get_method_self(callback1) self2 = six.get_method_self(callback2) return self1 is self2 - except AttributeError: + except AttributeError: # nosec pass return False diff --git a/oslo_utils/strutils.py b/oslo_utils/strutils.py index 2c643dca..02b6bdbd 100644 --- a/oslo_utils/strutils.py +++ b/oslo_utils/strutils.py @@ -212,7 +212,7 @@ def to_slug(value, incoming=None, errors="strict"): return SLUGIFY_HYPHENATE_RE.sub("-", value) -def mask_password(message, secret="***"): +def mask_password(message, secret="***"): # nosec """Replace password with *secret* in message. :param message: The string which includes security information. @@ -253,7 +253,7 @@ def mask_password(message, secret="***"): try: message = six.text_type(message) - except UnicodeDecodeError: + except UnicodeDecodeError: # nosec # NOTE(jecarey): Temporary fix to handle cases where message is a # byte string. A better solution will be provided in Kilo. pass diff --git a/oslo_utils/timeutils.py b/oslo_utils/timeutils.py index 47ceb5e9..d5c608af 100644 --- a/oslo_utils/timeutils.py +++ b/oslo_utils/timeutils.py @@ -217,7 +217,7 @@ def advance_time_delta(timedelta): See :py:class:`oslo_utils.fixture.TimeFixture`. """ - assert utcnow.override_time is not None + assert utcnow.override_time is not None # nosec try: for dt in utcnow.override_time: dt += timedelta @@ -450,7 +450,7 @@ class StopWatch(object): """Stops the watch (ignoring errors if stop fails).""" try: self.stop() - except RuntimeError: + except RuntimeError: # nosec: errors are meant to be ignored pass def leftover(self, return_none=False): diff --git a/test-requirements.txt b/test-requirements.txt index e544944c..2b6c5070 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -26,3 +26,6 @@ mock>=1.2 # used for oslotest cross-testing scripts oslo.config>=2.7.0 # Apache-2.0 + +# Bandit security code scanner +bandit>=0.13.2 diff --git a/tox.ini b/tox.ini index d7cecb00..b94b3213 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py34,py27,pep8 +envlist = py34,py27,pep8,bandit [testenv] deps = -r{toxinidir}/test-requirements.txt @@ -18,6 +18,10 @@ commands = python setup.py build_sphinx [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=oslo_utils --testr-args='{posargs}' +[testenv:bandit] +deps=-r{toxinidir}/test-requirements.txt +commands = bandit -c bandit.yaml -r oslo_utils -n5 -p gate + [flake8] ignore = E123,H405 show-source = True