Alex's Ramblings

AWS S3 Bucket Website Redirect through Terraform

April 9, 2019

If you ever needed to create a quick website redirect without using any servers you can do so very easily using this Terraform script I’ve created. It will create a SSL key via ACM, S3 Bucket with the redirect setup, A Cloudfront instance and a Route 53 record. You can also endlessy customise it and remove modules as needed. I’ve added helpful comments which show where the next module starts.

First you’ll need to run the following:

apt update
apt install terraform
mkdir s3-redirect
cd s3-redirect

Then paste the following into a file named main.tf (make sure you edit your domains, access and secret keys):

provider "aws" {
  access_key = "YOUR_ACCESS_KEY"
  secret_key = "YOUR_SECRET_KEY"
  region = "us-east-1"
}

variable "root_domain_name" {
  default = "example.com"
}

variable "www_domain_name" {
  default = "www.example.com"
}

variable "redirect_domain_name" {
  default = "redirect-example.com"
}

locals {
  origin_id = "${aws_s3_bucket.www.bucket}"
  cloudfront-authentication-user-agent = "somethingRandomEen8Ohta"
}

#
# S3 bucket for redirections
#

resource "aws_s3_bucket" "www" {
  bucket = "${var.root_domain_name}"
  acl    = "public-read"
  policy = <<POLICY
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"AddPerm",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::${var.root_domain_name}/*"]
    }
  ]
}
POLICY

  website {
    redirect_all_requests_to = "https://${var.redirect_domain_name}"
  }
}

#
# ACM cert and Route 53 validation
#

resource "aws_acm_certificate" "cert" {
  domain_name       = "${var.root_domain_name}"
  validation_method = "DNS"
  subject_alternative_names = ["www.${var.root_domain_name}"]
}

data "aws_route53_zone" "zone" {
  name         = "${var.root_domain_name}."
  private_zone = false
}

resource "aws_route53_record" "cert_validation" {
  name    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
  type    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
  zone_id = "${data.aws_route53_zone.zone.id}"
  records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
  ttl     = 60
}

resource "aws_route53_record" "cert_validation_www" {
  name    = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_name}"
  type    = "${aws_acm_certificate.cert.domain_validation_options.1.resource_record_type}"
  zone_id = "${data.aws_route53_zone.zone.id}"
  records = ["${aws_acm_certificate.cert.domain_validation_options.1.resource_record_value}"]
  ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
  certificate_arn         = "${aws_acm_certificate.cert.arn}"
  validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}", "${aws_route53_record.cert_validation_www.fqdn}"]
}


#
# Cloudfront Distribution for S3
#

resource "aws_cloudfront_distribution" "distribution" {
  origin {
    domain_name = "${aws_s3_bucket.www.website_endpoint}"
    origin_id   = "${local.origin_id}"
    custom_origin_config {
      http_port              = "80"
      https_port             = "443"
      origin_protocol_policy = "http-only"
      origin_ssl_protocols   = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
    custom_header {
      name  = "User-Agent"
      value = "${local.cloudfront-authentication-user-agent}"
    }
  }

  enabled = true

  default_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["HEAD", "GET", "OPTIONS"]
    target_origin_id = "${local.origin_id}"
    forwarded_values {
      query_string = false
      cookies {
        forward = "all"
      }
    }
    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    acm_certificate_arn      = "${aws_acm_certificate.cert.arn}"
    ssl_support_method       = "sni-only"
  }

  aliases = ["${var.root_domain_name}", "${var.www_domain_name}"]
  depends_on = ["aws_s3_bucket.www", "aws_acm_certificate_validation.cert"]
}

#
# Route 53 DNS Record for Cloudfront
#

resource "aws_route53_record" "root" {
  zone_id = "${data.aws_route53_zone.zone.id}"
  name    = "${var.root_domain_name}"
  type    = "A"

  alias = {
    name                   = "${aws_cloudfront_distribution.distribution.domain_name}"
    zone_id                = "${aws_cloudfront_distribution.distribution.hosted_zone_id}"
    evaluate_target_health = false
  }

  depends_on = ["aws_s3_bucket.www", "aws_acm_certificate_validation.cert", "aws_cloudfront_distribution.distribution"]
}

resource "aws_route53_record" "www" {
  zone_id = "${data.aws_route53_zone.zone.id}"
  name    = "${var.www_domain_name}"
  type    = "A"

  alias = {
    name                   = "${aws_cloudfront_distribution.distribution.domain_name}"
    zone_id                = "${aws_cloudfront_distribution.distribution.hosted_zone_id}"
    evaluate_target_health = false
  }

  depends_on = ["aws_s3_bucket.www", "aws_acm_certificate_validation.cert", "aws_cloudfront_distribution.distribution"]
}

To run it you will first need to initialise Terraform on that specific project using: terraform init

Then run the apply function to run the script: terraform apply

If you encounter any errors you can re-run the script as Terraform saves the current state automatically.