Skip to content

Commit

Permalink
Merge pull request #218 from nccgroup/develop
Browse files Browse the repository at this point in the history
4.3.0 release
  • Loading branch information
zer0x64 committed Mar 7, 2019
2 parents 1b4b502 + 78aa117 commit ed25eee
Show file tree
Hide file tree
Showing 112 changed files with 1,209 additions and 3,000 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
@@ -1,7 +1,7 @@
include LICENSE
include README.md
include requirements.txt
recursive-include opinel/data *
recursive-include ScoutSuite/data *
recursive-include ScoutSuite/output/data *
recursive-include ScoutSuite/providers/aws *
recursive-include ScoutSuite/providers/aws/rules *
Expand Down
9 changes: 5 additions & 4 deletions README.md
Expand Up @@ -67,6 +67,8 @@ Scout Suite is written in Python and supports the following versions:
- 3.6
- 3.7

WARNING: Python 2.7 & 3.4 support will soon be deprecated in the following releases.

The required libraries can be found in the
[requirements.txt](https://github.com/nccgroup/ScoutSuite/blob/master/requirements.txt) file.

Expand All @@ -82,6 +84,9 @@ permissions:
- `ReadOnlyAccess`
- `SecurityAudit`

If the usage of multi-factor authentication (MFA) is enabled, please consult [this page](https://aws.amazon.com/premiumsupport/knowledge-center/authenticate-mfa-cli/) in order to
configure MFA authentication through the management of session tokens.

#### Google Cloud Platform

There are two ways to run Scout against a GCP Organization or Project.
Expand Down Expand Up @@ -188,10 +193,6 @@ to use with the following command:

$ python Scout.py aws --profile <PROFILE_NAME>

If you have a CSV file containing the API access key ID and secret, you may run Scout with the following command:

$ python Scout.py aws --csv-credentials <CREDENTIALS.CSV>

#### Google Cloud Platform

Using a computer already configured to use gcloud command-line tool, you may use Scout using the following command:
Expand Down
2 changes: 1 addition & 1 deletion ScoutSuite/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

__author__ = 'NCC Group'
__version__ = '4.2.post5'
__version__ = '4.3.0'

AWSCONFIG = 42
EXCEPTIONS = 4242
Expand Down
12 changes: 6 additions & 6 deletions ScoutSuite/__listall__.py
Expand Up @@ -7,7 +7,7 @@

try:
from opinel.utils.globals import check_requirements
from opinel.utils.console import configPrintException, printError, printException, printInfo
from ScoutSuite.core.console import config_debug_level, print_error, print_exception, print_info
except Exception as e:
print('Error: Scout2 depends on the opinel package. Install all the requirements with the following command:')
print(' $ pip install -r requirements.txt')
Expand All @@ -28,7 +28,7 @@

def main(args):
# Configure the debug level
configPrintException(args.debug)
config_debug_level(args.debug)

# FIXME check that all requirements are installed
# # Check version of opinel
Expand All @@ -46,8 +46,8 @@ def main(args):
aws_config = report.jsrw.load_from_file(AWSCONFIG)
services = aws_config['service_list']
except Exception as e:
printException(e)
printError('Error, failed to load the configuration for profile %s' % profile_name)
print_exception(e)
print_error('Error, failed to load the configuration for profile %s' % profile_name)
continue

# Create a ruleset with only whatever rules were specified...
Expand All @@ -68,7 +68,7 @@ def main(args):
f.write(json.dumps(rule_dict))
ruleset = TmpRuleset(rule_dirs=[os.getcwd()], rule_filename=rule_filename, rule_args=[])
else:
printError(
print_error(
'Error, you must provide either a rule configuration file or the path to the resources targeted.')
continue

Expand Down Expand Up @@ -115,4 +115,4 @@ def main(args):
(lines, template) = format_listall_output(args.format_file[0], None, args.format, rule)

# Print the output
printInfo(generate_listall_output(lines, resources, aws_config, template, []))
print_info(generate_listall_output(lines, resources, aws_config, template, []))
40 changes: 20 additions & 20 deletions ScoutSuite/__main__.py
Expand Up @@ -6,10 +6,10 @@
import os
import webbrowser

from opinel.utils.console import configPrintException, printInfo, printDebug
from opinel.utils.profiles import AWSProfiles
from ScoutSuite.core.console import config_debug_level, print_info, print_debug
from ScoutSuite.providers.aws.profiles import AWSProfiles

from ScoutSuite.cli_parser import ScoutSuiteArgumentParser
from ScoutSuite.core.cli_parser import ScoutSuiteArgumentParser
from ScoutSuite import AWSCONFIG
from ScoutSuite.output.html import Scout2Report
from ScoutSuite.core.exceptions import RuleExceptions
Expand All @@ -32,7 +32,7 @@ def main(args=None):
args = args.__dict__

# Configure the debug level
configPrintException(args.get('debug'))
config_debug_level(args.get('debug'))

# Create a cloud provider object
cloud_provider = get_provider(provider=args.get('provider'),
Expand All @@ -45,12 +45,11 @@ def main(args=None):
timestamp=args.get('timestamp'),
services=args.get('services'),
skipped_services=args.get('skipped_services'),
thread_config=args.get('thread_config'),
)
thread_config=args.get('thread_config'))

report_file_name = generate_report_name(cloud_provider.provider_code, args)

# TODO move this to after authentication, so that the report can be more specific to what's being scanned.
# TODO: move this to after authentication, so that the report can be more specific to what's being scanned.
# For example if scanning with a GCP service account, the SA email can only be known after authenticating...
# Create a new report
report = Scout2Report(args.get('provider'), report_file_name, args.get('report_dir'), args.get('timestamp'))
Expand All @@ -59,9 +58,6 @@ def main(args=None):
if not args.get('fetch_local'):
# Authenticate to the cloud provider
authenticated = cloud_provider.authenticate(profile=args.get('profile'),
csv_credentials=args.get('csv_credentials'),
mfa_serial=args.get('mfa_serial'),
mfa_code=args.get('mfa_code'),
user_account=args.get('user_account'),
service_account=args.get('service_account'),
cli=args.get('cli'),
Expand All @@ -73,17 +69,16 @@ def main(args=None):
client_id=args.get('client_id'),
client_secret=args.get('client_secret'),
username=args.get('username'),
password=args.get('password')
)
password=args.get('password'))

if not authenticated:
return 42
return 401

# Fetch data from provider APIs
try:
cloud_provider.fetch(regions=args.get('regions'))
except KeyboardInterrupt:
printInfo('\nCancelled by user')
print_info('\nCancelled by user')
return 130

# Update means we reload the whole config and overwrite part of it
Expand Down Expand Up @@ -127,13 +122,14 @@ def main(args=None):
exceptions.process(cloud_provider)
exceptions = exceptions.exceptions
except Exception as e:
printDebug('Warning, failed to load exceptions. The file may not exist or may have an invalid format.')
print_debug('Warning, failed to load exceptions. The file may not exist or may have an invalid format.')
exceptions = {}

# Finalize
cloud_provider.postprocessing(report.current_time, finding_rules)

# TODO this is AWS-specific - move to postprocessing?
# TODO: this is AWS-specific - move to postprocessing?
# This is partially implemented
# Get organization data if it exists
try:
profile = AWSProfiles.get(args.get('profile'))[0]
Expand All @@ -156,20 +152,22 @@ def main(args=None):

# Open the report by default
if not args.get('no_browser'):
printInfo('Opening the HTML report...')
print_info('Opening the HTML report...')
url = 'file://%s' % os.path.abspath(html_report_path)
webbrowser.open(url, new=2)

return 0


def generate_report_name(provider_code, args):
# TODO this should be done within the provider
# A pre-requisite to this is to generate report AFTER authentication
if provider_code == 'aws':
if args.get('profile'):
report_file_name = 'aws-%s' % args.get('profile')[0]
report_file_name = 'aws-%s' % args.get('profile')
else:
report_file_name = 'aws'
if provider_code == 'gcp':
elif provider_code == 'gcp':
if args.get('project_id'):
report_file_name = 'gcp-%s' % args.get('project_id')
elif args.get('organization_id'):
Expand All @@ -178,6 +176,8 @@ def generate_report_name(provider_code, args):
report_file_name = 'gcp-%s' % args.get('folder_id')
else:
report_file_name = 'gcp'
if provider_code == 'azure':
elif provider_code == 'azure':
report_file_name = 'azure'
else:
report_file_name = 'unknown'
return report_file_name
12 changes: 6 additions & 6 deletions ScoutSuite/core/__init__.py
Expand Up @@ -2,10 +2,10 @@

import os

condition_operators = [ 'and', 'or' ]
condition_operators = ['and', 'or']

awsscout2_rules_data_dir = os.path.join(os.path.dirname(__file__), 'data')
awsscout2_conditions_dir = os.path.join(awsscout2_rules_data_dir, 'conditions')
awsscout2_filters_dir = os.path.join(awsscout2_rules_data_dir, 'filters')
awsscout2_findings_dir = os.path.join(awsscout2_rules_data_dir, 'findings')
awsscout2_rulesets_dir = os.path.join(awsscout2_rules_data_dir, 'rulesets')
awsscout2_rules_data_dir = os.path.join(os.path.dirname(__file__), 'data')
awsscout2_conditions_dir = os.path.join(awsscout2_rules_data_dir, 'conditions')
awsscout2_filters_dir = os.path.join(awsscout2_rules_data_dir, 'filters')
awsscout2_findings_dir = os.path.join(awsscout2_rules_data_dir, 'findings')
awsscout2_rulesets_dir = os.path.join(awsscout2_rules_data_dir, 'rulesets')
19 changes: 2 additions & 17 deletions ScoutSuite/cli_parser.py → ScoutSuite/core/cli_parser.py
Expand Up @@ -6,6 +6,7 @@

from ScoutSuite import DEFAULT_REPORT_DIR


class ScoutSuiteArgumentParser:

def __init__(self):
Expand All @@ -32,27 +33,11 @@ def _init_aws_parser(self):

parser = aws_parser.add_argument_group('Authentication parameters')

default_profile = os.environ.get('AWS_PROFILE', 'default')
default_profile_origin = " (from AWS_PROFILE)." if 'AWS_PROFILE' in os.environ else "."
parser.add_argument('-p',
'--profile',
dest='profile',
default=[default_profile],
nargs='+',
help='Name of the profile. Defaults to %(default)s' + default_profile_origin)
parser.add_argument('-c',
'--csv-credentials',
dest='csv_credentials',
default=None,
help='Path to a CSV file containing the access key ID and secret key')
parser.add_argument('--mfa-serial',
dest='mfa_serial',
default=None,
help='ARN of the user\'s MFA device')
parser.add_argument('--mfa-code',
dest='mfa_code',
default=None,
help='Six-digit code displayed on the MFA device.')
help='Name of the profile')

parser = aws_parser.add_argument_group('Additional arguments')

Expand Down

0 comments on commit ed25eee

Please sign in to comment.