Skip to content

Commit

Permalink
Merge pull request duo-labs#2 from duo-labs/master
Browse files Browse the repository at this point in the history
update from fork
  • Loading branch information
schosterbarak authored Jul 13, 2019
2 parents 12f7132 + ba3588e commit 8371a81
Show file tree
Hide file tree
Showing 29 changed files with 2,992 additions and 1,923 deletions.
11 changes: 5 additions & 6 deletions cloudmapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@
This script manages CloudMapper, a tool for analyzing AWS environments.
"""
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
import sys
import pkgutil
import importlib

__version__ = "2.5.6"
__version__ = "2.5.7"


def show_help(commands):
print("CloudMapper {}".format(__version__))
print("usage: {} [{}] [...]".format(
sys.argv[0], "|".join(sorted(commands.keys()))))
print("usage: {} [{}] [...]".format(sys.argv[0], "|".join(sorted(commands.keys()))))
for command, module in sorted(commands.items()):
print(" {}: {}".format(command, module.__description__))
exit(-1)
Expand All @@ -49,10 +48,10 @@ def main():
# TODO: This adds half a second to the start time. Is there a smarter way
# to do this?
commands = {}
commands_paths = ['commands', 'private_commands']
commands_paths = ["commands", "private_commands"]
for commands_path in commands_paths:
for importer, command_name, _ in pkgutil.iter_modules([commands_path]):
full_package_name = '%s.%s' % (commands_path, command_name)
full_package_name = "%s.%s" % (commands_path, command_name)
module = importlib.import_module(full_package_name)
commands[command_name] = module

Expand Down
2 changes: 1 addition & 1 deletion commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Allow for python3 style.
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
130 changes: 72 additions & 58 deletions commands/amis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,99 +12,113 @@


def log_warning(msg):
print('WARNING: {}'.format(msg), file=sys.stderr)
print("WARNING: {}".format(msg), file=sys.stderr)


def find_image(image_id, public_images, account_images):
for image in public_images:
if image_id == image['ImageId']:
return image, 'public'
if image_id == image["ImageId"]:
return image, "public"
for image in account_images:
if image_id == image['ImageId']:
return image, 'private'
if image_id == image["ImageId"]:
return image, "private"

return None, 'unknown_image'
return None, "unknown_image"


def get_instance_name(instance):
if 'Tags' in instance:
for tag in instance['Tags']:
if tag['Key'] == 'Name':
return tag['Value']
if "Tags" in instance:
for tag in instance["Tags"]:
if tag["Key"] == "Name":
return tag["Value"]
return None


def amis(args, accounts, config):
# Loading the list of public images from disk takes a while, so we'll iterate by region

regions_file = 'data/aws/us-east-1/ec2-describe-images.json'
regions_file = "data/aws/us-east-1/ec2-describe-images.json"
if not os.path.isfile(regions_file):
raise Exception("You need to download the set of public AMI images. Run:\n"
" mkdir -p data/aws\n"
" cd data/aws\n"
" aws ec2 describe-regions | jq -r '.Regions[].RegionName' | xargs -I{} mkdir {}\n"
" aws ec2 describe-regions | jq -r '.Regions[].RegionName' | xargs -I{} sh -c 'aws --region {} ec2 describe-images --executable-users all > {}/ec2-describe-images.json'\n"
)

print("{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}".format(
'Account Name',
'Region Name',
'Instance Id',
'Instance Name',
'AMI ID',
'Is Public',
'AMI Description',
'AMI Owner'))

for region in listdir('data/aws/'):
raise Exception(
"You need to download the set of public AMI images. Run:\n"
" mkdir -p data/aws\n"
" cd data/aws\n"
" aws ec2 describe-regions | jq -r '.Regions[].RegionName' | xargs -I{} mkdir {}\n"
" aws ec2 describe-regions | jq -r '.Regions[].RegionName' | xargs -I{} sh -c 'aws --region {} ec2 describe-images --executable-users all > {}/ec2-describe-images.json'\n"
)

print(
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}".format(
"Account Name",
"Region Name",
"Instance Id",
"Instance Name",
"AMI ID",
"Is Public",
"AMI Description",
"AMI Owner",
)
)

for region in listdir("data/aws/"):
# Get public images
public_images_file = 'data/aws/{}/ec2-describe-images.json'.format(region)
public_images_file = "data/aws/{}/ec2-describe-images.json".format(region)
public_images = json.load(open(public_images_file))
resource_filter = '.Images[]'
resource_filter = ".Images[]"
public_images = pyjq.all(resource_filter, public_images)

for account in accounts:
account = Account(None, account)
region = Region(account, {'RegionName': region})
region = Region(account, {"RegionName": region})

instances = query_aws(account, "ec2-describe-instances", region)
resource_filter = '.Reservations[].Instances[] | select(.State.Name == "running")'
if args.instance_filter != '':
resource_filter += '|{}'.format(args.instance_filter)
resource_filter = (
'.Reservations[].Instances[] | select(.State.Name == "running")'
)
if args.instance_filter != "":
resource_filter += "|{}".format(args.instance_filter)
instances = pyjq.all(resource_filter, instances)

account_images = query_aws(account, "ec2-describe-images", region)
resource_filter = '.Images[]'
resource_filter = ".Images[]"
account_images = pyjq.all(resource_filter, account_images)

for instance in instances:
image_id = instance['ImageId']
image_description = ''
owner = ''
image, is_public_image = find_image(image_id, public_images, account_images)
image_id = instance["ImageId"]
image_description = ""
owner = ""
image, is_public_image = find_image(
image_id, public_images, account_images
)
if image:
# Many images don't have all fields, so try the Name, then Description, then ImageLocation
image_description = image.get('Name', '')
if image_description == '':
image_description = image.get('Description', '')
if image_description == '':
image_description = image.get('ImageLocation', '')
owner = image.get('OwnerId', '')

print("{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}".format(
account.name,
region.name,
instance['InstanceId'],
get_instance_name(instance),
image_id,
is_public_image,
image_description,
owner))
image_description = image.get("Name", "")
if image_description == "":
image_description = image.get("Description", "")
if image_description == "":
image_description = image.get("ImageLocation", "")
owner = image.get("OwnerId", "")

print(
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}".format(
account.name,
region.name,
instance["InstanceId"],
get_instance_name(instance),
image_id,
is_public_image,
image_description,
owner,
)
)


def run(arguments):
parser = argparse.ArgumentParser()
parser.add_argument("--instance_filter",
help="Filter on the EC2 info, for example `select(.Platform == \"windows\")` or `select(.Architecture!=\"x86_64\")`", default='')
parser.add_argument(
"--instance_filter",
help='Filter on the EC2 info, for example `select(.Platform == "windows")` or `select(.Architecture!="x86_64")`',
default="",
)
args, accounts, config = parse_arguments(arguments, parser)
amis(args, accounts, config)
41 changes: 25 additions & 16 deletions commands/api_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,39 @@ def api_endpoints(accounts, config):
region = Region(account, region_json)

# Look for API Gateway
json_blob = query_aws(region.account, 'apigateway-get-rest-apis', region)
json_blob = query_aws(region.account, "apigateway-get-rest-apis", region)
if json_blob is None:
continue
for api in json_blob.get('items', []):
rest_id = api['id']
deployments = get_parameter_file(region, 'apigateway', 'get-deployments', rest_id)
for api in json_blob.get("items", []):
rest_id = api["id"]
deployments = get_parameter_file(
region, "apigateway", "get-deployments", rest_id
)
if deployments is None:
continue
for deployment in deployments['items']:
deployment_id = deployment['id']
stages = get_parameter_file(region, 'apigateway', 'get-stages', rest_id)
for deployment in deployments["items"]:
deployment_id = deployment["id"]
stages = get_parameter_file(
region, "apigateway", "get-stages", rest_id
)
if stages is None:
continue
for stage in stages['item']:
if stage['deploymentId'] == deployment_id:
resources = get_parameter_file(region, 'apigateway', 'get-resources', rest_id)
for stage in stages["item"]:
if stage["deploymentId"] == deployment_id:
resources = get_parameter_file(
region, "apigateway", "get-resources", rest_id
)
if resources is None:
continue
for resource in resources['items']:
print('{}.execute-api.{}.amazonaws.com/{}{}'.format(
api['id'],
region.name,
stage['stageName'],
resource['path']))
for resource in resources["items"]:
print(
"{}.execute-api.{}.amazonaws.com/{}{}".format(
api["id"],
region.name,
stage["stageName"],
resource["path"],
)
)


def run(arguments):
Expand Down
24 changes: 16 additions & 8 deletions commands/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def audit_command(accounts, config, args):

findings = audit(accounts)

with open("audit_config.yaml", 'r') as f:
with open("audit_config.yaml", "r") as f:
audit_config = yaml.safe_load(f)
# TODO: Check the file is formatted correctly

Expand All @@ -23,17 +23,25 @@ def audit_command(accounts, config, args):
if args.json:
print(finding)
else:
print('{} - {} ({}) - {}: {}'.format(
conf['severity'].upper(),
finding.region.account.name,
finding.region.name,
conf['title'],
finding.resource_id))
print(
"{} - {} ({}) - {}: {}".format(
conf["severity"].upper(),
finding.region.account.name,
finding.region.name,
conf["title"],
finding.resource_id,
)
)


def run(arguments):
parser = argparse.ArgumentParser()
parser.add_argument("--json", help="Print the json of the issues", default=False, action='store_true')
parser.add_argument(
"--json",
help="Print the json of the issues",
default=False,
action="store_true",
)
args, accounts, config = parse_arguments(arguments, parser)

audit_command(accounts, config, args)
Loading

0 comments on commit 8371a81

Please sign in to comment.