def set_prometheus_endpoint(self): """ Set a Managed Prometheus endpoint with full access and add it to private routes """ sg_res = t_ec2.SecurityGroup( title=alphanum(f"{self.name}ApsVpcEndpointSG"), VpcId=Ref(self.vpc), GroupDescription="Used by Prometheus VPC Endpoint", SecurityGroupIngress=[ t_ec2.SecurityGroupRule(IpProtocol="tcp", FromPort="443", ToPort="443", CidrIp="0.0.0.0/0") ], ) self._r[sg_res.title] = sg_res res = t_ec2.VPCEndpoint( title=alphanum(f"{self.name}ApsVpcEndpoint"), VpcId=Ref(self.vpc), ServiceName=f"com.amazonaws.{self.region}.aps-workspaces", SubnetIds=[Ref(subnet) for subnet in self.gateway_subnets], SecurityGroupIds=[Ref(self._r[sg_res.title])], VpcEndpointType="Interface", ) self._r[res.title] = res
def __init__(self, name: str, description: str = None): self.name = name self.clean_name = alphanum(name) self.description = description self.resources = dict() self.t_api = t_apigw2.Api( title=f"{self.clean_name}HTTPApi", ProtocolType="HTTP", Name=self.name, ) if description is not None: self.t_api.Description = description self.resources[self.t_api.title] = self.t_api
def set_s3_endpoint(self): """Set an S3 endpoint with full access and add it to private routes""" res = t_ec2.VPCEndpoint( title=alphanum(f"{self.name}S3EndpointGateway"), VpcId=Ref(self.vpc), ServiceName=f"com.amazonaws.{self.region}.s3", RouteTableIds=[ Ref(route_table) for route_table in self.natted_route_tables ], ) if self.public_route_table is not None: res.RouteTableIds.append(Ref(self.public_route_table)) self._r[res.title] = res
def multiaz_subnets( name_prefix: str, cidr_block: str, region: str, vpc: object = None, vpc_id: str = None, no_of_subnets: int = 4, network_acl: object = None, network_acl_id: str = None, route_table: object = None, route_table_id: str = None, ) -> list: """Split given CIDR block into subnets over multiple AZs Either `vpc` or both `vpc_id` and `region` are required. If a network ACL or route table are passed as parameters, they will be associated with the subnets. `vpc`, `network_acl` and `route_table` are expected to be Troposphere resource objects which can be passed to Ref and GetAtt functions. As an alternative, `vpc_id`, `region`, `network_acl_id` `route_table_id` can be passed directly. If both resource and *_id are specified, the *_id will take precedence. Returns a list of Troposphere resources that describes the subnets and can be attached to a Template object. Returned subnet objects have the following keys set in their Metadata attribute: az: full availability zone name ("eu-west-1a") az_index: uppercase AZ, without the region part ("A") suffix: the suffix that was added to the name to form a unique resource title. Probably a single digit. Args: name_prefix (str): Prefix each resource with this string. Use to assure unique name for the resource in the calling Template cidr_block (str): IP range to split into subnets region (str): AWS region vpc (object, optional): VPC Troposphere resource. One of vpc or vpc_id is required. Defaults to None. vpc_id (str, optional): VPC ID. One of vpc or vpc_id is required. Defaults to None. no_of_subnets (int, optional): Create this many subnets. must be a power of 2. Defaults to 4. network_acl (object, optional): Network ACL Troposphere resource. Defaults to None. network_acl_id (str, optional): Network ACL ID. Defaults to None. route_table (object, optional): Route table resource. Defaults to None. route_table_id (str, optional): Route table ID. Defaults to None. Raises: ValueError: If neither vpc nor vpc_id were specified. Returns: list: Troposphere resources to be added to Template. """ if vpc is None and vpc_id is None: raise ValueError("One of vpc or vpc_id must be specified") if vpc_id is None: vpc_id = Ref(vpc) # Resource names only accept alphanumeric prefix = alphanum(name_prefix).lower().capitalize() net_split = split_net_across_zones(cidr_block, region, no_of_subnets) resources = list() for index, net_segment in enumerate(net_split): # set subnet az_index = net_segment["az"][-1:].upper() subnet = t_ec2.Subnet( title=f"{prefix}{index+1}", AvailabilityZone=net_segment["az"], CidrBlock=net_segment["cidr"], VpcId=vpc_id, Tags=[{ "Key": "Name", "Value": f"{name_prefix} {az_index}" }], ) subnet.Metadata = {} subnet.Metadata["az"] = net_segment["az"].lower() subnet.Metadata["az_index"] = az_index subnet.Metadata["suffix"] = index + 1 resources.append(subnet) # associate network ACL with subnet if network_acl_id is None and network_acl is not None: network_acl_id = Ref(network_acl) if network_acl_id is not None: resources.append( t_ec2.SubnetNetworkAclAssociation( title=f"{subnet.title}NaclAssociation", SubnetId=Ref(subnet), NetworkAclId=network_acl_id, )) if route_table_id is None and route_table is not None: route_table_id = Ref(route_table) if route_table_id is not None: resources.append( t_ec2.SubnetRouteTableAssociation( title=f"{subnet.title}RouteAssociation", SubnetId=Ref(subnet), RouteTableId=route_table_id, )) return resources
def peer_with_another_vpc( self, peer_vpc_id: str, peer_vpc_name: str, peer_role_arn: str = None, peer_owner_id: str = None, peer_region: str = None, peer_cidrs: list = [], add_route_to_private_tables: bool = True, add_route_to_public_table: bool = True, ): """Set VPC Peering Args: peer_vpc_id (str): The ID of the VPC with which you are creating the VPC peering connection peer_vpc_name (str): A name for the peer VPC, used in the Name tag for the peering connection peer_role_arn (str, optional): VPC peer role for the peering connection in another AWS account. Required if peering with a different AWS account. Defaults to None. peer_owner_id (str, optional): Owner of the other AWS account, if any. Defaults to None. peer_region (str, optional): The Region code for the accepter VPC. Defaults to the same region as requester VPC. peer_cidrs: (list, optional): List of CIDR blocks used by the peer VPC. If non-empty and any add_route_* argument is set to true, a route entry will be added to the respective table pointing that CIDR block to the peered connection. Defaults to an empty list. add_route_to_private_tables (bool,optional): If True, add the peered VPC to the private routing tables. Defaults to True. add_route_to_public_tables (bool,optional): If True, add the peered VPC to the public routing table. Defaults to True. Notes: - For the peered VPC to be added to the routing tables, they must already exist when this method is called. That means subnets should be created before setting up VPC Connection Peering. - As of Jan 2022 CloudFormation can't enable DNS resolution """ res = t_ec2.VPCPeeringConnection( title=alphanum( f"Peer{peer_vpc_name.capitalize()}With{self.name.capitalize()}" ), VpcId=Ref(self.vpc), PeerVpcId=peer_vpc_id, Tags=[{ "Key": "Name", "Value": f"{peer_vpc_name} - {self.name}" }], ) if peer_region is not None: res.PeerRegion = peer_region if peer_owner_id is not None: res.PeerOwnerId = peer_owner_id if peer_role_arn is not None: res.PeerRoleArn = peer_role_arn self._r[res.title] = res if add_route_to_private_tables: self.add_vpc_peering_to_private_tables(peer_cidrs=peer_cidrs, vpc_peering_id=Ref(res)) if add_route_to_public_table: self.add_vpc_peering_to_public_table(peer_cidrs=peer_cidrs, vpc_peering_id=Ref(res))
def add_stage( self, name: str, auto_deploy: bool = False, log_format: str = "none", stage_variables: dict = {}, description: str = None, ): # TODO: # - DefaultRouteSettings (??) if log_format.lower() in ["none", "clf", "json", "xml", "csv"]: pass elif "$context.requestId" in log_format: pass else: raise ValueError(f"{log_format} is not a valid log format") # Create Troposphere resource api_stage = t_apigw2.Stage( title=f"{alphanum(name)}Stage", ApiId=Ref(self.t_api), StageName=name ) # Set logging if log_format.lower() != "none": api_stage_log = t_apigw2.AccessLogSettings( DestinationArn=Join( ":", [ "arn", "aws", "logs", Region, AccountId, f"{self.clean_name}HttpApi", alphanum(name), ], ) ) if log_format.lower() == "clf": api_stage_log.Format = '$context.identity.sourceIp - - [$context.requestTime] "$context.httpMethod $context.routeKey $context.protocol" $context.status $context.responseLength $context.requestId' # noqa: E501 elif log_format.lower() == "json": api_stage_log.Format = json.dumps( { "requestId": "$context.requestId", "ip": "$context.identity.sourceIp", "requestTime": "$context.requestTime", "httpMethod": "$context.httpMethod", "routeKey": "$context.routeKey", "status": "$context.status", "protocol": "$context.protocol", "responseLength": "$context.responseLength", } ) elif log_format.lower() == "xml": api_stage_log.Format = '<request id="$context.requestId"> <ip>$context.identity.sourceIp</ip> <requestTime>$context.requestTime</requestTime> <httpMethod>$context.httpMethod</httpMethod> <routeKey>$context.routeKey</routeKey> <status>$context.status</status> <protocol>$context.protocol</protocol> <responseLength>$context.responseLength</responseLength> </request>' # noqa: E501 elif log_format.lower() == "csv": api_stage_log.Format = "$context.identity.sourceIp,$context.requestTime,$context.httpMethod,$context.routeKey,$context.protocol,$context.status,$context.responseLength,$context.requestId" # noqa: E501 api_stage.AutoDeploy = auto_deploy if description is not None: api_stage.Description = description if len(stage_variables) > 0: api_stage.StageVariables = stage_variables self.resources[api_stage.title] = api_stage