5. Cloud Resume Challenge: Creating CloudFront Distribution

Michael MaratitaMichael Maratita
14 min read

In this module you will create an S3 static hosted website that will be accessible through a CloudFront Distribution. By doing this, you will block all direct public access to your S3 objects, and they will only be available through the content delivery network. This will also provide an HTTPS connection, instead of the static website hosting URL that is HTTP. You will create a bucket policy so that CloudFront will have access to "GetObject" from the S3 Bucket. Let's get started!


Terraform S3 Bucket Policy Reference

Terraform S3 Website Configuration Reference

Terraform CloudFront Distribution Reference

Terraform CloudFront Origin Access Control


Create a new branch in GitHub

  • Login to GitHub

  • Select the Issues tab > Select New Issue

  • Add the a title, e.g. Create static website

  • 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

Current Bucket State

Note the start of your current bucket, should be like below.

  • To view this, select your Bucket > select Properties

    • Scroll to the bottom and see Static website hosting


Modifying s3_website_configuration

  • To get started you will be modifying the main, variables and outputs Terraform files located in ./infra/modules/aws/s3/s3_website_configuration/.

main.tf

  • To create the configuration for the Static website hosting for your S3 Bucket, input the following:

      resource "aws_s3_bucket_website_configuration" "website_configuration" {
        bucket   = var.bucket_name
    
        index_document {
          suffix = "index.html"
        }
      }
    

    This will create the website configuration on the bucket defined as bucket_name.

  • Next you will add a bucket policy so that CloudFront will have access to the S3 Bucket.

    • Input the following:

        data "aws_iam_policy_document" "bucket_policy" {
          statement {
            sid = var.sid
            effect = "Allow"
      
            principals {
              type = "Service"
              identifiers = ["cloudfront.amazonaws.com"]
            }
      
            actions = [
              "s3:GetObject"
            ]
      
            resources = var.resource
      
            condition {
              test = "StringEquals"
              variable = "AWS:SourceArn"
              values = [ var.cloudfront_distribution_arn ]
            }
          }
        }
      
        resource "aws_s3_bucket_policy" "bucket_policy" {
          bucket = var.bucket_name
          policy = data.aws_iam_policy_document.bucket_policy.json
        }
      

      data.aws_iam_policy document creates a template of an IAM policy to allow the CloudFront service the ability to "GetObject" from the S3 bucket. You further define a condition for what Distribution will have the allow access to your defined bucket. The ARN for the CloudFront distribution will be an output variable from the CloudFront distribution module.

      The policy is then attached as a bucket policy to the bucket you created.

  • Save the file

variables.tf

  • Input the following to define the variables for the S3 Bucket website configuration:

      variable "bucket_name" {
        description = "The name of the S3 bucket"
        type = string
      }
    
      variable "sid" {
        description = "statement ID for Policy"
        type = string
      }
    
      variable "cloudfront_distribution_arn" {
        description = "ARN for CloudFront distribution"
        type = string
      }
    
      variable "resource" {
        description = "(Required in only some circumstances) – If you create an IAM permissions policy, you must specify a list of resources to which the actions apply. If you create a resource-based policy, this element is optional. If you do not include this element, then the resource to which the action applies is the resource to which the policy is attached."
        type = list(string)
      }
    
  • Save the file

Note: Defining output variables is optional here since you will not require any of them as inputs for other modules. Refer to the references above for the available outputs.


Modifying cloudfront_distribution

  • In this section, you will modify the terraform files located in ./infa/moudles/aws/cloudfront_distrution

main.tf

  • Input the following to create the CloudFront Distribution resource block

      resource "aws_cloudfront_distribution" "s3_distribution" {
        origin {
          domain_name              = var.bucket_regional_domain_name
          origin_access_control_id = aws_cloudfront_origin_access_control.default.id
          origin_id                = var.origin_id
        }
    
        enabled             = true
        is_ipv6_enabled     = true
        comment             = var.comment
        default_root_object = var.default_root_object
    
        default_cache_behavior {
          allowed_methods  = var.allowed_methods
          cached_methods   = var.cached_methods
          target_origin_id = var.origin_id
    
          forwarded_values {
            query_string = var.query_string
    
            cookies {
              forward = var.cookies_forward
            }
          }
    
          viewer_protocol_policy = var.viewer_protocol_policy
          min_ttl                = var.min_ttl
          default_ttl            = var.default_ttl
          max_ttl                = var.max_ttl
        }
    
        price_class = var.price_class
    
        restrictions {
          geo_restriction {
            restriction_type = var.restriction_type
            locations        = var.locations
          }
        }
    
        viewer_certificate {
          cloudfront_default_certificate = true
        }
      }
    

    Later in the variables section you will define the majority of these variables with default values.

    In order for the CloudFront distribution to be accessible, you will need to create an Origin Access Control for the S3 bucket website configuration resource.

  • Input the following to define the Origin Access Control

      resource "aws_cloudfront_origin_access_control" "default" {
        name                              = var.name
        description                       = var.description
        origin_access_control_origin_type = var.origin_access_control_origin_type
        signing_behavior                  = var.signing_behavior
        signing_protocol                  = var.signing_protocol
      }
    

  • Save the file

variables.tf

  • Input the following to define each of the variables and some with their default values.

      # aws_cloudfront_origin_access_contol.default
      variable "name" {
        description = "(Required) A name that identifies the Origin Access Control."
        type = string
      }
    
      variable "description" {
        description = "(Optional) The description of the Origin Access Control. Defaults to \"Managed by Terraform\" if omitted."
        type = string
      }
    
      variable "origin_access_control_origin_type" {
        description = "(Required) The type of origin that this Origin Access Control is for. Valid values are s3, and mediastore."
        type = string
        default = "s3"
      }
    
      variable "signing_behavior" {
        description = "(Required) Specifies which requests CloudFront signs. Specify always for the most common use case. Allowed values: always, never, and no-override."
        type = string
        default = "always"
      }
    
      variable "signing_protocol" {
        description = "Required) Determines how CloudFront signs (authenticates) requests. The only valid value is sigv4."
        type = string
        default = "sigv4"
      }
    
      # aws_cloudfront_distribution.s3_distribution
      variable "comment" {
        description = "(Optional) - Any comments you want to include about the distribution."
        type = string
      }
    
      variable "bucket_regional_domain_name" {
        description = "Regional Domain Name for S3 Bucket"
        type = string
      }
    
      variable "default_root_object" {
        description = "(Optional) - Object that you want CloudFront to return (for example, index.html) when an end user requests the root URL."
        type = string
        default = "index.html"
      }
    
      variable "allowed_methods" {
        description = "(Required) - Controls which HTTP methods CloudFront processes and forwards to your Amazon S3 bucket or your custom origin."
        type = list(string)
        default = [ "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT" ]
      }
    
      variable "cached_methods" {
        description = "(Required) - Controls whether CloudFront caches the response to requests using the specified HTTP methods."
        type = list(string)
        default = [ "GET", "HEAD" ]
      }
    
      variable "origin_id" {
        description = "Origin Access Control ID Value"
        type = string
      }
    
      variable "query_string" {
        description = "(Required) - Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior."
        type = bool
        default = false
      }
    
      variable "cookies_forward" {
        description = "(Required) - The forwarded values cookies that specify how CloudFront handles cookies (maximum one)."
        type = string
        default = "none"
      }
    
      variable "viewer_protocol_policy" {
        description = "(Required) - Use this element to specify the protocol that users can use to access the files in the origin specified by TargetOriginId when a request matches the path pattern in PathPattern. One of allow-all, https-only, or redirect-to-https."
        type = string
        default = "redirect-to-https"
      }
    
      variable "min_ttl" {
        description = "(Optional) - Minimum amount of time that you want objects to stay in CloudFront caches before CloudFront queries your origin to see whether the object has been updated. Defaults to 0 seconds."
        type = number
        default = 0
      }
    
      variable "default_ttl" {
        description = "(Optional) - Default amount of time (in seconds) that an object is in a CloudFront cache before CloudFront forwards another request in the absence of an Cache-Control max-age or Expires header."
        type = number
        default = 3600
      }
    
      variable "max_ttl" {
        description = "(Optional) - Maximum amount of time (in seconds) that an object is in a CloudFront cache before CloudFront forwards another request to your origin to determine whether the object has been updated. Only effective in the presence of Cache-Control max-age, Cache-Control s-maxage, and Expires headers."
        type = number
        default = 84600
      }
    
      variable "price_class" {
        description = "(Optional) - Price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100."
        type = string
        default = "PriceClass_200"
      }
    
      variable "restriction_type" {
        description = "(Required) - Method that you want to use to restrict distribution of your content by country: none, whitelist, or blacklist"
        type = string
        default = "none"
      }
    
      variable "locations" {
        description = "(Required) - ISO 3166-1-alpha-2 codes for which you want CloudFront either to distribute your content (whitelist) or not distribute your content (blacklist). If the type is specified as none an empty array can be used."
        type = list(string)
        default = []
      }
    

    • Save the file

outputs.tf

  • Input the following to define the output variables for your CloudFront distribution resource

      output "id" {
        description = "Identifier for the distribution. For example: EDFDVBD632BHDS5."
        value = aws_cloudfront_distribution.s3_distribution.id
      }
    
      output "arn" {
        description = "ARN for the distribution. For example: arn:aws:cloudfront::123456789012:distribution/EDFDVBD632BHDS5, where 123456789012 is your AWS account ID"
        value = aws_cloudfront_distribution.s3_distribution.arn
      }
    
      output "caller_reference" {
        description = "Internal value used by CloudFront to allow future updates to the distribution configuration."
        value = aws_cloudfront_distribution.s3_distribution.caller_reference
      }
    
      output "status" {
        description = "Current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system."
        value = aws_cloudfront_distribution.s3_distribution.status
      }
    
      output "tags_all" {
        description = "Map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
        value = aws_cloudfront_distribution.s3_distribution.tags_all
      }
    
      output "trusted_key_groups" {
        description = "List of nested attributes for active trusted key groups, if the distribution is set up to serve private content with signed URLs."
        value = aws_cloudfront_distribution.s3_distribution.trusted_key_groups
      }
    
      output "trusted_signers" {
        description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs."
        value = aws_cloudfront_distribution.s3_distribution.trusted_signers
      }
    
      output "domain_name" {
        description = "Domain name corresponding to the distribution. For example: d604721fxaaqy9.cloudfront.net"
        value = aws_cloudfront_distribution.s3_distribution.domain_name
      }
    
      output "last_modified_time" {
        description = "Date and time the distribution was last modified."
        value = aws_cloudfront_distribution.s3_distribution.last_modified_time
      }
    
      output "in_progress_validation_batches" {
        description = "Number of invalidation batches currently in progress."
        value = aws_cloudfront_distribution.s3_distribution.in_progress_validation_batches
      }
    
      output "etag" {
        description = "Current version of the distribution's information. For example: E2QWRUHAPOMQZL."
        value = aws_cloudfront_distribution.s3_distribution.etag
      }
    
      output "hosted_zone_id" {
        description = "CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to. This attribute is simply an alias for the zone ID Z2FDTNDATAQYW2."
        value = aws_cloudfront_distribution.s3_distribution.hosted_zone_id
      }
    

    • Save the file

Updating s3_cloudfront_site

  • In this section you will complete the s3_cloudfront_site module so it can be used in the my_portfolio module. You will include module blocks from the s3_website_configuration and the cloudfront_distribution.

main.tf

  • Input the following to update your current configuration.

      module "s3_website_config" {
        source = "../aws/s3/s3_website_configuration"
    
        sid                         = var.bucket_policy_sid
        bucket_name                 = module.bucket.id
        cloudfront_distribution_arn = module.cloudfront_distribution.arn
        resource                    = ["${module.bucket.arn}/*"]
      }
    

    Module block for the S3 Bucket website configuration

      module "cloudfront_distribution" {
        source = "../aws/cloudfront_distribution"
    
        name                        = var.name
        description                 = var.description
        comment                     = var.comment
        bucket_regional_domain_name = module.bucket.bucket_regional_domain_name
        origin_id                   = var.origin_id
      }
    

    Module block for CloudFront Distribution

  • Save the file

variables.tf

  • Input the following to update the input variables for s3_cloudfront_site:

      variable "bucket_name" {
        description = "Given name of the S3 bucket"
        type = string
      }
    
      variable "name" {
        description = "Name for Origin Access Control"
        type = string
      }
    
      variable "description" {
        description = "Description for Origin Access Control"
        type = string
      }
    
      variable "comment" {
        description = "Comment for CloudFront Distribution"
        type = string
      }
    
      variable "origin_id" {
        description = "ID for Origin Access Control"
        type = string
      }
    
      variable "bucket_policy_sid" {
        description = "Sid for Bucket Policy"
        type = string
      }
    

outputs.tf

  • Input the following to update your output variables for this module:

      output "cloudfront_id" {
        description = "Identifier for the distribution. For example: EDFDVBD632BHDS5."
        value = module.cloudfront_distribution.id
      }
    
      output "cloudfront_arn" {
        description = "ARN for the distribution. For example: arn:aws:cloudfront::123456789012:distribution/EDFDVBD632BHDS5, where 123456789012 is your AWS account ID"
        value = module.cloudfront_distribution.arn
      }
    
      output "cloudfront_caller_reference" {
        description = "Internal value used by CloudFront to allow future updates to the distribution configuration."
        value = module.cloudfront_distribution.caller_reference
      }
    
      output "cloudfront_status" {
        description = "Current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system."
        value = module.cloudfront_distribution.status
      }
    
      output "cloudfront_tags_all" {
        description = "Map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
        value = module.cloudfront_distribution.tags_all
      }
    
      output "cloudfront_trusted_key_groups" {
        description = "List of nested attributes for active trusted key groups, if the distribution is set up to serve private content with signed URLs."
        value = module.cloudfront_distribution.trusted_key_groups
      }
    
      output "cloudfront_trusted_signers" {
        description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs."
        value = module.cloudfront_distribution.trusted_signers
      }
    
      output "cloudfront_domain_name" {
        description = "Domain name corresponding to the distribution. For example: d604721fxaaqy9.cloudfront.net"
        value = module.cloudfront_distribution.domain_name
      }
    
      output "cloudfront_last_modified_time" {
        description = "Date and time the distribution was last modified."
        value = module.cloudfront_distribution.last_modified_time
      }
    
      output "cloudfront_in_progress_validation_batches" {
        description = "Number of invalidation batches currently in progress."
        value = module.cloudfront_distribution.in_progress_validation_batches
      }
    
      output "cloudfront_etag" {
        description = "Current version of the distribution's information. For example: E2QWRUHAPOMQZL."
        value = module.cloudfront_distribution.etag
      }
    
      output "cloudfront_hosted_zone_id" {
        description = "CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to. This attribute is simply an alias for the zone ID Z2FDTNDATAQYW2."
        value = module.cloudfront_distribution.hosted_zone_id
      }
    

  • Save the file


Modifying my_portfolio

  • In this section, you will update my_portfolio

main.tf

  • Input the following to update the module.static_website block:

        name              = var.origin_access_name
        description       = var.origin_access_description
        comment           = var.cloudfront_comment
        origin_id         = var.origin_id
        bucket_policy_sid = var.bucket_policy_sid
    

  • Save the file

variables.tf

  • Input the following to update your variables.tf

      variable "origin_access_name" {
        description = "Name for Origin Access Control"
        type = string
      }
    
      variable "origin_access_description" {
        description = "Description for Origin Access Control"
        type = string
      }
    
      variable "cloudfront_comment" {
        description = "Comment for CloudFront Distribution"
        type = string
      }
    
      variable "origin_id" {
        description = "ID for Origin Access Control"
        type = string
      }
    
      variable "bucket_policy_sid" {
        description = "Sid for Bucket Policy"
        type = string
      }
    

    • Save the file

outputs.tf

  • Input the following to update your output variables for this module:

      output "cloudfront_id" {
        description = "Identifier for the distribution. For example: EDFDVBD632BHDS5."
        value = module.static_website.cloudfront_id
      }
    
      output "cloudfront_arn" {
        description = "ARN for the distribution. For example: arn:aws:cloudfront::123456789012:distribution/EDFDVBD632BHDS5, where 123456789012 is your AWS account ID"
        value = module.static_website.cloudfront_arn
      }
    
      output "cloudfront_caller_reference" {
        description = "Internal value used by CloudFront to allow future updates to the distribution configuration."
        value = module.static_website.cloudfront_caller_reference
      }
    
      output "cloudfront_status" {
        description = "Current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system."
        value = module.static_website.cloudfront_status
      }
    
      output "cloudfront_tags_all" {
        description = "Map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
        value = module.static_website.cloudfront_tags_all
      }
    
      output "cloudfront_trusted_key_groups" {
        description = "List of nested attributes for active trusted key groups, if the distribution is set up to serve private content with signed URLs."
        value = module.static_website.cloudfront_trusted_key_groups
      }
    
      output "cloudfront_trusted_signers" {
        description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs."
        value = module.static_website.cloudfront_trusted_signers
      }
    
      output "cloudfront_domain_name" {
        description = "Domain name corresponding to the distribution. For example: d604721fxaaqy9.cloudfront.net"
        value = module.static_website.cloudfront_domain_name
      }
    
      output "cloudfront_last_modified_time" {
        description = "Date and time the distribution was last modified."
        value = module.static_website.cloudfront_last_modified_time
      }
    
      output "cloudfront_in_progress_validation_batches" {
        description = "Number of invalidation batches currently in progress."
        value = module.static_website.cloudfront_in_progress_validation_batches
      }
    
      output "cloudfront_etag" {
        description = "Current version of the distribution's information. For example: E2QWRUHAPOMQZL."
        value = module.static_website.cloudfront_etag
      }
    
      output "cloudfront_hosted_zone_id" {
        description = "CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to. This attribute is simply an alias for the zone ID Z2FDTNDATAQYW2."
        value = module.static_website.cloudfront_hosted_zone_id
      }
    

  • Save the file


Modifying ./infra/main.tf

  • In this section, you will define the input variables for your module.my_static_website

  • Input the following:

        bucket_policy_sid = "AllowCloudFrontServicePrincipalReadOnly"
        origin_id = "my_origin_access_id"
        origin_access_name = "Origin Access Cotrol for Bucket Website"
        origin_access_description = "Original Access Controls for Static Website Hosting"
        cloudfront_comment = "Static website hosting for my personal website."
    

  • Save the file

outputs.tf

  • There is only one output that you will require to complete CloudFront invalidations:

    • Input the following:

        output "cloudfront_id" {
          description = "Identifier for the distribution. For example: EDFDVBD632BHDS5."
          value = module.my_static_website.cloudfront_id
        }
      

  • 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 "create cloudfront distribution for static website"

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.

If you are using the same site files from the original template, the plan to add 4 should match.

  • Select Merge pull request > Confirm merge.

  • Delete branch.

  • In your IDE Terminal, type the following:

git checkout main
git pull

Validation:

  • AWS Management Console:

    • In the Search bar, search for "s3" > Select your bucket

    • Notice your bucket state under properties:

Note this URL will not be accessible directly due to the bucket policy restrictions from public access.

  • In the Search bar, search for "cloudfront" > Select "Distributions"

  • Locate your distribution and note the Domain name. This will be how you access your site.

  • Once the CloudFront Distribution completes it's deployment, navigate to the CloudFront URL.


You have successfully created a CloudFront distribution to host your S3 bucket website. In the next module, you will create a DynamoDB table and a table item to store a visitor count for your site. This will be integrated later with API Gateway and Lambda.

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.