Allow certain command line arguments to be passed from file

This commit allows the use of a .bandit file, which will be in
the ini format and used to pass command line arguments.  This is
useful for multiple projects which are running in the same gate,
want to be able to exclude different files per-project, and
aren't using tox.

Change-Id: I4256bdb7df2416f3cc01798882fb7e2e229790a3
This commit is contained in:
Travis McPeak 2016-01-21 19:00:38 -08:00 committed by Travis McPeak
parent 0959ab5312
commit 36f316ab82
4 changed files with 125 additions and 0 deletions

View File

@ -108,6 +108,8 @@ Usage::
Path to a baseline report, in JSON format. Note:
baseline reports must be output in one of the
following formats: ['txt', 'html']
--ini INI_PATH Path to a .bandit file which supplies command line
arguments to Bandit.
The following plugin suites were discovered and loaded:
any_other_function_with_shell_equals_true
@ -172,6 +174,29 @@ Mac OSX:
- /usr/local/etc/bandit/bandit.yaml
- <path to venv>/bandit/config/bandit.yaml (if running within virtualenv)
Per Project Command Line Args
-----------------------------
Projects may include a `.bandit` file that specifies command line arguments
that should be supplied for that project. The currently supported arguments
are:
- exclude: comma separated list of excluded paths
- skips: comma separated list of tests to skip
- tests: comma separated list of tests to run
To use this, put a .bandit file in your project's directory. For example:
::
[bandit]
exclude: /test
::
[bandit]
tests: B101,B102,B301
Exclusions
----------
In the event that a line of code triggers a Bandit issue, but that the line
@ -223,6 +248,7 @@ To write a test:
vulnerability might present itself and extend the example file and the test
function accordingly.
Extending Bandit
----------------

View File

@ -16,6 +16,7 @@
from __future__ import absolute_import
import argparse
import fnmatch
import logging
import os
import sys
@ -59,11 +60,54 @@ def _init_logger(debug=False, log_format=None):
logger.debug("logging initialized")
def _get_options_from_ini(ini_path, target):
"""Return a dictionary of config options or None if we can't load any."""
ini_file = None
if ini_path:
ini_file = ini_path
else:
bandit_files = []
for t in target:
for root, dirnames, filenames in os.walk(t):
for filename in fnmatch.filter(filenames, '.bandit'):
bandit_files.append(os.path.join(root, filename))
if len(bandit_files) > 1:
logger.error('Multiple .bandit files found - scan separately or '
'choose one with --ini\n\t%s',
', '.join(bandit_files))
sys.exit(2)
elif len(bandit_files) == 1:
ini_file = bandit_files[0]
logger.info('Found project level .bandit file: %s',
bandit_files[0])
if ini_file:
return utils.parse_ini_file(ini_file)
else:
return None
def _init_extensions():
from bandit.core import extension_loader as ext_loader
return ext_loader.MANAGER
def _log_option_source(arg_val, ini_val, option_name):
"""It's useful to show the source of each option."""
if arg_val:
logger.info("Using command line arg for %s", option_name)
return arg_val
elif ini_val:
logger.info("Using .bandit arg for %s", option_name)
return ini_val
else:
return None
def _running_under_virtualenv():
if hasattr(sys, 'real_prefix'):
return True
@ -192,6 +236,11 @@ def main():
'Note: baseline reports must be output in one of '
'the following formats: ' + str(baseline_formatters)
)
parser.add_argument(
'--ini', dest='ini_path', action='store', default=None,
help='Path to a .bandit file which supplies command line arguments to '
'Bandit.'
)
parser.set_defaults(debug=False)
parser.set_defaults(verbose=False)
parser.set_defaults(ignore_nosec=False)
@ -216,6 +265,21 @@ def main():
logger.error('%s', e)
sys.exit(2)
# Handle .bandit files in projects to pass cmdline args from file
ini_options = _get_options_from_ini(args.ini_path, args.targets)
if ini_options:
# prefer command line, then ini file
args.excluded_paths = _log_option_source(args.excluded_paths,
ini_options.get('exclude'),
'excluded paths')
args.skips = _log_option_source(args.skips, ini_options.get('skips'),
'skipped tests')
args.tests = _log_option_source(args.tests, ini_options.get('tests'),
'selected tests')
# TODO(tmcpeak): any other useful options to pass from .bandit?
# if the log format string was set in the options, reinitialize
if b_conf.get_option('log_format'):
log_format = b_conf.get_option('log_format')

View File

@ -21,6 +21,10 @@ import logging
import os.path
import sys
try:
import configparser
except ImportError:
import ConfigParser as configparser
logger = logging.getLogger(__name__)
@ -329,3 +333,16 @@ def get_path_for_function(f):
else:
logger.warn("Cannot resolve file path for module %s", module_name)
return None
def parse_ini_file(f_loc):
config = configparser.ConfigParser()
try:
config.read(f_loc)
return {k: v for k, v in config.items('bandit')}
except (configparser.Error, KeyError, TypeError):
logger.warning("Unable to parse config file %s or missing [bandit] "
"section", f_loc)
return None

View File

@ -272,3 +272,21 @@ class UtilTests(testtools.TestCase):
self.assertEqual('deep value', b_utils.deepgetattr(a, 'b.c.d'))
self.assertEqual('deep value 2', b_utils.deepgetattr(a, 'b.c.d2'))
self.assertRaises(AttributeError, b_utils.deepgetattr, a.b, 'z')
def test_parse_ini_file(self):
tests = [{'content': "[bandit]\nexclude=/abc,/def",
'expected': {'exclude': '/abc,/def'}},
{'content': '[Blabla]\nsomething=something',
'expected': None}]
with tempfile.NamedTemporaryFile('r+') as t:
for test in tests:
f = open(t.name, 'w')
f.write(test['content'])
f.close()
self.assertEqual(b_utils.parse_ini_file(t.name),
test['expected'])