5. Cloud Resume Challenge: Creating CloudFront Distribution
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.
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.