mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-26 14:50:36 +00:00
405 lines
14 KiB
Python
405 lines
14 KiB
Python
|
# -*- coding:utf-8 -*-
|
||
|
#
|
||
|
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||
|
# not use this file except in compliance with the License. You may obtain
|
||
|
# a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
# License for the specific language governing permissions and limitations
|
||
|
# under the License.
|
||
|
import argparse
|
||
|
import fnmatch
|
||
|
import logging
|
||
|
import os
|
||
|
import sys
|
||
|
import textwrap
|
||
|
|
||
|
|
||
|
import bandit
|
||
|
from bandit.core import config as b_config
|
||
|
from bandit.core import constants
|
||
|
from bandit.core import manager as b_manager
|
||
|
from bandit.core import utils
|
||
|
|
||
|
|
||
|
BASE_CONFIG = 'bandit.yaml'
|
||
|
LOG = logging.getLogger()
|
||
|
|
||
|
|
||
|
def _init_logger(debug=False, log_format=None):
|
||
|
'''Initialize the logger
|
||
|
|
||
|
:param debug: Whether to enable debug mode
|
||
|
:return: An instantiated logging instance
|
||
|
'''
|
||
|
LOG.handlers = []
|
||
|
log_level = logging.INFO
|
||
|
if debug:
|
||
|
log_level = logging.DEBUG
|
||
|
|
||
|
if not log_format:
|
||
|
# default log format
|
||
|
log_format_string = constants.log_format_string
|
||
|
else:
|
||
|
log_format_string = log_format
|
||
|
|
||
|
logging.captureWarnings(True)
|
||
|
|
||
|
LOG.setLevel(log_level)
|
||
|
handler = logging.StreamHandler(sys.stderr)
|
||
|
handler.setFormatter(logging.Formatter(log_format_string))
|
||
|
LOG.addHandler(handler)
|
||
|
LOG.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:
|
||
|
LOG.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]
|
||
|
LOG.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:
|
||
|
LOG.info("Using command line arg for %s", option_name)
|
||
|
return arg_val
|
||
|
elif ini_val:
|
||
|
LOG.info("Using ini file for %s", option_name)
|
||
|
return ini_val
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _running_under_virtualenv():
|
||
|
if hasattr(sys, 'real_prefix'):
|
||
|
return True
|
||
|
elif sys.prefix != getattr(sys, 'base_prefix', sys.prefix):
|
||
|
return True
|
||
|
|
||
|
|
||
|
def _get_profile(config, profile_name, config_path):
|
||
|
profile = {}
|
||
|
if profile_name:
|
||
|
profiles = config.get_option('profiles') or {}
|
||
|
profile = profiles.get(profile_name)
|
||
|
if profile is None:
|
||
|
raise utils.ProfileNotFound(config_path, profile_name)
|
||
|
LOG.debug("read in legacy profile '%s': %s", profile_name, profile)
|
||
|
else:
|
||
|
profile['include'] = set(config.get_option('tests') or [])
|
||
|
profile['exclude'] = set(config.get_option('skips') or [])
|
||
|
return profile
|
||
|
|
||
|
|
||
|
def _log_info(args, profile):
|
||
|
inc = ",".join([t for t in profile['include']]) or "None"
|
||
|
exc = ",".join([t for t in profile['exclude']]) or "None"
|
||
|
LOG.info("profile include tests: %s", inc)
|
||
|
LOG.info("profile exclude tests: %s", exc)
|
||
|
LOG.info("cli include tests: %s", args.tests)
|
||
|
LOG.info("cli exclude tests: %s", args.skips)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
# bring our logging stuff up as early as possible
|
||
|
debug = ('-d' in sys.argv or '--debug' in sys.argv)
|
||
|
_init_logger(debug)
|
||
|
extension_mgr = _init_extensions()
|
||
|
|
||
|
baseline_formatters = [f.name for f in filter(lambda x:
|
||
|
hasattr(x.plugin,
|
||
|
'_accepts_baseline'),
|
||
|
extension_mgr.formatters)]
|
||
|
|
||
|
# now do normal startup
|
||
|
parser = argparse.ArgumentParser(
|
||
|
description='Bandit - a Python source code security analyzer',
|
||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'targets', metavar='targets', type=str, nargs='*',
|
||
|
help='source file(s) or directory(s) to be tested'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-r', '--recursive', dest='recursive',
|
||
|
action='store_true', help='find and process files in subdirectories'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-a', '--aggregate', dest='agg_type',
|
||
|
action='store', default='file', type=str,
|
||
|
choices=['file', 'vuln'],
|
||
|
help='aggregate output by vulnerability (default) or by filename'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-n', '--number', dest='context_lines',
|
||
|
action='store', default=3, type=int,
|
||
|
help='maximum number of code lines to output for each issue'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-c', '--configfile', dest='config_file',
|
||
|
action='store', default=None, type=str,
|
||
|
help='optional config file to use for selecting plugins and '
|
||
|
'overriding defaults'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-p', '--profile', dest='profile',
|
||
|
action='store', default=None, type=str,
|
||
|
help='profile to use (defaults to executing all tests)'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-t', '--tests', dest='tests',
|
||
|
action='store', default=None, type=str,
|
||
|
help='comma-separated list of test IDs to run'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-s', '--skip', dest='skips',
|
||
|
action='store', default=None, type=str,
|
||
|
help='comma-separated list of test IDs to skip'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-l', '--level', dest='severity', action='count',
|
||
|
default=1, help='report only issues of a given severity level or '
|
||
|
'higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-i', '--confidence', dest='confidence', action='count',
|
||
|
default=1, help='report only issues of a given confidence level or '
|
||
|
'higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)'
|
||
|
)
|
||
|
output_format = 'screen' if sys.stdout.isatty() else 'txt'
|
||
|
parser.add_argument(
|
||
|
'-f', '--format', dest='output_format', action='store',
|
||
|
default=output_format, help='specify output format',
|
||
|
choices=sorted(extension_mgr.formatter_names)
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--msg-template', action='store',
|
||
|
default=None, help='specify output message template'
|
||
|
' (only usable with --format custom),'
|
||
|
' see CUSTOM FORMAT section'
|
||
|
' for list of available values',
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-o', '--output', dest='output_file', action='store', nargs='?',
|
||
|
type=argparse.FileType('w'), default=sys.stdout,
|
||
|
help='write report to filename'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-v', '--verbose', dest='verbose', action='store_true',
|
||
|
help='output extra information like excluded and included files'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-d', '--debug', dest='debug', action='store_true',
|
||
|
help='turn on debug mode'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--ignore-nosec', dest='ignore_nosec', action='store_true',
|
||
|
help='do not skip lines with # nosec comments'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-x', '--exclude', dest='excluded_paths', action='store',
|
||
|
default='', help='comma-separated list of paths to exclude from scan '
|
||
|
'(note that these are in addition to the excluded '
|
||
|
'paths provided in the config file)'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'-b', '--baseline', dest='baseline', action='store',
|
||
|
default=None, help='path of a baseline report to compare against '
|
||
|
'(only JSON-formatted files are accepted)'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--ini', dest='ini_path', action='store', default=None,
|
||
|
help='path to a .bandit file that supplies command line arguments'
|
||
|
)
|
||
|
python_ver = sys.version.replace('\n', '')
|
||
|
parser.add_argument(
|
||
|
'--version', action='version',
|
||
|
version='%(prog)s {version}\n python version = {python}'.format(
|
||
|
version=bandit.__version__, python=python_ver)
|
||
|
)
|
||
|
|
||
|
parser.set_defaults(debug=False)
|
||
|
parser.set_defaults(verbose=False)
|
||
|
parser.set_defaults(ignore_nosec=False)
|
||
|
|
||
|
plugin_info = ["%s\t%s" % (a[0], a[1].name) for a in
|
||
|
extension_mgr.plugins_by_id.items()]
|
||
|
blacklist_info = []
|
||
|
for a in extension_mgr.blacklist.items():
|
||
|
for b in a[1]:
|
||
|
blacklist_info.append('%s\t%s' % (b['id'], b['name']))
|
||
|
|
||
|
plugin_list = '\n\t'.join(sorted(set(plugin_info + blacklist_info)))
|
||
|
dedent_text = textwrap.dedent('''
|
||
|
CUSTOM FORMATTING
|
||
|
-----------------
|
||
|
|
||
|
Available tags:
|
||
|
|
||
|
{abspath}, {relpath}, {line}, {test_id},
|
||
|
{severity}, {msg}, {confidence}, {range}
|
||
|
|
||
|
Example usage:
|
||
|
|
||
|
Default template:
|
||
|
bandit -r examples/ --format custom --msg-template \\
|
||
|
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
|
||
|
|
||
|
Provides same output as:
|
||
|
bandit -r examples/ --format custom
|
||
|
|
||
|
Tags can also be formatted in python string.format() style:
|
||
|
bandit -r examples/ --format custom --msg-template \\
|
||
|
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
|
||
|
|
||
|
See python documentation for more information about formatting style:
|
||
|
https://docs.python.org/3.4/library/string.html
|
||
|
|
||
|
The following tests were discovered and loaded:
|
||
|
-----------------------------------------------
|
||
|
''')
|
||
|
parser.epilog = dedent_text + "\t{0}".format(plugin_list)
|
||
|
|
||
|
# setup work - parse arguments, and initialize BanditManager
|
||
|
args = parser.parse_args()
|
||
|
# Check if `--msg-template` is not present without custom formatter
|
||
|
if args.output_format != 'custom' and args.msg_template is not None:
|
||
|
parser.error("--msg-template can only be used with --format=custom")
|
||
|
|
||
|
try:
|
||
|
b_conf = b_config.BanditConfig(config_file=args.config_file)
|
||
|
except utils.ConfigError as e:
|
||
|
LOG.error(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')
|
||
|
ini_targets = ini_options.get('targets')
|
||
|
if ini_targets:
|
||
|
ini_targets = ini_targets.split(',')
|
||
|
args.targets = _log_option_source(args.targets, ini_targets,
|
||
|
'selected targets')
|
||
|
# TODO(tmcpeak): any other useful options to pass from .bandit?
|
||
|
|
||
|
if not args.targets:
|
||
|
LOG.error("No targets found in CLI or ini files, exiting.")
|
||
|
sys.exit(2)
|
||
|
# 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')
|
||
|
_init_logger(debug, log_format=log_format)
|
||
|
|
||
|
try:
|
||
|
profile = _get_profile(b_conf, args.profile, args.config_file)
|
||
|
_log_info(args, profile)
|
||
|
|
||
|
profile['include'].update(args.tests.split(',') if args.tests else [])
|
||
|
profile['exclude'].update(args.skips.split(',') if args.skips else [])
|
||
|
extension_mgr.validate_profile(profile)
|
||
|
|
||
|
except (utils.ProfileNotFound, ValueError) as e:
|
||
|
LOG.error(e)
|
||
|
sys.exit(2)
|
||
|
|
||
|
b_mgr = b_manager.BanditManager(b_conf, args.agg_type, args.debug,
|
||
|
profile=profile, verbose=args.verbose,
|
||
|
ignore_nosec=args.ignore_nosec)
|
||
|
|
||
|
if args.baseline is not None:
|
||
|
try:
|
||
|
with open(args.baseline) as bl:
|
||
|
data = bl.read()
|
||
|
b_mgr.populate_baseline(data)
|
||
|
except IOError:
|
||
|
LOG.warning("Could not open baseline report: %s", args.baseline)
|
||
|
sys.exit(2)
|
||
|
|
||
|
if args.output_format not in baseline_formatters:
|
||
|
LOG.warning('Baseline must be used with one of the following '
|
||
|
'formats: ' + str(baseline_formatters))
|
||
|
sys.exit(2)
|
||
|
|
||
|
if args.output_format != "json":
|
||
|
if args.config_file:
|
||
|
LOG.info("using config: %s", args.config_file)
|
||
|
|
||
|
LOG.info("running on Python %d.%d.%d", sys.version_info.major,
|
||
|
sys.version_info.minor, sys.version_info.micro)
|
||
|
|
||
|
# initiate file discovery step within Bandit Manager
|
||
|
b_mgr.discover_files(args.targets, args.recursive, args.excluded_paths)
|
||
|
|
||
|
if not b_mgr.b_ts.tests:
|
||
|
LOG.error('No tests would be run, please check the profile.')
|
||
|
sys.exit(2)
|
||
|
|
||
|
# initiate execution of tests within Bandit Manager
|
||
|
b_mgr.run_tests()
|
||
|
LOG.debug(b_mgr.b_ma)
|
||
|
LOG.debug(b_mgr.metrics)
|
||
|
|
||
|
# trigger output of results by Bandit Manager
|
||
|
sev_level = constants.RANKING[args.severity - 1]
|
||
|
conf_level = constants.RANKING[args.confidence - 1]
|
||
|
b_mgr.output_results(args.context_lines,
|
||
|
sev_level,
|
||
|
conf_level,
|
||
|
args.output_file,
|
||
|
args.output_format,
|
||
|
args.msg_template)
|
||
|
|
||
|
# return an exit code of 1 if there are results, 0 otherwise
|
||
|
if b_mgr.results_count(sev_filter=sev_level, conf_filter=conf_level) > 0:
|
||
|
sys.exit(1)
|
||
|
else:
|
||
|
sys.exit(0)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|