DEV Community

Cover image for AWS Networking with Terraform : Deploying a CloudFront distribution for S3 Static Website
Chinmay Tonape
Chinmay Tonape

Posted on • Updated on

AWS Networking with Terraform : Deploying a CloudFront distribution for S3 Static Website

In a previous blog post, we delved into the mechanics of hosting a static website using Amazon S3. Now, we're taking it up a notch by integrating Amazon CloudFront, a Content Delivery Network (CDN), into our architecture. By doing so, we aim to optimize website performance and elevate user experience to new heights. In this post, we'll walk through the process of seamlessly integrating CloudFront with S3 using Terraform modules.

What is CloudFront:

Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you're serving with CloudFront, the request is routed to the edge location that provides the lowest latency (time delay), so that content is delivered with the best possible performance.

If the content is already in the edge location with the lowest latency, CloudFront delivers it immediately.

If the content is not in that edge location, CloudFront retrieves it from an origin that you've defined—such as an Amazon S3 bucket, a MediaPackage channel, or an HTTP server (for example, a web server) that you have identified as the source for the definitive version of your content.

CloudFront speeds up the distribution of your content by routing each user request through the AWS backbone network to the edge location that can best serve your content.

Architecture Diagram:

Let's start by outlining the architecture we'll be working with:
CloudFront Architecture Diagram

Step 1: Create an S3 bucket with a unique name and host the static website by uploading files.

####################################################
# S3 static website bucket
####################################################
resource "aws_s3_bucket" "s3-static-website" {
  bucket = var.bucket_name
  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-s3-bucket"
  })
}

####################################################
# S3 public access settings
####################################################
resource "aws_s3_bucket_public_access_block" "static_site_bucket_public_access" {
  bucket = aws_s3_bucket.s3-static-website.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

####################################################
# S3 bucket static website configuration
####################################################
resource "aws_s3_bucket_website_configuration" "static_site_bucket_website_config" {
  bucket = aws_s3_bucket.s3-static-website.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

####################################################
# Upload files to S3 Bucket 
####################################################
resource "aws_s3_object" "provision_source_files" {
  bucket = aws_s3_bucket.s3-static-website.id

  # webfiles/ is the Directory contains files to be uploaded to S3
  for_each = fileset("${var.source_files}/", "**/*.*")

  key          = each.value
  source       = "${var.source_files}/${each.value}"
  content_type = each.value
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure a CloudFront distribution to serve as the CDN for our website.

data "aws_s3_bucket" "selected_bucket" {
  bucket = var.s3_bucket_id
}

####################################################
# Create AWS Cloudfront distribution
####################################################
resource "aws_cloudfront_origin_access_control" "cf-s3-oac" {
  name                              = "CloudFront S3 OAC"
  description                       = "CloudFront S3 OAC"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_distribution" "cf-dist" {
  enabled             = true
  default_root_object = "index.html"

  origin {
    domain_name              = data.aws_s3_bucket.selected_bucket.bucket_regional_domain_name
    origin_id                = var.s3_bucket_id
    origin_access_control_id = aws_cloudfront_origin_access_control.cf-s3-oac.id
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = var.s3_bucket_id
    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }
    viewer_protocol_policy = "allow-all"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  price_class = "PriceClass_All"

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["IN", "US", "CA"]
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-cloudfront"
  })
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Update the S3 Bucket policy to allow access from CloudFront.

To ensure that our S3 bucket only allows access through CloudFront, we'll update its bucket policy accordingly.

####################################################
# S3 bucket policy to allow access from cloudfront
####################################################
data "aws_iam_policy_document" "s3_bucket_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${var.bucket_arn}/*"]
    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceArn"
      values   = [var.cloudfront_distribution_arn]
    }
  }
}

resource "aws_s3_bucket_policy" "static_site_bucket_policy" {
  bucket = var.bucket_id
  policy = data.aws_iam_policy_document.s3_bucket_policy.json
}
Enter fullscreen mode Exit fullscreen mode

Steps to Run Terraform

Follow these steps to execute the Terraform configuration:

terraform init
terraform plan 
terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Upon successful completion, Terraform will provide relevant outputs.

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

cloudfront_domain_name = "http://d1rwkmekbjnbkd.cloudfront.net"
Enter fullscreen mode Exit fullscreen mode

Testing the outcome

S3 Bucket

S3 Bucket

Block public access:

S3 Block Public Access

Static Website setting:

S3 Static Website Hosting

CloudFront Distribution:

CloudFront Distribution

CloudFront Distribution Origin as S3 with Origin Access Control OAC:

CDN S3 Origin with OAC

S3 Bucket Policy to allow access from cloudfront

S3 Bucket Policy

Using cloudfront domain name to access S3 static website:

Accessing Website

Website accessed from country which is not in allowlist (used soft VPN)

CF AllowList

Cache invalidation occurred after TTL timeout and now website has different image (after static website content was updated)

Cache Invalidation

Cleanup

Remember to stop AWS components to avoid large bills.

terraform destroy -auto-approve
Enter fullscreen mode Exit fullscreen mode

Notes:

  1. Cache Invalidation can be forced from console for a cloudfront distribution
  2. Deprecated Origin Access Identity OAI also can be used instead of Origin Access Control OAC

Incorporating Amazon CloudFront into our static website architecture significantly enhances performance and improves user experience.

In the next module, we'll extend our exploration to leverage CloudFront with EC2 servers, further optimizing content delivery for dynamic web applications.

Resources:

GitHub Link: https://github.com/chinmayto/terraform-aws-cloudfront-s3
AWS CloudFront: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html

Top comments (0)