def create_dnsrecords(): return route53.RecordSetGroup( 'dns', HostedZoneName=Sub('${domain}.'), RecordSets=[ route53.RecordSet( 'dnsInstance', Name=_subdomain_for_instance(), ResourceRecords=[GetAtt('devserver', 'PublicIp')], Type='A', TTL=300, ), route53.RecordSet( 'dnsTlsApplication', Name=_subdomain_for_application(), Type='A', AliasTarget=route53.AliasTarget( DNSName=GetAtt('tlsFrontend', 'DNSName'), HostedZoneId=GetAtt('tlsFrontend', 'CanonicalHostedZoneID'), ), ), route53.RecordSet( 'dnsTlsJenkins', Name=_subdomain_for_jenkins(), Type='A', AliasTarget=route53.AliasTarget( DNSName=GetAtt('tlsFrontend', 'DNSName'), HostedZoneId=GetAtt('tlsFrontend', 'CanonicalHostedZoneID'), ), ), ], )
def handle(self, chain_context): template = chain_context.template template.add_resource(route53.RecordSetGroup( "Route53Records", RecordSets=[ route53.RecordSet( DNS_NAME % self.name, Weight=1, SetIdentifier="original", AliasTarget=route53.AliasTarget( HostedZoneId=self.hosted_zone_id, DNSName=self.target, EvaluateTargetHealth=False, ), Name=Join("", [ self.dns_name, ".", self.base_domain, "." ]), Type="A", ) ], HostedZoneName=Join("", [self.base_domain, "."]) ))
def resources(self, stack: Stack) -> list[AWSObject]: """Return list of AWSObject associated with the construct.""" # Add bucket policy granting read access to te cloudfront distribution self.add_oai_access_to_bucket() result = [ *self.bucket.resources(stack), self.cache_policy, self.distribution, self.origin_access_identity, ] # Add a lambda invalidating cloudfront cache when bucket objects are modified result.extend(self.add_cache_invalidation(stack)) # Add route53 records if needed if self.r53_route_from: for zone_id, domain in self.r53_route_from: result.append( route53.RecordSetType( name_to_id(f"{self.name}-{domain}-r53-rset"), AliasTarget=route53.AliasTarget( DNSName=self.domain_name, # Z2FDTNDATAQYW2 is always the hosted zone ID when you # create an alias record that routes traffic to a # CloudFront distribution HostedZoneId="Z2FDTNDATAQYW2", ), Name=domain, HostedZoneId=zone_id, Type="A", )) return result
def create_r53_record(self, hosted_zone_name): """ Function to create r53 recourdset to associate with ELB :param hosted_zone_name: R53 hosted zone to create record in """ if self.elb_config.public_unit: name = Join('', [Ref('AWS::StackName'), '-', self.title, '.', hosted_zone_name]) else: name = Join('', [self.title, '.', hosted_zone_name]) self.elb_r53 = self.template.add_resource(route53.RecordSetGroup( self.title + 'R53', RecordSets=[route53.RecordSet( Name=name, AliasTarget=route53.AliasTarget(dnsname=GetAtt(self.trop_elb, 'DNSName'), hostedzoneid=GetAtt(self.trop_elb, 'CanonicalHostedZoneNameID')), Type='A')])) if not self.elb_config.public_unit: self.elb_r53.HostedZoneId = self.network_config.private_hosted_zone_id else: self.elb_r53.HostedZoneName = hosted_zone_name self.template.add_output(Output( self.trop_elb.title, Description='URL of the {0} ELB'.format(self.title), Value=Join('', ['http://', self.elb_r53.RecordSets[0].Name]) ))
def route53_record_set(self): t = self.template LocalDNS = t.add_resource( route53.RecordSetGroup( "LocalDNS", HostedZoneName=self.vars["HostedZoneDomainName"] + ".", RecordSets=[ route53.RecordSet( Name=self.vars["FQDNInternal"] + ".", Type="A", AliasTarget=route53.AliasTarget( HostedZoneId=CLOUDFRONT_HOSTEDZONEID, DNSName=GetAtt(self.SiteCFDistribution, "DomainName"), ), ) ], )) LocalDNS = t.add_output( Output( "LocalDNS", Description="Internal DNS domainname set in route53", Value=self.vars["FQDNInternal"], ))
def entry(hostname, i): if _is_domain_2nd_level(hostname): # must be an alias as it is a 2nd-level domain like elifesciences.net hostedzone = hostname + "." ensure(context['elb'], "2nd-level domains aliases are only supported for ELBs") return route53.RecordSetType(R53_CNAME_TITLE % (i + 1), HostedZoneName=hostedzone, Name=hostname, Type="A", AliasTarget=route53.AliasTarget( GetAtt( ELB_TITLE, "CanonicalHostedZoneNameID"), GetAtt(ELB_TITLE, "DNSName"))) else: hostedzone = context['domain'] + "." return route53.RecordSetType( R53_CNAME_TITLE % (i + 1), HostedZoneName=hostedzone, Name=hostname, Type="CNAME", TTL="60", ResourceRecords=[context['full_hostname']], )
def handle(self, chain_context): template = chain_context.template name = 'AlbAlias%s' % chain_context.instance_name template.add_resource( route53.RecordSetGroup( "Route53Records", RecordSets=[ route53.RecordSet( name, Weight=1, SetIdentifier="original", AliasTarget=route53.AliasTarget( HostedZoneId=self.hosted_zone_id, DNSName=self.dns_name, EvaluateTargetHealth=False, ), Name=Join("", [ Ref("namespace"), "-", Ref("env"), ".", self.base_domain, "." ]), Type="A", ) ], HostedZoneName=Join("", [self.base_domain, "."])))
def __init__(self, title, name, domain_name, zoneid_name, **kwargs): super().__init__(title, **kwargs) self.AliasTarget = r53.AliasTarget(DNSName=GetAtt(name, domain_name), HostedZoneId=GetAtt( name, zoneid_name)) self.Name = Ref(name) self.Type = 'A'
def declare_domain(self, domain_name: str, hosted_zone_id: str, stage_name: str) -> list[AWSObject]: """Declare a custom domain for one of the API stage. Note that when a custom domain is created then a certificate is automatically created for that domain. :param domain_name: domain name :param hosted_zone_id: hosted zone in which the domain belongs to :param stage_name: stage that should be associated with that domain :return: a list of AWSObject """ result = [] certificate_id = name_to_id(self.name + domain_name + "Certificate") certificate = Certificate( certificate_id, DomainName=domain_name, DomainValidationOptions=[ DomainValidationOption(DomainName=domain_name, HostedZoneId=hosted_zone_id) ], ValidationMethod="DNS", ) result.append(certificate) domain = apigatewayv2.DomainName( name_to_id(self.name + domain_name + "Domain"), DomainName=domain_name, DomainNameConfigurations=[ apigatewayv2.DomainNameConfiguration( CertificateArn=certificate.ref()) ], ) result.append(domain) result.append( apigatewayv2.ApiMapping( name_to_id(self.name + domain_name + "ApiMapping"), DomainName=domain.ref(), ApiId=self.ref, Stage=self.stage_ref(stage_name), )) result.append( route53.RecordSetType( name_to_id(self.name + domain_name + "DNS"), Name=domain_name, Type="A", HostedZoneId=hosted_zone_id, AliasTarget=route53.AliasTarget( DNSName=GetAtt( name_to_id(self.name + domain_name + "Domain"), "RegionalDomainName", ), HostedZoneId=GetAtt( name_to_id(self.name + domain_name + "Domain"), "RegionalHostedZoneId", ), EvaluateTargetHealth=False, ), )) return result
def __init__(self, title, **kwargs): super().__init__(title, **kwargs) self.Condition = 'RecordSetCloudFront' self.AliasTarget = r53.AliasTarget(DNSName=GetAtt( 'CloudFrontDistribution', 'DomainName'), HostedZoneId=cfg.HostedZoneIdCF) self.Name = Sub('${EnvRole}${RecordSetCloudFrontSuffix}.cdn.%s' % cfg.HostedZoneNameEnv) self.Type = 'A'
def create_dns_records(self, tile_server_lb): self.add_condition('BlueCondition', Equals('Blue', Ref(self.color))) self.add_condition('GreenCondition', Equals('Green', Ref(self.color))) self.add_resource(r53.RecordSetGroup( 'dnsPublicRecordsBlue', Condition='BlueCondition', HostedZoneName=Join('', [Ref(self.public_hosted_zone_name), '.']), RecordSets=[ r53.RecordSet( 'dnsTileServersBlue', AliasTarget=r53.AliasTarget( GetAtt(tile_server_lb, 'CanonicalHostedZoneNameID'), GetAtt(tile_server_lb, 'DNSName'), True ), Name=Join('', ['blue-tiles.', Ref(self.public_hosted_zone_name), '.']), Type='A' ) ] )) self.add_resource(r53.RecordSetGroup( 'dnsPublicRecordsGreen', Condition='GreenCondition', HostedZoneName=Join('', [Ref(self.public_hosted_zone_name), '.']), RecordSets=[ r53.RecordSet( 'dnsTileServersGreen', AliasTarget=r53.AliasTarget( GetAtt(tile_server_lb, 'CanonicalHostedZoneNameID'), GetAtt(tile_server_lb, 'DNSName'), True ), Name=Join('', ['green-tiles.', Ref(self.public_hosted_zone_name), '.']), Type='A' ) ] ))
def add_to_template(self, t): dns_param = ensure_param(t, self.value.output_dns(), 'String') zone_id = "Z2FDTNDATAQYW2" r = route53.RecordSet( '{}CFRecord'.format(self._safe_dns_name(self.name)), Name="{}{}.".format(self.name, self.stack.domain_name), Type="A", AliasTarget=route53.AliasTarget(HostedZoneId=zone_id, DNSName=Ref(dns_param))) return r
def add_route53(self, stack_name, template, provision_refs): template.add_resource( route53.RecordSetType( "NetworkDNSRecord", HostedZoneName="network.shipchain.io.", Comment=f"DNS name for {stack_name} network ALB", Name=f"{stack_name}.network.shipchain.io.", Type="A", AliasTarget=route53.AliasTarget( DNSName=GetAtt(provision_refs.alb, 'DNSName'), HostedZoneId=GetAtt(provision_refs.alb, "CanonicalHostedZoneID"))))
def create_s3_resources(self): s3_bucket = self.add_resource( s3.Bucket('s3TileCacheBucket', BucketName=Join( '.', ['tile-cache', Ref(self.public_hosted_zone_name)]), AccessControl=s3.PublicRead, CorsConfiguration=s3.CorsConfiguration(CorsRules=[ s3.CorsRules( AllowedOrigins=['*'], AllowedMethods=['GET'], MaxAge=3000, AllowedHeaders=['*'], ) ]))) self.add_resource( s3.BucketPolicy( 's3TileCacheBucketPolicy', Bucket=Ref(s3_bucket), PolicyDocument={ 'Statement': [{ 'Action': ['s3:GetObject'], 'Effect': 'Allow', 'Resource': { 'Fn::Join': ['', ['arn:aws:s3:::', Ref(s3_bucket), '/*']] }, 'Principal': '*' }] })) self.add_resource( r53.RecordSetGroup( 'dnsPublicRecordsCache', HostedZoneName=Join('', [Ref(self.public_hosted_zone_name), '.']), RecordSets=[ r53.RecordSet('dnsTileServersCache', AliasTarget=r53.AliasTarget( AMAZON_S3_HOSTED_ZONE_ID, AMAZON_S3_WEBSITE_DOMAIN, True, ), Name=Join('', [ 'tile-cache.', Ref(self.public_hosted_zone_name), '.' ]), Type='A') ]))
def add_to_template(self, t): """ """ zone_param = ensure_param(t, self.value.output_hosted_zone(), 'String') dns_param = ensure_param(t, self.value.output_dns_name(), 'String') r = route53.RecordSet( '{}ELBRecord'.format(self._safe_dns_name(self.name)), Name="{}{}.".format(self.name, self.stack.domain_name), Type="A", AliasTarget=route53.AliasTarget(HostedZoneId=Ref(zone_param), DNSName=Ref(dns_param))) return r
def alias2resource(record, zone_name): name = get_resource_name(record, suffix='ALIAS') resource = route53.RecordSetType(name) resource.HostedZoneName = zone_name.ref() resource.Name = record['Name'] resource.Type = record['Type'] alias_target = route53.AliasTarget() alias_target.HostedZoneId = record['AliasTarget']['HostedZoneId'] alias_target.DNSName = record['AliasTarget']['DNSName'] alias_target.EvaluateTargetHealth = record['AliasTarget'][ 'EvaluateTargetHealth'] resource.AliasTarget = alias_target return resource
def internal_dns_elb(context): # The DNS name of an existing Amazon Route 53 hosted zone hostedzone = context['int_domain'] + "." # TRAILING DOT IS IMPORTANT! dns_record = route53.RecordSetType(R53_INT_TITLE, HostedZoneName=hostedzone, Comment="Internal DNS record for ELB", Name=context['int_full_hostname'], Type="A", AliasTarget=route53.AliasTarget( GetAtt(ELB_TITLE, "CanonicalHostedZoneNameID"), GetAtt(ELB_TITLE, "DNSName"))) return dns_record
def external_dns_elb(context): # http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html # The DNS name of an existing Amazon Route 53 hosted zone hostedzone = context['domain'] + "." # TRAILING DOT IS IMPORTANT! dns_record = route53.RecordSetType(R53_EXT_TITLE, HostedZoneName=hostedzone, Comment="External DNS record for ELB", Name=context['full_hostname'], Type="A", AliasTarget=route53.AliasTarget( GetAtt(ELB_TITLE, "CanonicalHostedZoneNameID"), GetAtt(ELB_TITLE, "DNSName"))) return dns_record
def add_api(self): api = self.add_resource( apigatewayv2.Api( 'HttpApi', Name=StackName, Description=Join(' ', [Ref(self.domain), 'Terraform Registry']), ProtocolType='HTTP', Target=Ref(self._lambda_function), )) self.add_resource( awslambda.Permission( f'ApigatewayPermission', Principal='apigateway.amazonaws.com', Action='lambda:InvokeFunction', FunctionName=Ref(self._lambda_function), SourceArn=Join('', [ 'arn:aws:execute-api:', Region, ':', AccountId, ':', Ref(api), '/*' ]))) domain = self.add_resource( apigatewayv2.DomainName('HttpApiDomain', DomainName=Ref(self.domain), DomainNameConfigurations=[ apigatewayv2.DomainNameConfiguration( CertificateArn=Ref( self.certificate), ) ])) mapping = self.add_resource( apigatewayv2.ApiMapping('Mapping', DomainName=Ref(domain), ApiId=Ref(api), Stage='$default')) dns_record = self.add_resource( route53.RecordSetGroup( 'ApiDnsRecord', HostedZoneId=Ref(self.hosted_zone), RecordSets=[ route53.RecordSet( Name=Ref(self.domain), AliasTarget=route53.AliasTarget( DNSName=GetAtt(domain, 'RegionalDomainName'), HostedZoneId=GetAtt(domain, 'RegionalHostedZoneId')), Type='A') ]))
def add_dns_alias(self, name, dns_name, zone_id, zone_name): """ Helper to attach an alias dns entry to an elb @param dns_name [string] name of the domain @param zone_id [string] hostzone id for the target @param zone_name [string] hostzone name """ return self.add_resource( route53.RecordSetGroup( name.replace(".", "") + "AliasRecordSetGroup" + zone_name.replace('.', ''), HostedZoneName=zone_name, RecordSets=[ route53.RecordSet(Name=name, Type='A', AliasTarget=route53.AliasTarget( zone_id, dns_name)) ]))
def route53_record_set(self): t = self.template t.add_resource( route53.RecordSetGroup( "RecordSetGroup", HostedZoneName=self.vars["HostedZone"] + ".", RecordSets=[ route53.RecordSet( Name=self.vars['ServiceFqdn'] + ".", Type="A", AliasTarget=route53.AliasTarget( HostedZoneId=get_elb_hosted_zone_id( self.vars['LoadBalancerArn']), DNSName=self.vars['LoadBalancerUrl'], ), ) ], )) return
def external_dns_cloudfront(context): # http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html dns_records = [] i = 1 for cdn_hostname in context['cloudfront']['subdomains']: if _is_domain_2nd_level(cdn_hostname): hostedzone = cdn_hostname + "." else: hostedzone = context['domain'] + "." dns_records.append( route53.RecordSetType( R53_CDN_TITLE % i, HostedZoneName=hostedzone, Comment="External DNS record for Cloudfront distribution", Name=cdn_hostname + ".", Type="A", AliasTarget=route53.AliasTarget( CLOUDFRONT_HOSTED_ZONE_ID, GetAtt(CLOUDFRONT_TITLE, "DomainName")))) i = i + 1 return dns_records
def add_elb_dns_alias(self, elb, name, zone_name): """ Helper to attach an alias dns entry to an elb @param elb [ELB] target elb @param name [string] name of the domain @param zone_name [string] hostzone name """ if name: name = name.lower() + '.' + zone_name else: name = zone_name.lower() return self.add_resource( route53.RecordSetGroup( name.replace('.', '') + "ELBRecordSetGroup" + zone_name.replace('.', ''), HostedZoneName=zone_name.lower(), RecordSets=[ route53.RecordSet(Name=name, Type='A', AliasTarget=route53.AliasTarget( GetAtt(elb, "CanonicalHostedZoneID"), GetAtt(elb, "DNSName"))) ]))
Actions=[ elb.Action(Type="forward", TargetGroupArn=Ref("{}{}TargetGroup".format( e, s))) ], Priority=priority)) t.add_resource( route53.RecordSetType("{}{}DNSRecord".format(e, s), HostedZoneName=Join("", [domain, "."]), Name=Join("", [s, ".", URLPathMod, domain, "."]), Type="A", AliasTarget=route53.AliasTarget( FindInMap("RegionZIDMap", Ref("AWS::Region"), "ZoneID"), GetAtt("{}LoadBalancer".format(e), "DNSName")))) # Outputs for s in services: t.add_output( Output("{}{}TargetGroup".format(e, s), Description="Target group for {} {}".format(e, s), Value=Ref("{}{}TargetGroup".format(e, s)), Export=Export(Sub("{}-{}-tg".format(e, s))))) t.add_output( Output("{}{}URL".format(e, s), Description="Loadbalancer URL for {} in {}".format(s, e),
def add_api(self): rest_api = self.add_resource( apigateway.RestApi( 'Api', Description=Join(' ', [Ref(self.domain), 'Terraform Registry']), Name=StackName)) methods = self.add_registry_api(rest_api) methods += [self.add_service_discovery_api(rest_api)] self.add_resource( awslambda.Permission( f'ApigatewayPermission', Principal='apigateway.amazonaws.com', Action='lambda:InvokeFunction', FunctionName=Ref(self._lambda_function), SourceArn=Join('', [ 'arn:aws:execute-api:', Region, ':', AccountId, ':', Ref(rest_api), '/*' ]))) deployment_id = 'ApiDeployment' + ''.join( random.choice(string.ascii_letters) for _ in range(5)) deployment = self.add_resource( apigateway.Deployment(deployment_id, Description=self._build_version, RestApiId=Ref(rest_api), DependsOn=methods, DeletionPolicy=Retain)) stage = self.add_resource( apigateway.Stage('ApiStage', MethodSettings=[ apigateway.MethodSetting( HttpMethod='*', LoggingLevel='INFO', MetricsEnabled=True, ResourcePath='/*', DataTraceEnabled=True, ) ], TracingEnabled=True, StageName='prd', RestApiId=Ref(rest_api), DeploymentId=Ref(deployment), DependsOn=[deployment])) domain = self.add_resource( apigateway.DomainName( 'ApiDomain', DomainName=Ref(self.domain), CertificateArn=Ref(self.certificate), EndpointConfiguration=apigateway.EndpointConfiguration( Types=['EDGE']))) mapping = self.add_resource( apigateway.BasePathMapping('Mapping', DomainName=Ref(domain), RestApiId=Ref(rest_api), Stage='prd', DependsOn=['ApiStage'])) dns_record = self.add_resource( route53.RecordSetGroup('ApiDnsRecord', HostedZoneId=Ref(self.hosted_zone), RecordSets=[ route53.RecordSet( Name=Ref(self.domain), AliasTarget=route53.AliasTarget( DNSName=GetAtt( domain, 'DistributionDomainName'), HostedZoneId='Z2FDTNDATAQYW2'), Type='A') ]))
HostedZoneConfig=route53.HostedZoneConfiguration( Comment=f'{CONFIG["STACK_NAME"]} stack HostedZone'), )) t.add_resource( route53.RecordSetGroup( 'HostedZoneRecordSetGroup', HostedZoneId=Ref(HostedZone), RecordSets=[ route53.RecordSet( 'HostedZoneAliasToCloudFront', Name=f'{CONFIG["DOMAIN_NAME"]}.', Type='A', AliasTarget=route53.AliasTarget( HostedZoneId= 'Z2FDTNDATAQYW2', # CloudFront HostedZoneId magic string DNSName=GetAtt(CloudfrontDistribution, 'DomainName'), )), route53.RecordSet( 'HostedZoneAliasToCloudFrontIpv6', Name=f'{CONFIG["DOMAIN_NAME"]}.', Type='AAAA', AliasTarget=route53.AliasTarget( HostedZoneId= 'Z2FDTNDATAQYW2', # CloudFront HostedZoneId magic string DNSName=GetAtt(CloudfrontDistribution, 'DomainName'), )) ])) if __name__ == '__main__':
AcmCertificateArn=Ref(acm_cert), SslSupportMethod='sni-only', ), ), Tags=GetAtt(cloudformation_tags, 'TagList'), )) hosted_zone_map = "HostedZoneMap" template.add_mapping(hosted_zone_map, cfnutils.mappings.r53_hosted_zone_id()) template.add_resource( route53.RecordSetType( "DomainA", AliasTarget=route53.AliasTarget( DNSName=GetAtt(example_distribution, 'DomainName'), HostedZoneId=FindInMap(hosted_zone_map, Ref(AWS_REGION), 'CloudFront'), ), Comment=Sub('DNS for ${AWS::StackName}'), HostedZoneName=Join('', [Ref(param_hosted_zone_name), '.']), Name=domain_name, Type='A', )) template.add_resource( route53.RecordSetType( "DomainAAAA", AliasTarget=route53.AliasTarget( DNSName=GetAtt(example_distribution, 'DomainName'), HostedZoneId=FindInMap(hosted_zone_map, Ref(AWS_REGION), 'CloudFront'), ),
DistributionConfig=cloudfront.DistributionConfig( Aliases=[Ref(domain)], Origins=[ cloudfront.Origin(Id=Ref(root_bucket), DomainName=GetAtt(root_bucket, 'DomainName'), S3OriginConfig=cloudfront.S3Origin()) ], DefaultCacheBehavior=cloudfront.DefaultCacheBehavior( Compress=True, ForwardedValues=cloudfront.ForwardedValues(QueryString=False), TargetOriginId=Ref(root_bucket), ViewerProtocolPolicy='redirect-to-https'), DefaultRootObject=Ref(index_page), Enabled=True))) hosted_zone = Join('', [Ref(zone), '.']) template.add_resource( route53.RecordSetGroup('WebsiteDNSRecord', HostedZoneName=hosted_zone, Comment='Records for the root of the hosted zone', RecordSets=[ route53.RecordSet( Name=Ref(domain), Type='A', AliasTarget=route53.AliasTarget( CLOUDFRONT_HOSTED_ZONE_ID, GetAtt(cdn, 'DomainName'))) ])) print(template.to_json())
def set_up_stack(self): """Sets up the stack""" if not self.INPUTS or not self.STACK_NAME_PREFIX or not self.HEALTH_ENDPOINT: raise MKInputError( 'Must define INPUTS, STACK_NAME_PREFIX, and HEALTH_ENDPOINT') super(AppServerStack, self).set_up_stack() tags = self.get_input('Tags').copy() self.add_description('{} App Server Stack for Cac'.format( self.STACK_NAME_PREFIX)) assert isinstance(tags, dict), 'tags must be a dictionary' self.availability_zones = get_availability_zones() tags.update({'StackType': 'AppServer'}) self.default_tags = tags self.app_server_instance_type_parameter = self.add_parameter( Parameter( 'AppServerInstanceType', Type='String', Default='t2.medium', Description='NAT EC2 instance type', AllowedValues=EC2_INSTANCE_TYPES, ConstraintDescription='must be a valid EC2 instance type.'), source='AppServerInstanceType') self.param_app_server_iam_profile = self.add_parameter( Parameter('AppServerIAMProfile', Type='String', Description='IAM Profile for instances'), source='AppServerIAMProfile') self.app_server_ami = self.add_parameter(Parameter( 'AppServerAMI', Type='String', Description='{} Server EC2 AMI'.format(self.STACK_NAME_PREFIX)), source='AppServerAMI') self.keyname_parameter = self.add_parameter(Parameter( 'KeyName', Type='String', Default='cac', Description='Name of an existing EC2 key pair'), source='KeyName') self.param_color = self.add_parameter(Parameter( 'StackColor', Type='String', Description='Stack color', AllowedValues=['Blue', 'Green', 'Orange']), source='StackColor') self.param_stacktype = self.add_parameter(Parameter( 'StackType', Type='String', Description='Stack type', AllowedValues=['Development', 'Staging', 'Production']), source='StackType') self.param_public_hosted_zone_name = self.add_parameter( Parameter('PublicHostedZoneName', Type='String', Description='Public hosted zone name'), source='PublicHostedZoneName') self.param_vpc = self.add_parameter(Parameter( 'VpcId', Type='String', Description='Name of an existing VPC'), source='VpcId') self.param_notification_arn = self.add_parameter( Parameter( 'GlobalNotificationsARN', Type='String', Description='Physical resource ID on an AWS::SNS::Topic for ' 'notifications'), source='GlobalNotificationsARN') self.param_ssl_certificate_arn = self.add_parameter( Parameter('SSLCertificateARN', Type='String', Description= 'Physical resource ID on an AWS::IAM::ServerCertificate ' 'for the application server load balancer'), source='SSLCertificateARN') self.param_public_subnets = self.add_parameter( Parameter('PublicSubnets', Type='CommaDelimitedList', Description='A list of public subnets'), source='AppServerPublicSubnets') self.param_private_subnets = self.add_parameter( Parameter('PrivateSubnets', Type='CommaDelimitedList', Description='A list of private subnets'), source='AppServerPrivateSubnets') self.param_bastion_security_group = self.add_parameter( Parameter('BastionSecurityGroup', Type='String', Description='The ID of the bastion security group'), source='BastionSecurityGroup') self.param_database_security_group = self.add_parameter( Parameter('DatabaseSecurityGroup', Type='String', Description='The ID of the database security group'), source='DatabaseSecurityGroup') self.param_nat_security_group = self.add_parameter( Parameter('NATSecurityGroup', Type='String', Description='The ID of the NAT security group'), source='NATSecurityGroup') self.param_min_size = self.add_parameter(Parameter( 'ASGMinSize', Type='Number', Default='1', Description='Min size of ASG'), source='ASGMinSize') self.param_max_size = self.add_parameter(Parameter( 'ASGMaxSize', Type='Number', Default='1', Description='Max size of ASG'), source='ASGMaxSize') self.param_desired_capacity = self.add_parameter( Parameter('ASGDesiredCapacity', Type='Number', Default='1', Description='Desired capacity of ASG'), source='ASGDesiredCapacity') # # Security Group # app_server_load_balancer_security_group = self.add_resource( ec2.SecurityGroup( 'sgAppServerLoadBalancer', GroupDescription= 'Enables access to app servers via a load balancer', VpcId=Ref(self.param_vpc), SecurityGroupIngress=[ ec2.SecurityGroupRule(IpProtocol='tcp', CidrIp=ALLOW_ALL_CIDR, FromPort=p, ToPort=p) for p in [80, 443] ], Tags=Tags(Name='sgAppServerLoadBalancer', Color=Ref(self.param_color)))) app_server_security_group = self.add_resource( ec2.SecurityGroup( 'sgAppServer', GroupDescription='Enables access to App Servers', VpcId=Ref(self.param_vpc), SecurityGroupIngress=[ ec2.SecurityGroupRule(IpProtocol='tcp', CidrIp=VPC_CIDR, FromPort=p, ToPort=p) for p in [22, 80, 443] ] + [ ec2.SecurityGroupRule(IpProtocol='tcp', SourceSecurityGroupId=Ref(sg), FromPort=80, ToPort=80) for sg in [app_server_load_balancer_security_group] ] + [ ec2.SecurityGroupRule(IpProtocol='tcp', SourceSecurityGroupId=Ref(sg), FromPort=443, ToPort=443) for sg in [app_server_load_balancer_security_group] ], SecurityGroupEgress=[ ec2.SecurityGroupRule(IpProtocol='tcp', CidrIp=ALLOW_ALL_CIDR, FromPort=p, ToPort=p) for p in [80, 443, PAPERTRAIL_PORT] ], Tags=Tags(Name='sgAppServer', Color=Ref(self.param_color)))) # ELB to App Server self.add_resource( ec2.SecurityGroupEgress( 'sgEgressELBtoAppHTTP', GroupId=Ref(app_server_load_balancer_security_group), DestinationSecurityGroupId=Ref(app_server_security_group), IpProtocol='tcp', FromPort=80, ToPort=80)) self.add_resource( ec2.SecurityGroupEgress( 'sgEgressELBtoAppHTTPS', GroupId=Ref(app_server_load_balancer_security_group), DestinationSecurityGroupId=Ref(app_server_security_group), IpProtocol='tcp', FromPort=443, ToPort=443)) # Bastion to App Server, app server to db, app server to inet rules = [(self.param_bastion_security_group, app_server_security_group, [80, 443, 22]), (app_server_security_group, self.param_database_security_group, [POSTGRES]), (app_server_security_group, self.param_nat_security_group, [80, 443, 22, 587, PAPERTRAIL_PORT])] for num, (srcsg, destsg, ports) in enumerate(rules): for port in ports: self.add_resource( ec2.SecurityGroupEgress( 'sgEgress{}p{}'.format(num, port), GroupId=Ref(srcsg), DestinationSecurityGroupId=Ref(destsg), IpProtocol='tcp', FromPort=port, ToPort=port)) self.add_resource( ec2.SecurityGroupIngress('sgIngress{}p{}'.format( num, port), GroupId=Ref(destsg), SourceSecurityGroupId=Ref(srcsg), IpProtocol='tcp', FromPort=port, ToPort=port)) # # ELB # app_server_load_balancer = self.add_resource( elb.LoadBalancer( 'elbAppServer', ConnectionDrainingPolicy=elb.ConnectionDrainingPolicy( Enabled=True, Timeout=300), CrossZone=True, SecurityGroups=[Ref(app_server_load_balancer_security_group)], Listeners=[ elb.Listener(LoadBalancerPort='80', Protocol='HTTP', InstancePort='80', InstanceProtocol='HTTP'), elb.Listener(LoadBalancerPort='443', Protocol='HTTPS', InstancePort='443', InstanceProtocol='HTTP', SSLCertificateId=Ref( self.param_ssl_certificate_arn)) ], HealthCheck=elb.HealthCheck( Target=self.HEALTH_ENDPOINT, HealthyThreshold='3', UnhealthyThreshold='2', Interval='30', Timeout='5', ), Subnets=Ref(self.param_public_subnets), Tags=Tags(Name='elbAppServer', Color=Ref(self.param_color)))) self.add_resource( cw.Alarm('alarmAppServerBackend4xx', AlarmActions=[Ref(self.param_notification_arn)], Statistic='Sum', Period=300, Threshold='5', EvaluationPeriods=1, ComparisonOperator='GreaterThanThreshold', MetricName='HTTPCode_Backend_4XX', Namespace='AWS/ELB', Dimensions=[ cw.MetricDimension( 'metricLoadBalancerName', Name='LoadBalancerName', Value=Ref(app_server_load_balancer)) ])) self.add_resource( cw.Alarm('alarmAppServerBackend5xx', AlarmActions=[Ref(self.param_notification_arn)], Statistic='Sum', Period=60, Threshold='0', EvaluationPeriods=1, ComparisonOperator='GreaterThanThreshold', MetricName='HTTPCode_Backend_5XX', Namespace='AWS/ELB', Dimensions=[ cw.MetricDimension( 'metricLoadBalancerName', Name='LoadBalancerName', Value=Ref(app_server_load_balancer)) ])) # # ASG # app_server_launch_config = self.add_resource( asg.LaunchConfiguration( 'lcAppServer', ImageId=Ref(self.app_server_ami), IamInstanceProfile=Ref(self.param_app_server_iam_profile), InstanceType=Ref(self.app_server_instance_type_parameter), KeyName=Ref(self.keyname_parameter), SecurityGroups=[Ref(app_server_security_group)])) autoscaling_group = self.add_resource( asg.AutoScalingGroup( 'asgAppServer', AvailabilityZones=self.get_input( 'AppServerAvailabilityZones').split(','), Cooldown=300, DesiredCapacity=Ref(self.param_desired_capacity), HealthCheckGracePeriod=600, HealthCheckType='ELB', LaunchConfigurationName=Ref(app_server_launch_config), LoadBalancerNames=[Ref(app_server_load_balancer)], MaxSize=Ref(self.param_max_size), MinSize=Ref(self.param_min_size), NotificationConfiguration=asg.NotificationConfiguration( TopicARN=Ref(self.param_notification_arn), NotificationTypes=[ asg.EC2_INSTANCE_LAUNCH, asg.EC2_INSTANCE_LAUNCH_ERROR, asg.EC2_INSTANCE_TERMINATE, asg.EC2_INSTANCE_TERMINATE_ERROR ]), VPCZoneIdentifier=Ref(self.param_private_subnets), Tags=[ asg.Tag('Name', '{}Server'.format(self.STACK_NAME_PREFIX), True), asg.Tag('Color', Ref(self.param_color), True) ])) # autoscaling policies autoscaling_policy_add = self.add_resource( asg.ScalingPolicy('scalingPolicyAddAppServer', AdjustmentType='ChangeInCapacity', AutoScalingGroupName=Ref(autoscaling_group), Cooldown=600, ScalingAdjustment='1')) autoscaling_policy_remove = self.add_resource( asg.ScalingPolicy('scalingPolicyRemoveAppServer', AdjustmentType='ChangeInCapacity', AutoScalingGroupName=Ref(autoscaling_group), Cooldown=600, ScalingAdjustment='-1')) if self.STACK_NAME_PREFIX == 'Otp': # trigger scale down if CPU avg usage < 10% for 3 consecutive 5 min periods self.add_resource( cw.Alarm('alarmAppServerLowCPU', AlarmActions=[Ref(autoscaling_policy_remove)], Statistic='Average', Period=300, Threshold='10', EvaluationPeriods=3, ComparisonOperator='LessThanThreshold', MetricName='CPUUtilization', Namespace='AWS/EC2', Dimensions=[ cw.MetricDimension('metricAutoScalingGroupName', Name='AutoScalingGroupName', Value=Ref(autoscaling_group)) ])) # trigger scale up if CPU avg usage >= 30% for a 5 min period self.add_resource( cw.Alarm('alarmAppServerHighCPU', AlarmActions=[ Ref(self.param_notification_arn), Ref(autoscaling_policy_add) ], Statistic='Average', Period=300, Threshold='30', EvaluationPeriods=1, ComparisonOperator='GreaterThanOrEqualToThreshold', MetricName='CPUUtilization', Namespace='AWS/EC2', Dimensions=[ cw.MetricDimension('metricAutoScalingGroupName', Name='AutoScalingGroupName', Value=Ref(autoscaling_group)) ])) else: # scale web servers based on network usage self.add_resource( cw.Alarm('alarmAppServerLowNetworkUsage', AlarmActions=[Ref(autoscaling_policy_remove)], Statistic='Average', Period=300, Threshold='500000', EvaluationPeriods=3, ComparisonOperator='LessThanThreshold', MetricName='NetworkOut', Namespace='AWS/EC2', Dimensions=[ cw.MetricDimension('metricAutoScalingGroupName', Name='AutoScalingGroupName', Value=Ref(autoscaling_group)) ])) self.add_resource( cw.Alarm('alarmAppServerHighNetworkUsage', AlarmActions=[ Ref(self.param_notification_arn), Ref(autoscaling_policy_add) ], Statistic='Average', Period=300, Threshold='10000000', EvaluationPeriods=1, ComparisonOperator='GreaterThanOrEqualToThreshold', MetricName='NetworkOut', Namespace='AWS/EC2', Dimensions=[ cw.MetricDimension('metricAutoScalingGroupName', Name='AutoScalingGroupName', Value=Ref(autoscaling_group)) ])) # # DNS name # self.create_resource( route53.RecordSetType( 'dnsName', Name=Join('.', [ Ref(self.param_color), Ref(self.param_stacktype), self.STACK_NAME_PREFIX, Ref(self.param_public_hosted_zone_name) ]), Type='A', AliasTarget=route53.AliasTarget( GetAtt(app_server_load_balancer, 'CanonicalHostedZoneNameID'), GetAtt(app_server_load_balancer, 'DNSName')), HostedZoneName=Ref(self.param_public_hosted_zone_name))) self.add_output([ Output('{}ServerLoadBalancerEndpoint'.format( self.STACK_NAME_PREFIX), Description='Application server endpoint', Value=GetAtt(app_server_load_balancer, 'DNSName')), Output('{}ServerLoadBalancerHostedZoneNameID'.format( self.STACK_NAME_PREFIX), Description='ID of canonical hosted zone name for ELB', Value=GetAtt(app_server_load_balancer, 'CanonicalHostedZoneNameID')) ])
], PriceClass='PriceClass_100', ViewerCertificate=cloudfront.ViewerCertificate( AcmCertificateArn=Ref(certificate), SslSupportMethod='sni-only', )))) record_set_group = template.add_resource( route53.RecordSetGroup( 'RecordSetGroup', HostedZoneId=Ref(hosted_zone_id), RecordSets=[ route53.RecordSet(Name=Ref(domain), Type='A', AliasTarget=route53.AliasTarget( HostedZoneId=CLOUDFRONT_HOSTED_ZONE_ID, DNSName=GetAtt(distribution, 'DomainName'), )), ])) # endregion # region Outputs template.add_output(Output('Distribution', Value=Ref(distribution))) # endregion # region Metadata template.add_metadata({ 'AWS::CloudFormation::Interface': { 'ParameterLabels': { # Project email.title: { 'default': 'Notifications'