Terraform configuration for S3 static site with CloudFront for https and Cloudflare DNS

Published:

In this example, I am using Cloudflare as my DNS provider, Amazon s3 for the static site hosting, and Cloudfront for https.

Create the S3 Bucket

The first thing you need to do is create an AWS s3 bucket, and a zone in Cloudflare

resource "cloudflare_zone" "jasonraimondi_com" {
  zone = "jasonraimondi.com"
}

resource "aws_s3_bucket" "static_jasonraimondi_com" {
  bucket = "examplebucket"
  policy = file("jasonraimondi_com_bucket_policy.json")
  region = "us-east-1"

  website {
    error_document = "404.html"
    index_document = "index.html"
  }
}

Add content to the bucket

Seed a index.html file into your bucket.

mkdir site
echo "<h1>Hello crazy world!</h1>" > site/index.html
aws s3 sync site s3://examplebucket

Create a cloudfront distribution for the s3 bucket

You need to create both an aws_cloudfront_origin_access_identity and an aws_cloudfront_distribution

resource "aws_cloudfront_origin_access_identity" "jasonraimondi_com" {
  comment = "access-identity-${aws_s3_bucket.static_jasonraimondi_com.bucket_domain_name}"
}

resource "aws_cloudfront_distribution" "jasonraimondi_com" {
  aliases = [
    aws_acm_certificate.jasonraimondi_com.domain_name,
  ]

  comment = "jasonraimondi.com static site"

  origin {
    domain_name = aws_s3_bucket.static_jasonraimondi_com.bucket_domain_name
    origin_id   = "S3-${aws_s3_bucket.static_jasonraimondi_com.id}"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.jasonraimondi_com.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.static_jasonraimondi_com.id}"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations        = []
    }
  }

  viewer_certificate {
    acm_certificate_arn            = aws_acm_certificate.jasonraimondi_com.arn
    cloudfront_default_certificate = false
    minimum_protocol_version       = "TLSv1.2_2018"
    ssl_support_method             = "sni-only"
  }

  custom_error_response {
    error_caching_min_ttl = 300
    error_code            = 400
    response_code         = 404
    response_page_path    = "/404.html"
  }

  custom_error_response {
    error_caching_min_ttl = 300
    error_code            = 403
    response_code         = 404
    response_page_path    = "/404.html"
  }
}

Enable https using Amazon Certificate Manager

We will need to create an using the Amazon Certificate Manager provider. We are also going to need to add a cloudflare_record DNS record that will allow ACM to sign the certificate.

Create an aws_acm_certificate using Amazon Certificate Manager and add with appropriate Cloudflare records.

resource "aws_acm_certificate" "jasonraimondi_com" {
  provider          = aws.east_1
  domain_name       = "jasonraimondi.com"
  validation_method = "DNS"
}

resource "cloudflare_record" "jasonraimondi_com" {
  zone_id = cloudflare_zone.jasonraimondi_com.id
  name    = aws_acm_certificate.jasonraimondi_com.domain_validation_options.0.resource_record_name
  value   = trimsuffix(aws_acm_certificate.jasonraimondi_com.domain_validation_options.0.resource_record_value, ".")
  type    = aws_acm_certificate.jasonraimondi_com.domain_validation_options.0.resource_record_type
  ttl     = 1
}

resource "cloudflare_record" "jasonraimondi_com_root" {
  zone_id = cloudflare_zone.jasonraimondi_com.id
  name    = "@"
  value   = aws_acm_certificate.jasonraimondi_com.domain_name
  type    = "CNAME"
  ttl     = 1
}

Fix the subdirectory index.html navigation issue

You may have noticed that if you have subdirectories such as /about/index.html and you want the user to be able to navigate to https://example.com/about with no success.

You’ll need to add a lamba function to have requests for URI paths that end in “/” are rewritten into “/index.html” before the request is passed on to the CloudFront Origin.

There is a great project that I am using https://github.com/digital-sailors/standard-redirects-for-cloudfront

resource "aws_cloudfront_distribution" "jasonraimondi_com" {
  ...
  default_cache_behavior {
    ...

    lambda_function_association {
      event_type   = "origin-request"
      include_body = false
      lambda_arn   = "arn:aws:lambda:us-east-1:191669008337:function:serverlessrepo-standard-r-StandardRedirectsForClou-9U6H7EG2RX95:1"
    }
  }
}