Skip to content

Commit

Permalink
Security commands initial commit #127
Browse files Browse the repository at this point in the history
  • Loading branch information
leandrodamascena committed Jul 16, 2020
1 parent b05c80b commit 1034f7a
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 0 deletions.
21 changes: 21 additions & 0 deletions cloudiscovery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from provider.iot.command import Iot
from provider.all.command import All
from provider.limit.command import Limit
from provider.security.command import Security

from shared.common import (
exit_critical,
Expand Down Expand Up @@ -107,6 +108,20 @@ def generate_parser():
For example: --threshold 50 will report all resources with more than 50%% threshold.",
)

security_parser = subparsers.add_parser(
"aws-security", help="Analyze aws several security checks."
)
add_default_arguments(security_parser, diagram_enabled=False, filters_enabled=False)
security_parser.add_argument(
"-c",
"--commands",
action="append",
required=False,
help='Select the security check command that you want to run. \
To see available commands, please type "-c list". \
If not passed, command will check all services.',
)

return parser


Expand Down Expand Up @@ -262,12 +277,18 @@ def main():
command = Limit(
region_names=region_names, session=session, threshold=args.threshold,
)
elif args.command == "aws-security":
command = Security(
region_names=region_names, session=session, commands=args.commands,
)
else:
raise NotImplementedError("Unknown command")

if "services" in args and args.services is not None:
services = args.services.split(",")
else:
services = []

command.run(diagram, args.verbose, services, filters)


Expand Down
Empty file.
70 changes: 70 additions & 0 deletions cloudiscovery/provider/security/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from typing import List

from shared.common import (
ResourceCache,
Filterable,
BaseOptions,
)
from shared.common_aws import BaseAwsOptions, BaseAwsCommand, AwsCommandRunner
from shared.diagram import NoDiagram


class SecurityOptions(BaseAwsOptions, BaseOptions):
commands: List[str]

# pylint: disable=too-many-arguments
def __init__(
self, verbose: bool, filters: List[Filterable], session, region_name, commands,
):
BaseAwsOptions.__init__(self, session, region_name)
BaseOptions.__init__(self, verbose, filters)
self.commands = commands


class SecurityParameters:
def __init__(self, session, region: str, commands, options: SecurityOptions):
self.region = region
self.cache = ResourceCache()
self.session = session
self.options = options
self.commands = commands


class Security(BaseAwsCommand):
def __init__(self, region_names, session, commands):
"""
All AWS resources
:param region_names:
:param session:
:param commands:
"""
super().__init__(region_names, session)
self.commands = commands

def run(
self,
diagram: bool,
verbose: bool,
services: List[str],
filters: List[Filterable],
):

for region in self.region_names:
security_options = SecurityOptions(
verbose=verbose,
filters=filters,
session=self.session,
region_name=region,
commands=self.commands,
)

command_runner = AwsCommandRunner()
command_runner.run(
provider="security",
options=security_options,
diagram_builder=NoDiagram(),
title="AWS Security - Region {}".format(region),
# pylint: disable=no-member
filename=security_options.resulting_file_name("security"),
)
Empty file.
8 changes: 8 additions & 0 deletions cloudiscovery/provider/security/data/commands_enabled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
COMMANDS_ENABLED = {
"access-keys-rotated": {
"parameters": [{"name": "max_age", "default_value": "90", "type": "int"}],
"class": "IAM",
"method": "access_keys_rotated",
"short_description": "Checks whether the active access keys are rotated within the number of days.",
},
}
Empty file.
81 changes: 81 additions & 0 deletions cloudiscovery/provider/security/resource/all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import List

import importlib

from provider.security.command import SecurityOptions
from provider.security.data.commands_enabled import COMMANDS_ENABLED
from shared.common import (
ResourceProvider,
Resource,
message_handler,
)
from shared.error_handler import exception


class SecuritytResources(ResourceProvider):
def __init__(self, options: SecurityOptions):
"""
All resources
:param options:
"""
super().__init__()
self.options = options

@exception
# pylint: disable=too-many-locals
def get_resources(self) -> List[Resource]:

commands = self.options.commands

result = []

# commands informed, checking for specific commands
if commands:
# show all commands to check
if commands[0] == "list":
message_handler("\nFollowing commands are enabled\n", "HEADER")
for detail_command in COMMANDS_ENABLED:
parameters = COMMANDS_ENABLED[detail_command]["parameters"][0][
"name"
]
default_value = COMMANDS_ENABLED[detail_command]["parameters"][0][
"default_value"
]
description = COMMANDS_ENABLED[detail_command]["short_description"]

formated_command = 'cloudiscovery aws-security -c {}="{}={}"'.format(
detail_command, parameters, default_value
)
message_handler(
"{} - {} \nExample: {}\n".format(
detail_command, description, formated_command
),
"OKGREEN",
)
else:
for command in commands:
command = command.split("=")

# First position always is command
if command[0] not in COMMANDS_ENABLED:
message_handler(
"Command {} doesn't exists.".format(command[0]), "WARNING"
)
else:
# Second and thrid parameters are class and method
_class = COMMANDS_ENABLED[command[0]]["class"]
_method = COMMANDS_ENABLED[command[0]]["method"]
_parameter = {command[1]: command[2]}

module = importlib.import_module(
"provider.security.resource.commands." + _class
)
instance = getattr(module, _class)(self.options)
result = getattr(instance, _method)(**_parameter)
else:
print(
'You must inform a command. Run this command using "-c list" to check all commands available'
)

return result
51 changes: 51 additions & 0 deletions cloudiscovery/provider/security/resource/commands/IAM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from datetime import datetime, timedelta
import pytz

from provider.security.command import SecurityOptions

from shared.common import (
Resource,
ResourceDigest,
SecurityValues,
)


class IAM:
def __init__(self, options: SecurityOptions):
self.options = options

def access_keys_rotated(self, max_age):

client = self.options.client("iam")

users = client.list_users()

resources_found = []

for user in users["Users"]:
paginator = client.get_paginator("list_access_keys")
for keys in paginator.paginate(UserName=user["UserName"]):
for key in keys["AccessKeyMetadata"]:

date_compare = datetime.utcnow() - timedelta(days=int(max_age))
date_compare = date_compare.replace(tzinfo=pytz.utc)
last_rotate = key["CreateDate"]

if last_rotate < date_compare:
resources_found.append(
Resource(
digest=ResourceDigest(
id=key["AccessKeyId"], type="access_keys_rotated"
),
details="You must rotate your keys.",
name=key["UserName"],
group="iam_security",
security=SecurityValues(
status="CRITICAL",
parameter="max_age",
value=str(max_age),
),
)
)

return resources_found
7 changes: 7 additions & 0 deletions cloudiscovery/shared/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class LimitsValues(NamedTuple):
percent: float


class SecurityValues(NamedTuple):
status: str
parameter: str
value: str


class ResourceTag(NamedTuple, Filterable):
key: str
value: str
Expand All @@ -74,6 +80,7 @@ class Resource(NamedTuple):
group: str = ""
tags: List[ResourceTag] = []
limits: LimitsValues = None
security: SecurityValues = None
attributes: Dict[str, object] = {}


Expand Down

0 comments on commit 1034f7a

Please sign in to comment.