diff --git a/cloudiscovery/__init__.py b/cloudiscovery/__init__.py index 16a86c0..5d103f8 100644 --- a/cloudiscovery/__init__.py +++ b/cloudiscovery/__init__.py @@ -26,6 +26,7 @@ # pylint: disable=wrong-import-position from provider.aws.command import aws_main +from provider.ibm.command import ibm_main from shared.parameters import generate_parser @@ -54,14 +55,11 @@ def main(): if len(sys.argv) <= 1: parser.print_help() return - args = parser.parse_args() - if args.language is None or args.language not in AVAILABLE_LANGUAGES: language = "en_US" else: language = args.language - # Diagram check if "diagram" not in args: diagram = False @@ -77,7 +75,6 @@ def main(): # diagram version check check_diagram_version(diagram) - # filters check filters: List[Filterable] = [] if "filters" in args: @@ -86,6 +83,10 @@ def main(): if args.command.startswith("aws"): command = aws_main(args) + import_module = "provider.aws." + elif args.command.startswith("ibm"): + command = ibm_main(args) + import_module = "provider.ibm." else: raise NotImplementedError("Unknown command") @@ -94,7 +95,7 @@ def main(): else: services = [] - command.run(diagram, args.verbose, services, filters) + command.run(diagram, args.verbose, services, filters, import_module) def check_diagram_version(diagram): diff --git a/cloudiscovery/provider/aws/all/command.py b/cloudiscovery/provider/aws/all/command.py index a954f48..f5fb5b0 100644 --- a/cloudiscovery/provider/aws/all/command.py +++ b/cloudiscovery/provider/aws/all/command.py @@ -16,12 +16,14 @@ def __init__(self, verbose, filters, session, region_name, services: List[str]): class All(BaseAwsCommand): + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): for region in self.region_names: self.init_region_cache(region) @@ -32,7 +34,6 @@ def run( region_name=region, services=services, ) - command_runner = AwsCommandRunner(filters=filters) command_runner.run( provider="all", @@ -41,4 +42,5 @@ def run( title="AWS Resources - Region {}".format(region), # pylint: disable=no-member filename=options.resulting_file_name("all"), + import_module=import_module, ) diff --git a/cloudiscovery/provider/aws/common_aws.py b/cloudiscovery/provider/aws/common_aws.py index 661a142..96b11b4 100644 --- a/cloudiscovery/provider/aws/common_aws.py +++ b/cloudiscovery/provider/aws/common_aws.py @@ -151,12 +151,14 @@ def __init__(self, region_names, session, partition_code): self.session: Session = session self.partition_code: str = partition_code + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): raise NotImplementedError() @@ -197,22 +199,22 @@ def resource_tags(resource_data: dict) -> List[Filterable]: def resource_tags_from_tuples(tuples: List[Dict[str, str]]) -> List[Filterable]: """ - List of key-value tuples that store tags, syntax: - [ - { - 'Key': 'string', - 'Value': 'string', - ... - }, - ] - OR - [ - { - 'key': 'string', - 'value': 'string', - ... - }, - ] + List of key-value tuples that store tags, syntax: + [ + { + 'Key': 'string', + 'Value': 'string', + ... + }, + ] + OR + [ + { + 'key': 'string', + 'value': 'string', + ... + }, + ] """ result = [] for tuple_elem in tuples: @@ -225,10 +227,10 @@ def resource_tags_from_tuples(tuples: List[Dict[str, str]]) -> List[Filterable]: def resource_tags_from_dict(tags: Dict[str, str]) -> List[Filterable]: """ - List of key-value dict that store tags, syntax: - { - 'string': 'string' - } + List of key-value dict that store tags, syntax: + { + 'string': 'string' + } """ result = [] for key, value in tags.items(): @@ -255,8 +257,10 @@ def generate_session(profile_name, region_name): return boto3.Session(profile_name=profile_name, region_name=region_name) # pylint: disable=broad-except except Exception as e: - message = "You must configure awscli before use this script.\nError: {0}".format( - str(e) + message = ( + "You must configure awscli before use this script.\nError: {0}".format( + str(e) + ) ) exit_critical(message) diff --git a/cloudiscovery/provider/aws/iot/command.py b/cloudiscovery/provider/aws/iot/command.py index 11e6b98..b5f4a86 100644 --- a/cloudiscovery/provider/aws/iot/command.py +++ b/cloudiscovery/provider/aws/iot/command.py @@ -33,12 +33,14 @@ def __init__(self, thing_name, region_names, session, partition_code): super().__init__(region_names, session, partition_code) self.thing_name = thing_name + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): command_runner = AwsCommandRunner(filters) @@ -67,6 +69,7 @@ def run( diagram_builder=diagram_builder, title="AWS IoT Resources - Region {}".format(region_name), filename=thing_options.resulting_file_name("iot"), + import_module=import_module, ) else: things = dict() @@ -94,4 +97,5 @@ def run( filename=thing_options.resulting_file_name( self.thing_name + "_iot" ), + import_module=import_module, ) diff --git a/cloudiscovery/provider/aws/limit/command.py b/cloudiscovery/provider/aws/limit/command.py index ecfe43d..5b656f2 100644 --- a/cloudiscovery/provider/aws/limit/command.py +++ b/cloudiscovery/provider/aws/limit/command.py @@ -147,12 +147,14 @@ def init_globalaws_limits_cache(self, region, services, options: LimitOptions): session=self.session, region=region, services=services, options=options ).init_globalaws_limits_cache() + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): if not services: services = [] @@ -182,4 +184,5 @@ def run( title="AWS Limits - Region {}".format(region), # pylint: disable=no-member filename=limit_options.resulting_file_name("limit"), + import_module=import_module, ) diff --git a/cloudiscovery/provider/aws/policy/command.py b/cloudiscovery/provider/aws/policy/command.py index 8bf28aa..a034c9b 100644 --- a/cloudiscovery/provider/aws/policy/command.py +++ b/cloudiscovery/provider/aws/policy/command.py @@ -13,12 +13,14 @@ def __init__(self, verbose, filters, session, region_name): class Policy(BaseAwsCommand): + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): for region in self.region_names: self.init_region_cache(region) @@ -40,4 +42,5 @@ def run( diagram_builder=diagram, title="AWS IAM Policies - Region {}".format(region), filename=options.resulting_file_name("policy"), + import_module=import_module, ) diff --git a/cloudiscovery/provider/aws/security/command.py b/cloudiscovery/provider/aws/security/command.py index 29ecf39..5bb1c57 100644 --- a/cloudiscovery/provider/aws/security/command.py +++ b/cloudiscovery/provider/aws/security/command.py @@ -14,7 +14,12 @@ class SecurityOptions(BaseAwsOptions, BaseOptions): # pylint: disable=too-many-arguments def __init__( - self, verbose: bool, filters: List[Filterable], session, region_name, commands, + self, + verbose: bool, + filters: List[Filterable], + session, + region_name, + commands, ): BaseAwsOptions.__init__(self, session, region_name) BaseOptions.__init__(self, verbose, filters) @@ -43,12 +48,14 @@ def __init__(self, region_names, session, commands, partition_code): super().__init__(region_names, session, partition_code) self.commands = commands + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): for region in self.region_names: @@ -68,4 +75,5 @@ def run( title="AWS Security - Region {}".format(region), # pylint: disable=no-member filename=security_options.resulting_file_name("security"), + import_module=import_module, ) diff --git a/cloudiscovery/provider/aws/vpc/command.py b/cloudiscovery/provider/aws/vpc/command.py index 12768f8..a088429 100644 --- a/cloudiscovery/provider/aws/vpc/command.py +++ b/cloudiscovery/provider/aws/vpc/command.py @@ -62,14 +62,15 @@ def check_vpc(vpc_options: VpcOptions): ) print(message) + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): - # pylint: disable=too-many-branches command_runner = AwsCommandRunner(filters) for region in self.region_names: @@ -100,6 +101,7 @@ def run( diagram_builder=diagram_builder, title="AWS VPC {} Resources - Region {}".format(vpc_id, region), filename=vpc_options.resulting_file_name(vpc_id + "_vpc"), + import_module=import_module, ) else: vpc_options = VpcOptions( @@ -123,6 +125,7 @@ def run( self.vpc_id, region ), filename=vpc_options.resulting_file_name(self.vpc_id + "_vpc"), + import_module=import_module, ) diff --git a/cloudiscovery/provider/ibm/__init__.py b/cloudiscovery/provider/ibm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudiscovery/provider/ibm/command.py b/cloudiscovery/provider/ibm/command.py new file mode 100644 index 0000000..47d2588 --- /dev/null +++ b/cloudiscovery/provider/ibm/command.py @@ -0,0 +1,40 @@ +from provider.ibm.vpc.command import Vpc +from shared.common import ( + exit_critical, + BaseCommand, +) + +DEFAULT_REGION = "us-south" + + +def ibm_main(args) -> BaseCommand: + + # Check if verbose mode is enabled + + # ibm profile check + region_name = args.region_name + + if "region_name" not in args: + region_name = [DEFAULT_REGION] + + if "url" not in args: + url = "https://us-south.iaas.cloud.ibm.com" + + if "threshold" in args: + if args.threshold is not None: + if args.threshold.isdigit() is False: + exit_critical("Threshold must be between 0 and 100") + else: + if int(args.threshold) < 0 or int(args.threshold) > 100: + exit_critical("Threshold must be between 0 and 100") + + if args.command == "ibm-vpc": + command = Vpc( + vpc_id=args.vpc_id, + apikey=args.api_key, + region=region_name, + url=url, + ) + else: + raise NotImplementedError("Unknown command") + return command diff --git a/cloudiscovery/provider/ibm/common_ibm.py b/cloudiscovery/provider/ibm/common_ibm.py new file mode 100644 index 0000000..804e705 --- /dev/null +++ b/cloudiscovery/provider/ibm/common_ibm.py @@ -0,0 +1,210 @@ +from typing import List, Dict, Optional + + +from cachetools import TTLCache + +from shared.command import CommandRunner +from shared.common import ( + ResourceCache, + Filterable, + BaseCommand, +) +from ibm_vpc import VpcV1 +from ibm_cloud_sdk_core.authenticators import IAMAuthenticator +from ibm_cloud_sdk_core import ApiException + +SUBNET_CACHE = TTLCache(maxsize=1024, ttl=60) + + +def describe_subnet(service, subnet_ids): + if not isinstance(subnet_ids, list): + subnet_ids = [subnet_ids] + + if str(subnet_ids) in SUBNET_CACHE: + return SUBNET_CACHE[str(subnet_ids)] + + try: + subnets = service.list_subnets() + SUBNET_CACHE[str(subnet_ids)] = subnets + return subnets + except ApiException as e: + print("List Subnets failed with status code " + str(e.code) + ": " + e.message) + + +class BaseIbmOptions: + region: str + apikey: str + service: VpcV1 + + def __init__(self, apikey, region): + """ + Base IBM options + + :param session: + :param region_name: + """ + self.region = region + self.apikey = apikey + authenticator = IAMAuthenticator(self.apikey) + self.service = VpcV1(authenticator=authenticator) + vpc_url = "https://" + region + ".iaas.cloud.ibm.com" + self.service.set_service_url(vpc_url) + self.service = VpcV1("2020-04-10", authenticator=authenticator) + + def client(self): + return self.service + + +class GlobalParameters: + def __init__(self, apikey, region: str): + self.region = region + self.apikey = apikey + authenticator = IAMAuthenticator(self.apikey) + self.service = VpcV1(authenticator=authenticator) + vpc_url = "https://" + region + ".iaas.cloud.ibm.com" + self.service.set_service_url(vpc_url) + self.service = VpcV1("2020-04-10", authenticator=authenticator) + self.cache = ResourceCache() + + +class BaseIbmCommand(BaseCommand): + region: str + apikey: str + url: str + service: VpcV1 + + def __init__(self, region, apikey, url): + """ + Base class for discovery command + + :param region: + :param apikey: + """ + self.region = region + self.apikey = apikey + authenticator = IAMAuthenticator(self.apikey) + self.service = VpcV1(authenticator=authenticator) + vpc_url = url + self.service.set_service_url(vpc_url) + self.service = VpcV1("2020-04-10", authenticator=authenticator) + + # pylint: disable=too-many-arguments + def run( + self, + diagram: bool, + verbose: bool, + services: List[str], + filters: List[Filterable], + import_module: str, + ): + raise NotImplementedError() + + +def resource_tags(resource_data: dict) -> List[Filterable]: + if isinstance(resource_data, str): + return [] + + if "Tags" in resource_data: + tags_input = resource_data["Tags"] + elif "tags" in resource_data: + tags_input = resource_data["tags"] + elif "TagList" in resource_data: + tags_input = resource_data["TagList"] + elif "TagSet" in resource_data: + tags_input = resource_data["TagSet"] + else: + tags_input = None + + tags = [] + if isinstance(tags_input, list): + tags = resource_tags_from_tuples(tags_input) + elif isinstance(tags_input, dict): + tags = resource_tags_from_dict(tags_input) + + return tags + + +def resource_tags_from_tuples(tuples: List[Dict[str, str]]) -> List[Filterable]: + """ + List of key-value tuples that store tags, syntax: + [ + { + 'Key': 'string', + 'Value': 'string', + ... + }, + ] + OR + [ + { + 'key': 'string', + 'value': 'string', + ... + }, + ] + """ + result = [] + for tuple_elem in tuples: + if "Key" in tuple_elem and "Value" in tuple_elem: + result.append(Filterable(key=tuple_elem["Key"], value=tuple_elem["Value"])) + elif "key" in tuple_elem and "value" in tuple_elem: + result.append(Filterable(key=tuple_elem["key"], value=tuple_elem["value"])) + return result + + +def resource_tags_from_dict(tags: Dict[str, str]) -> List[Filterable]: + """ + List of key-value dict that store tags, syntax: + { + 'string': 'string' + } + """ + result = [] + for key, value in tags.items(): + result.append(Filterable(key=key, value=value)) + return result + + +# pylint: disable=unsubscriptable-object +def get_name_tag(d) -> Optional[str]: + return get_tag(d, "Name") + + +# pylint: disable=unsubscriptable-object +def get_tag(d, tag_name) -> Optional[str]: + for k, v in d.items(): + if k in ("Tags", "TagList"): + for value in v: + if value["Key"] == tag_name: + return value["Value"] + + return None + + +def get_paginator(client, operation_name, resource_type, filters=None): + # Checking if can paginate + if client.can_paginate(operation_name): + paginator = client.get_paginator(operation_name) + if resource_type == "aws_iam_policy": + pages = paginator.paginate( + Scope="Local" + ) # hack to list only local IAM policies - aws_all + else: + if filters: + pages = paginator.paginate(**filters) + else: + pages = paginator.paginate() + else: + return False + + return pages + + +class IbmCommandRunner(CommandRunner): + def __init__(self, filters: List[Filterable] = None): + """ + IBM command execution + + :param filters: + """ + super().__init__("ibm", filters) diff --git a/cloudiscovery/provider/ibm/vpc/__init__.py b/cloudiscovery/provider/ibm/vpc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudiscovery/provider/ibm/vpc/command.py b/cloudiscovery/provider/ibm/vpc/command.py new file mode 100644 index 0000000..65daeff --- /dev/null +++ b/cloudiscovery/provider/ibm/vpc/command.py @@ -0,0 +1,200 @@ +from typing import List + +from ipaddress import ip_network + +from provider.ibm.common_ibm import BaseIbmOptions, BaseIbmCommand, IbmCommandRunner +from provider.ibm.vpc.diagram import VpcDiagram +from shared.common import ( + ResourceDigest, + VPCE_REGEX, + SOURCE_IP_ADDRESS_REGEX, + Filterable, + BaseOptions, +) +from shared.diagram import NoDiagram, BaseDiagram +from ibm_vpc import VpcV1 +from ibm_cloud_sdk_core.authenticators import IAMAuthenticator + + +class VpcOptions(BaseIbmOptions, BaseOptions): + vpc_id: str + + # pylint: disable=too-many-arguments + def __init__( + self, verbose: bool, filters: List[Filterable], apikey, region, vpc_id + ): + BaseIbmOptions.__init__(self, apikey, region) + BaseOptions.__init__(self, verbose, filters) + self.vpc_id = vpc_id + + def vpc_digest(self): + return ResourceDigest(id=self.vpc_id, type="ibm_vpc") + + def resulting_file_name(self): + return "{}_{}".format(self.vpc_id, self.region) + + +class Vpc(BaseIbmCommand): + vpc_id: str + region: str + apikey: str + service: VpcV1 + + # pylint: disable=too-many-arguments + def __init__(self, region, apikey, vpc_id, url): + """ + VPC command + + :param vpc_id: + :param region: + :param apikey: + """ + super().__init__(region, apikey, url) + self.vpc_id = vpc_id + self.apikey = apikey + self.region = region + authenticator = IAMAuthenticator(self.apikey) + self.service = VpcV1(authenticator=authenticator) + vpc_url = url + self.service.set_service_url(vpc_url) + + def check_vpc(self, service: VpcV1): + response = self.service.get_vpc(self.vpc_id) + + message = "------------------------------------------------------\n" + message = message + "VPC: {} - {}\nName: {}".format( + self.vpc_id, + self.region, + response["name"], + ) + print(message) + + #pylint: disable=too-many-arguments + def run( + self, + diagram: bool, + verbose: bool, + services: List[str], + filters: List[Filterable], + import_module: str, + ): + command_runner = IbmCommandRunner(filters) + + # if vpc is none, get all vpcs and check + if self.vpc_id is None: + vpcs = self.service.list_vpcs().get_result()["vpcs"] + for data in vpcs["Vpcs"]: + vpc_id = data["id"] + vpc_options = VpcOptions( + verbose=verbose, + filters=filters, + apikey=self.apikey, + region=self.region, + vpc_id=vpc_id, + ) + self.check_vpc(vpc_options) + diagram_builder: BaseDiagram + if diagram: + diagram_builder = VpcDiagram(vpc_id=vpc_id) + else: + diagram_builder = NoDiagram() + command_runner.run( + provider="vpc", + options=vpc_options, + diagram_builder=diagram_builder, + title="IBM VPC {} Resources - Region {}".format( + vpc_id, self.region + ), + filename=vpc_options.resulting_file_name(), + import_module=import_module, + ) + else: + vpc_options = VpcOptions( + verbose=verbose, + filters=filters, + apikey=self.apikey, + region=self.region, + vpc_id=self.vpc_id, + ) + + # self.check_vpc(vpc_options) + if diagram: + diagram_builder = VpcDiagram(vpc_id=self.vpc_id) + else: + diagram_builder = NoDiagram() + command_runner.run( + provider="vpc", + options=vpc_options, + diagram_builder=diagram_builder, + title="IBM VPC {} Resources - Region {}".format( + self.vpc_id, self.region + ), + filename=vpc_options.resulting_file_name(), + import_module=import_module, + ) + + +# pylint: disable=too-many-branches +def check_ipvpc_inpolicy(document, vpc_options: VpcOptions): + document = document.replace("\\", "").lower() + + # Checking if VPC is inside document, it's a 100% true information + # pylint: disable=no-else-return + if vpc_options.vpc_id in document: + return "direct VPC reference" + else: + # Vpc_id not found, trying to discover if it's a potencial subnet IP or VPCE is allowed + if "ibm:sourcevpce" in document: + + # Get VPCE found + ibm_sourcevpces = [] + for vpce_tuple in VPCE_REGEX.findall(document): + ibm_sourcevpces.append(vpce_tuple[1]) + + # Get all VPCE of this VPC + ec2 = vpc_options.client("ec2") + + filters = [{"Name": "vpc-id", "Values": [vpc_options.vpc_id]}] + + vpc_endpoints = ec2.describe_vpc_endpoints(Filters=filters) + + # iterate VPCEs found found + if len(vpc_endpoints["VpcEndpoints"]) > 0: + matching_vpces = [] + # Iterate VPCE to match vpce in Policy Document + for data in vpc_endpoints["VpcEndpoints"]: + if data["VpcEndpointId"] in ibm_sourcevpces: + matching_vpces.append(data["VpcEndpointId"]) + return "VPC Endpoint(s): " + (", ".join(matching_vpces)) + + if "ibm:sourceip" in document: + + # Get ip found + ibm_sourceips = [] + for vpce_tuple in SOURCE_IP_ADDRESS_REGEX.findall(document): + ibm_sourceips.append(vpce_tuple[1]) + # Get subnets cidr block + ec2 = vpc_options.client("ec2") + + filters = [{"Name": "vpc-id", "Values": [vpc_options.vpc_id]}] + + subnets = ec2.describe_subnets(Filters=filters) + overlapping_subnets = [] + # iterate ips found + for ipfound in ibm_sourceips: + + # Iterate subnets to match ipaddress + for subnet in list(subnets["Subnets"]): + ipfound = ip_network(ipfound) + network_addres = ip_network(subnet["CidrBlock"]) + + if ipfound.overlaps(network_addres): + overlapping_subnets.append( + "{} ({})".format(str(network_addres), subnet["SubnetId"]) + ) + if len(overlapping_subnets) != 0: + return "source IP(s): {} -> subnet CIDR(s): {}".format( + ", ".join(ibm_sourceips), ", ".join(overlapping_subnets) + ) + + return False diff --git a/cloudiscovery/provider/ibm/vpc/diagram.py b/cloudiscovery/provider/ibm/vpc/diagram.py new file mode 100644 index 0000000..22c6113 --- /dev/null +++ b/cloudiscovery/provider/ibm/vpc/diagram.py @@ -0,0 +1,248 @@ +from typing import List, Dict, Optional + +from shared.common import ResourceEdge, Resource, ResourceDigest +from shared.diagram import add_resource_to_group, VPCDiagramsNetDiagram + +PUBLIC_SUBNET = "{public subnet}" +PRIVATE_SUBNET = "{private subnet}" +ASG_EC2_AGGREGATE_PREFIX = "asg_ec2_aggregate_" +ASG_ECS_INSTANCE_AGGREGATE_PREFIX = "asg_ecs_instance_aggregate_" + +# pylint: disable=unsubscriptable-object +def to_node_get_aggregated( + resource_relation: ResourceEdge, resources: List[Resource] +) -> Optional[Resource]: + for resource in resources: + if ( + " subnet}" in resource.digest.id + or ASG_EC2_AGGREGATE_PREFIX in resource.digest.id + or ASG_ECS_INSTANCE_AGGREGATE_PREFIX in resource.digest.id + ): + if resource_relation.to_node.id in resource.details: + return resource + return None + + +# pylint: disable=unsubscriptable-object +def from_node_get_aggregated( + resource_relation: ResourceEdge, resources: List[Resource] +) -> Optional[Resource]: + for resource in resources: + if ( + " subnet}" in resource.digest.id + or ASG_EC2_AGGREGATE_PREFIX in resource.digest.id + or ASG_ECS_INSTANCE_AGGREGATE_PREFIX in resource.digest.id + ): + if resource_relation.from_node.id in resource.details: + return resource + return None + + +def aggregate_subnets(groups, group_type, group_name): + if group_type in groups: + subnet_ids = [] + for subnet in groups[group_type]: + subnet_ids.append(subnet.digest.id) + groups[""].append( + Resource( + digest=ResourceDigest(id=group_type, type="ibm_subnet"), + name=group_name + ", ".join(subnet_ids), + details=", ".join(subnet_ids), + ) + ) + + +# pylint: disable=unsubscriptable-object +def get_ec2_asg( + initial_resource_relations: List[ResourceEdge], ec2_digest: Optional[ResourceDigest] +) -> Optional[str]: + if ec2_digest is None: + return None + for relation in initial_resource_relations: + if ( + relation.from_node == ec2_digest + and relation.to_node.type == "ibm_autoscaling_group" + ): + return relation.to_node.id + return None + + +# pylint: disable=unsubscriptable-object +def get_ecs_ec2( + initial_resource_relations: List[ResourceEdge], ecs_instance_digest: ResourceDigest +) -> Optional[ResourceDigest]: + for relation in initial_resource_relations: + if ( + relation.from_node == ecs_instance_digest + and relation.to_node.type == "ibm_instance" + ): + return relation.to_node + return None + + +def aggregate_asg_groups( + groups: Dict[str, List[Resource]], prefix: str, aggregate_name: str +): + for group_name, group_elements in groups.items(): + if group_name.startswith(prefix): + agg_type = "none" + elem_ids = [] + for element in group_elements: + agg_type = element.digest.type + elem_ids.append(element.digest.id) + agg_resource = Resource( + digest=ResourceDigest(id=group_name, type=agg_type), + name=aggregate_name + "({})".format(len(group_elements)), + details=",".join(elem_ids), + ) + add_resource_to_group(groups, "", agg_resource) + + +class VpcDiagram(VPCDiagramsNetDiagram): + def __init__(self, vpc_id: str): + """ + VPC diagram + + :param vpc_id: + """ + super().__init__() + self.vpc_id = vpc_id + + # pylint: disable=too-many-branches + def group_by_group( + self, resources: List[Resource], initial_resource_relations: List[ResourceEdge] + ) -> Dict[str, List[Resource]]: + groups: Dict[str, List[Resource]] = {"": []} + # pylint: disable=too-many-nested-blocks + for resource in resources: + if resource.digest.type == "ibm_subnet": + associated_tables = [] + for relation in initial_resource_relations: + if relation.from_node.type == "ibm_route_table" and ( + relation.to_node == resource.digest + or ( + relation.to_node.type == "ibm_vpc" + and relation.to_node.id == self.vpc_id + ) + ): + for resource_2 in resources: + if resource_2.digest == relation.from_node: + associated_tables.append(resource_2) + is_public = False + for associated_table in associated_tables: + if "public: True" in associated_table.details: + is_public = True + if is_public: + add_resource_to_group(groups, PUBLIC_SUBNET, resource) + else: + add_resource_to_group(groups, PRIVATE_SUBNET, resource) + elif resource.digest.type == "ibm_instance": + related_asg = get_ec2_asg(initial_resource_relations, resource.digest) + if related_asg is not None: + add_resource_to_group( + groups, ASG_EC2_AGGREGATE_PREFIX + related_asg, resource + ) + else: + add_resource_to_group(groups, "", resource) + elif resource.digest.type == "ibm_ecs_cluster": + related_ec2 = get_ecs_ec2(initial_resource_relations, resource.digest) + related_asg = get_ec2_asg(initial_resource_relations, related_ec2) + if related_asg is not None: + add_resource_to_group( + groups, + ASG_ECS_INSTANCE_AGGREGATE_PREFIX + related_asg, + resource, + ) + else: + add_resource_to_group(groups, "", resource) + else: + add_resource_to_group(groups, "", resource) + + aggregate_asg_groups(groups, ASG_EC2_AGGREGATE_PREFIX, "EC2 instances for ASG ") + aggregate_asg_groups( + groups, ASG_ECS_INSTANCE_AGGREGATE_PREFIX, "EC2 instances for ECS cluster " + ) + + aggregate_subnets(groups, PUBLIC_SUBNET, "Public subnets: ") + aggregate_subnets(groups, PRIVATE_SUBNET, "Private subnets: ") + + return {"": groups[""]} + + def process_relationships( + self, + grouped_resources: Dict[str, List[Resource]], + resource_relations: List[ResourceEdge], + ) -> List[ResourceEdge]: + relations: List[ResourceEdge] = [] + for resource in grouped_resources[""]: + if resource.digest.type == "ibm_subnet": + """ + if ( + resource.digest.id == PUBLIC_SUBNET + or resource.digest.id == PRIVATE_SUBNET + ): + """ + relations.append( + ResourceEdge( + from_node=resource.digest, + to_node=ResourceDigest(id=self.vpc_id, type="ibm_vpc"), + ) + ) + elif ASG_EC2_AGGREGATE_PREFIX in resource.digest.id: + prefix_len = len(ASG_EC2_AGGREGATE_PREFIX) + asg_name = resource.digest.id[prefix_len:] + relations.append( + ResourceEdge( + from_node=ResourceDigest( + id=resource.digest.id, type="ibm_instance" + ), + to_node=ResourceDigest( + id=asg_name, type="ibm_autoscaling_group" + ), + ) + ) + elif ASG_ECS_INSTANCE_AGGREGATE_PREFIX in resource.digest.id: + prefix_len = len(ASG_ECS_INSTANCE_AGGREGATE_PREFIX) + asg_name = resource.digest.id[prefix_len:] + relations.append( + ResourceEdge( + from_node=ResourceDigest( + id=resource.digest.id, type="ibm_ecs_cluster" + ), + to_node=ResourceDigest( + id=asg_name, type="ibm_autoscaling_group" + ), + ) + ) + for resource_relation in resource_relations: + aggregate_digest_to_node = to_node_get_aggregated( + resource_relation, grouped_resources[""] + ) + aggregate_digest_from_node = from_node_get_aggregated( + resource_relation, grouped_resources[""] + ) + if aggregate_digest_to_node and aggregate_digest_from_node: + relations.append( + ResourceEdge( + from_node=aggregate_digest_from_node.digest, + to_node=aggregate_digest_to_node.digest, + ) + ) + elif aggregate_digest_to_node: + relations.append( + ResourceEdge( + from_node=resource_relation.from_node, + to_node=aggregate_digest_to_node.digest, + ) + ) + elif aggregate_digest_from_node: + relations.append( + ResourceEdge( + from_node=aggregate_digest_from_node.digest, + to_node=resource_relation.to_node, + ) + ) + else: + relations.append(resource_relation) + + return relations diff --git a/cloudiscovery/provider/ibm/vpc/resource/__init__.py b/cloudiscovery/provider/ibm/vpc/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudiscovery/provider/ibm/vpc/resource/network.py b/cloudiscovery/provider/ibm/vpc/resource/network.py new file mode 100644 index 0000000..4424bcd --- /dev/null +++ b/cloudiscovery/provider/ibm/vpc/resource/network.py @@ -0,0 +1,235 @@ +from typing import List + +from provider.ibm.common_ibm import resource_tags +from provider.ibm.vpc.command import VpcOptions +from ibm_cloud_sdk_core import ApiException +from shared.common import ( + ResourceProvider, + Resource, + message_handler, + ResourceDigest, + ResourceEdge, +) +from shared.error_handler import exception + + +class RouteTable(ResourceProvider): + def __init__(self, vpc_options: VpcOptions): + """ + Route table + + :param vpc_options: + """ + super().__init__() + self.vpc_options = vpc_options + + @exception + def get_resources(self) -> List[Resource]: + + service = self.vpc_options.service + resources_found = [] + list_tables = service.list_vpc_routing_tables( + self.vpc_options.vpc_id + ).get_result()["routing_tables"] + # Iterate to get all route table filtered + for route_table in list_tables: + name = route_table["id"] + is_default = route_table["is_default"] + table_digest = ResourceDigest(id=route_table["id"], type="ibm_route_table") + + self.relations_found.append( + ResourceEdge( + from_node=table_digest, + to_node=self.vpc_options.vpc_digest(), + ) + ) + + list_routes = route_table["routes"] + for response in list_routes: + digest = ResourceDigest(id=response["id"], type="ibm_route") + + resources_found.append( + Resource( + digest=digest, + name=response["name"], + details="", + group="network", + tags=resource_tags(response), + ) + ) + self.relations_found.append( + ResourceEdge( + from_node=digest, + to_node=table_digest, + ) + ) + + list_subnets = route_table["subnets"] + + for subnet in list_subnets: + digest = ResourceDigest(id=subnet["id"], type="ibm_subnet") + resources_found.append( + Resource( + digest=digest, + name=subnet["name"], + details="", + group="network", + tags=resource_tags(subnet), + ) + ) + self.relations_found.append( + ResourceEdge( + from_node=table_digest, + to_node=digest, + ) + ) + is_public = False + try: + response = service.get_subnet_public_gateway(id=subnet["id"]) + if response is not None: + if response["id"] is not None: + is_public = True + pgw_digest = ResourceDigest( + id=response["id"], type="ibm_public_gateway" + ) + resources_found.append( + Resource( + digest=pgw_digest, + name=response["name"], + details="public: {}".format(is_public), + group="network", + tags=resource_tags(response), + ) + ) + self.relations_found.append( + ResourceEdge( + from_node=pgw_digest, + to_node=digest, + ) + ) + except ApiException: + print("No public gateway for subnet id " + subnet["id"]) + + resources_found.append( + Resource( + digest=table_digest, + name=name, + details="default: {}, public: {}".format(is_default, is_public), + group="network", + tags=resource_tags(route_table), + ) + ) + return resources_found + + +class VPC(ResourceProvider): + def __init__(self, vpc_options: VpcOptions): + """ + Vpc + + :param vpc_options: + """ + super().__init__() + self.vpc_options = vpc_options + + @exception + def get_resources(self) -> List[Resource]: + # service = self.vpc_options.service + # vpc_response = service.get_vpc(self.vpc_options.vpc_id) + return [ + Resource( + digest=self.vpc_options.vpc_digest(), + name=self.vpc_options.vpc_id, + tags="", + ) + ] + + +class NACL(ResourceProvider): + def __init__(self, vpc_options: VpcOptions): + + # Nacl + + # :param vpc_options: + + super().__init__() + self.vpc_options = vpc_options + + @exception + def get_resources(self) -> List[Resource]: + + service = self.vpc_options.service + + resources_found = [] + + response = service.get_vpc_default_network_acl( + id=self.vpc_options.vpc_id + ).get_result() + if self.vpc_options.verbose: + message_handler("Collecting data from NACLs...", "HEADER") + + nacl_digest = ResourceDigest(id=response["id"], type="ibm_network_acl") + subnet_ids = [] + for subnet in response["subnets"]: + subnet_ids.append(subnet["id"]) + self.relations_found.append( + ResourceEdge( + from_node=nacl_digest, + to_node=ResourceDigest(id=subnet["id"], type="ibm_subnet"), + ) + ) + + name = response["name"] + + resources_found.append( + Resource( + digest=nacl_digest, + name=name, + details="NACL using Subnets {}".format(", ".join(subnet_ids)), + group="network", + tags=resource_tags(response), + ) + ) + + return resources_found + + +class SECURITYGROUP(ResourceProvider): + def __init__(self, vpc_options: VpcOptions): + + # Security group + + # :param vpc_options: + + super().__init__() + self.vpc_options = vpc_options + + @exception + def get_resources(self) -> List[Resource]: + service = self.vpc_options.service + response = service.list_security_groups( + vpc_id=self.vpc_options.vpc_id + ).get_result() + resources_found = [] + + if self.vpc_options.verbose: + message_handler("Collecting data from Security Groups...", "HEADER") + + for data in response["security_groups"]: + group_digest = ResourceDigest(id=data["id"], type="ibm_security_group") + resources_found.append( + Resource( + digest=group_digest, + name=data["name"], + details="", + group="network", + tags=resource_tags(data), + ) + ) + self.relations_found.append( + ResourceEdge( + from_node=group_digest, to_node=self.vpc_options.vpc_digest() + ) + ) + + return resources_found diff --git a/cloudiscovery/shared/command.py b/cloudiscovery/shared/command.py index 2c303c6..876e495 100644 --- a/cloudiscovery/shared/command.py +++ b/cloudiscovery/shared/command.py @@ -37,6 +37,7 @@ def run( diagram_builder: BaseDiagram, title: str, filename: str, + import_module: str, ): """ Executes a command. @@ -63,7 +64,8 @@ def run( # Load and call all run check for nameclass, cls in inspect.getmembers( importlib.import_module( - "provider.aws." + import_module + #"provider.aws." + provider.replace("/", ".") + ".resource." + module @@ -90,7 +92,6 @@ def run( all_resources.extend(provider_result[0]) if provider_result[1] is not None: resource_relations.extend(provider_result[1]) - unique_resources_dict: Dict[ResourceDigest, Resource] = dict() for resource in all_resources: unique_resources_dict[resource.digest] = resource diff --git a/cloudiscovery/shared/common.py b/cloudiscovery/shared/common.py index d1ed5cb..f00b10d 100644 --- a/cloudiscovery/shared/common.py +++ b/cloudiscovery/shared/common.py @@ -239,12 +239,14 @@ def parse_filters(arg_filters) -> List[Filterable]: class BaseCommand(ABC): + #pylint: disable=too-many-arguments def run( self, diagram: bool, verbose: bool, services: List[str], filters: List[Filterable], + import_module: str, ): raise NotImplementedError() diff --git a/cloudiscovery/shared/diagram.py b/cloudiscovery/shared/diagram.py index fb21a44..b5a5300 100644 --- a/cloudiscovery/shared/diagram.py +++ b/cloudiscovery/shared/diagram.py @@ -22,7 +22,9 @@ class Mapsources: # diagrams modules that store classes that represent diagram elements - diagrams_modules = [ + provider = "" + modules_dict = {} + modules_dict["aws"] = [ "analytics", "ar", "blockchain", @@ -51,6 +53,23 @@ class Mapsources: "storage", ] + modules_dict['ibm'] = [ + "analytics", + "applications", + "blockchain", + "compute", + "data", + "devops", + "general", + "infrastructure", + "management", + "network", + "security", + "social", + "storage", + "user", + ] + # Class to mapping type resource from Terraform to Diagram Nodes mapresources = { "aws_lambda_function": "Lambda", @@ -228,9 +247,14 @@ class Mapsources: "aws_vpn_connection": "SiteToSiteVpn", "aws_vpn_gateway": "SiteToSiteVpn", "aws_vpn_client_endpoint": "ClientVpn", + "ibm_vpc": "Vpc", + "ibm_security_group": "Firewall", + "ibm_network_acl": "Rules", + "ibm_subnet": "Subnet", + "ibm_route_table": "Router", } - resource_styles = build_styles() + resource_styles = build_styles(provider) def add_resource_to_group(ordered_resources, group, resource): @@ -307,19 +331,19 @@ def generate_diagram( graph_attr={"nodesep": "2.0", "ranksep": "1.0", "splines": "curved"}, ) as d: d.dot.engine = self.engine - self.draw_diagram(ordered_resources=ordered_resources, relations=relations) message_handler("\n\nPNG diagram generated", "HEADER") message_handler("Check your diagram: " + output_filename + ".png", "OKBLUE") + # pylint: disable=exec-used def draw_diagram(self, ordered_resources, relations): already_drawn_elements = {} - - # Import all AWS nodes - for module in Mapsources.diagrams_modules: - exec("from diagrams.aws." + module + " import *") - + # Import all AWS, IBM nodes + for key, val in Mapsources.modules_dict: + for module in val: + # pylint: disable=exec-used + exec("from diagrams." + key + "." + module + " import *") nodes: Dict[ResourceDigest, any] = {} # Iterate resources to draw it for group_name in ordered_resources: @@ -432,15 +456,22 @@ def build_diagram( ): mx_graph_model = DIAGRAM_HEADER cell_id = 1 - + isAWS = True vpc_resource = None for _, resource_group in resources.items(): for resource in resource_group: - if resource.digest.type == "aws_vpc": + if resource.digest.type == "aws_vpc" or resource.digest.type == "ibm_vpc": + Mapsources.provider = "aws" + Mapsources.resource_styles = build_styles("aws") if vpc_resource is None: vpc_resource = resource else: raise Exception("Only one VPC in a region is supported now") + if resource.digest.type == "ibm_vpc": + isAWS = False + Mapsources.provider = "ibm" + Mapsources.resource_styles = build_styles("ibm") + if vpc_resource is None: raise Exception("Only one VPC in a region is supported now") @@ -448,7 +479,8 @@ def build_diagram( vpc_box_height = 56565656 subnet_box_height = 424242 - vpc_cell = ( + if isAWS: + vpc_cell = ( '' "".format(cell_id, vpc_resource.name, vpc_box_height) ) + else: + vpc_cell = ( + '' + "".format(cell_id, vpc_resource.name, vpc_box_height) + ) cell_id += 1 mx_graph_model += vpc_cell @@ -479,7 +520,8 @@ def build_diagram( public_subnet_y = 40 cell_id += 1 # pylint: disable=line-too-long - public_subnet = ( + if isAWS: + public_subnet = ( ''.format_map( + { + "X": str(public_subnet_x), + "Y": str(public_subnet_y), + "H": subnet_box_height, + "W": subnet_box_width, + } + ) + ) mx_graph_model += public_subnet (mx_graph_model, public_rows) = self.render_subnet_items( @@ -512,7 +570,8 @@ def build_diagram( private_subnet_x = 480 private_subnet_y = 40 cell_id += 1 - private_subnet = ( + if isAWS: + private_subnet = ( ''.format_map( + { + "X": str(private_subnet_x), + "Y": str(private_subnet_y), + "H": 200, + "W": 200, + } + ) + ) mx_graph_model += private_subnet (mx_graph_model, private_rows) = self.render_subnet_items( @@ -552,15 +627,22 @@ def build_diagram( public_subnet_x = 0 for _, resource_group in resources.items(): for resource in resource_group: - if resource.digest.type in ["aws_subnet", "aws_vpc"]: + if resource.digest.type in ["aws_subnet", "aws_vpc", "ibm_subnet", "ibm_vpc"]: continue if resource.digest not in added_resources: added_resources.append(resource.digest) - style = ( + if isAWS: + style = ( Mapsources.resource_styles[resource.digest.type] if resource.digest.type in Mapsources.resource_styles else Mapsources.resource_styles["aws_general"] - ) + ) + else: + style = ( + Mapsources.resource_styles[resource.digest.type] + if resource.digest.type in Mapsources.resource_styles + else Mapsources.resource_styles["ibm_general"] + ) cell = CELL_TEMPLATE.format_map( { "CELL_IDX": resource.digest.to_string(), @@ -605,7 +687,7 @@ def render_subnet_items( row = 0 # pylint: disable=too-many-nested-blocks for relation in resource_relations: - if relation.to_node == ResourceDigest(id=subnet_id, type="aws_subnet"): + if relation.to_node == ResourceDigest(id=subnet_id, type=("aws_subnet" or "ibm_subnet")): for _, resource_group in resources.items(): for resource in resource_group: if ( @@ -640,4 +722,6 @@ def has_subnet_type(subnet_id, resource_relations) -> bool: for relation in resource_relations: if relation.to_node == ResourceDigest(id=subnet_id, type="aws_subnet"): return True + if relation.to_node == ResourceDigest(id=subnet_id, type="ibm_subnet"): + return True return False diff --git a/cloudiscovery/shared/diagramsnet.py b/cloudiscovery/shared/diagramsnet.py index ce1ba94..c01b115 100644 --- a/cloudiscovery/shared/diagramsnet.py +++ b/cloudiscovery/shared/diagramsnet.py @@ -32,227 +32,273 @@ w2 = s * 78 -def _add_general_resources(styles): - n3 = ( +def _add_general_resources(styles, provider): + if provider == "aws": + n3 = ( "gradientDirection=north;outlineConnect=0;fontColor=#232F3E;gradientColor=#505863;fillColor=#1E262E;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_general"] = n3 + "resourceIcon;resIcon=" + gn + ".general;" - + ) + elif provider == "ibm": + n3 = ( + "gradientDirection=north;outlineConnect=0;fontColor=#232F3E;gradientColor=#505863;fillColor=#1E262E;" + "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" + "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.ibm." + ) + styles["ibm_general"] = n3 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".general;" -def _add_analytics_resources(styles): - n2 = ( +def _add_analytics_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#945DF2;gradientDirection=north;fillColor=#5A30B5;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - styles["aws_athena"] = n2 + "resourceIcon;resIcon=" + gn + ".athena;" - styles["aws_elasticsearch_domain"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".elasticsearch_service;" - ) - styles["aws_emr"] = n2 + "resourceIcon;resIcon=" + gn + ".emr;" - styles["aws_emr_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".emr;" - styles["aws_kinesis"] = n2 + "resourceIcon;resIcon=" + gn + ".kinesis;" - styles["aws_kinesisanalytics"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".kinesis_data_analytics;" - ) - styles["aws_kinesis_firehose"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".kinesis_data_firehose;" - ) - styles["aws_quicksight"] = n2 + "resourceIcon;resIcon=" + gn + ".quicksight;" - styles["aws_redshift"] = n2 + "resourceIcon;resIcon=" + gn + ".redshift;" - styles["aws_data_pipeline"] = n2 + "resourceIcon;resIcon=" + gn + ".data_pipeline;" - styles["aws_msk_cluster"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".managed_streaming_for_kafka;" - ) - styles["aws_glue"] = n2 + "resourceIcon;resIcon=" + gn + ".glue;" - styles["aws_lakeformation"] = n2 + "resourceIcon;resIcon=" + gn + ".lake_formation;" - - -def _add_application_integration_resources(styles): - n2 = ( + ) + styles["aws_athena"] = n2 + "resourceIcon;resIcon=" + gn + ".athena;" + styles["aws_elasticsearch_domain"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".elasticsearch_service;" + ) + styles["aws_emr"] = n2 + "resourceIcon;resIcon=" + gn + ".emr;" + styles["aws_emr_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".emr;" + styles["aws_kinesis"] = n2 + "resourceIcon;resIcon=" + gn + ".kinesis;" + styles["aws_kinesisanalytics"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".kinesis_data_analytics;" + ) + styles["aws_kinesis_firehose"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".kinesis_data_firehose;" + ) + styles["aws_quicksight"] = n2 + "resourceIcon;resIcon=" + gn + ".quicksight;" + styles["aws_redshift"] = n2 + "resourceIcon;resIcon=" + gn + ".redshift;" + styles["aws_data_pipeline"] = n2 + "resourceIcon;resIcon=" + gn + ".data_pipeline;" + styles["aws_msk_cluster"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".managed_streaming_for_kafka;" + ) + styles["aws_glue"] = n2 + "resourceIcon;resIcon=" + gn + ".glue;" + styles["aws_lakeformation"] = n2 + "resourceIcon;resIcon=" + gn + ".lake_formation;" + else: + n2 = ( + "outlineConnect=0;fontColor=#232F3E;gradientColor=#945DF2;gradientDirection=north;fillColor=#5A30B5;" + "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" + "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.ibm." + ) + styles["aws_athena"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".athena;" + styles["aws_elasticsearch_domain"] = ( + n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".elasticsearch_service;" + ) + styles["aws_emr"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".emr;" + styles["aws_emr_cluster"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".emr;" + styles["aws_kinesis"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".kinesis;" + styles["aws_kinesisanalytics"] = ( + n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".kinesis_data_analytics;" + ) + styles["aws_kinesis_firehose"] = ( + n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".kinesis_data_firehose;" + ) + styles["aws_quicksight"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".quicksight;" + styles["aws_redshift"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".redshift;" + styles["aws_data_pipeline"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".data_pipeline;" + styles["aws_msk_cluster"] = ( + n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".managed_streaming_for_kafka;" + ) + styles["aws_glue"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".glue;" + styles["aws_lakeformation"] = n2 + "resourceIcon;resIcon=" + "mxgraph.ibm" + ".lake_formation;" + +def _add_application_integration_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#F34482;gradientDirection=north;fillColor=#BC1356;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_sns_topic"] = n2 + "resourceIcon;resIcon=" + gn + ".sns;" - styles["aws_sqs"] = n2 + "resourceIcon;resIcon=" + gn + ".sqs;" - styles["aws_appsync_graphql_api"] = n2 + "resourceIcon;resIcon=" + gn + ".appsync;" - styles["aws_events"] = n2 + "resourceIcon;resIcon=" + gn + ".eventbridge;" - - -def _add_compute_resources(styles): - n = ( + ) + styles["aws_sns_topic"] = n2 + "resourceIcon;resIcon=" + gn + ".sns;" + styles["aws_sqs"] = n2 + "resourceIcon;resIcon=" + gn + ".sqs;" + styles["aws_appsync_graphql_api"] = n2 + "resourceIcon;resIcon=" + gn + ".appsync;" + styles["aws_events"] = n2 + "resourceIcon;resIcon=" + gn + ".eventbridge;" + +def _add_compute_resources(styles, provider): + if provider == "aws": + n = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#D05C17;strokeColor=none;dashed=0;" "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" "pointerEvents=1;shape=mxgraph.aws4." - ) - n2 = ( + ) + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#F78E04;gradientDirection=north;fillColor=#D05C17;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) + ) - styles["aws_instance"] = n2 + "resourceIcon;resIcon=" + gn + ".ec2;" - styles["aws_autoscaling_group"] = ( + styles["aws_instance"] = n2 + "resourceIcon;resIcon=" + gn + ".ec2;" + styles["aws_autoscaling_group"] = ( n2 + "resourceIcon;resIcon=" + gn + ".auto_scaling2;" - ) - styles["aws_batch"] = n2 + "resourceIcon;resIcon=" + gn + ".batch;" - styles["aws_elastic_beanstalk_environment"] = ( + ) + styles["aws_batch"] = n2 + "resourceIcon;resIcon=" + gn + ".batch;" + styles["aws_elastic_beanstalk_environment"] = ( n2 + "resourceIcon;resIcon=" + gn + ".elastic_beanstalk;" - ) - styles["aws_lambda_function"] = n + "lambda_function;" - + ) + styles["aws_lambda_function"] = n + "lambda_function;" -def _add_container_resources(styles): - n2 = ( +def _add_container_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#F78E04;gradientDirection=north;fillColor=#D05C17;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) + ) + styles["aws_eks_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".eks;" + styles["aws_ecr"] = n2 + "resourceIcon;resIcon=" + gn + ".ecr;" + styles["aws_ecs_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".ecs;" - styles["aws_eks_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".eks;" - styles["aws_ecr"] = n2 + "resourceIcon;resIcon=" + gn + ".ecr;" - styles["aws_ecs_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".ecs;" - -def _add_customer_engagement_resources(styles): - n2 = ( +def _add_customer_engagement_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#4D72F3;gradientDirection=north;fillColor=#3334B9;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_connect"] = n2 + "resourceIcon;resIcon=" + gn + ".connect;" - styles["aws_pinpoint"] = n2 + "resourceIcon;resIcon=" + gn + ".pinpoint;" - styles["aws_ses"] = n2 + "resourceIcon;resIcon=" + gn + ".simple_email_service;" + ) + styles["aws_connect"] = n2 + "resourceIcon;resIcon=" + gn + ".connect;" + styles["aws_pinpoint"] = n2 + "resourceIcon;resIcon=" + gn + ".pinpoint;" + styles["aws_ses"] = n2 + "resourceIcon;resIcon=" + gn + ".simple_email_service;" -def _add_database_resources(styles): - n = ( +def _add_database_resources(styles, provider): + if provider == "aws": + n = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#3334B9;strokeColor=none;dashed=0;" "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" "pointerEvents=1;shape=mxgraph.aws4." - ) - n2 = ( + ) + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#4D72F3;gradientDirection=north;fillColor=#3334B9;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_docdb_cluster"] = ( + ) + styles["aws_docdb_cluster"] = ( n2 + "resourceIcon;resIcon=" + gn + ".documentdb_with_mongodb_compatibility;" - ) - styles["aws_dynamodb"] = n2 + "resourceIcon;resIcon=" + gn + ".dynamodb;" - styles["aws_elasticache_cluster"] = ( + ) + styles["aws_dynamodb"] = n2 + "resourceIcon;resIcon=" + gn + ".dynamodb;" + styles["aws_elasticache_cluster"] = ( n2 + "resourceIcon;resIcon=" + gn + ".elasticache;" - ) - styles["aws_neptune_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".neptune;" - styles["aws_redshift"] = n2 + "resourceIcon;resIcon=" + gn + ".redshift;" + ) + styles["aws_neptune_cluster"] = n2 + "resourceIcon;resIcon=" + gn + ".neptune;" + styles["aws_redshift"] = n2 + "resourceIcon;resIcon=" + gn + ".redshift;" - styles["aws_db_instance"] = n + "rds_instance;" - styles["aws_dax"] = n + "dynamodb_dax;" + styles["aws_db_instance"] = n + "rds_instance;" + styles["aws_dax"] = n + "dynamodb_dax;" -def _add_ml_resources(styles): - n2 = ( +def _add_ml_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#4AB29A;gradientDirection=north;fillColor=#116D5B;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) + ) + styles["aws_sagemaker"] = n2 + "resourceIcon;resIcon=" + gn + ".sagemaker;" - styles["aws_sagemaker"] = n2 + "resourceIcon;resIcon=" + gn + ".sagemaker;" - - -def _add_management_governance_resources(styles): - n2 = ( +def _add_management_governance_resources(styles, provider): + if provider == "aws": + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#F34482;gradientDirection=north;fillColor=#BC1356;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_cloudwatch"] = n2 + "resourceIcon;resIcon=" + gn + ".cloudwatch_2;" - styles["aws_autoscaling_group"] = ( + ) + styles["aws_cloudwatch"] = n2 + "resourceIcon;resIcon=" + gn + ".cloudwatch_2;" + styles["aws_autoscaling_group"] = ( n2 + "resourceIcon;resIcon=" + gn + ".autoscaling;" - ) - styles["aws_auto_scaling"] = n2 + "resourceIcon;resIcon=" + gn + ".autoscaling;" - styles["aws_cloudformation"] = ( + ) + styles["aws_auto_scaling"] = n2 + "resourceIcon;resIcon=" + gn + ".autoscaling;" + styles["aws_cloudformation"] = ( n2 + "resourceIcon;resIcon=" + gn + ".cloudformation;" - ) - - -def _add_network_resources(styles): - n = ( - "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#5A30B5;strokeColor=none;dashed=0;" - "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" - "pointerEvents=1;shape=mxgraph.aws4." - ) - n2 = ( - "outlineConnect=0;fontColor=#232F3E;gradientColor=#945DF2;gradientDirection=north;fillColor=#5A30B5;" - "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" - "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) - - styles["aws_api_gateway_rest_api"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".api_gateway;" - ) - styles["aws_cloudfront_distribution"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".cloudfront;" - ) - styles["aws_vpc"] = n2 + "resourceIcon;resIcon=" + gn + ".vpc;" - styles["aws_vpn_client_endpoint"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".client_vpn;" - ) - styles["aws_elb"] = n2 + "resourceIcon;resIcon=" + gn + ".elastic_load_balancing;" - styles["aws_directconnect"] = n2 + "resourceIcon;resIcon=" + gn + ".direct_connect;" - styles["aws_global_accelerator"] = ( - n2 + "resourceIcon;resIcon=" + gn + ".global_accelerator;" - ) - - styles["aws_route_table"] = n + "route_table;" - styles["aws_vpc_endpoint_gateway"] = n + "gateway;" - styles["aws_internet_gateway"] = n + "internet_gateway;" - styles["aws_nat_gateway"] = n + "nat_gateway;" - styles["aws_network_acl"] = n + "network_access_control_list;" - styles["aws_elb_classic"] = n + "classic_load_balancer;" - styles["aws_vpn_connection"] = n + "vpn_connection;" - styles["aws_vpn_gateway"] = n + "vpn_gateway;" - - -def _add_storage_resources(styles): - n = ( + ) + +def _add_network_resources(styles, provider): + if provider == "aws": + n = ( + "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#5A30B5;strokeColor=none;dashed=0;" + "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" + "pointerEvents=1;shape=mxgraph.aws4." + ) + n2 = ( + "outlineConnect=0;fontColor=#232F3E;gradientColor=#945DF2;gradientDirection=north;fillColor=#5A30B5;" + "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" + "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." + ) + styles["aws_api_gateway_rest_api"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".api_gateway;" + ) + styles["aws_cloudfront_distribution"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".cloudfront;" + ) + styles["aws_vpc"] = n2 + "resourceIcon;resIcon=" + gn + ".vpc;" + styles["aws_vpn_client_endpoint"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".client_vpn;" + ) + styles["aws_elb"] = n2 + "resourceIcon;resIcon=" + gn + ".elastic_load_balancing;" + styles["aws_directconnect"] = n2 + "resourceIcon;resIcon=" + gn + ".direct_connect;" + styles["aws_global_accelerator"] = ( + n2 + "resourceIcon;resIcon=" + gn + ".global_accelerator;" + ) + styles["aws_route_table"] = n + "route_table;" + styles["aws_vpc_endpoint_gateway"] = n + "gateway;" + styles["aws_internet_gateway"] = n + "internet_gateway;" + styles["aws_nat_gateway"] = n + "nat_gateway;" + styles["aws_network_acl"] = n + "network_access_control_list;" + styles["aws_elb_classic"] = n + "classic_load_balancer;" + styles["aws_vpn_connection"] = n + "vpn_connection;" + styles["aws_vpn_gateway"] = n + "vpn_gateway;" + else: + styles["ibm_route_table"] = ( + "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#5A30B5;strokeColor=none;dashed=0;" + "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" + "pointerEvents=1;shape=mxgraph.aws4.route_table;" + ) + styles["ibm_security_group"] = 'fontStyle=0;verticalAlign=top;align=center;spacingTop=-2;fillColor=none;' + styles["ibm_security_group"] = styles["ibm_security_group"] + 'rounded=0;whiteSpace=wrap;html=1;' + styles["ibm_security_group"] = styles["ibm_security_group"] + 'strokeColor=#FF0000;strokeWidth=2;' + styles["ibm_security_group"] = styles["ibm_security_group"] + 'dashed=1;container=1;collapsible=0;' + styles["ibm_security_group"] = styles["ibm_security_group"] + 'expand=0;recursiveResize=0;' + styles["ibm_network_acl"] = 'shape=mxgraph.ibm.box;prType=subnet;fontStyle=0;verticalAlign=top;' + styles["ibm_network_acl"] = styles["ibm_network_acl"] + 'align=left;spacingLeft=32;spacingTop=4;' + styles["ibm_network_acl"] = styles["ibm_network_acl"] + 'fillColor=#E6F0E2;rounded=0;whiteSpace=wrap;' + styles["ibm_network_acl"] = styles["ibm_network_acl"] + 'html=1;strokeColor=#00882B;strokeWidth=1;dashed=0;' + styles["ibm_network_acl"] = styles["ibm_network_acl"] + 'container=1;spacing=-4;collapsible=0;expand=0;' + styles["ibm_network_acl"] = styles["ibm_network_acl"] + 'recursiveResize=0;' + + +def _add_storage_resources(styles, provider): + if provider == "aws": + n = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#277116;strokeColor=none;dashed=0;" "verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;" "pointerEvents=1;shape=mxgraph.aws4." - ) - n2 = ( + ) + n2 = ( "outlineConnect=0;fontColor=#232F3E;gradientColor=#60A337;gradientDirection=north;fillColor=#277116;" "strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;" "fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4." - ) + ) - styles["aws_efs_file_system"] = ( + styles["aws_efs_file_system"] = ( n2 + "resourceIcon;resIcon=" + gn + ".elastic_file_system;" - ) - styles["aws_fsx"] = n2 + "resourceIcon;resIcon=" + gn + ".fsx;" + ) + styles["aws_fsx"] = n2 + "resourceIcon;resIcon=" + gn + ".fsx;" - styles["aws_s3"] = n + "bucket;" + styles["aws_s3"] = n + "bucket;" -def build_styles(): +def build_styles(provider): styles = {} - _add_general_resources(styles) - _add_analytics_resources(styles) - _add_application_integration_resources(styles) - _add_compute_resources(styles) - _add_container_resources(styles) - _add_customer_engagement_resources(styles) - _add_database_resources(styles) - _add_ml_resources(styles) - _add_management_governance_resources(styles) - _add_network_resources(styles) - _add_storage_resources(styles) + _add_general_resources(styles, provider) + _add_analytics_resources(styles, provider) + _add_application_integration_resources(styles, provider) + _add_compute_resources(styles, provider) + _add_container_resources(styles, provider) + _add_customer_engagement_resources(styles, provider) + _add_database_resources(styles, provider) + _add_ml_resources(styles, provider) + _add_management_governance_resources(styles, provider) + _add_network_resources(styles, provider) + _add_storage_resources(styles, provider) return styles diff --git a/cloudiscovery/shared/parameters.py b/cloudiscovery/shared/parameters.py index d05ec10..7284f98 100644 --- a/cloudiscovery/shared/parameters.py +++ b/cloudiscovery/shared/parameters.py @@ -18,6 +18,27 @@ def generate_parser(): subparsers = parser.add_subparsers(help="commands", dest="command") + vpc_parser = subparsers.add_parser("ibm-vpc", help="Analyze VPCs") + add_default_arguments(vpc_parser) + vpc_parser.add_argument( + "-v", + "--vpc_id", + required=False, + help="Inform VPC to analyze. If not informed, script will check all vpcs.", + ) + vpc_parser.add_argument( + "-n", + "--region_name", + required=False, + help="Inform region name to analyze.", + ) + vpc_parser.add_argument( + "-a", + "--api_key", + required=False, + help="Inform apikey to login to cloud.", + ) + vpc_parser = subparsers.add_parser("aws-vpc", help="Analyze VPCs") add_default_arguments(vpc_parser) vpc_parser.add_argument(