Terraform Cloud Project Bootcamp with Andrew Brown — 1.5.0 Content Delivery Network
This article is part of my Terraform journey with Terraform Bootcamp by Andrew Brown and Andrew Bayko with Chris Williams and Shala.
My wild career adventure into Cloud Computing continues with Andrews, and I highly, highly recommend you to come join us if you are interested. Check out Andrews’ free youtube learning contents. You can also buy some paid courses here to support their good cause.
Agenda
Video here: 1 5 0 Content Delivery Network
Issue #31 Goals
- ✅ 1) Set up CloudFront Distribution
- ✅ 2) Set up CloudFront OAC (Origin Controls)
- ✅ 3) Bucket Policy
Workflow
- ✅ 1) Refactor
./modules/terrahouse_aws/main.tf
by resource types - ✅ 2) Run
terraform apply
- ✅ Trouble shooting 3–1) Apply change sets and invalidate CF caches
- ✅ Trouble shooting 3–2) Check your bucket policy and permissions.
1. Refactor ./modules/terrahouse_aws/main.tf
by resource types
cd
into ./modules/terrahouse_aws
then create:
code resource-cdn.tf
code resource-storage.tf
Now move (refactor) the corresponding code blocks in main.tf
at root to either resource-cdn.tf
or resource-storage.tf
depending on their functions. You can find the final codes here:
Once you are done refactoring and the code is tf valid
, run terraform apply
and the resources will be created as below.
# module.terrahouse_aws.aws_cloudfront_distribution.s3_distribution will be created
+ resource "aws_cloudfront_distribution" "s3_distribution" {
+ arn = (known after apply)
+ caller_reference = (known after apply)
+ comment = "Static website hosting for: nomadiachi-in-terratown-bucket"
+ default_root_object = "index.html"
+ domain_name = (known after apply)
+ enabled = true
+ etag = (known after apply)
+ hosted_zone_id = (known after apply)
+ http_version = "http2"
+ id = (known after apply)
+ in_progress_validation_batches = (known after apply)
+ is_ipv6_enabled = true
+ last_modified_time = (known after apply)
+ price_class = "PriceClass_200"
+ retain_on_delete = false
+ staging = false
+ status = (known after apply)
+ tags = {
+ "UserUuid" = "aa0d742c-..."
}
+ tags_all = {
+ "UserUuid" = "aa0d742c-..."
}
+ trusted_key_groups = (known after apply)
+ trusted_signers = (known after apply)
+ wait_for_deployment = true
+ default_cache_behavior {
+ allowed_methods = [
+ "DELETE",
+ "GET",
+ "HEAD",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
]
+ compress = false
+ default_ttl = 3600
+ max_ttl = 86400
+ min_ttl = 0
+ target_origin_id = "MyS3Origin"
+ trusted_key_groups = (known after apply)
+ trusted_signers = (known after apply)
+ viewer_protocol_policy = "allow-all"
+ forwarded_values {
+ headers = (known after apply)
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
+ whitelisted_names = (known after apply)
}
}
}
+ origin {
+ connection_attempts = 3
+ connection_timeout = 10
+ domain_name = (known after apply)
+ origin_access_control_id = (known after apply)
+ origin_id = "MyS3Origin"
}
+ viewer_certificate {
+ cloudfront_default_certificate = true
+ minimum_protocol_version = "TLSv1"
}
}
# module.terrahouse_aws.aws_cloudfront_origin_access_control.default will be created
+ resource "aws_cloudfront_origin_access_control" "default" {
+ description = "Origin Access Controls for Static Website Hosting nomadiachi-in-terratown-bucket"
+ etag = (known after apply)
+ id = (known after apply)
+ name = "OAC nomadiachi-in-terratown-bucket"
+ origin_access_control_origin_type = "s3"
+ signing_behavior = "always"
+ signing_protocol = "sigv4"
}
# module.terrahouse_aws.aws_s3_bucket.website_bucket will be created
+ resource "aws_s3_bucket" "website_bucket" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "nomadiachi-in-terratown-bucket"
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags = {
+ "UserUuid" = "aa0d742c-..."
}
+ tags_all = {
+ "UserUuid" = "aa0d742c-..."
}
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
# module.terrahouse_aws.aws_s3_bucket_policy.bucket_policy will be created
+ resource "aws_s3_bucket_policy" "bucket_policy" {
+ bucket = "nomadiachi-in-terratown-bucket"
+ id = (known after apply)
+ policy = (known after apply)
}
# module.terrahouse_aws.aws_s3_bucket_website_configuration.website_configuration will be created
+ resource "aws_s3_bucket_website_configuration" "website_configuration" {
+ bucket = "nomadiachi-in-terratown-bucket"
+ id = (known after apply)
+ routing_rules = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ error_document {
+ key = "error.html"
}
+ index_document {
+ suffix = "index.html"
}
}
# module.terrahouse_aws.aws_s3_object.error_html will be created
+ resource "aws_s3_object" "error_html" {
+ acl = (known after apply)
+ bucket = "nomadiachi-in-terratown-bucket"
+ bucket_key_enabled = (known after apply)
+ content_type = (known after apply)
+ etag = "2b96c2762607896d07b95d7eba4ac39e"
+ force_destroy = false
+ id = (known after apply)
+ key = "error.html"
+ kms_key_id = (known after apply)
+ server_side_encryption = (known after apply)
+ source = "./public/error.html"
+ storage_class = (known after apply)
+ tags_all = (known after apply)
+ version_id = (known after apply)
}
# module.terrahouse_aws.aws_s3_object.index_html will be created
+ resource "aws_s3_object" "index_html" {
+ acl = (known after apply)
+ bucket = "nomadiachi-in-terratown-bucket"
+ bucket_key_enabled = (known after apply)
+ content_type = (known after apply)
+ etag = "831e8b36c4ac8c46618a89886088b7c4"
+ force_destroy = false
+ id = (known after apply)
+ key = "index.html"
+ kms_key_id = (known after apply)
+ server_side_encryption = (known after apply)
+ source = "/workspace/terraform-beginner-bootcamp-2023/public/index.html"
+ storage_class = (known after apply)
+ tags_all = (known after apply)
+ version_id = (known after apply)
}
Plan: 7 to add, 0 to change, 0 to destroy.
Troubleshooting
Cache Invalidation
In the video, Andrew’s CF distribution works but the browser downloads the index.html
file instead of displaying the content. He tries clearing the cache. This may be due to the missing content_type
configuration. Andrew explicitly defines it to be "text/html"
then clears the CF distribution caches manually on AWS CloudFront console (it is called ‘invalidate cache’ in CF).
To invalidate cache, navigate to: CloudFront > specific Distribution > Invalidation tab, then > “Create invalidation”.
However, even after the cache invalidation, Andrew’s CF distribution would still download the files from the bucket!
Andrew’s solution was making some more changes to the html files so terraform
picks up those changes and reapplies the change set. Then he invalidated cache and then tried accessing the distribution and it worked!
Access Denied
In my case, on the other hand, I kept running into the access denied issue. This may be because the restrictions
configuration in ./modules/terrahouse_aws/resource-cdn.tf
so I removed it.
But I would still run into the same issue. It turns out, the actual culprit was the bucket_policy
configuration. As we are being denied access, the logical assumption is that something should be wrong with the bucket access configuration. I compared my code with Andrew’s, and realised that the CloudFront distribution ID was missing in my resource-storage.tf
configuration.
resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = aws_s3_bucket.website_bucket.bucket
...
"Condition" = {
"StringEquals" = {
"AWS:SourceArn": "arn:aws:cloudfront::
${data.aws_caller_identity.current.account_id}:distribution/
${aws_cloudfront_distribution.s3_distribution.id}" # <- add this.
}
}
}
})
}
Notes
Data sources
The data source data.aws_iam_policy_document.allow_access_from_another_account
below is a good example of how data source works. It is not referring to any local file but it is grabbing the data on the server through an API call. This, in turn, means that we will have to configure the bucket policy if we want to use this data source, as it interacts with the bucket on AWS cloud and to do so requires permissions.
resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = aws_s3_bucket.website_bucket.bucket
# policy = data.aws_iam_policy_document.allow_access_from_another_account.json
policy = jsonencode({
Resources
Bootcamp
- Video: 1 5 0 Content Delivery Network
- My feature branch: 29-cdn-implementation
Terraform
- Resource:
aws_cloudfront_distribution
- Resource:
aws_cloudfront_origin_access_control
- Resource:
aws_s3_bucket_policy
- Data Source:
aws_caller_identity