11. Cloud Resume Challenge: Configuring Amazon Certificate Manager (ACM) Certificate and Route53 Entries

Michael MaratitaMichael Maratita
13 min read

In this final module, you will create an ACM certificate and Route53 entries for CloudFront and API Gateway using your custom domain name. This will present a more profession website that can be shared with others.


Terraform ACM Reference

Terraform API Gateway Domain Name Reference

Terraform Route53 Record Reference


Create a new branch in GitHub

  • Login to GitHub.

  • Select the Issues tab > Select New Issue.

  • Add the a title, e.g. Create ACM Certificate and Route53 Entries

  • Select Submit new issue.

  • On the right pane, under Development, Select Create a branch.

  • Leave the defaults > Select Create branch.

  • Open your IDE Terminal.

  • Input the following:

git fetch origin
git checkout YOUR_BRANCH_NAME

Modifying acm files

In this section, you will configure the creation for your Amazon Certificate Manager Certificate. Locate your acm files in ./infra/modules/aws/acm.

main.tf

  • Input the following to create the ACM certificate

    ```plaintext provider "aws" { alias = "us-east-1" region = "us-east-1" }

    resource "aws_acm_certificate" "cert" { provider = aws.us-east-1 domain_name = var.domain_name subject_alternative_names = var.subject_alternative_names validation_method = var.validation_method

    tags = var.tags

    lifecycle { create_before_destroy = true } }

resource "aws_acm_certificate_validation" "cert" { provider = aws.us-east-1 certificate_arn = aws_acm_certificate.cert.arn validation_record_fqdns = var.validation_record }


    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707846091712/4c19ac13-d465-427a-8d15-699468713c0b.png align="center")

    > The requirement for CloudFront to utilize the ACM certificate, it has to be created in the us-east-1 region. The provider block creates an alias for that region so that it can be utilized for CloudFront. The ACM Certificate Validation block automates the DNS validation for the certificate in Route53.

* Save the file


### variables.tf

* Input the following to define the input variables for the ACM module:

    ```plaintext
    variable "domain_name" {
      description = "The name of the custom domain associated with Route53 and CloudFront"
      type = string
    }

    variable "subject_alternative_names" {
      description = "(Optional) Set of domains that should be SANs in the issued certificate. To remove all elements of a previously configured list, set this value equal to an empty list ([]) or use the terraform taint command to trigger recreation."
      type = list(string)
      default = [ ]
    }

    variable "validation_method" {
      description = "(Optional) Which method to use for validation. DNS or EMAIL are valid. This parameter must not be set for certificates that were imported into ACM and then into Terraform."
      type = string
      default = "DNS"
    }

    variable "validation_record" {
      type = list(string)
    }

    variable "tags" {
      description = "(Optional) Map of tags to assign to the resource. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level."
      type = map(string)
      default = {
        "Enivronment" = "Production",
        "Terraform"   = true
      }
    }

  • Save the file

outputs.tf

  • Input the following to define the return values

      # aws_acm_certificate.cert
    
      output "id" {
        description = "ARN of the certificate"
        value = aws_acm_certificate.cert.id
      }
    
      output "arn" {
        description = "ARN of the certificate"
        value = aws_acm_certificate.cert.arn
      }
    
      output "domain_name" {
        description = "Domain name for which the certificate is issued"
        value = aws_acm_certificate.cert.domain_name
      }
    
      output "domain_validation_options" {
        description = "Set of domain validation objects which can be used to complete certificate validation. Can have more than one element, e.g., if SANs are defined. Only set if DNS-validation was used."
        value = aws_acm_certificate.cert.domain_validation_options
      }
    
      output "not_after" {
        description = "Expiration date and time of the certificate."
        value = aws_acm_certificate.cert.not_after
      }
    
      output "not_before" {
        description = "Start of the validity period of the certificate."
        value = aws_acm_certificate.cert.not_before
      }
    
      output "pending_renewal" {
        description = "true if a Private certificate eligible for managed renewal is within the early_renewal_duration period."
        value = aws_acm_certificate.cert.pending_renewal
      }
    
      output "renewal_eligibility" {
        description = "Whether the certificate is eligible for managed renewal."
        value = aws_acm_certificate.cert.renewal_eligibility
      }
    
      output "renewal_summary" {
        description = "Contains information about the status of ACM's managed renewal for the certificate."
        value = aws_acm_certificate.cert.renewal_summary
      }
    
      output "status" {
        description = "Status of the certificate."
        value = aws_acm_certificate.cert.status
      }
    
      output "validation_method" {
        description = "(Optional) Which method to use for validation. DNS or EMAIL are valid. This parameter must not be set for certificates that were imported into ACM and then into Terraform."
        value = aws_acm_certificate.cert.validation_method
      }
    
      output "type" {
        description = "Source of the certificate."
        value = aws_acm_certificate.cert.type
      }
    
      output "tags_all" {
        description = "Map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
        value = aws_acm_certificate.cert.tags_all
      }
    
      output "validation_emails" {
        description = "List of addresses that received a validation email. Only set if EMAIL validation was used."
        value = aws_acm_certificate.cert.validation_emails
      }
    
      # aws_acm_certificate_validation.cert
    
      output "validation_certificate_arn" {
        description = "Certificate ARN for Validation certificate from ACM"
        value = aws_acm_certificate_validation.cert.certificate_arn
      }
    
      output "validation_id" {
        description = "Time at which the certificate was issued"
        value = aws_acm_certificate_validation.cert.id
      }
    

  • Save the file


Modifying route53 files

  • Locate your terraform files in ./infra/modules/aws/route53.

You will start by creating the validation in Route53 for the ACM certificate.

  • Locate your acm_validation folder in your ./infra/modules/aws/route53 folder

acm_validation

main.tf

  • Input the following to create the validation option for your ACM certificate

      resource "aws_route53_record" "validation" {
        for_each = {
          for dvo in var.domain_validation_options : dvo.domain_name => {
            name   = dvo.resource_record_name
            record = dvo.resource_record_value
            type   = dvo.resource_record_type
          }
        }
    
        allow_overwrite = true
        name            = each.value.name
        records         = [each.value.record]
        ttl             = 60
        type            = each.value.type
        zone_id         = var.zone_id
      }
    

  • Save the file

variables.tf

  • Input the following to create the input variables for this module

      variable "domain_validation_options" {
        description = "Passed value from aws_acm_certificate.cert for domain_validation_options"
        type = set(object({
            domain_name           = string
            resource_record_name  = string
            resource_record_type  = string
            resource_record_value = string
          }))
      }
    
      variable "zone_id" {
        description = "Passed value from data.aws_route53_zone.my_domain.zone_id"
        type = string
      }
    
      # Required for outputs.tf
      variable "domain_name" {
        description = "Domain name for which the certificate is issued."
        type = string
      }
    
      variable "subject_alternative_names" {
        description = "Set of domains that should be SANs in the issued certificate. To remove all elements of a previously configured list, set this value equal to an empty list ([]) or use the terraform taint command to trigger recreation."
        type = string
      }
    

  • Save the file

outputs.tf

  • Input the following to define the return variables for the ACM validation

      output "domain_name" {
        description = "DNS Record validation"
        value = "${aws_route53_record.validation[var.domain_name].fqdn}"
      }
    
      output "subject_alternative_names" {
        description = "DNS Record validation"
        value = "${aws_route53_record.validation[var.subject_alternative_names].fqdn}"
      }
    

  • Save the file

route53

In this section, you will create an entry for Route53 "A" records

main.tf

  • Input the following:

      resource "aws_route53_record" "record" {
        zone_id = var.zone_id
        name    = var.name
        type    = var.type
    
        alias {
          name                   = var.alias_name
          zone_id                = var.alias_zone_id
          evaluate_target_health = true
        }
      }
    

  • Save the file

variables.tf

  • Input the following to define the input variables for the Route53 A record

      variable "name" {
        description = "Name for Route53 CNAME Record. e.g. blog.example.com"
        type = string
      }
    
      variable "type" {
        description = "Type of DNS record"
        default = "A"
        type = string
      }
    
      variable "zone_id" {
        description = "Passed value from data.aws_route53_zone.my_domain.zone_id"
        type = string
      }
    
      variable "alias_name" {
        description = "Alias for A record"
        type = string
      }
    
      variable "alias_zone_id" {
        description = "Zone ID for A record"
      }
    

  • Save the file

outputs.tf

  • Input the following to define the return variables

      output "name" {
        description = "The name of the record."
        value = aws_route53_record.record.name
      }
    
      output "fqdn" {
        description = "FQDN built using the zone domain and name."
        value = aws_route53_record.record.fqdn
      }
    
      output "type" {
        description = "The record type. Valid values are A, AAAA, CAA, CNAME, DS, MX, NAPTR, NS, PTR, SOA, SPF, SRV and TXT."
        value = aws_route53_record.record.type
      }
    
      output "ttl" {
        description = "The TTL of the record."
        value = aws_route53_record.record.ttl
      }
    
      output "zone_id" {
        description = "The ID of the hosted zone to contain this record."
        value = aws_route53_record.record.zone_id
      }
    

  • Save the file


Modifying cloudfront_distribution

Now that the entries for Route53 have been made, you will need to update your CloudFront distribution and create an alias and change the viewer_certificate. Locate your cloudfront_distribution files in ./infra/modules/aws/cloudfront_distribution

main.tf

  • Modify main.tf to include the following:

    • Alias
    aliases             = ["${var.my_domain}"]

Before

After

  • viewer_certificate

      viewer_certificate {
          acm_certificate_arn      = var.valid_certificate_arn
          ssl_support_method       = var.ssl_support_method
          minimum_protocol_version = var.minimum_protocol_version
        }
      }
    

    Before

    After

  • Save the file

variables.tf

  • Input the following to define the input variables for the updated values

      variable "my_domain" {
        description = "The name of the custom domain associated with Route53 and CloudFront"
        type = string
      }
    
      variable "valid_certificate_arn" {
        description = "Certificate ARN for Validation certificate from ACM"
        type = string
      }
    
      variable "ssl_support_method" {
        description = "How you want CloudFront to serve HTTPS requests. One of vip, sni-only, or static-ip. Required if you specify acm_certificate_arn or iam_certificate_id. NOTE: vip causes CloudFront to use a dedicated IP address and may incur extra charges."
        type = string
        default = "sni-only"
      }
    
      variable "minimum_protocol_version" {
        description = "Minimum version of the SSL protocol that you want CloudFront to use for HTTPS connections. Can only be set if cloudfront_default_certificate = false. See all possible values in this table under \"Security policy.\" Some examples include: TLSv1.2_2019 and TLSv1.2_2021. Default: TLSv1. NOTE: If you are using a custom certificate (specified with acm_certificate_arn or iam_certificate_id), and have specified sni-only in ssl_support_method, TLSv1 or later must be specified. If you have specified vip in ssl_support_method, only SSLv3 or TLSv1 can be specified. If you have specified cloudfront_default_certificate, TLSv1 must be specified."
        type = string
        default = "TLSv1.2_2021"
      }
    

  • Save the file

  • There are no additional outputs that need to be defined


Modifying s3_cloudfront_site

Because the cloudfront_distribution module has been updated, you will need to update the s3_cloudfront_site module that calls that module.

main.tf

  • Input the following to update the cloudfront_distribution module

        my_domain                   = var.domain_name
        valid_certificate_arn       = var.certificate
    

variables.tf

  • Input the following to include the new input variables

      variable "certificate" {
        description = "Certificate issued by ACM"
        type = string
      }
    
      variable "domain_name" {
        description = "The name of the custom domain associated with Route53 and CloudFront"
        type        = string
      }
    

  • Save the file

  • There are no additional outputs that need to be defined


Modifying api_gateway_domain

In this section, you will create the custom domain name for your API Gateway. Locate your api_gateway_domain files in ./infra/modules/aws/api_gateway/api_gateway_domain.

main.tf

  • Input the following to create an API custom domain

      resource "aws_api_gateway_domain_name" "api" {
        certificate_arn = var.certificate_arn
        domain_name     = var.domain_name
      }
    

  • Save the file

variables.tf

  • Input the following to define the input variables

      variable "certificate_arn" {
        description = "Optional) ARN for an AWS-managed certificate. AWS Certificate Manager is the only supported source. Used when an edge-optimized domain name is desired. Conflicts with certificate_name, certificate_body, certificate_chain, certificate_private_key, regional_certificate_arn, and regional_certificate_name."
        type = string
      }
    
      variable "domain_name" {
        description = "The name of the custom domain associated with Route53 and CloudFront"
        type = string
      }
    

  • Save the file

outputs.tf

  • Input the following to define the return values

      output "domain_name" {
        description = "(Required) Fully-qualified domain name to register."
        value = aws_api_gateway_domain_name.api.domain_name
      }
    
      output "arn" {
        description = "ARN of domain name."
        value = aws_api_gateway_domain_name.api.arn
      }
    
      output "certificate_upload_date" {
        description = "Upload date associated with the domain certificate."
        value = aws_api_gateway_domain_name.api.certificate_upload_date
      }
    
      output "cloudfront_domain_name" {
        description = "Hostname created by Cloudfront to represent the distribution that implements this domain name mapping."
        value = aws_api_gateway_domain_name.api.cloudfront_domain_name
      }
    
      output "cloudfront_zone_id" {
        description = "For convenience, the hosted zone ID (Z2FDTNDATAQYW2) that can be used to create a Route53 alias record for the distribution."
        value = aws_api_gateway_domain_name.api.cloudfront_zone_id
      }
    
      output "id" {
        description = "Internal identifier assigned to this domain name by API Gateway."
        value = aws_api_gateway_domain_name.api.id
      }
    
      output "regional_domain_name" {
        description = "Hostname for the custom domain's regional endpoint."
        value = aws_api_gateway_domain_name.api.regional_domain_name
      }
    
      output "regional_zone_id" {
        description = "Hosted zone ID that can be used to create a Route53 alias record for the regional endpoint."
        value = aws_api_gateway_domain_name.api.regional_zone_id
      }
    
      output "tags_all" {
        description = "Map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
        value = aws_api_gateway_domain_name.api.tags_all
      }
    

  • Save the file


Modifying api_gateway_deployment

In this section, you will point your currently deployed API Gateway to the custom domain name.

main.tf

  • Input the following to create the base path mapping for your API Gateway

      resource "aws_api_gateway_base_path_mapping" "api_domain_map" {
        api_id      = var.api_id
        stage_name  = aws_api_gateway_stage.stage.stage_name
        domain_name = var.domain_name
      }
    

  • Save the file

variables.tf

  • Input the following to define the input variable for domain_name

      variable "domain_name" {
        description = "(Required) Already-registered domain name to connect the API to."
        type = string
      }
    

  • Save the file


Modifying my_portfolio

In this section, you will update your static_website module and include entries for the ACM certificate and Route53.

main.tf

  • Include the following

        domain_name       = var.domain_name
        certificate       = module.acm.validation_certificate_arn
    

  • Include the following to update your api_deployment

      domain_name = var.api_domain_name
    

  • Include the following to create the ACM certificate

      module "acm" {
        source = "../aws/acm"
    
        domain_name         = var.my_domain
        subject_alternative_names = [var.subject_alternative_names]
        validation_record   = [ 
          module.acm_validation.domain_name,
          module.acm_validation.subject_alternative_names
        ]
      }
    
      module "acm_validation" {
        source = "../aws/route53/acm_validation"
    
        domain_validation_options = module.acm.domain_validation_options
        zone_id                   = data.aws_route53_zone.my_domain.zone_id
        domain_name               = var.my_domain
        subject_alternative_names = var.subject_alternative_names
      }
    

    • Input the following to define the API Gateway domain name

        module "api_gateway_domain" {
          source = "../aws/api_gateway/api_gateway_domain"
      
          certificate_arn = module.acm.validation_certificate_arn
          domain_name     = var.api_domain_name
        }
      
        # Amazon Route53
        module "api_record" {
          source = "../aws/route53"
      
          zone_id       = data.aws_route53_zone.my_domain.zone_id
          name          = module.api_gateway_domain.domain_name
          alias_name    = module.api_gateway_domain.cloudfront_domain_name
          alias_zone_id = module.api_gateway_domain.cloudfront_zone_id
      
          depends_on = [ module.acm.validation_certificate_arn ]
        }
      

  • Input the following to define the Route53 record for your CloudFront Distribution

      module "cloudfront_record" {
        source = "../aws/route53"
    
        zone_id       = data.aws_route53_zone.my_domain.zone_id
        name          = var.my_domain
        alias_name    = module.static_website.cloudfront_domain_name
        alias_zone_id = module.static_website.cloudfront_hosted_zone_id
    
        depends_on = [module.acm.validation_certificate_arn ]
      }
    

  • Save the file

variables.tf

  • Input the following to define the input variables for this module

      variable "my_domain" {
        description = "The name of the custom domain associated with Route53 and CloudFront"
        type        = string
      }
    
      variable "subject_alternative_names" {
        description = "Alternative name for custom domain"
        type = string
      }
    
      variable "api_domain_name" {
        description = "The name of the custom domain associated with Route53 and CloudFront"
        type = string
      }
    

  • Save the file

data.tf

  • Input the following to define the data block for your domain

      data "aws_route53_zone" "my_domain" {
        name         = var.my_domain
        private_zone = false
      }
    

  • Save the file


Modifying ./infra/main.tf

  • Input the following to define the input variables for the my_portfolio module

      my_domain = "YOUR_DOMAIN_NAME"
        api_domain_name = "YOUR_API_DOMAIN_NAME"
    

  • Save the file


Updating JavaScript files

In this section, you will update your JavaScript files to include your new API Gateway custom domain.

views.js

  • Update your JavaScript file to include your new URL

  • Save the file

sns.js

  • Update your JavaScript file to include your new URL

  • Save the file


Pushing to GitHub

  • Ensure your files are saved.

  • In your IDE Terminal, type the following:

git add .

Add all files that were changed.

git commit -m "Created ACM certificate and Route53 entries for API Gateway and CloudFront"

Commit the changes with a comment.

git push

Push to GitHub.


Create Pull Request

  • Login to GitHub.

  • You should see the push on your repository.

  • Select Compare and pull request.

  • Validate the changes that were made to be pushed to main

  • Select Create pull request.

Your Terraform Plan should run before you can merge to main.

Note: My plan outputs will be different due to having an already existing ACM certificate.

  • Select Merge pull request > Confirm merge.

  • Delete branch.

  • In your IDE Terminal, type the following:

git checkout main
git pull

Validation:

  • Visit your custom domain

  • Validate your Viewer Count populates with a value

  • Test your SNS functionality and ensure you are able to receive emails


You have successfully implemented all aspects of deploying a mirror image of my Cloud Portfolio website!

0
Subscribe to my newsletter

Read articles from Michael Maratita directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Michael Maratita
Michael Maratita

I am a Sr. Windows Administrator looking to sharpen my skills within the cloud space. This blog serves not only as a guide for others to use but also to document my cloud journey.