mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-26 23:00:36 +00:00
225 lines
7.5 KiB
Python
225 lines
7.5 KiB
Python
|
# -*- coding:utf-8 -*-
|
||
|
#
|
||
|
# Copyright 2015 Hewlett-Packard Enterprise
|
||
|
#
|
||
|
# 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.
|
||
|
|
||
|
# #############################################################################
|
||
|
# Bandit Baseline is a tool that runs Bandit against a Git commit, and compares
|
||
|
# the current commit findings to the parent commit findings.
|
||
|
|
||
|
# To do this it checks out the parent commit, runs Bandit (with any provided
|
||
|
# filters or profiles), checks out the current commit, runs Bandit, and then
|
||
|
# reports on any new findings.
|
||
|
# #############################################################################
|
||
|
|
||
|
import argparse
|
||
|
import contextlib
|
||
|
import logging
|
||
|
import os
|
||
|
import shutil
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tempfile
|
||
|
|
||
|
import git
|
||
|
|
||
|
bandit_args = sys.argv[1:]
|
||
|
baseline_tmp_file = '_bandit_baseline_run.json_'
|
||
|
current_commit = None
|
||
|
default_output_format = 'terminal'
|
||
|
LOG = logging.getLogger(__name__)
|
||
|
repo = None
|
||
|
report_basename = 'bandit_baseline_result'
|
||
|
valid_baseline_formats = ['txt', 'html', 'json']
|
||
|
|
||
|
|
||
|
def main():
|
||
|
# our cleanup function needs this and can't be passed arguments
|
||
|
global current_commit
|
||
|
global repo
|
||
|
|
||
|
parent_commit = None
|
||
|
output_format = None
|
||
|
repo = None
|
||
|
report_fname = None
|
||
|
|
||
|
init_logger()
|
||
|
|
||
|
output_format, repo, report_fname = initialize()
|
||
|
|
||
|
if not repo:
|
||
|
sys.exit(2)
|
||
|
|
||
|
# #################### Find current and parent commits ####################
|
||
|
try:
|
||
|
commit = repo.commit()
|
||
|
current_commit = commit.hexsha
|
||
|
LOG.info('Got current commit: [%s]', commit.name_rev)
|
||
|
|
||
|
commit = commit.parents[0]
|
||
|
parent_commit = commit.hexsha
|
||
|
LOG.info('Got parent commit: [%s]', commit.name_rev)
|
||
|
|
||
|
except git.GitCommandError:
|
||
|
LOG.error("Unable to get current or parent commit")
|
||
|
sys.exit(2)
|
||
|
except IndexError:
|
||
|
LOG.error("Parent commit not available")
|
||
|
sys.exit(2)
|
||
|
|
||
|
# #################### Run Bandit against both commits ####################
|
||
|
output_type = (['-f', 'txt'] if output_format == default_output_format
|
||
|
else ['-o', report_fname])
|
||
|
|
||
|
with baseline_setup() as t:
|
||
|
|
||
|
bandit_tmpfile = "{}/{}".format(t, baseline_tmp_file)
|
||
|
|
||
|
steps = [{'message': 'Getting Bandit baseline results',
|
||
|
'commit': parent_commit,
|
||
|
'args': bandit_args + ['-f', 'json', '-o', bandit_tmpfile]},
|
||
|
|
||
|
{'message': 'Comparing Bandit results to baseline',
|
||
|
'commit': current_commit,
|
||
|
'args': bandit_args + ['-b', bandit_tmpfile] + output_type}]
|
||
|
|
||
|
return_code = None
|
||
|
|
||
|
for step in steps:
|
||
|
repo.head.reset(commit=step['commit'], working_tree=True)
|
||
|
|
||
|
LOG.info(step['message'])
|
||
|
|
||
|
bandit_command = ['bandit'] + step['args']
|
||
|
|
||
|
try:
|
||
|
output = subprocess.check_output(bandit_command)
|
||
|
except subprocess.CalledProcessError as e:
|
||
|
output = e.output
|
||
|
return_code = e.returncode
|
||
|
else:
|
||
|
return_code = 0
|
||
|
output = output.decode('utf-8') # subprocess returns bytes
|
||
|
|
||
|
if return_code not in [0, 1]:
|
||
|
LOG.error("Error running command: %s\nOutput: %s\n",
|
||
|
bandit_args, output)
|
||
|
|
||
|
# #################### Output and exit ####################################
|
||
|
# print output or display message about written report
|
||
|
if output_format == default_output_format:
|
||
|
print(output)
|
||
|
else:
|
||
|
LOG.info("Successfully wrote %s", report_fname)
|
||
|
|
||
|
# exit with the code the last Bandit run returned
|
||
|
sys.exit(return_code)
|
||
|
|
||
|
|
||
|
# #################### Clean up before exit ###################################
|
||
|
@contextlib.contextmanager
|
||
|
def baseline_setup():
|
||
|
d = tempfile.mkdtemp()
|
||
|
yield d
|
||
|
shutil.rmtree(d, True)
|
||
|
|
||
|
if repo:
|
||
|
repo.head.reset(commit=current_commit, working_tree=True)
|
||
|
|
||
|
|
||
|
# #################### Setup logging ##########################################
|
||
|
def init_logger():
|
||
|
LOG.handlers = []
|
||
|
log_level = logging.INFO
|
||
|
log_format_string = "[%(levelname)7s ] %(message)s"
|
||
|
logging.captureWarnings(True)
|
||
|
LOG.setLevel(log_level)
|
||
|
handler = logging.StreamHandler(sys.stdout)
|
||
|
handler.setFormatter(logging.Formatter(log_format_string))
|
||
|
LOG.addHandler(handler)
|
||
|
|
||
|
|
||
|
# #################### Perform initialization and validate assumptions ########
|
||
|
def initialize():
|
||
|
valid = True
|
||
|
|
||
|
# #################### Parse Args #########################################
|
||
|
parser = argparse.ArgumentParser(
|
||
|
description='Bandit Baseline - Generates Bandit results compared to "'
|
||
|
'a baseline',
|
||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
|
epilog='Additional Bandit arguments such as severity filtering (-ll) '
|
||
|
'can be added and will be passed to Bandit.'
|
||
|
)
|
||
|
|
||
|
parser.add_argument('targets', metavar='targets', type=str, nargs='+',
|
||
|
help='source file(s) or directory(s) to be tested')
|
||
|
|
||
|
parser.add_argument('-f', dest='output_format', action='store',
|
||
|
default='terminal', help='specify output format',
|
||
|
choices=valid_baseline_formats)
|
||
|
|
||
|
args, unknown = parser.parse_known_args()
|
||
|
|
||
|
# #################### Setup Output #######################################
|
||
|
# set the output format, or use a default if not provided
|
||
|
output_format = (args.output_format if args.output_format
|
||
|
else default_output_format)
|
||
|
|
||
|
if output_format == default_output_format:
|
||
|
LOG.info("No output format specified, using %s", default_output_format)
|
||
|
|
||
|
# set the report name based on the output format
|
||
|
report_fname = "{}.{}".format(report_basename, output_format)
|
||
|
|
||
|
# #################### Check Requirements #################################
|
||
|
try:
|
||
|
repo = git.Repo(os.getcwd())
|
||
|
|
||
|
except git.exc.InvalidGitRepositoryError:
|
||
|
LOG.error("Bandit baseline must be called from a git project root")
|
||
|
valid = False
|
||
|
|
||
|
except git.exc.GitCommandNotFound:
|
||
|
LOG.error("Git command not found")
|
||
|
valid = False
|
||
|
|
||
|
else:
|
||
|
if repo.is_dirty():
|
||
|
LOG.error("Current working directory is dirty and must be "
|
||
|
"resolved")
|
||
|
valid = False
|
||
|
|
||
|
# if output format is specified, we need to be able to write the report
|
||
|
if output_format != default_output_format and os.path.exists(report_fname):
|
||
|
LOG.error("File %s already exists, aborting", report_fname)
|
||
|
valid = False
|
||
|
|
||
|
# Bandit needs to be able to create this temp file
|
||
|
if os.path.exists(baseline_tmp_file):
|
||
|
LOG.error("Temporary file %s needs to be removed prior to running",
|
||
|
baseline_tmp_file)
|
||
|
valid = False
|
||
|
|
||
|
# we must validate -o is not provided, as it will mess up Bandit baseline
|
||
|
if '-o' in bandit_args:
|
||
|
LOG.error("Bandit baseline must not be called with the -o option")
|
||
|
valid = False
|
||
|
|
||
|
return (output_format, repo, report_fname) if valid else (None, None, None)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|