def generate(name, num_of_instances): with Diagram(name, direction='TB', show=True): with Cluster('Auto Scaling Group'): app_group = [EC2Instance('App') for _ in range(num_of_instances)] rds_master = RDS('DB (Master)') rds_standby = RDS('DB (Standby)') Users('Users') >> Route53('Route 53') >> ALB('ALB') >> app_group >> rds_master rds_master - rds_standby
} node_attr = { "width": "1", "height": "1", "fontsize": "8", "fontname": "IBM Plex Mono", "fontcolor": "grey" } with Diagram("Setup a blog on k3s", show=False, graph_attr=graph_attr, node_attr=node_attr, direction="TB"): users = Users("Users") with Cluster("LetsEncrypt API Servers", graph_attr=graph_attr): letsencrypt = LetsEncrypt("LE SSL Certificate") with Cluster("BareMetal Server", graph_attr=graph_attr): with Cluster("K8S Cluster", graph_attr=graph_attr): with Cluster("NS Cert-Manager", graph_attr=graph_attr): certificate_request = CertManager("mywebsite.com") with Cluster("NS www", graph_attr=graph_attr): website_secret = Secret("mywebsite.com") with Cluster("Ingress", graph_attr=graph_attr): ingress = Ingress("https") with Cluster("Pods", graph_attr=graph_attr): pod = Pod('website') with Cluster("Deployment", graph_attr=graph_attr): pod_deploy = Deployment("website") with Cluster("RS", graph_attr=graph_attr):
""" An example use of the diagrams module to create an architecture diagram similar to 'Reference Architecture: Cross Account AWS CodePipeline': https://github.com/awslabs/aws-refarch-cross-account-pipeline """ from diagrams import Cluster, Diagram, Edge from diagrams.aws.compute import Lambda from diagrams.aws.devtools import Codebuild, Codecommit, Codepipeline from diagrams.aws.general import Users from diagrams.aws.management import Cloudformation from diagrams.aws.mobile import APIGateway from diagrams.aws.storage import S3 with Diagram(None, filename="aws-cross-account-pipeline", show=False): developers = Users("Developers") with Cluster("Developer Account"): source_code = Codecommit("CodeCommit") source_code << Edge(label="merge pr") << developers with Cluster("Shared Services Account"): with Cluster("Pipeline"): pipeline = Codepipeline("CodePipeline") build = Codebuild("Codebuild") artifacts = S3("Build Artifacts") source_code >> Edge(label="trigger") >> pipeline developers >> Edge(label="manual approval") >> pipeline pipeline >> build >> Edge(label="yaml file") >> artifacts with Cluster("Test Workload Account"):
# diagram.py from diagrams import Cluster, Diagram from diagrams.aws.general import Users from diagrams.aws.storage import S3 from diagrams.aws.network import CF, ELB from diagrams.aws.compute import EC2 with Diagram("Terrascan Website", show=False, filename="01-s3-public-bucket"): users = Users("users") with Cluster("Internet Exposed"): s3 = S3("static-assets") users >> s3 with Diagram("Terrascan Website", show=False, filename="02-s3-block-public-access"): users = Users("users") with Cluster("Private Only"): s3 = S3("static-assets") with Diagram("Terrascan Website", show=False, filename="03-s3-behind-cloudfront"): users = Users("users") with Cluster("Internet Exposed"): cf = CF("CDN") with Cluster("Private Only"): s3 = S3("static-assets") users >> cf >> s3
def build_arch_diag(self, account_dict, account_refs_dict): with Diagram(account_dict['account_alias'], show=True, direction="TB"): ## TIER 1: EXTERNAL TO AWS CLUSTERLESS ## Contains: Users, DNS, WAF, CloudFront, Cross Account VPC Peering (happens out of order) ## ------------------------------------------------------------------------------- ## Users users = Users('Actors') self.ingress.append(users) ## DNS if account_dict['dns']['name'] == 'Route53': dns = Route53('Route53') else: dns = TradicionalServer('External DNS') self.ingress.append(dns) ## WAF try: if account_dict['waf']['cloudfront']: waf = WAF('WAF - CloudFront') except KeyError: waf = None ## CloudFront if self.check_key(account_dict, 'cf'): cloudfront = CloudFront('CloudFront') self.ingress.append(cloudfront) cf_check = True ## CAVP cross_accounts = self.check_cross_account_refs(account_dict) ## TIER 2: ACCOUNT CLUSTER ## Contains: CAVP Accounts, Account ID, Account Alias, Global S3 Buckets ## ------------------------------------------------------------------------------- ## CAVP Accounts for x_account in cross_accounts: ## Since x_account is the key value for the account dict - xaccount_dict = cross_accounts['{}'.format(x_account)] ## Next with Accepter Accounts/VPC's/Nodes try: if xaccount_dict['xacc_account']: alias = self.get_account_refs( xaccount_dict['xacc_account'], account_refs_dict) with Cluster('Account: {}'.format(alias), graph_attr={ 'margin': '50', 'bgcolor': 'white', 'penwidth': '5' }): ## Make the cross account VPC with Cluster(xaccount_dict['xacc_vpc'], graph_attr={'bgcolor': 'white'}): ## Create the accuester node and add it to the dictionary with the ## Connection ID for diagram connection later. xacc_node = VPCPeering( 'VPC Peering - Accepter') cross_accounts['{}'.format( x_account)]['xacc_node'] = xacc_node except KeyError: try: if xaccount_dict['xreq_account']: alias = self.get_account_refs( xaccount_dict['xreq_account'], account_refs_dict) with Cluster('Account: {}'.format(alias), graph_attr={ 'margin': '50', 'bgcolor': 'white', 'penwidth': '5' }): ## Make the cross account VPC with Cluster(xaccount_dict['xreq_vpc'], graph_attr={'bgcolor': 'white'}): ## Create the Requester node and add it to the dictionary with the ## Connection ID for diagram connection later. xreq_node = VPCPeering( 'VPC Peering - Requester') cross_accounts['{}'.format( x_account)]['xreq_node'] = xreq_node except KeyError: continue ## Account ID and Alias with Cluster('Account: {}'.format(account_dict['account_alias']), graph_attr={ 'margin': '150', 'bgcolor': 'white', 'penwidth': '5' }): ## Global S3 Buckets s3_overflow = {} ## Diagram S3's in alternate Regions for s3 in account_dict['s3']: if s3['region'] == None: s3_node = SimpleStorageServiceS3(s3['Name']) else: ## If the S3 bucket isn't in one of our known AZ's, ## append to a list for the new region if s3['region'] in s3_overflow.keys(): s3_overflow[s3['region']].append(s3) else: ## If the S3 in a region that doesn't have an existing AZ, ## create a new key for the region and put the nodes in it for az in account_dict['az']: if az['RegionName'] != s3['region']: s3_overflow[s3['region']] = [] s3_overflow[s3['region']].append(s3) ## Create new cluster for each region in s3_overflow for region in account_dict['regions']: if region in s3_overflow.keys(): with Cluster(region, graph_attr={ 'bgcolor': 'white', 'style': 'dashed', 'pencolor': 'blue' }): for s3 in s3_overflow[region]: s3_node = SimpleStorageServiceS3(s3['Name']) ## TIER 3: VPC CLUSTER ## Contains: VPC, CAVP, VPC Peering, Internet Gateways, Load balancers, S3 buckets ## ------------------------------------------------------------------------------- ## VPC for v in range(len(account_dict['vpc'])): ## Dynamically add contents of VPC Cluster with Cluster(account_dict['vpc'][v]['VpcId'], graph_attr={'bgcolor': 'white'}): ## CAVP and VPC Peering account_dict['vpc'][v]['RequestNodes'] = [] account_dict['vpc'][v]['AcceptNodes'] = [] ## Create VPC Peering nodes, but connections can only be made once ## all Requester and Accepter nodes have been created. Store the nodes ## in a list for later reference, as dynamically created nodes can't be ## referred to uniquely and will need to be iterated upon later. for req in account_dict['vpc'][v]['PeerRequest']: if req['RequesterVpcInfo'][ 'OwnerId'] == account_dict['account_id']: req_node = VPCPeering( 'VPC Peering - Requester') cross_accounts[req['VpcPeeringConnectionId']][ 'req_node'] = req_node for acc in account_dict['vpc'][v]['PeerAccept']: if acc['AccepterVpcInfo'][ 'OwnerId'] == account_dict['account_id']: acc_node = VPCPeering('VPC Peering - Accepter') cross_accounts[acc['VpcPeeringConnectionId']][ 'acc_node'] = acc_node ## Internet Gateway try: ## Check if Internet Gateway exists if account_dict['igw']: for igw in account_dict['igw']: for attach in igw['Attachments']: ## If the IGW is attached to the VPC and available ## add it to igws list, which we will attach the ## previous ingress point to if attach['VpcId'] == account_dict[ 'vpc'][v]['VpcId'] and attach[ 'State'] == 'attached': internet_gw = InternetGateway( igw['InternetGatewayId']) igws.append(internet_gw) ig_check = True if len(igws) > 0: ## Append igws list to ingress list as the next connection point self.ingress.append(igws) except KeyError: ig_check = False ## Load Balancer try: for elb in account_dict['elb']: ## Check if a WAF is associated with the Load Balancer #waf_check = check_key(elb) elastic_lb = ElasticLoadBalancing( '{} ({})'.format(elb['DNSName'], elb['Scheme'])) for az in elb['AvailabilityZones']: elb_tuple = elastic_lb, az['SubnetId'] self.elb_subnets.append(elb_tuple) if elb['Scheme'] == 'internet-facing' and elb[ 'State'] == 'active': ## If the Load Balancer is internet-facing, a WAF should be put in place self.ingress.append(elastic_lb) elb_check = True except KeyError: elb_check = False ## TIER 4: REGION CLUSTER ## Contains: S3 buckets, WAF Regional, DynamoDB ## ------------------------------------------------------------------------------- region_list = self.kill_dupes(account_dict['regions']) for r in range(len(region_list)): for a in range(len(account_dict['az'])): if account_dict['az'][a][ 'RegionName'] == region_list[r]: with Cluster(region_list[r], graph_attr=self.region_sheet): for s3 in account_dict['s3']: if s3['region'] == region_list[r]: s3_node = SimpleStorageServiceS3( s3['Name']) ## WAF Regional ## PLACEHOLDER ## DynamoDB for ddb in account_dict['ddb']: ddb_region = account_dict['ddb'][ ddb]['TableArn'].split(':')[3] if ddb_region == region_list[r]: ddb_node = Dynamodb( account_dict['ddb'][ddb] ['TableName']) ## TIER 5: AVAILABILITY ZONE (AZ) CLUSTER ## Contains: RDS ## ------------------------------------------------------------------------------- with Cluster(account_dict['az'][a] ['ZoneName'], graph_attr={ 'bgcolor': 'white', 'style': 'dashed', 'pencolor': 'orange' }): ## Dynamically add RDS for rds in account_dict['rds']: if rds['AvailabilityZone'] == account_dict[ 'az'][a]['ZoneName']: rds_node = RDS( rds['DBName']) ## TIER 6: SUBNET CLUSTER ## Contains: Security Groups, EC2 Instances ## ------------------------------------------------------------------------------- for s in range( len(account_dict['az'][a] ['Subnets'])): with Cluster(account_dict['az'] [a]['Subnets'][s] ['SubnetId']): for rezzie in account_dict[ 'az'][a][ 'Subnets'][s][ 'ec2']: for instance in rezzie[ 'Instances']: ec2_name = self.get_name_tag( instance, 'EC2') ec2_instance = EC2( '{} ({})'. format( ec2_name, instance[ 'InstanceType'] )) self.ec2_instances.append( ec2_instance) ## If there is a load balancer, and the load balancer connects ## to the subnet of the EC2 instance, connect the ELB to the ## EC2 instance in the diagram for elb_tuple in self.elb_subnets: if account_dict['az'][ a]['Subnets'][ s]['SubnetId'] == elb_tuple[ 1]: self.ingress[ -1] >> elb_tuple[ 0] >> ec2_instance for i in range(len(self.ingress)): try: self.ingress[i] >> self.ingress[i + 1] except IndexError: error = "Dead end, baby." for conn in cross_accounts: try: req_node = cross_accounts[conn]['xreq_node'] acc_node = cross_accounts[conn]['acc_node'] req_node >> acc_node except KeyError: continue