-
-
Save filipeandre/a673a8ac9e669907980f402136d3bcb8 to your computer and use it in GitHub Desktop.
Creates an Amazon Route 53 hosted zone and a certificate *.hz that is automaticaly validated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: '2010-09-09' | |
Description: > | |
This CloudFormation template validates ACM certificate using AWS Route53 DNS | |
service. | |
Parameters: | |
HostedZoneName: | |
Type: String | |
Description: The DNS name of an Amazon Route 53 hosted zone e.g. enterprise.filipeandre.com | |
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-) | |
ConstraintDescription: must be a valid DNS zone name. | |
Resources: | |
PortalACMCertificate: | |
Type: AWS::CertificateManager::Certificate | |
Properties: | |
DomainName: !Sub | |
- "portal.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
SubjectAlternativeNames: | |
- !Sub | |
- "www.portal.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
DomainValidationOptions: | |
- DomainName: !Sub | |
- "portal.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
- DomainName: !Sub | |
- "www.portal.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
ValidationMethod: DNS | |
AppACMCertificate: | |
Type: AWS::CertificateManager::Certificate | |
Properties: | |
DomainName: !Sub | |
- "app.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
SubjectAlternativeNames: | |
- !Sub | |
- "www.app.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
DomainValidationOptions: | |
- DomainName: !Sub | |
- "app.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
- DomainName: !Sub | |
- "www.app.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
ValidationMethod: DNS | |
RootACMCertificate: | |
Type: AWS::CertificateManager::Certificate | |
Properties: | |
DomainName: !Ref HostedZoneName | |
SubjectAlternativeNames: | |
- !Sub | |
- "*.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
DomainValidationOptions: | |
- DomainName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
- DomainName: !Sub | |
- "*.${HostedZoneName}" | |
- HostedZoneName: !Ref HostedZoneName | |
ValidationDomain: !Ref HostedZoneName | |
ValidationMethod: DNS | |
ACMLambdaFunctionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: | |
- lambda.amazonaws.com | |
Action: | |
- sts:AssumeRole | |
Path: "/" | |
Policies: | |
- PolicyName: root | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- logs:CreateLogGroup | |
- logs:CreateLogStream | |
- logs:PutLogEvents | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- route53:ChangeResourceRecordSets | |
- route53:ListHostedZonesByName | |
- cloudformation:DescribeStackEvents | |
Resource: '*' | |
ACMLambdaFunction: | |
Type: AWS::Lambda::Function | |
Properties: | |
Runtime: python3.9 | |
Timeout: '300' | |
Handler: index.handler | |
Role: !GetAtt ACMLambdaFunctionRole.Arn | |
Code: | |
ZipFile: | | |
#!/usr/bin/env python3 | |
import cfnresponse | |
import boto3 | |
import logging | |
import traceback | |
CFN_CLIENT = boto3.client('cloudformation') | |
ROUTE53_CLIENT = boto3.client('route53') | |
LOGGER = logging.getLogger() | |
LOGGER.setLevel(logging.INFO) | |
def get_route53_record_from_stack_events(stack_name): | |
status_reason_text = '' | |
params = {'StackName': stack_name} | |
while True: | |
cfn_response = CFN_CLIENT.describe_stack_events(**params) | |
LOGGER.info('Stack events: %s', cfn_response) | |
for event in cfn_response['StackEvents']: | |
if ( | |
event['ResourceType'] == 'AWS::CertificateManager::Certificate' and | |
event['ResourceStatus'] == 'CREATE_IN_PROGRESS' and | |
'ResourceStatusReason' in event and | |
'Content of DNS Record' in event['ResourceStatusReason'] | |
): | |
status_reason_text = event['ResourceStatusReason'] | |
if 'NextToken' in cfn_response: | |
params['NextToken'] = cfn_response['NextToken'] | |
if status_reason_text != '': | |
break | |
_dns_request_text=status_reason_text[status_reason_text.find("{")+1:status_reason_text.find("}")] | |
_name_text = _dns_request_text.split(',')[0] | |
_type_text = _dns_request_text.split(',')[1] | |
_value_text = _dns_request_text.split(',')[2] | |
return { | |
'Name': _name_text.split(': ')[1], | |
'Type': _type_text.split(': ')[1], | |
'Value': _value_text.split(': ')[1] | |
} | |
def handler(event, context): | |
try: | |
LOGGER.info('Event structure: %s', event) | |
if event['RequestType'] == 'Create': | |
stack_name = event['ResourceProperties']['StackName'] | |
hosted_zone_name = event['ResourceProperties']['Route53HostedZoneName'] | |
route53_record = get_route53_record_from_stack_events(stack_name) | |
LOGGER.info('Route 53 record: %s', route53_record) | |
route53_response = ROUTE53_CLIENT.list_hosted_zones_by_name(DNSName=hosted_zone_name) | |
hosted_zone_id = route53_response['HostedZones'][0]['Id'] | |
route53_request_params = { | |
'HostedZoneId': hosted_zone_id, | |
'ChangeBatch': { | |
'Changes': [ | |
{ | |
'Action': 'UPSERT', | |
'ResourceRecordSet': { | |
'Name': route53_record['Name'], | |
'Type': route53_record['Type'], | |
'TTL': 60, | |
'ResourceRecords': [ | |
{ | |
'Value': route53_record['Value'] | |
} | |
] | |
} | |
} | |
] | |
} | |
} | |
LOGGER.info('Route 53 request params: %s', route53_request_params) | |
ROUTE53_CLIENT.change_resource_record_sets(**route53_request_params) | |
except Exception as e: | |
LOGGER.error(e) | |
traceback.print_exc() | |
finally: | |
cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) | |
ACMCertificateValidationResource: | |
Type: Custom::ACMCertificateValidation | |
Properties: | |
ServiceToken: !GetAtt ACMLambdaFunction.Arn | |
Route53HostedZoneName: !Ref HostedZoneName | |
StackName: !Ref 'AWS::StackName' | |
Outputs: | |
PortalCertificateArn: | |
Value: !Ref PortalACMCertificate | |
Description: 'The portal certificate arn' | |
AppCertificateArn: | |
Value: !Ref AppACMCertificate | |
Description: 'The app certificate arn' | |
RootCertificateArn: | |
Value: !Ref RootACMCertificate | |
Description: 'The root certificate arn' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: '2010-09-09' | |
Description: Creates an Amazon Route 53 hosted zone | |
Parameters: | |
DomainName: | |
Type: String | |
Description: The DNS name of an Amazon Route 53 hosted zone e.g. enterprise.filipeandre.com | |
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-) | |
ConstraintDescription: must be a valid DNS zone name. | |
Resources: | |
DNS: | |
Type: AWS::Route53::HostedZone | |
Properties: | |
HostedZoneConfig: | |
Comment: !Join ['', ['Hosted zone for ', !Ref 'DomainName']] | |
Name: !Ref 'DomainName' | |
DeleteHostedZoneRecordsLambdaFunction: | |
Type: 'AWS::Lambda::Function' | |
Properties: | |
Code: | |
ZipFile: | | |
import traceback | |
import boto3 | |
import logging | |
import cfnresponse | |
LOGGER = logging.getLogger() | |
LOGGER.setLevel(logging.INFO) | |
ROUTE53_CLIENT = boto3.client('route53') | |
def lambda_handler(event, context): | |
hosted_zone_id = event['ResourceProperties']['HostedZoneId'] | |
try: | |
LOGGER.info('Event structure: %s', event) | |
if event['RequestType'] == 'Delete': | |
response = ROUTE53_CLIENT.list_resource_record_sets(HostedZoneId=hosted_zone_id) | |
changes = [] | |
for record_set in response['ResourceRecordSets']: | |
if record_set['Type'] != 'NS' and record_set['Type'] != 'SOA': | |
changes.append({ | |
'Action': 'DELETE', | |
'ResourceRecordSet': record_set | |
}) | |
if changes: | |
ROUTE53_CLIENT.change_resource_record_sets(HostedZoneId=hosted_zone_id, ChangeBatch={'Changes': changes}) | |
LOGGER.info(f'Deleted all records in Hosted Zone {hosted_zone_id}') | |
except Exception as e: | |
LOGGER.error(e) | |
traceback.print_exc() | |
finally: | |
cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, hosted_zone_id) | |
Handler: index.lambda_handler | |
Runtime: python3.9 | |
Timeout: 30 | |
Role: !GetAtt LambdaExecutionRole.Arn | |
LambdaExecutionRole: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: lambda.amazonaws.com | |
Action: 'sts:AssumeRole' | |
Path: / | |
Policies: | |
- PolicyName: 'lambda-execution-policy' | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'logs:CreateLogGroup' | |
- 'logs:CreateLogStream' | |
- 'logs:PutLogEvents' | |
Resource: 'arn:aws:logs:*:*:*' | |
- Effect: Allow | |
Action: | |
- 'route53:ListResourceRecordSets' | |
- 'route53:ChangeResourceRecordSets' | |
- 'route53:DeleteHostedZone' | |
Resource: '*' | |
DeleteHostedZoneRecordsCustomResource: | |
Type: 'Custom::DeleteHostedZoneRecords' | |
Properties: | |
ServiceToken: !GetAtt DeleteHostedZoneRecordsLambdaFunction.Arn | |
HostedZoneId: !Ref DNS | |
Outputs: | |
NS: | |
Description: NameServers | |
Value: !Join [',', !GetAtt DNS.NameServers] | |
HostedZoneName: | |
Description: 'The fully qualified domain name' | |
Value: !Ref DomainName | |
Export: | |
Name: HostedZoneName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment