From e39747b37e889e8d08bbe6ba840d5221b75c7d1e Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 15 May 2020 01:04:39 +0100 Subject: [PATCH 01/13] Refactoring code to implement resource collector and load dynamic #21 --- shared/awscommands.py | 91 ++++++++++++++++++-------------------- shared/common.py | 8 ++++ shared/internal/compute.py | 11 +++-- shared/internal/network.py | 8 +--- 4 files changed, 58 insertions(+), 60 deletions(-) diff --git a/shared/awscommands.py b/shared/awscommands.py index 049c7a3..99dc64b 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -1,14 +1,10 @@ from shared.common import * -from shared.internal.security import IAM, IAMPOLICY -from shared.internal.network import VPC, IGW, NATGATEWAY, ELB, ELBV2, ROUTETABLE, SUBNET, NACL, SG, VPCPEERING -from shared.internal.network import VPCENDPOINT -from shared.internal.compute import LAMBDA, EC2, EKS, EMR -from shared.internal.database import RDS, ELASTICACHE, DOCUMENTDB -from shared.internal.storage import EFS, S3POLICY -from shared.internal.analytics import ELASTICSEARCH, MSK -from shared.internal.application import SQSPOLICY -from shared.internal.management import CLOUDFORMATION, CANARIES +from shared.internal.security import IAM +from shared.internal.network import VPC +import importlib, inspect +import os +PATH_CHECKS = "shared/internal" class AwsCommands(object): @@ -16,49 +12,46 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options def run(self): - - """ IAM and VPC validations """ - IAM(self.vpc_options).run() - VPC(self.vpc_options).run() - """ Compute resources """ - EC2(self.vpc_options).run() - LAMBDA(self.vpc_options).run() - EKS(self.vpc_options).run() - EMR(self.vpc_options).run() + """ + The project's development pattern is a file with the respective name of the parent resource (e.g. compute, network), + classes of child resources inside this file and run() method to execute respective check. So it makes sense to load dynamically. - """ Database resources """ - RDS(self.vpc_options).run() - ELASTICACHE(self.vpc_options).run() - DOCUMENTDB(self.vpc_options).run() + Only IAM and VPC checks that reamins in the old way. - """ Application resources """ - SQSPOLICY(self.vpc_options).run() + TODO: improve IAM and VPC check methods + TODO: order by resource + """ - """ Storage resources """ - EFS(self.vpc_options).run() - S3POLICY(self.vpc_options).run() + """ IAM and VPC validations """ + IAM(self.vpc_options).run() + VPC(self.vpc_options).run() - """ Analytics resources """ - ELASTICSEARCH(self.vpc_options).run() - MSK(self.vpc_options).run() - - """ Security resources """ - IAMPOLICY(self.vpc_options).run() + checks = [] + + """ Iterate to get all modules """ + for name in os.listdir(PATH_CHECKS): + if name.endswith(".py"): + #strip the extension + module = name[:-3] + + """ Load and call all run check """ + for nameclass, cls in inspect.getmembers(importlib.import_module("shared.internal."+module), inspect.isclass): + if hasattr(cls, 'run') and callable(getattr(cls, 'run')) and nameclass not in ['VPC','IAM']: + checks.append(cls(self.vpc_options).run()) - """ Network resources """ - IGW(self.vpc_options).run() - NATGATEWAY(self.vpc_options).run() - ELB(self.vpc_options).run() - ELBV2(self.vpc_options).run() - ROUTETABLE(self.vpc_options).run() - SUBNET(self.vpc_options).run() - NACL(self.vpc_options).run() - SG(self.vpc_options).run() - VPCPEERING(self.vpc_options).run() - VPCENDPOINT(self.vpc_options).run() - - """ Management resources """ - CLOUDFORMATION(self.vpc_options).run() - CANARIES(self.vpc_options).run() - \ No newline at end of file + + """ + TODO: Generate reports + """ + #....reports(checks).... + + """ + TODO: Generate diagrams + """ + #....diagrams(checks).... + + """ + TODO: Export in csv/json/yaml/tf... future.... + """" + #....exporttf(checks).... \ No newline at end of file diff --git a/shared/common.py b/shared/common.py index cc03a3d..ec9078d 100644 --- a/shared/common.py +++ b/shared/common.py @@ -28,6 +28,14 @@ class VpcOptions(NamedTuple): def client(self, service_name: str): return self.session.client(service_name, region_name=self.region_name) +class Resource(NamedTuple): + + id: str + name: str + type: str + details: str + + def generate_session(profile_name): try: diff --git a/shared/internal/compute.py b/shared/internal/compute.py index 32ab626..7a6ca84 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -1,5 +1,6 @@ from shared.common import * from shared.error_handler import exception +from typing import List class LAMBDA(object): @@ -40,7 +41,7 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') @@ -66,7 +67,8 @@ def run(self): ) message_handler("Found {0} EC2 Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + + return Resource(id='1',name='2',type='3',details='4') class EKS(object): @@ -74,7 +76,7 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('eks') @@ -100,7 +102,8 @@ def run(self): message_handler("Found {0} EKS Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + + return Resource(id='5', name='6',type='7',details='8') class EMR(object): diff --git a/shared/internal/network.py b/shared/internal/network.py index f0f48c4..3cda1ac 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -386,10 +386,4 @@ def run(self): message_handler("Found {0} VPC Endpoints using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True - -""" aliases """ -IGW = INTERNETGATEWAY -ELB = ELASTICLOADBALANCING -ELBV2 = ELASTICLOADBALANCINGV2 -SG = SECURITYGROUP \ No newline at end of file + return True \ No newline at end of file From 438162d30246d8d4dbad0608eae858b37ca26dc9 Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sat, 23 May 2020 14:44:05 +0700 Subject: [PATCH 02/13] improved reporting of ECS, network and database resources --- shared/awscommands.py | 1 - shared/internal/compute.py | 7 ++-- shared/internal/containers.py | 68 ++++++++++++++++++++--------------- shared/internal/database.py | 29 +++++++++++---- shared/internal/network.py | 49 +++++++++++++++++++------ 5 files changed, 103 insertions(+), 51 deletions(-) diff --git a/shared/awscommands.py b/shared/awscommands.py index 53f2cd0..60e75ab 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -66,4 +66,3 @@ def run(self): """ Containers """ ECS(self.vpc_options).run() - \ No newline at end of file diff --git a/shared/internal/compute.py b/shared/internal/compute.py index 39356fe..eec2938 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -58,7 +58,7 @@ def run(self): if "VpcId" in instances: if instances['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nInstanceId: {} - PrivateIpAddress: {} -> Subnet id(s): {} - VpcId {}".format( + message = message + "\nInstanceId: {} - PrivateIpAddress: {} -> Subnet id(s): {} -> VpcId {}".format( instances["InstanceId"], instances["PrivateIpAddress"], instances['SubnetId'], @@ -176,13 +176,14 @@ def run(self): if data_subnet['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nAutoScalingGroupName: {} -> LaunchConfigurationName: {} -> VPC id {}".format( + message = message + "\nAutoScalingGroupName: {} -> LaunchConfigurationName: {} -> subnet: {} -> VPC id {}".format( data["AutoScalingGroupName"], data["LaunchConfigurationName"], + data_subnet["SubnetId"], self.vpc_options.vpc_id ) - message_handler("Found {0} Autoscaling groups using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + message_handler("Found {0} Autoscaling groups associations using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') return True diff --git a/shared/internal/containers.py b/shared/internal/containers.py index 061e096..96e3ac0 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -11,8 +11,11 @@ def __init__(self, vpc_options: VpcOptions): def run(self): client = self.vpc_options.client('ecs') - - response = client.describe_clusters() + + clusters_list = client.list_clusters() + response = client.describe_clusters( + clusters=clusters_list['clusterArns'] + ) message_handler("\nChecking ECS CLUSTER...", "HEADER") @@ -23,34 +26,41 @@ def run(self): message = "" for data in response['clusters']: - """ Searching all cluster services """ - services = client.list_services(cluster=data['clusterName']) - - for data_service in services['serviceArns']: - - """ Checking service detail """ - service_detail = client.describe_services(services=[data_service]) - - for data_service_detail in service_detail['services']: - - service_subnets = data_service_detail["networkConfiguration"]["awsvpcConfiguration"]["subnets"] - - """ describe subnet to get VpcId """ - ec2 = self.vpc_options.client('ec2') - - subnets = ec2.describe_subnets(SubnetIds=service_subnets) - - """ Iterate subnet to get VPC """ - for data_subnet in subnets['Subnets']: - - if data_subnet['VpcId'] == self.vpc_options.vpc_id: + """ Searching all cluster services """ + paginator = client.get_paginator('list_services') + pages = paginator.paginate( + cluster=data['clusterName'] + ) + for services in pages: + service_details = client.describe_services( + cluster=data['clusterName'], + services=services['serviceArns'] + ) + + for data_service_detail in service_details['services']: + if data_service_detail['launchType'] == 'FARGATE': + service_subnets = data_service_detail["networkConfiguration"]["awsvpcConfiguration"]["subnets"] + + """ describe subnet to get VpcId """ + ec2 = self.vpc_options.client('ec2') + + subnets = ec2.describe_subnets(SubnetIds=service_subnets) + + """ Iterate subnet to get VPC """ + for data_subnet in subnets['Subnets']: + + if data_subnet['VpcId'] == self.vpc_options.vpc_id: + + found += 1 + message = message + "\nclusterName: {} -> ServiceName: {} -> VPC id {}".format( + data["clusterName"], + data_service_detail['serviceName'], + self.vpc_options.vpc_id + ) + else: + """ EC2 services require container instances, list of them should be fine for now """ + pass - found += 1 - message = message + "\nclusterName: {} -> ServiceName: {} -> VPC id {}".format( - data["clusterName"], - data_service, - self.vpc_options.vpc_id - ) message_handler("Found {0} ECS Cluster using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') diff --git a/shared/internal/database.py b/shared/internal/database.py index c73fced..abea9cf 100644 --- a/shared/internal/database.py +++ b/shared/internal/database.py @@ -28,10 +28,15 @@ def run(self): message = "" for data in response["DBInstances"]: if data['DBSubnetGroup']['VpcId'] == self.vpc_options.vpc_id: + subnet_ids = [] + for subnet in data['DBSubnetGroup']['Subnets']: + subnet_ids.append(subnet['SubnetIdentifier']) + found += 1 - message = message + "\nDBInstanceIdentifier: {0} - Engine: {1} - VpcId {2}".format( + message = message + "\nDBInstanceIdentifier: {} - Engine: {} -> Subnet id: {} -> VpcId {}".format( data["DBInstanceIdentifier"], - data["Engine"], + data["Engine"], + ', '.join(subnet_ids), data['DBSubnetGroup']['VpcId'] ) message_handler("Found {0} RDS Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') @@ -66,11 +71,16 @@ def run(self): cachesubnet = client.describe_cache_subnet_groups(CacheSubnetGroupName=data['CacheSubnetGroupName']) if cachesubnet['CacheSubnetGroups'][0]['VpcId'] == self.vpc_options.vpc_id: + subnet_ids = [] + for subnet in cachesubnet['CacheSubnetGroups'][0]['Subnets']: + subnet_ids.append(subnet['SubnetIdentifier']) + found += 1 - message = message + "\nCacheClusterId: {0} - CacheSubnetGroupName: {1} - Engine: {2} - VpcId: {3}".format( + message = message + "\nCacheClusterId: {} - CacheSubnetGroupName: {} - Engine: {} -> Subnet id: {} -> VpcId: {}".format( data["CacheClusterId"], - data["CacheSubnetGroupName"], - data["Engine"], + data["CacheSubnetGroupName"], + data["Engine"], + ', '.join(subnet_ids), cachesubnet['CacheSubnetGroups'][0]['VpcId'] ) message_handler("Found {0} Elasticache Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') @@ -105,11 +115,16 @@ def run(self): for data in response['DBInstances']: if data['DBSubnetGroup']['VpcId'] == self.vpc_options.vpc_id: + subnet_ids = [] + for subnet in data['DBSubnetGroup']['Subnets']: + subnet_ids.append(subnet['SubnetIdentifier']) + found += 1 - message = message + "\nDBInstanceIdentifier: {0} - DBInstanceClass: {1} - Engine: {2} - VpcId: {3}".format( + message = message + "\nDBInstanceIdentifier: {} - DBInstanceClass: {} - Engine: {} -> Subnet id: {} -> VpcId: {}".format( data['DBInstanceIdentifier'], data["DBInstanceClass"], - data["Engine"], + data["Engine"], + ', '.join(subnet_ids), self.vpc_options.vpc_id ) message_handler("Found {0} DocumentoDB Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') diff --git a/shared/internal/network.py b/shared/internal/network.py index f0f48c4..985a928 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -15,9 +15,10 @@ def run(self): ) dataresponse = response['Vpcs'][0] - message = "VPC: {0}\nCIDR Block: {1}\nTenancy: {2}".format(self.vpc_options.vpc_id, + message = "VPC: {}\nCIDR Block: {}\nTenancy: {}\nIs default: {}".format(self.vpc_options.vpc_id, dataresponse['CidrBlock'], - dataresponse['InstanceTenancy']) + dataresponse['InstanceTenancy'], + dataresponse['IsDefault']) print(message) return True @@ -121,8 +122,9 @@ def run(self): if data['VPCId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nLoadBalancerName: {} -> VPC id {}".format( + message = message + "\nLoadBalancerName: {} - Subnet id: {} -> VPC id {}".format( data['LoadBalancerName'], + ', '.join(data['Subnets']), self.vpc_options.vpc_id ) @@ -154,10 +156,14 @@ def run(self): for data in response['LoadBalancers']: if data['VpcId'] == self.vpc_options.vpc_id: + subnet_ids = [] + for availabilityZone in data['AvailabilityZones']: + subnet_ids.append(availabilityZone['SubnetId']) found += 1 message = message + "\nLoadBalancerName: {} -> VPC id {}".format( data['LoadBalancerName'], + ', '.join(subnet_ids), self.vpc_options.vpc_id ) @@ -230,8 +236,10 @@ def run(self): for data in response['Subnets']: found += 1 - message = message + "\nSubnetId: {} -> VPC id {}".format( + message = message + "\nSubnetId: {} - CIDR block: {} - AZ: {} -> VPC id {}".format( data['SubnetId'], + data['CidrBlock'], + data['AvailabilityZone'], self.vpc_options.vpc_id ) @@ -265,10 +273,14 @@ def run(self): """ Iterate to get all NACL filtered """ for data in response['NetworkAcls']: + subnet_ids = [] + for subnet in data['Associations']: + subnet_ids.append(subnet['SubnetId']) found += 1 - message = message + "\nNetworkAclId: {} -> VPC id {}".format( + message = message + "\nNetworkAclId: {} -> Subnet Id: {} -> VPC Id: {}".format( data['NetworkAclId'], + ', '.join(subnet_ids), self.vpc_options.vpc_id ) @@ -341,9 +353,14 @@ def run(self): or data['RequesterVpcInfo']['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nVpcPeeringConnectionId: {} -> VPC id {}".format( + message = message + "\nVpcPeeringConnectionId: {} -> Owner: {} - Region: {} - Requester VPC id: {} -> Owner: {} - Region: {} - Accepter VPC id: {}".format( data['VpcPeeringConnectionId'], - self.vpc_options.vpc_id + data['AccepterVpcInfo']['OwnerId'], + data['AccepterVpcInfo']['Region'], + data['AccepterVpcInfo']['VpcId'], + data['RequesterVpcInfo']['OwnerId'], + data['RequesterVpcInfo']['Region'], + data['RequesterVpcInfo']['VpcId'] ) message_handler("Found {0} VPC Peering using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') @@ -379,10 +396,20 @@ def run(self): if data['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nVpcEndpointId: {} -> VPC id {}".format( - data['VpcEndpointId'], - self.vpc_options.vpc_id - ) + if data['VpcEndpointType'] == 'Gateway': + message = message + "\nGateway VpcEndpointId: {} - Service: {} -> Route table Id: {} -> VPC id: {}".format( + data['VpcEndpointId'], + data['ServiceName'], + ', '.join(data['RouteTableIds']), + self.vpc_options.vpc_id + ) + else: + message = message + "\nInterface VpcEndpointId: {} - Service: {} -> Subnet Id: {} -> VPC id: {}".format( + data['VpcEndpointId'], + data['ServiceName'], + ', '.join(data['SubnetIds']), + self.vpc_options.vpc_id + ) message_handler("Found {0} VPC Endpoints using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') From f5033ff772f584906c97df4b133f876ecbe7aaab Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sat, 23 May 2020 15:19:21 +0700 Subject: [PATCH 03/13] added EC2 instances detection for ECS clusters --- shared/internal/analytics.py | 2 +- shared/internal/containers.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/shared/internal/analytics.py b/shared/internal/analytics.py index a070146..b8f9b02 100644 --- a/shared/internal/analytics.py +++ b/shared/internal/analytics.py @@ -36,7 +36,7 @@ def run(self): if elasticsearch_domain['DomainStatus']['VPCOptions']['VPCId'] == self.vpc_options.vpc_id \ or ipvpc_found is True: found += 1 - message = message + "\nDomainId: {0} - DomainName: {1} - VpcId {2}".format( + message = message + "\nDomainId: {0} - DomainName: {1} -> VPC Id: {2}".format( elasticsearch_domain['DomainStatus']['DomainId'], elasticsearch_domain['DomainStatus']['DomainName'], self.vpc_options.vpc_id diff --git a/shared/internal/containers.py b/shared/internal/containers.py index 96e3ac0..13aca64 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -11,6 +11,7 @@ def __init__(self, vpc_options: VpcOptions): def run(self): client = self.vpc_options.client('ecs') + ec2_client = self.vpc_options.client('ec2') clusters_list = client.list_clusters() response = client.describe_clusters( @@ -61,6 +62,39 @@ def run(self): """ EC2 services require container instances, list of them should be fine for now """ pass + """ Looking for container instances - they are dynamically associated, so manual review is necessary """ + list_paginator = client.get_paginator('list_container_instances') + list_pages = list_paginator.paginate( + cluster=data['clusterName'] + ) + for list_page in list_pages: + container_instances = client.describe_container_instances( + cluster=data['clusterName'], + containerInstances=list_page['containerInstanceArns'] + ) + ec2_ids = [] + for instance_details in container_instances['containerInstances']: + ec2_ids.append(instance_details['ec2InstanceId']) + paginator = ec2_client.get_paginator('describe_instances') + pages = paginator.paginate( + InstanceIds=ec2_ids + ) + for page in pages: + for reservation in page['Reservations']: + for instance in reservation['Instances']: + for network_interfaces in instance['NetworkInterfaces']: + if network_interfaces['VpcId'] == self.vpc_options.vpc_id: + found += 1 + message = message + "\nclusterName: {} -> Instance Id: {} -> Subnet id: {} -> VPC id {}".format( + data["clusterName"], + instance['InstanceId'], + network_interfaces['SubnetId'], + self.vpc_options.vpc_id + ) + pass + pass + pass + message_handler("Found {0} ECS Cluster using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') From a9c697d1957f2ad65c6da2716b0881c3863fade7 Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sat, 23 May 2020 15:20:15 +0700 Subject: [PATCH 04/13] added EC2 instances detection for ECS clusters --- shared/internal/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/internal/containers.py b/shared/internal/containers.py index 96e3ac0..ecdcbc4 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -62,6 +62,6 @@ def run(self): pass - message_handler("Found {0} ECS Cluster using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + message_handler("Found {0} ECS Cluster relations using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') return True \ No newline at end of file From 32d11595d2cd74e8d5f0e06427f28df8953b9c9e Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sat, 23 May 2020 16:35:08 +0700 Subject: [PATCH 05/13] fixed Fargate ECS clusters --- shared/internal/containers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/internal/containers.py b/shared/internal/containers.py index 1dfd3bc..6ab47dc 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -53,9 +53,10 @@ def run(self): if data_subnet['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nclusterName: {} -> ServiceName: {} -> VPC id {}".format( + message = message + "\nclusterName: {} -> ServiceName: {} -> Subnet Id: {} -> VPC id {}".format( data["clusterName"], data_service_detail['serviceName'], + data_subnet['SubnetId'], self.vpc_options.vpc_id ) else: @@ -68,6 +69,9 @@ def run(self): cluster=data['clusterName'] ) for list_page in list_pages: + if len(list_page['containerInstanceArns']) == 0: + continue + container_instances = client.describe_container_instances( cluster=data['clusterName'], containerInstances=list_page['containerInstanceArns'] From 82e0d1df77fe0465dcbc2ed5e6f014c86039fc37 Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sun, 24 May 2020 00:32:22 +0700 Subject: [PATCH 06/13] added profile docs, improved nat gateway reporting --- README.md | 4 +++- aws-network-discovery.py | 2 +- shared/internal/network.py | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b21d7cd..267c39b 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,14 @@ arn:aws:iam::aws:policy/job-function/ViewOnlyAccess arn:aws:iam::aws:policy/SecurityAudit ``` +- (Optional) If you want to be able to switch between multiple AWS credentials and settings, you can configure [named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) and later pass profile name when running the tool. + ### Usage 1. Run the aws-network-discovery command with follow options (if a region not informed, this script will try to get from ~/.aws/credentials): ```sh -$ ./aws-network-discovery.py --vpc-id vpc-xxxxxxx --region-name xx-xxxx-xxx +$ ./aws-network-discovery.py --vpc-id vpc-xxxxxxx --region-name xx-xxxx-xxx [--profile-name profile] ``` 2. For help use: diff --git a/aws-network-discovery.py b/aws-network-discovery.py index 1172ed1..356c029 100644 --- a/aws-network-discovery.py +++ b/aws-network-discovery.py @@ -56,7 +56,7 @@ def show_options(args="sys.argv[1:]"): ) parser.add_argument( "-p", - "--profile_name", + "--profile-name", required=False, help="Profile to be used" ) diff --git a/shared/internal/network.py b/shared/internal/network.py index 985a928..74ad78d 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -87,8 +87,11 @@ def run(self): if data['VpcId'] == self.vpc_options.vpc_id: found += 1 - message = message + "\nNatGatewayId: {} -> VPC id {}".format( + message = message + "\nNatGatewayId: {} -> Private IP {} - Public IP {} -> Subnet Id: {} -> VPC id {}".format( data['NatGatewayId'], + data['NatGatewayAddresses'][0]['PrivateIp'], + data['NatGatewayAddresses'][0]['PublicIp'], + data['SubnetId'], self.vpc_options.vpc_id ) From 67c078d34b5d2d5e1e8fa900ad45bb478d2d5e7d Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Sun, 24 May 2020 01:02:44 +0700 Subject: [PATCH 07/13] removed CloudFormation --- README.md | 1 - shared/awscommands.py | 3 +-- shared/internal/management.py | 42 ----------------------------------- 3 files changed, 1 insertion(+), 45 deletions(-) diff --git a/README.md b/README.md index b21d7cd..77e1fa6 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ Following services are integrated - VPC PEERING - VPC ENDPOINT - EKS -- CLOUDFORMATION - SYNTHETIC CANARIES - EMR - ECS diff --git a/shared/awscommands.py b/shared/awscommands.py index 60e75ab..e6b06fe 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -7,7 +7,7 @@ from shared.internal.storage import EFS, S3POLICY from shared.internal.analytics import ELASTICSEARCH, MSK from shared.internal.application import SQSPOLICY -from shared.internal.management import CLOUDFORMATION, CANARIES +from shared.internal.management import CANARIES from shared.internal.containers import ECS @@ -61,7 +61,6 @@ def run(self): VPCENDPOINT(self.vpc_options).run() """ Management resources """ - CLOUDFORMATION(self.vpc_options).run() CANARIES(self.vpc_options).run() """ Containers """ diff --git a/shared/internal/management.py b/shared/internal/management.py index 398eee9..c9d5e3e 100644 --- a/shared/internal/management.py +++ b/shared/internal/management.py @@ -1,48 +1,6 @@ from shared.common import * -import json from shared.error_handler import exception -class CLOUDFORMATION(object): - - def __init__(self, vpc_options: VpcOptions): - self.vpc_options = vpc_options - - @exception - def run(self): - - client = self.vpc_options.client('cloudformation') - - response = client.describe_stacks() - - message_handler("\nChecking CLOUDFORMATION STACKS...", "HEADER") - - if len(response["Stacks"]) == 0: - message_handler("Found 0 Cloudformation Stacks in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" - for data in response["Stacks"]: - - template = client.get_template(StackName=data['StackName']) - - documenttemplate = template['TemplateBody'] - - document = json.dumps(documenttemplate, default=datetime_to_string) - - """ check either vpc_id or potencial subnet ip are found """ - ipvpc_found = check_ipvpc_inpolicy(document=document, vpc_options=self.vpc_options) - - """ elasticsearch uses accesspolicies too, so check both situation """ - if ipvpc_found is True: - found += 1 - message = message + "\nStackName: {} -> VpcId {}".format( - data['StackName'], - self.vpc_options.vpc_id - ) - - message_handler("Found {0} Cloudformation Stacks using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True class SYNTHETICSCANARIES(object): From 78cb09ebbd124633b705ffe9d38b685a5f4a14ae Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 24 May 2020 18:11:54 +0100 Subject: [PATCH 08/13] Refactored code for better performance. New class of reports to allow new kind of reports.. Output now using terraform resources pattern. --- shared/awscommands.py | 47 +---- shared/internal/analytics.py | 55 +++--- shared/internal/application.py | 47 ++--- shared/internal/compute.py | 135 +++++++-------- shared/internal/containers.py | 78 ++++----- shared/internal/database.py | 93 +++++----- shared/internal/management.py | 28 ++- shared/internal/network.py | 303 +++++++++++++-------------------- shared/internal/security.py | 25 ++- shared/internal/storage.py | 54 +++--- shared/report.py | 10 +- 11 files changed, 363 insertions(+), 512 deletions(-) diff --git a/shared/awscommands.py b/shared/awscommands.py index 27c6fd9..73d5385 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -1,21 +1,9 @@ from shared.common import * -<<<<<<< HEAD from shared.internal.security import IAM from shared.internal.network import VPC +from shared.report import Report import importlib, inspect import os -======= -from shared.internal.security import IAM, IAMPOLICY -from shared.internal.network import VPC, IGW, NATGATEWAY, ELB, ELBV2, ROUTETABLE, SUBNET, NACL, SG, VPCPEERING -from shared.internal.network import VPCENDPOINT -from shared.internal.compute import LAMBDA, EC2, EKS, EMR, ASG -from shared.internal.database import RDS, ELASTICACHE, DOCUMENTDB -from shared.internal.storage import EFS, S3POLICY -from shared.internal.analytics import ELASTICSEARCH, MSK -from shared.internal.application import SQSPOLICY -from shared.internal.management import CANARIES -from shared.internal.containers import ECS ->>>>>>> developer PATH_CHECKS = "shared/internal" @@ -40,9 +28,10 @@ def run(self): IAM(self.vpc_options).run() VPC(self.vpc_options).run() - checks = [] + resources_check = [] """ Iterate to get all modules """ + message_handler("\nRESOURCES INSPECT", "HEADER") for name in os.listdir(PATH_CHECKS): if name.endswith(".py"): #strip the extension @@ -51,40 +40,20 @@ def run(self): """ Load and call all run check """ for nameclass, cls in inspect.getmembers(importlib.import_module("shared.internal."+module), inspect.isclass): if hasattr(cls, 'run') and callable(getattr(cls, 'run')) and nameclass not in ['VPC','IAM']: - checks.append(cls(self.vpc_options).run()) + resources_check.append(cls(self.vpc_options).run()) -<<<<<<< HEAD """ - TODO: Generate reports + TODO: Generate reports in json/csv/pdf/xls """ - #....reports(checks).... + Report(resources=resources_check).generalReport() """ - TODO: Generate diagrams + TODO: Generate diagrams... future... """ #....diagrams(checks).... """ - TODO: Export in csv/json/yaml/tf... future.... + TODO: Export in csv/json/yaml/tf... future... """ #....exporttf(checks).... -======= - """ Network resources """ - IGW(self.vpc_options).run() - NATGATEWAY(self.vpc_options).run() - ELB(self.vpc_options).run() - ELBV2(self.vpc_options).run() - ROUTETABLE(self.vpc_options).run() - SUBNET(self.vpc_options).run() - NACL(self.vpc_options).run() - SG(self.vpc_options).run() - VPCPEERING(self.vpc_options).run() - VPCENDPOINT(self.vpc_options).run() - - """ Management resources """ - CANARIES(self.vpc_options).run() - - """ Containers """ - ECS(self.vpc_options).run() ->>>>>>> developer diff --git a/shared/internal/analytics.py b/shared/internal/analytics.py index b8f9b02..cebe430 100644 --- a/shared/internal/analytics.py +++ b/shared/internal/analytics.py @@ -1,6 +1,7 @@ from shared.common import * import json from shared.error_handler import exception +from typing import List class ELASTICSEARCH(object): @@ -8,19 +9,18 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('es') + + resources_found = [] response = client.list_domain_names() - message_handler("\nChecking ELASTICSEARCH DOMAINS...", "HEADER") + message_handler("Collecting data from ELASTICSEARCH DOMAINS...", "HEADER") + + if len(response["DomainNames"]) > 0: - if len(response["DomainNames"]) == 0: - message_handler("Found 0 Elastic Search Domains in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" for data in response["DomainNames"]: elasticsearch_domain = client.describe_elasticsearch_domain(DomainName=data['DomainName']) @@ -35,15 +35,13 @@ def run(self): """ elasticsearch uses accesspolicies too, so check both situation """ if elasticsearch_domain['DomainStatus']['VPCOptions']['VPCId'] == self.vpc_options.vpc_id \ or ipvpc_found is True: - found += 1 - message = message + "\nDomainId: {0} - DomainName: {1} -> VPC Id: {2}".format( - elasticsearch_domain['DomainStatus']['DomainId'], - elasticsearch_domain['DomainStatus']['DomainName'], - self.vpc_options.vpc_id - ) - message_handler("Found {0} ElasticSearch Domains using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + resources_found.append(Resource(id=elasticsearch_domain['DomainStatus']['DomainId'], + name=elasticsearch_domain['DomainStatus']['DomainName'], + type='aws_elasticsearch_domain', + details='')) + + return resources_found class MSK(object): @@ -51,20 +49,18 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('kafka') + resources_found = [] + """ get all cache clusters """ response = client.list_clusters() - message_handler("\nChecking MSK CLUSTERS...", "HEADER") + message_handler("Collecting data from MSK CLUSTERS...", "HEADER") - if len(response['ClusterInfoList']) == 0: - message_handler("Found 0 MSK Clusters in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response['ClusterInfoList']) > 0: """ iterate cache clusters to get subnet groups """ for data in response['ClusterInfoList']: @@ -82,13 +78,10 @@ def run(self): if subnet.id in msk_subnets: - found += 1 - message = message + "\nClusterName: {0} - VpcId: {1}".format( - data['ClusterName'], - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['ClusterArn'], + name=data['ClusterName'], + type='aws_msk_cluster', + details='')) + break - - message_handler("Found {0} MSK Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True \ No newline at end of file + return resources_found \ No newline at end of file diff --git a/shared/internal/application.py b/shared/internal/application.py index 0d48a85..e4278fb 100644 --- a/shared/internal/application.py +++ b/shared/internal/application.py @@ -2,6 +2,7 @@ from shared.error_handler import exception from shared.common import * import json +from typing import List class SQSPOLICY(object): @@ -10,50 +11,50 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('sqs') + resources_found = [] + response = client.list_queues() - message_handler("\nChecking SQS QUEUE POLICY...", "HEADER") + message_handler("Collecting data from SQS QUEUE POLICY...", "HEADER") - if "QueueUrls" not in response: - message_handler("Found 0 SQS Queues in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if "QueueUrls" in response: with ThreadPoolExecutor(15) as executor: results = executor.map(lambda data: self.analyze_queues(client, data[1]), enumerate(response["QueueUrls"])) for result in results: + if result[0] is True: - found += 1 - message += result[1] - message_handler("Found {0} SQS Queue Policy using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + resources_found.append(result[1]) + + return resources_found @exception def analyze_queues(self, client, queue): - sqs_queue_policy = client.get_queue_attributes(QueueUrl=queue, AttributeNames=['Policy']) + sqs_queue_policy = client.get_queue_attributes(QueueUrl=queue, AttributeNames=['QueueArn','Policy']) + if "Attributes" in sqs_queue_policy: - """ Not sure about boto3 return """ + if "Policy" in sqs_queue_policy['Attributes']: + + documentpolicy = sqs_queue_policy['Attributes']['Policy'] + queuearn = sqs_queue_policy['Attributes']['QueueArn'] + document = json.dumps(documentpolicy, default=datetime_to_string) - documentpolicy = sqs_queue_policy['Attributes']['Policy'] - document = json.dumps(documentpolicy, default=datetime_to_string) + """ check either vpc_id or potencial subnet ip are found """ + ipvpc_found = check_ipvpc_inpolicy(document=document, vpc_options=self.vpc_options) - """ check either vpc_id or potencial subnet ip are found """ - ipvpc_found = check_ipvpc_inpolicy(document=document, vpc_options=self.vpc_options) + if ipvpc_found is not False: - if ipvpc_found is not False: - return True, "\nQueueUrl: {0} -> {1} -> VPC {2}".format( - queue, - ipvpc_found, - self.vpc_options.vpc_id - ) + return True, Resource(id=queuearn, + name=queue, + type='aws_sqs_queue_policy', + details='') return False, None diff --git a/shared/internal/compute.py b/shared/internal/compute.py index 5f7153b..48be299 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -9,30 +9,27 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('lambda') + + resources_found = [] response = client.list_functions() - message_handler("\nChecking LAMBDA FUNCTIONS...", "HEADER") + message_handler("Collecting data from LAMBDA FUNCTIONS...", "HEADER") + + if len(response["Functions"]) > 0: - if len(response["Functions"]) == 0: - message_handler("Found 0 Lambda Functions in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" for data in response["Functions"]: if 'VpcConfig' in data and data['VpcConfig']['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nFunctionName: {} -> Subnet id(s): {} -> VPC id {}".format( - data["FunctionName"], - ", ".join(data['VpcConfig']['SubnetIds']), - data['VpcConfig']['VpcId'] - ) - message_handler("Found {0} Lambda Functions using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + resources_found.append(Resource(id=data['FunctionArn'], + name=data["FunctionName"], + type='aws_lambda_function', + details='')) + + return resources_found class EC2(object): @@ -45,30 +42,26 @@ def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + response = client.describe_instances() - message_handler("\nChecking EC2 Instances...", "HEADER") + message_handler("Collecting data from EC2 Instances...", "HEADER") - if len(response["Reservations"]) == 0: - message_handler("Found 0 EC2 Instances in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response["Reservations"]) > 0: + for data in response["Reservations"]: for instances in data['Instances']: if "VpcId" in instances: if instances['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nInstanceId: {} - PrivateIpAddress: {} -> Subnet id(s): {} -> VpcId {}".format( - instances["InstanceId"], - instances["PrivateIpAddress"], - instances['SubnetId'], - instances['VpcId'] - ) - message_handler("Found {0} EC2 Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - + + resources_found.append(Resource(id=instances['InstanceId'], + name=instances["InstanceId"], + type='aws_instance', + details='')) + - return Resource(id='1',name='2',type='3',details='4') + return resources_found class EKS(object): @@ -80,30 +73,27 @@ def run(self) -> List[Resource]: client = self.vpc_options.client('eks') + resources_found = [] + response = client.list_clusters() - message_handler("\nChecking EKS CLUSTERS...", "HEADER") + message_handler("Collecting data from EKS CLUSTERS...", "HEADER") - if len(response["clusters"]) == 0: - message_handler("Found 0 EKS Clusters in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response["clusters"]) > 0: + for data in response["clusters"]: cluster = client.describe_cluster(name=data) if cluster['cluster']['resourcesVpcConfig']['vpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\ncluster: {} - VpcId {}".format( - data, - self.vpc_options.vpc_id - ) + + resources_found.append(Resource(id=cluster['cluster']['arn'], + name=cluster['cluster']["name"], + type='aws_eks_cluster', + details='')) + + return resources_found - message_handler("Found {0} EKS Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - - return Resource(id='5', name='6',type='7',details='8') class EMR(object): @@ -111,19 +101,18 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('emr') + + resources_found = [] response = client.list_clusters() - message_handler("\nChecking EMR CLUSTERS...", "HEADER") + message_handler("Collecting data from EMR CLUSTERS...", "HEADER") + + if len(response["Clusters"]) > 0: - if len(response["Clusters"]) == 0: - message_handler("Found 0 EMR Clusters in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" for data in response["Clusters"]: cluster = client.describe_cluster(ClusterId=data['Id']) @@ -135,15 +124,13 @@ def run(self): if subnets['Subnets'][0]['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nClusterId: {} - VpcId {}".format( - data['Id'], - self.vpc_options.vpc_id - ) - message_handler("Found {0} EMR Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['Id'], + name=data['Name'], + type='aws_emr_cluster', + details='')) - return True + return resources_found class AUTOSCALING(object): @@ -151,19 +138,18 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('autoscaling') + + resources_found = [] response = client.describe_auto_scaling_groups() - message_handler("\nChecking AUTOSCALING GROUPS...", "HEADER") + message_handler("Collecting data from AUTOSCALING GROUPS...", "HEADER") if len(response["AutoScalingGroups"]) == 0: - message_handler("Found 0 Autoscaling groups in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + for data in response["AutoScalingGroups"]: asg_subnets = data['VPCZoneIdentifier'].split(",") @@ -178,16 +164,9 @@ def run(self): if data_subnet['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nAutoScalingGroupName: {} -> LaunchConfigurationName: {} -> subnet: {} -> VPC id {}".format( - data["AutoScalingGroupName"], - data["LaunchConfigurationName"], - data_subnet["SubnetId"], - self.vpc_options.vpc_id - ) - - message_handler("Found {0} Autoscaling groups associations using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + resources_found.append(Resource(id=data['AutoScalingGroupARN'], + name=data['AutoScalingGroupName'], + type='aws_autoscaling_group', + details='Using LaunchConfigurationName {0}'.format(data["LaunchConfigurationName"]))) -ASG = AUTOSCALING \ No newline at end of file + return resources_found \ No newline at end of file diff --git a/shared/internal/containers.py b/shared/internal/containers.py index 6ab47dc..f1ca5d0 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -1,6 +1,6 @@ from shared.common import * from shared.error_handler import exception - +from typing import List class ECS(object): @@ -8,23 +8,22 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ecs') ec2_client = self.vpc_options.client('ec2') + resources_found = [] + clusters_list = client.list_clusters() response = client.describe_clusters( clusters=clusters_list['clusterArns'] ) - message_handler("\nChecking ECS CLUSTER...", "HEADER") + message_handler("Collecting data from ECS CLUSTER...", "HEADER") - if len(response['clusters']) == 0: - message_handler("Found 0 ECS Cluster in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response['clusters']) > 0: + for data in response['clusters']: """ Searching all cluster services """ @@ -32,36 +31,36 @@ def run(self): pages = paginator.paginate( cluster=data['clusterName'] ) + for services in pages: - service_details = client.describe_services( - cluster=data['clusterName'], - services=services['serviceArns'] - ) - for data_service_detail in service_details['services']: - if data_service_detail['launchType'] == 'FARGATE': - service_subnets = data_service_detail["networkConfiguration"]["awsvpcConfiguration"]["subnets"] + if len(services['serviceArns']) > 0: + service_details = client.describe_services( + cluster=data['clusterName'], + services=services['serviceArns'] + ) - """ describe subnet to get VpcId """ - ec2 = self.vpc_options.client('ec2') + for data_service_detail in service_details['services']: + if data_service_detail['launchType'] == 'FARGATE': + service_subnets = data_service_detail["networkConfiguration"]["awsvpcConfiguration"]["subnets"] - subnets = ec2.describe_subnets(SubnetIds=service_subnets) + """ describe subnet to get VpcId """ + ec2 = self.vpc_options.client('ec2') - """ Iterate subnet to get VPC """ - for data_subnet in subnets['Subnets']: + subnets = ec2.describe_subnets(SubnetIds=service_subnets) - if data_subnet['VpcId'] == self.vpc_options.vpc_id: + """ Iterate subnet to get VPC """ + for data_subnet in subnets['Subnets']: - found += 1 - message = message + "\nclusterName: {} -> ServiceName: {} -> Subnet Id: {} -> VPC id {}".format( - data["clusterName"], - data_service_detail['serviceName'], - data_subnet['SubnetId'], - self.vpc_options.vpc_id - ) - else: - """ EC2 services require container instances, list of them should be fine for now """ - pass + if data_subnet['VpcId'] == self.vpc_options.vpc_id: + + resources_found.append(Resource(id=data['clusterArn'], + name=data["clusterName"], + type='aws_ecs_cluster', + details='')) + else: + """ EC2 services require container instances, list of them should be fine for now """ + pass """ Looking for container instances - they are dynamically associated, so manual review is necessary """ list_paginator = client.get_paginator('list_container_instances') @@ -88,18 +87,13 @@ def run(self): for instance in reservation['Instances']: for network_interfaces in instance['NetworkInterfaces']: if network_interfaces['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nclusterName: {} -> Instance Id: {} -> Subnet id: {} -> VPC id {}".format( - data["clusterName"], - instance['InstanceId'], - network_interfaces['SubnetId'], - self.vpc_options.vpc_id - ) + + resources_found.append(Resource(id=data['InstanceId'], + name=data["clusterName"], + type='aws_ecs_cluster', + details='Instance in EC2 cluster')) pass pass pass - - message_handler("Found {0} ECS Cluster relations using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True \ No newline at end of file + return resources_found \ No newline at end of file diff --git a/shared/internal/database.py b/shared/internal/database.py index abea9cf..0836dc6 100644 --- a/shared/internal/database.py +++ b/shared/internal/database.py @@ -1,5 +1,6 @@ from shared.common import * from shared.error_handler import exception +from typing import List class RDS(object): @@ -7,9 +8,11 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('rds') + + resources_found = [] response = client.describe_db_instances(Filters=[ {'Name': 'engine', @@ -19,29 +22,23 @@ def run(self): 'sqlserver-se','sqlserver-ex','sqlserver-web'] }]) - message_handler("\nChecking RDS INSTANCES...", "HEADER") + message_handler("Collecting data from RDS INSTANCES...", "HEADER") - if len(response["DBInstances"]) == 0: - message_handler("Found 0 RDS Instances in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response["DBInstances"]) > 0: + for data in response["DBInstances"]: if data['DBSubnetGroup']['VpcId'] == self.vpc_options.vpc_id: subnet_ids = [] for subnet in data['DBSubnetGroup']['Subnets']: subnet_ids.append(subnet['SubnetIdentifier']) - found += 1 - message = message + "\nDBInstanceIdentifier: {} - Engine: {} -> Subnet id: {} -> VpcId {}".format( - data["DBInstanceIdentifier"], - data["Engine"], - ', '.join(subnet_ids), - data['DBSubnetGroup']['VpcId'] - ) - message_handler("Found {0} RDS Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['DBInstanceArn'], + name=data["DBInstanceIdentifier"], + type='aws_db_instance', + details='DBInstance using subnets {} and engine {}'\ + .format(', '.join(subnet_ids), data["Engine"]))) - return True + return resources_found class ELASTICACHE(object): @@ -50,21 +47,19 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('elasticache') + + resources_found = [] """ get all cache clusters """ response = client.describe_cache_clusters() - message_handler("\nChecking ELASTICACHE CLUSTERS...", "HEADER") - - if len(response['CacheClusters']) == 0: - message_handler("Found 0 Elasticache Clusters in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + message_handler("Collecting data from ELASTICACHE CLUSTERS...", "HEADER") + if len(response['CacheClusters']) > 0: + """ iterate cache clusters to get subnet groups """ for data in response['CacheClusters']: @@ -75,17 +70,13 @@ def run(self): for subnet in cachesubnet['CacheSubnetGroups'][0]['Subnets']: subnet_ids.append(subnet['SubnetIdentifier']) - found += 1 - message = message + "\nCacheClusterId: {} - CacheSubnetGroupName: {} - Engine: {} -> Subnet id: {} -> VpcId: {}".format( - data["CacheClusterId"], - data["CacheSubnetGroupName"], - data["Engine"], - ', '.join(subnet_ids), - cachesubnet['CacheSubnetGroups'][0]['VpcId'] - ) - message_handler("Found {0} Elasticache Clusters using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + resources_found.append(Resource(id=data['CacheClusterId'], + name=data["CacheSubnetGroupName"], + type='aws_elasticache_cluster', + details='Elasticache Cluster using subnets {} and engine {}' \ + .format(', '.join(subnet_ids), data["Engine"]))) + + return resources_found class DOCUMENTDB(object): @@ -94,23 +85,21 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('docdb') + + resources_found = [] response = client.describe_db_instances(Filters=[ {'Name': 'engine', 'Values': ['docdb'] }]) - message_handler("\nChecking DOCUMENTDB INSTANCES...", "HEADER") - - if len(response['DBInstances']) == 0: - message_handler("Found 0 DocumentoDB Instances in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + message_handler("Collecting data from DOCUMENTDB INSTANCES...", "HEADER") + if len(response['DBInstances']) > 0: + """ iterate cache clusters to get subnet groups """ for data in response['DBInstances']: @@ -119,14 +108,10 @@ def run(self): for subnet in data['DBSubnetGroup']['Subnets']: subnet_ids.append(subnet['SubnetIdentifier']) - found += 1 - message = message + "\nDBInstanceIdentifier: {} - DBInstanceClass: {} - Engine: {} -> Subnet id: {} -> VpcId: {}".format( - data['DBInstanceIdentifier'], - data["DBInstanceClass"], - data["Engine"], - ', '.join(subnet_ids), - self.vpc_options.vpc_id - ) - message_handler("Found {0} DocumentoDB Instances using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True \ No newline at end of file + resources_found.append(Resource(id=data['DBInstanceArn'], + name=data["DBInstanceIdentifier"], + type='aws_docdb_cluster', + details='Documentdb using subnets {} and engine {}'\ + .format(', '.join(subnet_ids), data["Engine"]))) + + return resources_found \ No newline at end of file diff --git a/shared/internal/management.py b/shared/internal/management.py index c9d5e3e..ce86af7 100644 --- a/shared/internal/management.py +++ b/shared/internal/management.py @@ -1,5 +1,6 @@ from shared.common import * from shared.error_handler import exception +from typing import List class SYNTHETICSCANARIES(object): @@ -8,33 +9,28 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('synthetics') + + resources_found = [] response = client.describe_canaries() - message_handler("\nChecking SYNTHETICS CANARIES...", "HEADER") + message_handler("Collecting data from SYNTHETICS CANARIES...", "HEADER") - if len(response["Canaries"]) == 0: - message_handler("Found 0 Synthetic Canaries in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response["Canaries"]) > 0: + for data in response["Canaries"]: """ Check if VpcConfig is in dict """ if "VpcConfig" in data: if data['VpcConfig']['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nCanariesName: {} -> VpcId {}".format( - data['Name'], - self.vpc_options.vpc_id - ) - - message_handler("Found {0} Synthetic Canaries using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + resources_found.append(Resource(id=data['Id'], + name=data["Name"], + type='aws_canaries_function', + details='')) -CANARIES = SYNTHETICSCANARIES \ No newline at end of file + return resources_found diff --git a/shared/internal/network.py b/shared/internal/network.py index a572a1d..bbfd389 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -1,5 +1,6 @@ from shared.common import * from shared.error_handler import exception +from typing import List class VPC(object): @@ -29,34 +30,28 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'attachment.vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_internet_gateways(Filters=filters) - message_handler("\nChecking INTERNET GATEWAYS...", "HEADER") + message_handler("Collecting data from INTERNET GATEWAYS...", "HEADER") """ One VPC has only 1 IGW then it's a direct check """ - if len(response["InternetGateways"]) == 0: - message_handler("Found 0 Internet Gateway in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 1 - - message = "\nInternetGatewayId: {} -> VPC id {}".format( - response['InternetGateways'][0]['InternetGatewayId'], - self.vpc_options.vpc_id - ) - - message_handler("Found {0} Internet Gateway using VPC {1} {2}".format(str(found), \ - self.vpc_options.vpc_id, message), \ - 'OKBLUE') + if len(response["InternetGateways"]) > 0: - return True + resources_found.append(Resource(id=response['InternetGateways'][0]['InternetGatewayId'], + name=response['InternetGateways'][0]['InternetGatewayId'], + type='aws_internet_gateway', + details='')) + + return resources_found class NATGATEWAY(object): @@ -64,40 +59,34 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_nat_gateways(Filters=filters) - message_handler("\nChecking NAT GATEWAYS...", "HEADER") + message_handler("Collecting data from NAT GATEWAYS...", "HEADER") - if len(response["NatGateways"]) == 0: - message_handler("Found 0 NAT Gateways in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response["NatGateways"]) > 0: for data in response["NatGateways"]: if data['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nNatGatewayId: {} -> Private IP {} - Public IP {} -> Subnet Id: {} -> VPC id {}".format( - data['NatGatewayId'], - data['NatGatewayAddresses'][0]['PrivateIp'], - data['NatGatewayAddresses'][0]['PublicIp'], - data['SubnetId'], - self.vpc_options.vpc_id - ) - - message_handler("Found {0} NAT Gateways using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['NatGatewayId'], + name=data['NatGatewayId'], + type='aws_nat_gateway', + details='NAT Gateway Private IP {}, Public IP {}, Subnet id {}' \ + .format(data['NatGatewayAddresses'][0]['PrivateIp'], + data['NatGatewayAddresses'][0]['PublicIp'], + data['SubnetId']))) - return True + return resources_found class ELASTICLOADBALANCING(object): @@ -105,35 +94,28 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('elb') + resources_found = [] + response = client.describe_load_balancers() - message_handler("\nChecking CLASSIC LOAD BALANCING...", "HEADER") + message_handler("Collecting data from CLASSIC LOAD BALANCING...", "HEADER") - if len(response['LoadBalancerDescriptions']) == 0: - message_handler("Found 0 Classic Load Balancing in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['LoadBalancerDescriptions']) > 0: for data in response['LoadBalancerDescriptions']: if data['VPCId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nLoadBalancerName: {} - Subnet id: {} -> VPC id {}".format( - data['LoadBalancerName'], - ', '.join(data['Subnets']), - self.vpc_options.vpc_id - ) - - message_handler("Found {0} Classic Load Balancing using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['LoadBalancerName'], + name=data['LoadBalancerName'], + type='aws_elb_classic', + details='')) - return True + return resources_found class ELASTICLOADBALANCINGV2(object): @@ -141,20 +123,17 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('elbv2') + resources_found = [] + response = client.describe_load_balancers() - message_handler("\nChecking APPLICATION LOAD BALANCING...", "HEADER") + message_handler("Collecting data from APPLICATION LOAD BALANCING...", "HEADER") - if len(response['LoadBalancers']) == 0: - message_handler("Found 0 Application Load Balancing in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['LoadBalancers']) > 0: for data in response['LoadBalancers']: @@ -163,16 +142,12 @@ def run(self): for availabilityZone in data['AvailabilityZones']: subnet_ids.append(availabilityZone['SubnetId']) - found += 1 - message = message + "\nLoadBalancerName: {} -> VPC id {}".format( - data['LoadBalancerName'], - ', '.join(subnet_ids), - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['LoadBalancerName'], + name=data['LoadBalancerName'], + type='aws_elb', + details='')) - message_handler("Found {0} Application Load Balancing using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + return resources_found class ROUTETABLE(object): @@ -180,36 +155,30 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_route_tables(Filters=filters) - message_handler("\nChecking ROUTE TABLES...", "HEADER") + message_handler("Collecting data from ROUTE TABLES...", "HEADER") - if len(response['RouteTables']) == 0: - message_handler("Found 0 Route Table in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['RouteTables']) > 0: """ Iterate to get all route table filtered """ for data in response['RouteTables']: - found += 1 - message = message + "\nRouteTableId: {} -> VPC id {}".format( - data['RouteTableId'], - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['RouteTableId'], + name=data['RouteTableId'], + type='aws_route_table', + details='')) - message_handler("Found {0} Route Tables using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + return resources_found class SUBNET(object): @@ -217,38 +186,31 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_subnets(Filters=filters) - message_handler("\nChecking SUBNETS...", "HEADER") + message_handler("Collecting data from SUBNETS...", "HEADER") - if len(response['Subnets']) == 0: - message_handler("Found 0 Subnets in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['Subnets']) > 0: """ Iterate to get all route table filtered """ for data in response['Subnets']: - found += 1 - message = message + "\nSubnetId: {} - CIDR block: {} - AZ: {} -> VPC id {}".format( - data['SubnetId'], - data['CidrBlock'], - data['AvailabilityZone'], - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['SubnetId'], + name=data['SubnetId'], + type='aws_subnet', + details='Subnet using CidrBlock {} and AZ {}' \ + .format(data['CidrBlock'], data['AvailabilityZone']))) - message_handler("Found {0} Subnets using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + return resources_found class NACL(object): @@ -256,23 +218,20 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_network_acls(Filters=filters) - message_handler("\nChecking NACLs...", "HEADER") + message_handler("Collecting data from NACLs...", "HEADER") - if len(response['NetworkAcls']) == 0: - message_handler("Found 0 NACL in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['NetworkAcls']) > 0: """ Iterate to get all NACL filtered """ for data in response['NetworkAcls']: @@ -280,16 +239,13 @@ def run(self): for subnet in data['Associations']: subnet_ids.append(subnet['SubnetId']) - found += 1 - message = message + "\nNetworkAclId: {} -> Subnet Id: {} -> VPC Id: {}".format( - data['NetworkAclId'], - ', '.join(subnet_ids), - self.vpc_options.vpc_id - ) - - message_handler("Found {0} NACL using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['NetworkAclId'], + name=data['NetworkAclId'], + type='aws_network_acl', + details='NACL using Subnets {}' \ + .format(', '.join(subnet_ids)))) - return True + return resources_found class SECURITYGROUP(object): @@ -297,36 +253,31 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_security_groups(Filters=filters) - message_handler("\nChecking SECURITY GROUPS...", "HEADER") + message_handler("Collecting data from SECURITY GROUPS...", "HEADER") - if len(response['SecurityGroups']) == 0: - message_handler("Found 0 Security Group in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['SecurityGroups']) > 0: """ Iterate to get all SG filtered """ for data in response['SecurityGroups']: - found += 1 - message = message + "\nGroupName: {} -> VPC id {}".format( - data['GroupName'], - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['GroupId'], + name=data['GroupName'], + type='aws_security_group', + details='')) - message_handler("Found {0} Security Groups using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - return True + return resources_found class VPCPEERING(object): @@ -334,41 +285,36 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') - response = client.describe_vpc_peering_connections() + resources_found = [] - message_handler("\nChecking VPC PEERING...", "HEADER") + response = client.describe_vpc_peering_connections() - if len(response['VpcPeeringConnections']) == 0: - message_handler("Found 0 VPC Peering in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + message_handler("Collecting data from VPC PEERING...", "HEADER") + if len(response['VpcPeeringConnections']) > 0: + """ Iterate to get all vpc peering and check either accepter or requester """ for data in response['VpcPeeringConnections']: if data['AccepterVpcInfo']['VpcId'] == self.vpc_options.vpc_id \ or data['RequesterVpcInfo']['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nVpcPeeringConnectionId: {} -> Owner: {} - Region: {} - Requester VPC id: {} -> Owner: {} - Region: {} - Accepter VPC id: {}".format( - data['VpcPeeringConnectionId'], - data['AccepterVpcInfo']['OwnerId'], - data['AccepterVpcInfo']['Region'], - data['AccepterVpcInfo']['VpcId'], - data['RequesterVpcInfo']['OwnerId'], - data['RequesterVpcInfo']['Region'], - data['RequesterVpcInfo']['VpcId'] - ) - - message_handler("Found {0} VPC Peering using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') - - return True + resources_found.append(Resource(id=data['VpcPeeringConnectionId'], + name=data['VpcPeeringConnectionId'], + type='aws_vpc_peering_connection', + details='Vpc Peering Accepter OwnerId {}, Accepter Region {}, Accepter VpcId {} \ + Requester OwnerId {}, Requester Region {}, Requester VpcId' \ + .format(data['AccepterVpcInfo']['OwnerId'], + data['AccepterVpcInfo']['Region'], + data['AccepterVpcInfo']['VpcId'], + data['RequesterVpcInfo']['OwnerId'], + data['RequesterVpcInfo']['Region'], + data['RequesterVpcInfo']['VpcId']))) + return resources_found class VPCENDPOINT(object): @@ -376,23 +322,20 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('ec2') + resources_found = [] + filters = [{'Name': 'vpc-id', 'Values': [self.vpc_options.vpc_id]}] response = client.describe_vpc_endpoints(Filters=filters) - message_handler("\nChecking VPC ENDPOINTS...", "HEADER") + message_handler("Collecting data from VPC ENDPOINTS...", "HEADER") - if len(response['VpcEndpoints']) == 0: - message_handler("Found 0 VPC Endpoints in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - - found = 0 - message = "" + if len(response['VpcEndpoints']) > 0: """ Iterate to get all VPCE filtered """ for data in response['VpcEndpoints']: @@ -400,20 +343,16 @@ def run(self): if data['VpcId'] == self.vpc_options.vpc_id: found += 1 if data['VpcEndpointType'] == 'Gateway': - message = message + "\nGateway VpcEndpointId: {} - Service: {} -> Route table Id: {} -> VPC id: {}".format( - data['VpcEndpointId'], - data['ServiceName'], - ', '.join(data['RouteTableIds']), - self.vpc_options.vpc_id - ) + resources_found.append(Resource(id=data['VpcEndpointId'], + name=data['ServiceName'], + type='aws_vpc_endpoint_gateway', + details='Vpc Endpoint Gateway RouteTable {}' \ + .format(', '.join(data['RouteTableIds'])))) else: - message = message + "\nInterface VpcEndpointId: {} - Service: {} -> Subnet Id: {} -> VPC id: {}".format( - data['VpcEndpointId'], - data['ServiceName'], - ', '.join(data['SubnetIds']), - self.vpc_options.vpc_id - ) - - message_handler("Found {0} VPC Endpoints using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['VpcEndpointId'], + name=data['ServiceName'], + type='aws_vpc_endpoint_gateway', + details='Vpc Endpoint Service Subnet {}' \ + .format(', '.join(data['SubnetIds'])))) - return True \ No newline at end of file + return resources_found \ No newline at end of file diff --git a/shared/internal/security.py b/shared/internal/security.py index 3fa2c36..4bd8a2f 100644 --- a/shared/internal/security.py +++ b/shared/internal/security.py @@ -2,6 +2,7 @@ from shared.error_handler import exception from shared.common import * import json +from typing import List class IAM(object): @@ -29,13 +30,15 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.session.client('iam') + resources_found = [] + response = client.list_policies(Scope='Local') - message_handler("\nChecking IAM POLICY...", "HEADER") + message_handler("Collecting data from IAM POLICY...", "HEADER") if len(response['Policies']) == 0: message_handler("Found 0 Customer managed IAM Policy", "OKBLUE") @@ -47,13 +50,10 @@ def run(self): results = executor.map(lambda data: self.analyze_policy(client, data), response['Policies']) for result in results: if result[0] is True: - found += 1 - message += result[1] - message_handler("Found {0} Customer managed IAM Policy using VPC {1} {2}".format(str(found), - self.vpc_options.vpc_id, message), - 'OKBLUE') - return True + resources_found.append(result[1]) + + return resources_found def analyze_policy(self, client, data): @@ -68,10 +68,9 @@ def analyze_policy(self, client, data): ipvpc_found = check_ipvpc_inpolicy(document=document, vpc_options=self.vpc_options) if ipvpc_found is True: - return True, "\nPolicyName: {0} - DefaultVersionId: {1} - VpcId: {2}".format( - data['PolicyName'], - data['DefaultVersionId'], - self.vpc_options.vpc_id - ) + return True, Resource(id=data['Arn'], + name=data['PolicyName'], + type='aws_iam_policy', + details='IAM Policy version {}'.format(data['DefaultVersionId'])) return False, None diff --git a/shared/internal/storage.py b/shared/internal/storage.py index c12957c..afcabdb 100644 --- a/shared/internal/storage.py +++ b/shared/internal/storage.py @@ -2,6 +2,7 @@ from shared.error_handler import exception from shared.common import * import json +from typing import List class EFS(object): @@ -9,20 +10,18 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('efs') + + resources_found = [] """ get filesystems available """ response = client.describe_file_systems() - message_handler("\nChecking EFS MOUNT TARGETS...", "HEADER") + message_handler("Collecting data from EFS MOUNT TARGETS...", "HEADER") - if len(response["FileSystems"]) == 0: - message_handler("Found 0 EFS File Systems in region {0}".format(self.vpc_options.region_name), "OKBLUE") - else: - found = 0 - message = "" + if len(response["FileSystems"]) > 0: """ iterate filesystems to get mount targets """ for data in response["FileSystems"]: @@ -38,16 +37,13 @@ def run(self): subnets = ec2.describe_subnets(SubnetIds=[datafilesystem['SubnetId']]) if subnets['Subnets'][0]['VpcId'] == self.vpc_options.vpc_id: - found += 1 - message = message + "\nFileSystemId: {0} - NumberOfMountTargets: {1} - VpcId: {2}".format( - data['FileSystemId'], - data['NumberOfMountTargets'], - subnets['Subnets'][0]['VpcId'] - ) - message_handler("Found {0} EFS Mount Target using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(Resource(id=data['FileSystemId'], + name=data['Name'], + type='aws_efs_file_system', + details='')) - return True + return resources_found class S3POLICY(object): @@ -55,31 +51,28 @@ def __init__(self, vpc_options: VpcOptions): self.vpc_options = vpc_options @exception - def run(self): + def run(self) -> List[Resource]: client = self.vpc_options.client('s3') + + resources_found = [] """ get buckets available """ response = client.list_buckets() - message_handler("\nChecking S3 BUCKET POLICY...", "HEADER") + message_handler("Collecting data from S3 BUCKET POLICY...", "HEADER") - if len(response["Buckets"]) == 0: - message_handler("Found 0 S3 Buckets", "OKBLUE") - else: - found = 0 - message = "" + if len(response["Buckets"]) > 0: """ iterate buckets to get policy """ with ThreadPoolExecutor(15) as executor: results = executor.map(lambda data: self.analyze_bucket(client, data), response['Buckets']) + for result in results: if result[0] is True: - found += 1 - message += result[1] - message_handler("Found {0} S3 Bucket Policy using VPC {1} {2}".format(str(found), self.vpc_options.vpc_id, message),'OKBLUE') + resources_found.append(result[1]) - return True + return resources_found def analyze_bucket(self, client, data): try: @@ -91,10 +84,11 @@ def analyze_bucket(self, client, data): ipvpc_found = check_ipvpc_inpolicy(document=document, vpc_options=self.vpc_options) if ipvpc_found is True: - return True, "\nBucketName: {0} - {1}".format( - data['Name'], - self.vpc_options.vpc_id - ) + + return True, Resource(id=data['Name'], + name=data['Name'], + type='aws_s3_bucket_policy', + details='') except: pass return False, None diff --git a/shared/report.py b/shared/report.py index 56ef32d..41994f3 100644 --- a/shared/report.py +++ b/shared/report.py @@ -12,9 +12,11 @@ def generalReport(self): message_handler("\n\nRESOURCES FOUND", "HEADER") for alldata in self.resources: - for rundata in alldata: + """ In case of some resource check failure, response may be None""" + if isinstance(alldata, list): + for rundata in alldata: - message = "resource type: {} -> resource id: {} -> resource name: {} -> resource detail: {}" \ - .format(rundata.type, rundata.id, rundata.name, rundata.details) + message = "resource type: {} -> resource id: {} -> resource name: {} -> resource details: {}" \ + .format(rundata.type, rundata.id, rundata.name, rundata.details) - message_handler(message,"OKBLUE") + message_handler(message,"OKBLUE") From 3e5ae4afcf8dce6e3cacd04ffa3f5b0953c51c7a Mon Sep 17 00:00:00 2001 From: Patryk Orwat Date: Mon, 25 May 2020 00:48:55 +0700 Subject: [PATCH 09/13] fix ECS+VPCE bugs, small reporting adjustments --- shared/awscommands.py | 2 +- shared/internal/compute.py | 6 +++++- shared/internal/containers.py | 2 +- shared/internal/network.py | 1 - 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/awscommands.py b/shared/awscommands.py index 73d5385..f86cdfd 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -31,7 +31,7 @@ def run(self): resources_check = [] """ Iterate to get all modules """ - message_handler("\nRESOURCES INSPECT", "HEADER") + message_handler("\nInspecting resources", "HEADER") for name in os.listdir(PATH_CHECKS): if name.endswith(".py"): #strip the extension diff --git a/shared/internal/compute.py b/shared/internal/compute.py index 48be299..0e733b1 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -54,9 +54,13 @@ def run(self) -> List[Resource]: for instances in data['Instances']: if "VpcId" in instances: if instances['VpcId'] == self.vpc_options.vpc_id: + instance_name = instances["InstanceId"] + for tag in instances['Tags']: + if tag['Key'] == 'Name': + instance_name = tag['Value'] resources_found.append(Resource(id=instances['InstanceId'], - name=instances["InstanceId"], + name=instance_name, type='aws_instance', details='')) diff --git a/shared/internal/containers.py b/shared/internal/containers.py index f1ca5d0..cfd9393 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -88,7 +88,7 @@ def run(self) -> List[Resource]: for network_interfaces in instance['NetworkInterfaces']: if network_interfaces['VpcId'] == self.vpc_options.vpc_id: - resources_found.append(Resource(id=data['InstanceId'], + resources_found.append(Resource(id=instance['InstanceId'], name=data["clusterName"], type='aws_ecs_cluster', details='Instance in EC2 cluster')) diff --git a/shared/internal/network.py b/shared/internal/network.py index bbfd389..9c382ee 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -341,7 +341,6 @@ def run(self) -> List[Resource]: for data in response['VpcEndpoints']: if data['VpcId'] == self.vpc_options.vpc_id: - found += 1 if data['VpcEndpointType'] == 'Gateway': resources_found.append(Resource(id=data['VpcEndpointId'], name=data['ServiceName'], From 5ec2aeff34a6f9caabf0ab209b84db712cf7dffb Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 24 May 2020 19:18:54 +0100 Subject: [PATCH 10/13] Ability to check all vpcs in the region #1 --- aws-network-discovery.py | 4 ++-- commands/vpc.py | 16 +++++++++++++--- shared/internal/network.py | 9 +++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/aws-network-discovery.py b/aws-network-discovery.py index 356c029..ea21348 100644 --- a/aws-network-discovery.py +++ b/aws-network-discovery.py @@ -45,8 +45,8 @@ def show_options(args="sys.argv[1:]"): parser.add_argument( "-v", "--vpc-id", - required=True, - help="Inform VPC to analyze" + required=False, + help="Inform VPC to analyze. If not informed, script try all vpcs." ) parser.add_argument( "-r", diff --git a/commands/vpc.py b/commands/vpc.py index 292a2bd..27f264f 100644 --- a/commands/vpc.py +++ b/commands/vpc.py @@ -23,8 +23,18 @@ def run(self): if self.region_name is not None: region_name = self.region_name - """ init class awscommands """ - awscommands = AwsCommands(VpcOptions(session=session, vpc_id=self.vpc_id, region_name=region_name)) - awscommands.run() + """ if vpc is none, get all vpcs and check """ + if self.vpc_id is None: + client = session.client('ec2') + vpcs = client.describe_vpcs() + for data in vpcs['Vpcs']: + """ init class awscommands """ + awscommands = AwsCommands(VpcOptions(session=session, vpc_id=data['VpcId'], region_name=region_name)).run() + else: + """ init class awscommands """ + awscommands = AwsCommands(VpcOptions(session=session, vpc_id=self.vpc_id, region_name=region_name)).run() + + + diff --git a/shared/internal/network.py b/shared/internal/network.py index bbfd389..49d3673 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -16,10 +16,11 @@ def run(self): ) dataresponse = response['Vpcs'][0] - message = "VPC: {}\nCIDR Block: {}\nTenancy: {}\nIs default: {}".format(self.vpc_options.vpc_id, - dataresponse['CidrBlock'], - dataresponse['InstanceTenancy'], - dataresponse['IsDefault']) + message = "------------------------------------------------------\n" + message = message + "VPC: {}\nCIDR Block: {}\nTenancy: {}\nIs default: {}".format(self.vpc_options.vpc_id, + dataresponse['CidrBlock'], + dataresponse['InstanceTenancy'], + dataresponse['IsDefault']) print(message) return True From fe9b080829746582a9a22b1680649993c73f8cd4 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 24 May 2020 23:51:05 +0100 Subject: [PATCH 11/13] Added integration with Diagrams project #16 --- .gitignore | 1 + aws-network-discovery.py | 18 ++++++- commands/vpc.py | 9 ++-- shared/awscommands.py | 9 ++-- shared/common.py | 1 + shared/diagram.py | 96 ++++++++++++++++++++++++++++++++++ shared/internal/analytics.py | 6 ++- shared/internal/application.py | 3 +- shared/internal/compute.py | 15 ++++-- shared/internal/containers.py | 6 ++- shared/internal/database.py | 9 ++-- shared/internal/management.py | 3 +- shared/internal/network.py | 33 ++++++++---- shared/internal/security.py | 3 +- shared/internal/storage.py | 6 ++- 15 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 shared/diagram.py diff --git a/.gitignore b/.gitignore index 9493762..7c87347 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +assets/diagrams/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/aws-network-discovery.py b/aws-network-discovery.py index ea21348..02f547e 100644 --- a/aws-network-discovery.py +++ b/aws-network-discovery.py @@ -39,6 +39,7 @@ __version__ = "0.7.0" AVAILABLE_LANGUAGES = ['en_US','pt_BR'] +DIAGRAMS_OPTIONS = ['True','False'] def show_options(args="sys.argv[1:]"): parser = argparse.ArgumentParser() @@ -66,6 +67,13 @@ def show_options(args="sys.argv[1:]"): required=False, help="available languages: pt_BR, en_US" ) + parser.add_argument( + "-d", + "--diagram", + required=False, + help="print diagram with resources (need Graphviz installed). Use options \"True\" to " \ + "view image or \"False\" to save image to disk. Default True" + ) args = parser.parse_args() return args @@ -85,6 +93,11 @@ def main(): else: language = args.language + """ Diagram check """ + if args.diagram is not None and args.diagram not in DIAGRAMS_OPTIONS: + diagram = "True" + else: + diagram = args.diagram """ defining default language to show messages """ defaultlanguage = gettext.translation('messages', localedir='locales', languages=[language]) @@ -92,7 +105,10 @@ def main(): _ = defaultlanguage.gettext - vpc = Vpc(vpc_id=args.vpc_id, region_name=args.region_name, profile_name=args.profile_name) + vpc = Vpc(vpc_id=args.vpc_id, + region_name=args.region_name, + profile_name=args.profile_name, + diagram=diagram) vpc.run() diff --git a/commands/vpc.py b/commands/vpc.py index 27f264f..773e7d6 100644 --- a/commands/vpc.py +++ b/commands/vpc.py @@ -4,10 +4,11 @@ class Vpc(object): - def __init__(self, vpc_id, region_name, profile_name): + def __init__(self, vpc_id, region_name, profile_name, diagram): self.vpc_id = vpc_id self.region_name = region_name self.profile_name = profile_name + self.diagram = diagram def run(self): @@ -29,10 +30,12 @@ def run(self): vpcs = client.describe_vpcs() for data in vpcs['Vpcs']: """ init class awscommands """ - awscommands = AwsCommands(VpcOptions(session=session, vpc_id=data['VpcId'], region_name=region_name)).run() + awscommands = AwsCommands(VpcOptions(session=session, vpc_id=data['VpcId'], region_name=region_name), + diagram=self.diagram).run() else: """ init class awscommands """ - awscommands = AwsCommands(VpcOptions(session=session, vpc_id=self.vpc_id, region_name=region_name)).run() + awscommands = AwsCommands(VpcOptions(session=session, vpc_id=self.vpc_id, region_name=region_name), + diagram=self.diagram).run() diff --git a/shared/awscommands.py b/shared/awscommands.py index f86cdfd..0c0544a 100644 --- a/shared/awscommands.py +++ b/shared/awscommands.py @@ -2,6 +2,7 @@ from shared.internal.security import IAM from shared.internal.network import VPC from shared.report import Report +from shared.diagram import _Diagram import importlib, inspect import os @@ -9,8 +10,9 @@ class AwsCommands(object): - def __init__(self, vpc_options: VpcOptions): + def __init__(self, vpc_options: VpcOptions, diagram): self.vpc_options = vpc_options + self.diagram = diagram def run(self): @@ -49,9 +51,10 @@ def run(self): Report(resources=resources_check).generalReport() """ - TODO: Generate diagrams... future... + Diagram integration """ - #....diagrams(checks).... + if self.diagram is not None: + _Diagram(vpc_id=self.vpc_options.vpc_id, diagram=self.diagram, resources=resources_check).generateDiagram() """ TODO: Export in csv/json/yaml/tf... future... diff --git a/shared/common.py b/shared/common.py index ec9078d..889802c 100644 --- a/shared/common.py +++ b/shared/common.py @@ -34,6 +34,7 @@ class Resource(NamedTuple): name: str type: str details: str + group: str diff --git a/shared/diagram.py b/shared/diagram.py new file mode 100644 index 0000000..647933f --- /dev/null +++ b/shared/diagram.py @@ -0,0 +1,96 @@ +from shared.common import * +from shared.error_handler import exception +import os +from diagrams import Cluster, Diagram +""" Importing all AWS nodes """ +from diagrams.aws.analytics import * +from diagrams.aws.compute import * +from diagrams.aws.database import * +from diagrams.aws.devtools import * +from diagrams.aws.engagement import * +from diagrams.aws.integration import * +from diagrams.aws.iot import * +from diagrams.aws.management import * +from diagrams.aws.media import * +from diagrams.aws.migration import * +from diagrams.aws.ml import * +from diagrams.aws.network import * +from diagrams.aws.security import * +from diagrams.aws.storage import * + + +PATH_DIAGRAM_OUTPUT = "./assets/diagrams/" + +class Mapsources: + + """ Class to mapping type resource from Terraform to Diagram Nodes """ + mapresources = {"aws_lambda_function": "Lambda", "aws_emr_cluster": "EMRCluster", + "aws_elasticsearch_domain": "ES", "aws_msk_cluster": "ManagedStreamingForKafka", + "aws_sqs_queue_policy": "SQS", "aws_instance": "EC2", + "aws_eks_cluster": "EKS", "aws_autoscaling_group": "AutoScaling", + "aws_ecs_cluster": "ECS", "aws_db_instance": "RDS", + "aws_elasticache_cluster": "ElastiCache", "aws_docdb_cluster": "DocumentDB", + "aws_internet_gateway": "InternetGateway", "aws_nat_gateway": "NATGateway", + "aws_elb_classic": "ELB", "aws_elb": "ELB", + "aws_route_table": "RouteTable", "aws_subnet": "PublicSubnet", + "aws_network_acl": "Nacl", "aws_vpc_peering_connection": "VPCPeering", + "aws_vpc_endpoint_gateway": "Endpoint", "aws_iam_policy": "IAM", + "aws_efs_file_system": "EFS", "aws_s3_bucket_policy": "S3"} + + +class _Diagram(object): + + def __init__ (self, vpc_id, diagram, resources): + self.resources = resources + self.vpc_id = vpc_id + self.diagram = diagram + + @exception + def generateDiagram(self): + + #diagram = [[], [], [], [], [], [], [Resource(id='j-HCK3AB8SJ0JK', name='My cluster', type='aws_emr_cluster', details='', group='compute'), Resource(id='j-UU8T5E0AI99S', name='My cluster', type='aws_emr_cluster', details='', group='compute')], [Resource(id='arn:aws:lambda:us-east-1:200984112386:function:cwsyn-meucana-2b5c2c74-cbfc-46b5-8400-db853e98dca3', name='cwsyn-meucana-2b5c2c74-cbfc-46b5-8400-db853e98dca3', type='aws_lambda_function', details='', group='compute'), Resource(id='arn:aws:lambda:us-east-1:200984112386:function:myfnn', name='myfnn', type='aws_lambda_function', details='', group='compute'), Resource(id='arn:aws:lambda:us-east-1:200984112386:function:aaaaaaaaaaaaa', name='aaaaaaaaaaaaa', type='aws_lambda_function', details='', group='compute')], [], [], [], [], [], [], [], [Resource(id='igw-09cc041a3f6e7d38b', name='igw-09cc041a3f6e7d38b', type='aws_internet_gateway', details='', group='network')], [Resource(id='acl-01a0765c917e4e733', name='acl-01a0765c917e4e733', type='aws_network_acl', details='NACL using Subnets subnet-0267b72fb6dead183, subnet-023da38a98c179cce, subnet-0c63113a164c83e0b', group='network')], [], [Resource(id='rtb-0405795a9a7f730e8', name='rtb-0405795a9a7f730e8', type='aws_route_table', details='', group='network'), Resource(id='rtb-0bf969c6f52d3ca40', name='rtb-0bf969c6f52d3ca40', type='aws_route_table', details='', group='network')], [Resource(id='sg-0521cf0ebff65c70d', name='ElasticMapReduce-slave', type='aws_security_group', details='', group='network'), Resource(id='sg-0abf4b1f1a9ecfe9c', name='MEUSG', type='aws_security_group', details='', group='network'), Resource(id='sg-0b21dea57386a36b4', name='ElasticMapReduce-master', type='aws_security_group', details='', group='network'), Resource(id='sg-0f2d79f705ff0d97d', name='default', type='aws_security_group', details='', group='network')], [Resource(id='subnet-0c63113a164c83e0b', name='subnet-0c63113a164c83e0b', type='aws_subnet', details='Subnet using CidrBlock 10.0.2.0/24 and AZ us-east-1e', group='network'), Resource(id='subnet-0267b72fb6dead183', name='subnet-0267b72fb6dead183', type='aws_subnet', details='Subnet using CidrBlock 10.0.3.0/24 and AZ us-east-1b', group='network'), Resource(id='subnet-023da38a98c179cce', name='subnet-023da38a98c179cce', type='aws_subnet', details='Subnet using CidrBlock 10.0.1.0/24 and AZ us-east-1a', group='network')], [], [], [], [], []] + """ Check if assets/diagram directory exists """ + if not os.path.isdir(PATH_DIAGRAM_OUTPUT): + print("OI") + try: + os.mkdir(PATH_DIAGRAM_OUTPUT) + except OSError: + print ("Creation of the directory %s failed" % PATH_DIAGRAM_OUTPUT) + else: + print ("Successfully created the directory %s " % PATH_DIAGRAM_OUTPUT) + + """ Ordering Resource list to group resources into cluster """ + ordered_resources = dict() + for alldata in self.resources: + if isinstance(alldata, list): + for rundata in alldata: + if Mapsources.mapresources.get(rundata.type) is not None: + if rundata.group in ordered_resources: + ordered_resources[rundata.group].append({"id": rundata.id, + "type": rundata.type, + "name": rundata.name, + "details": rundata.details}) + else: + ordered_resources[rundata.group] = [{"id": rundata.id, + "type": rundata.type, + "name": rundata.name, + "details": rundata.details}] + + """ Start mounting Cluster """ + resource_id = list() + with Diagram(name="AWS VPC {} Resources".format(self.vpc_id), filename=PATH_DIAGRAM_OUTPUT+self.vpc_id, + show=self.diagram, direction="TB"): + + """ VPC to represent main resource """ + _vpc = VPC("VPC {}".format(self.vpc_id)) + + """ Iterate resources to draw it """ + for alldata in ordered_resources: + with Cluster(alldata.capitalize() + " resources"): + for rundata in ordered_resources[alldata]: + resource_id.append(eval(Mapsources.mapresources.get(rundata["type"]))(rundata["name"])) + + + """ Connecting resources and vpc """ + for resource in resource_id: + resource >> _vpc diff --git a/shared/internal/analytics.py b/shared/internal/analytics.py index cebe430..1369dbc 100644 --- a/shared/internal/analytics.py +++ b/shared/internal/analytics.py @@ -39,7 +39,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=elasticsearch_domain['DomainStatus']['DomainId'], name=elasticsearch_domain['DomainStatus']['DomainName'], type='aws_elasticsearch_domain', - details='')) + details='', + group='analytics')) return resources_found @@ -81,7 +82,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['ClusterArn'], name=data['ClusterName'], type='aws_msk_cluster', - details='')) + details='', + group='analytics')) break return resources_found \ No newline at end of file diff --git a/shared/internal/application.py b/shared/internal/application.py index e4278fb..f385788 100644 --- a/shared/internal/application.py +++ b/shared/internal/application.py @@ -55,6 +55,7 @@ def analyze_queues(self, client, queue): return True, Resource(id=queuearn, name=queue, type='aws_sqs_queue_policy', - details='') + details='', + group='application') return False, None diff --git a/shared/internal/compute.py b/shared/internal/compute.py index 0e733b1..ee462dc 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -27,7 +27,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['FunctionArn'], name=data["FunctionName"], type='aws_lambda_function', - details='')) + details='', + group='compute')) return resources_found @@ -62,7 +63,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=instances['InstanceId'], name=instance_name, type='aws_instance', - details='')) + details='', + group='compute')) return resources_found @@ -94,7 +96,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=cluster['cluster']['arn'], name=cluster['cluster']["name"], type='aws_eks_cluster', - details='')) + details='', + group='compute')) return resources_found @@ -132,7 +135,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['Id'], name=data['Name'], type='aws_emr_cluster', - details='')) + details='', + group='compute')) return resources_found @@ -171,6 +175,7 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['AutoScalingGroupARN'], name=data['AutoScalingGroupName'], type='aws_autoscaling_group', - details='Using LaunchConfigurationName {0}'.format(data["LaunchConfigurationName"]))) + details='Using LaunchConfigurationName {0}'.format(data["LaunchConfigurationName"]), + group='compute')) return resources_found \ No newline at end of file diff --git a/shared/internal/containers.py b/shared/internal/containers.py index cfd9393..4b80dad 100644 --- a/shared/internal/containers.py +++ b/shared/internal/containers.py @@ -57,7 +57,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['clusterArn'], name=data["clusterName"], type='aws_ecs_cluster', - details='')) + details='', + group='container')) else: """ EC2 services require container instances, list of them should be fine for now """ pass @@ -91,7 +92,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=instance['InstanceId'], name=data["clusterName"], type='aws_ecs_cluster', - details='Instance in EC2 cluster')) + details='Instance in EC2 cluster', + group='container')) pass pass pass diff --git a/shared/internal/database.py b/shared/internal/database.py index 0836dc6..770ab26 100644 --- a/shared/internal/database.py +++ b/shared/internal/database.py @@ -36,7 +36,8 @@ def run(self) -> List[Resource]: name=data["DBInstanceIdentifier"], type='aws_db_instance', details='DBInstance using subnets {} and engine {}'\ - .format(', '.join(subnet_ids), data["Engine"]))) + .format(', '.join(subnet_ids), data["Engine"]), + group='database')) return resources_found @@ -74,7 +75,8 @@ def run(self) -> List[Resource]: name=data["CacheSubnetGroupName"], type='aws_elasticache_cluster', details='Elasticache Cluster using subnets {} and engine {}' \ - .format(', '.join(subnet_ids), data["Engine"]))) + .format(', '.join(subnet_ids), data["Engine"]), + group='database')) return resources_found @@ -112,6 +114,7 @@ def run(self) -> List[Resource]: name=data["DBInstanceIdentifier"], type='aws_docdb_cluster', details='Documentdb using subnets {} and engine {}'\ - .format(', '.join(subnet_ids), data["Engine"]))) + .format(', '.join(subnet_ids), data["Engine"]), + group='database')) return resources_found \ No newline at end of file diff --git a/shared/internal/management.py b/shared/internal/management.py index ce86af7..192a750 100644 --- a/shared/internal/management.py +++ b/shared/internal/management.py @@ -31,6 +31,7 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['Id'], name=data["Name"], type='aws_canaries_function', - details='')) + details='', + group='management')) return resources_found diff --git a/shared/internal/network.py b/shared/internal/network.py index 476b06e..08336db 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -50,7 +50,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=response['InternetGateways'][0]['InternetGatewayId'], name=response['InternetGateways'][0]['InternetGatewayId'], type='aws_internet_gateway', - details='')) + details='', + group='network')) return resources_found @@ -85,7 +86,8 @@ def run(self) -> List[Resource]: details='NAT Gateway Private IP {}, Public IP {}, Subnet id {}' \ .format(data['NatGatewayAddresses'][0]['PrivateIp'], data['NatGatewayAddresses'][0]['PublicIp'], - data['SubnetId']))) + data['SubnetId']), + group='network')) return resources_found @@ -114,7 +116,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['LoadBalancerName'], name=data['LoadBalancerName'], type='aws_elb_classic', - details='')) + details='', + group='network')) return resources_found @@ -146,7 +149,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['LoadBalancerName'], name=data['LoadBalancerName'], type='aws_elb', - details='')) + details='', + group='network')) return resources_found @@ -177,7 +181,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['RouteTableId'], name=data['RouteTableId'], type='aws_route_table', - details='')) + details='', + group='network')) return resources_found @@ -209,7 +214,8 @@ def run(self) -> List[Resource]: name=data['SubnetId'], type='aws_subnet', details='Subnet using CidrBlock {} and AZ {}' \ - .format(data['CidrBlock'], data['AvailabilityZone']))) + .format(data['CidrBlock'], data['AvailabilityZone']), + group='network')) return resources_found @@ -244,7 +250,8 @@ def run(self) -> List[Resource]: name=data['NetworkAclId'], type='aws_network_acl', details='NACL using Subnets {}' \ - .format(', '.join(subnet_ids)))) + .format(', '.join(subnet_ids)), + group='network')) return resources_found @@ -275,7 +282,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['GroupId'], name=data['GroupName'], type='aws_security_group', - details='')) + details='', + group='network')) return resources_found @@ -314,7 +322,8 @@ def run(self) -> List[Resource]: data['AccepterVpcInfo']['VpcId'], data['RequesterVpcInfo']['OwnerId'], data['RequesterVpcInfo']['Region'], - data['RequesterVpcInfo']['VpcId']))) + data['RequesterVpcInfo']['VpcId']), + group='network')) return resources_found class VPCENDPOINT(object): @@ -347,12 +356,14 @@ def run(self) -> List[Resource]: name=data['ServiceName'], type='aws_vpc_endpoint_gateway', details='Vpc Endpoint Gateway RouteTable {}' \ - .format(', '.join(data['RouteTableIds'])))) + .format(', '.join(data['RouteTableIds'])), + group='network')) else: resources_found.append(Resource(id=data['VpcEndpointId'], name=data['ServiceName'], type='aws_vpc_endpoint_gateway', details='Vpc Endpoint Service Subnet {}' \ - .format(', '.join(data['SubnetIds'])))) + .format(', '.join(data['SubnetIds'])), + group='network')) return resources_found \ No newline at end of file diff --git a/shared/internal/security.py b/shared/internal/security.py index 4bd8a2f..2e8878a 100644 --- a/shared/internal/security.py +++ b/shared/internal/security.py @@ -71,6 +71,7 @@ def analyze_policy(self, client, data): return True, Resource(id=data['Arn'], name=data['PolicyName'], type='aws_iam_policy', - details='IAM Policy version {}'.format(data['DefaultVersionId'])) + details='IAM Policy version {}'.format(data['DefaultVersionId']), + group='security') return False, None diff --git a/shared/internal/storage.py b/shared/internal/storage.py index afcabdb..0f63ee0 100644 --- a/shared/internal/storage.py +++ b/shared/internal/storage.py @@ -41,7 +41,8 @@ def run(self) -> List[Resource]: resources_found.append(Resource(id=data['FileSystemId'], name=data['Name'], type='aws_efs_file_system', - details='')) + details='', + group='storage')) return resources_found @@ -88,7 +89,8 @@ def analyze_bucket(self, client, data): return True, Resource(id=data['Name'], name=data['Name'], type='aws_s3_bucket_policy', - details='') + details='', + group='storage') except: pass return False, None From ef59388f620f38050bb2fa192d477867ec71804c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 25 May 2020 11:10:58 +0100 Subject: [PATCH 12/13] Fixed get names using Tags --- shared/common.py | 13 ++++++++++++- shared/internal/compute.py | 8 ++++---- shared/internal/network.py | 36 ++++++++++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/shared/common.py b/shared/common.py index 889802c..dec0f99 100644 --- a/shared/common.py +++ b/shared/common.py @@ -36,7 +36,18 @@ class Resource(NamedTuple): details: str group: str - +def get_name_tags(d): + for k, v in d.items(): + if isinstance(v, dict): + get_name_tags(v) + else: + if k == "Tags": + for value in v: + if value["Key"] == 'Name': + return value["Value"] + + + return False def generate_session(profile_name): try: diff --git a/shared/internal/compute.py b/shared/internal/compute.py index ee462dc..f4e7500 100644 --- a/shared/internal/compute.py +++ b/shared/internal/compute.py @@ -53,13 +53,13 @@ def run(self) -> List[Resource]: for data in response["Reservations"]: for instances in data['Instances']: + if "VpcId" in instances: if instances['VpcId'] == self.vpc_options.vpc_id: - instance_name = instances["InstanceId"] - for tag in instances['Tags']: - if tag['Key'] == 'Name': - instance_name = tag['Value'] + nametags = get_name_tags(instances) + instance_name = instances["InstanceId"] if nametags is False else nametags + resources_found.append(Resource(id=instances['InstanceId'], name=instance_name, type='aws_instance', diff --git a/shared/internal/network.py b/shared/internal/network.py index 08336db..aee2035 100644 --- a/shared/internal/network.py +++ b/shared/internal/network.py @@ -47,8 +47,12 @@ def run(self) -> List[Resource]: """ One VPC has only 1 IGW then it's a direct check """ if len(response["InternetGateways"]) > 0: + nametags = get_name_tags(response) + + name = response['InternetGateways'][0]['InternetGatewayId'] if nametags is False else nametags + resources_found.append(Resource(id=response['InternetGateways'][0]['InternetGatewayId'], - name=response['InternetGateways'][0]['InternetGatewayId'], + name=name, type='aws_internet_gateway', details='', group='network')) @@ -80,8 +84,12 @@ def run(self) -> List[Resource]: if data['VpcId'] == self.vpc_options.vpc_id: + nametags = get_name_tags(data) + + name = data['NatGatewayId'] if nametags is False else nametags + resources_found.append(Resource(id=data['NatGatewayId'], - name=data['NatGatewayId'], + name=name, type='aws_nat_gateway', details='NAT Gateway Private IP {}, Public IP {}, Subnet id {}' \ .format(data['NatGatewayAddresses'][0]['PrivateIp'], @@ -178,8 +186,12 @@ def run(self) -> List[Resource]: """ Iterate to get all route table filtered """ for data in response['RouteTables']: + nametags = get_name_tags(data) + + name = data['RouteTableId'] if nametags is False else nametags + resources_found.append(Resource(id=data['RouteTableId'], - name=data['RouteTableId'], + name=name, type='aws_route_table', details='', group='network')) @@ -210,8 +222,12 @@ def run(self) -> List[Resource]: """ Iterate to get all route table filtered """ for data in response['Subnets']: + nametags = get_name_tags(data) + + name = data['SubnetId'] if nametags is False else nametags + resources_found.append(Resource(id=data['SubnetId'], - name=data['SubnetId'], + name=name, type='aws_subnet', details='Subnet using CidrBlock {} and AZ {}' \ .format(data['CidrBlock'], data['AvailabilityZone']), @@ -245,9 +261,13 @@ def run(self) -> List[Resource]: subnet_ids = [] for subnet in data['Associations']: subnet_ids.append(subnet['SubnetId']) + + nametags = get_name_tags(data) + + name = data['NetworkAclId'] if nametags is False else nametags resources_found.append(Resource(id=data['NetworkAclId'], - name=data['NetworkAclId'], + name=name, type='aws_network_acl', details='NACL using Subnets {}' \ .format(', '.join(subnet_ids)), @@ -312,8 +332,12 @@ def run(self) -> List[Resource]: if data['AccepterVpcInfo']['VpcId'] == self.vpc_options.vpc_id \ or data['RequesterVpcInfo']['VpcId'] == self.vpc_options.vpc_id: + nametags = get_name_tags(data) + + name = data['VpcPeeringConnectionId'] if nametags is False else nametags + resources_found.append(Resource(id=data['VpcPeeringConnectionId'], - name=data['VpcPeeringConnectionId'], + name=name, type='aws_vpc_peering_connection', details='Vpc Peering Accepter OwnerId {}, Accepter Region {}, Accepter VpcId {} \ Requester OwnerId {}, Requester Region {}, Requester VpcId' \ From 862edc4df50007233c6b0e21b73d4282a06a1af6 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 25 May 2020 11:31:34 +0100 Subject: [PATCH 13/13] README issues and version bump --- LICENSE | 201 +++++++++++++++++++++++++++++++++++++++ README.md | 49 +++++----- aws-network-discovery.py | 32 +++---- requirements.txt | 1 + 4 files changed, 237 insertions(+), 46 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..989e2c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/README.md b/README.md index c4074fc..a8a1009 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # AWS Network Discovery +![python version](https://img.shields.io/badge/python-3.6%2C3.7%2C3.8-blue?logo=python) [![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger) AWS Network Discovery helps you analyze what's resources are using a custom VPC. @@ -38,6 +39,8 @@ Following services are integrated - Performs checks using thread concurrency - Best information provided +- Integration with [Diagram](https://github.com/mingrammer/diagrams) +- Now this tool can check all VPCS in the same regions ### Requirements and Installation @@ -62,6 +65,24 @@ arn:aws:iam::aws:policy/job-function/ViewOnlyAccess arn:aws:iam::aws:policy/SecurityAudit ``` +- Due to fact AWS has not updated these policies to include Kafka Cluster and Synthetics Canaries read/list permissions, you must create a new policy with permissions bellow and attach to user. + +```sh +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "kafka:ListClusters", + "synthetics:DescribeCanaries" + ], + "Effect": "Allow", + "Resource": "*" + } + ] +} +``` + - (Optional) If you want to be able to switch between multiple AWS credentials and settings, you can configure [named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) and later pass profile name when running the tool. ### Usage @@ -69,7 +90,7 @@ arn:aws:iam::aws:policy/SecurityAudit 1. Run the aws-network-discovery command with follow options (if a region not informed, this script will try to get from ~/.aws/credentials): ```sh -$ ./aws-network-discovery.py --vpc-id vpc-xxxxxxx --region-name xx-xxxx-xxx [--profile-name profile] +$ ./aws-network-discovery.py [--vpc-id vpc-xxxxxxx] --region-name xx-xxxx-xxx [--profile-name profile] [--diagram True/False] ``` 2. For help use: @@ -90,33 +111,11 @@ This project support English and Portuguese (Brazil) languages. To contribute wi $ python msgfmt.py -o locales/NEWFOLDER/LC_MESSAGES/messages.mo locales/NEWFOLDER/LC_MESSAGES/messages ``` - ### TODO - Improve documentation and code comments - More services that uses VPC (I'll try add one a week) -- Custom logging control and reporting improvement. - -### License - -Copyright 2020 Conversando Na Nuvem (https://www.youtube.com/channel/UCuI2nDGLq_yjY9JNsDYStMQ/) - Leandro Damascena - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +### Contributing +If you have improvements or fixes, we would love to have your contributions. \ No newline at end of file diff --git a/aws-network-discovery.py b/aws-network-discovery.py index 02f547e..f14a4df 100644 --- a/aws-network-discovery.py +++ b/aws-network-discovery.py @@ -1,26 +1,16 @@ #!/usr/bin/env python3 """ -Copyright 2020 Conversando Na Nuvem (https://www.youtube.com/channel/UCuI2nDGLq_yjY9JNsDYStMQ/) - Leandro Damascena +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 -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -following conditions are met: + http://www.apache.org/licenses/LICENSE-2.0 -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- +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. This script manages aws-network-discovery, a tool for analyzing VPC dependencies. """ @@ -36,7 +26,7 @@ from commands.vpc import Vpc -__version__ = "0.7.0" +__version__ = "0.8.0" AVAILABLE_LANGUAGES = ['en_US','pt_BR'] DIAGRAMS_OPTIONS = ['True','False'] @@ -47,7 +37,7 @@ def show_options(args="sys.argv[1:]"): "-v", "--vpc-id", required=False, - help="Inform VPC to analyze. If not informed, script try all vpcs." + help="Inform VPC to analyze. If not informed, script will check all vpcs." ) parser.add_argument( "-r", diff --git a/requirements.txt b/requirements.txt index da26b6c..89d6260 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ boto3 ipaddress +diagrams