Secure Serverless Static Website on AWS (Terraform)

This project implements a secure, serverless static website architecture on AWS using Amazon S3 as the origin, Amazon CloudFront as the CDN layer, and Terraform as the infrastructure provisioning tool. The goal was to deliver static content securely and reliably while enforcing best practices around access control, metadata correctness, and Infrastructure as Code.

Architecture Overview

  • Static site hosted in a private Amazon S3 bucket
  • Public access provided through Amazon CloudFront
  • Origin Access Identity (OAI) used to prevent direct S3 access
  • HTTPS enforced at the CloudFront distribution
  • Full environment provisioned and managed using Terraform

Architecture Diagram


















Key Engineering Decisions

  • Kept the S3 bucket private and restricted access to CloudFront only
  • Explicitly defined Content-Type metadata in Terraform to prevent CDN rendering issues
  • Used CloudFront cache invalidation to propagate metadata changes correctly
  • Designed the project to be reproducible, auditable, and free of manual console changes

Problem Solved

During deployment, CloudFront initially served the site as a downloadable file rather than rendering it in the browser. This was traced to incorrect system-defined S3 metadata (application/octet-stream). The issue was resolved by replacing the S3 object with the correct MIME type and locking the fix into Terraform to prevent regressions.

Terraform Highlights

resource "aws_s3_object" "index" {
  bucket = aws_s3_bucket.site.bucket
  key    = "index.html"
  source = "${path.module}/site/index.html"

  content_type = "text/html"
  etag         = filemd5("${path.module}/site/index.html")
}
  

S3 Bucket Security

The static website content is stored in a private Amazon S3 bucket. Public access is fully blocked, and access is granted only to CloudFront through an Origin Access Identity (OAI).

resource "aws_s3_bucket" "site" {
  bucket = var.site_bucket_name
}

resource "aws_s3_bucket_public_access_block" "site" {
  bucket = aws_s3_bucket.site.id

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

CloudFront Distribution

Amazon CloudFront is used as the public entry point for the website, providing HTTPS enforcement, global edge caching, and controlled access to the private S3 origin.

resource "aws_cloudfront_distribution" "site" {
  enabled             = true
  default_root_object = "index.html"

  origin {
    domain_name = aws_s3_bucket.site.bucket_regional_domain_name
    origin_id   = "s3-site-origin"

    s3_origin_config {
      origin_access_identity =
        aws_cloudfront_origin_access_identity.site.cloudfront_access_identity_path
    }
  }

  default_cache_behavior {
    target_origin_id       = "s3-site-origin"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
  }
}

Origin Access Identity (OAI)

An Origin Access Identity (OAI) is used to allow CloudFront to access the private S3 bucket while preventing all direct public access to S3.

resource "aws_cloudfront_origin_access_identity" "site" {
  comment = "OAI for CloudFront private S3 access"
}

Live Demo

https://d3nmn17w739ob6.cloudfront.net/ View the CloudFront Distribution

Previous
Previous

Automated Azure Storage DR Backup Using Data Factory

Next
Next

Serverless Backend API Using API Gateway, Lambda & DynamoDB