Allow creation of test profiles and switch config to yaml

This commit is contained in:
Travis McPeak 2014-09-03 12:58:20 -07:00
parent 854b124529
commit bb2f7cae4d
9 changed files with 156 additions and 28 deletions

View File

@ -43,8 +43,10 @@ Usage:
-h, --help show this help message and exit
-C CONTEXT_LINES, --context CONTEXT_LINES
number of context lines to print
-t TEST_CONFIG, --testconfig TEST_CONFIG
test config file (default: bandit.ini)
-f CONFIG_FILE, --configfile TEST_CONFIG
test config file (default: bandit.yaml)
-p PROFILE_NAME, --profile PROFILE_NAME
run using specified test profile
-l, --level results level filter
-o OUTPUT_FILE, --output OUTPUT_FILE
write report to filename

View File

@ -1,17 +0,0 @@
[Import]
import_name_match = test_imports
import_name_telnetlib = test_imports
[ImportFrom]
import_name_match = test_imports
[Call]
call_bad_names = test_calls
call_subprocess_popen = test_calls
call_shell_true = test_calls
call_no_cert_validation = test_calls
call_bad_permissions = test_calls
call_wildcard_injection = test_calls
[Str]
str_hardcoded_bind_all_interfaces = test_strs

6
bandit.yaml Normal file
View File

@ -0,0 +1,6 @@
profiles:
ShellInjection:
include:
- call_subprocess_popen
- call_shell_true
exclude:

View File

@ -14,6 +14,7 @@
#    License for the specific language governing permissions and limitations
#    under the License.
import config
import context
import manager
import meta_ast

48
bandit/config.py Normal file
View File

@ -0,0 +1,48 @@
# -*- 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 sys
import yaml
class BanditConfig():
_config = dict()
def __init__(self, logger, config_file):
'''
Attempt to initialize a config dictionary from a yaml file, error out
if this fails for any reason.
:param logger: Logger to be used in the case of errors
:param config_file: The Bandit yaml config file
:return: -
'''
try:
f = open(config_file, 'r')
except IOError:
logger.error("could not open config file: %s" % config_file)
sys.exit(2)
else:
# yaml parser does its own exception handling
self._config = yaml.load(f)
@property
def config(self):
'''
Property to return the config dictionary
:return: Config dictionary
'''
return self._config

View File

@ -17,6 +17,7 @@
import _ast
from bandit import utils
class Context():
def __init__(self, context_object=None):
'''

View File

@ -18,6 +18,7 @@
import sys
import logging
import ast
from bandit import config as b_config
from bandit import result_store as b_result_store
from bandit import node_visitor as b_node_visitor
from bandit import test_set as b_test_set
@ -29,11 +30,24 @@ class BanditManager():
scope = []
progress = 50
def __init__(self, test_config, debug=False):
def __init__(self, config_file, debug=False, profile_name=None):
self.logger = self._init_logger(debug)
self.b_ma = b_meta_ast.BanditMetaAst(self.logger)
self.b_rs = b_result_store.BanditResultStore(self.logger)
self.b_ts = b_test_set.BanditTestSet(self.logger, test_config)
self.b_conf = b_config.BanditConfig(self.logger, config_file)
# if the profile name was specified, try to find it in the config
if profile_name:
if profile_name in self.b_conf.config['profiles']:
profile = self.b_conf.config['profiles'][profile_name]
else:
self.logger.error('unable to find profile (%s) in config file: '
'%s' % (profile_name, config_file))
sys.exit(2)
else:
profile = None
self.b_ts = b_test_set.BanditTestSet(self.logger, profile=profile)
def get_logger(self):
return self.logger

View File

@ -15,6 +15,7 @@
#    under the License.
import copy
import sys
from collections import OrderedDict
import glob
@ -22,14 +23,78 @@ from inspect import getmembers, isfunction
import importlib
class BanditTestSet():
tests = OrderedDict()
def __init__(self, logger, test_config):
def __init__(self, logger, profile=None):
self.logger = logger
self.load_tests()
filter_list = self._filter_list_from_config(profile=profile)
self.load_tests(filter=filter_list)
def _filter_list_from_config(self, profile=None):
# will create an (include,exclude) list tuple from a specified name config
# section
# if a profile isn't set, there is nothing to do here
if not profile:
return_tuple = ([],[])
return return_tuple
# an empty include list means that all are included
include_list = []
# profile needs to be a dict, include needs to be an element in profile,
# include needs to be a list, and 'all' is not in include
if(isinstance(profile, dict) and 'include' in profile and
isinstance(profile['include'], list) and
not 'all' in profile['include']):
# there is a list of specific includes, add them to the include list
for inc in profile['include']:
include_list.append(inc)
# an empty exclude list means that none are excluded, an exclude list with
# 'all' means that all are excluded. Specifically named excludes are
# subtracted from the include list.
exclude_list = []
if(isinstance(profile, dict) and 'exclude' in profile and
isinstance(profile['exclude'], list)):
# it's a list, exclude specific tests
for exc in profile['exclude']:
exclude_list.append(exc)
return_tuple = (include_list, exclude_list)
return return_tuple
def _filter_tests(self, filter):
'''
Filters the test set according to the filter tuple which contains
include and exclude lists.
:param filter: Include, exclude lists tuple
:return: -
'''
include_list = filter[0]
exclude_list = filter[1]
# copy of tests dictionary for removing tests from
temp_dict = copy.deepcopy(self.tests)
# if the include list is empty, we don't have to do anything, if it
# isn't, we need to remove all tests except the ones in the list
if include_list:
for check_type in self.tests:
for test_name in self.tests[check_type]:
if test_name not in include_list:
del temp_dict[check_type][test_name]
# remove the items specified in exclude list
if exclude_list:
for check_type in self.tests:
for test_name in self.tests[check_type]:
if test_name in exclude_list:
del temp_dict[check_type][test_name]
# copy tests back over from temp copy
self.tests = copy.deepcopy(temp_dict)
def _get_decorators_list(self):
'''
@ -48,7 +113,7 @@ class BanditTestSet():
return_list.append(d[0])
return return_list
def load_tests(self):
def load_tests(self, filter=None):
'''
Loads all tests from the plugins directory and puts them into the tests
dictionary.
@ -103,6 +168,8 @@ class BanditTestSet():
self.tests[check] = {}
self.tests[check][function_name] = function
self._filter_tests(filter)
def get_tests(self, checktype):
'''
Returns all tests that are of type checktype

12
main.py
View File

@ -20,7 +20,7 @@ import sys
import argparse
from bandit import manager as b_manager
default_test_config = 'bandit.ini'
default_test_config = 'bandit.yaml'
if __name__ == '__main__':
parser = argparse.ArgumentParser(
@ -36,12 +36,17 @@ if __name__ == '__main__':
help='number of context lines to print'
)
parser.add_argument(
'-t', '--testconfig', dest='test_config',
'-f', '--configfile', dest='config_file',
action='store', default=default_test_config, type=str,
help='test config file (default: %s)' % (
default_test_config
)
)
parser.add_argument(
'-p', '--profile', dest='profile',
action='store', default=None, type=str,
help='test set profile in config to use'
)
parser.add_argument(
'-l', '--level', dest='level', action='count',
default=1, help='results level filter'
@ -58,7 +63,8 @@ if __name__ == '__main__':
args = parser.parse_args()
b_mgr = b_manager.BanditManager(args.test_config, args.debug)
b_mgr = b_manager.BanditManager(args.config_file, args.debug,
profile_name=args.profile)
b_mgr.run_scope(args.files)
if args.debug:
b_mgr.output_metaast()