Terraform files

This commit is contained in:
Alex Hyett 2021-01-14 09:15:06 +00:00
parent f34931256c
commit 445397df40
10 changed files with 455 additions and 1 deletions

View file

@ -1,2 +1,91 @@
# terraform-s3-static-website
Terraform for hosting a secure static website on AWS using S3
Terraform for hosting a secure static website hosted on AWS using S3.
The terraform will set up the following components:
- S3 bucket for www.yourdomain.com which will host your website files.
- S3 bucket for yourdomain.com which redirects to www.yourdomain.com.
- SSL certificate for yourdomain.com.
- Cloudfront distribution for www S3 bucket.
- Cloudfront distribution for S3 bucket redirect.
- Route 53 records for:
- root
- www
## Using this repository
### Create your S3 bucket to store your state files.
Create a new S3 bucket manually to store your state files. This should have the following policy set:
```
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1234567890:root"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::yourdomain-terraform"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1234567890:root"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::yourdomain-terraform/*"
}
]
}
```
Where `1234567890` is replaced with your AWS account ID and `yourdomain-terraform` is replaced with the name of your S3 bucket.
### Update your provider settings
In `providers.tf` update the bucket (`yourdomain-terraform`) and region (`eu-west-1`) parameters to match your setup. Note, the `acm_provider` needs to point to `us-east-1` for Cloudfront to be able to use it.
### Update the variables
In `terraform.tfvars` update the variables with the name of your domain and S3 bucket. You can just replace `yourdomain` with your actual domain.
Generally you will want to call you S3 bucket the name of your domain but if that is not possible (due to a conflict) then you can call it something else.
## Commands to run
- `terraform init` - To initialise the project and download any required packages.
- `terraform plan` - To see what has changed compared to your current configuration.
- `terrform apply` - To apply your changes.
The terraform scripts are set up to validate your SSL certificate via email. So you should receive an email from AWS to `webmaster@yourdomain.com` when running apply which you need to approve in order for the SSL certificate to be validated.
Alternatively, I have left the code for DNS validation in which can be uncommented if you don't have email set up (`acm.tf` and `route53.tf`).
Note however, that DNS validation requires the domain nameservers to already be pointing to AWS. You won't actually know the nameservers until after the NS route 53 record has been created. Alternatively, you can follow the validation instructions from the ACM page for your domain and apply to where your nameservers are currently hosted. DNS validation can take 30 minutes or more during which the terraform script will still be running.
## Post Changes
Once the terraform scripts have been run successfully you will need to upload your scripts to your `www.yourdomain.com` S3 bucket.
At the very least you need an `index.html` and a `404.html` file.
You can upload the contents of a directory to your S3 bucket by using the command:
```
aws s3 sync . s3://www.yourdomain.com
```
Whenever you make changes to the files in your S3 bucket you need to invalidate the Cloudfront cache.
```
aws cloudfront create-invalidation --distribution-id E3EDVELPIKTLHJ --paths "/*";
```
Where `E3EDVELPIKTLHJ` is the Cloudfront ID associated with your www S3 bucket.

37
src/.terraform.lock.hcl Executable file
View file

@ -0,0 +1,37 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.23.0"
constraints = "~> 3.0"
hashes = [
"h1:tSznQxPJvolDnmqqaTK9SsJ0bluTws7OAWcnc1t0ABs=",
"zh:30b0733027c00472618da998bc77967c692e238ae117c07e046fdd7336b83fa3",
"zh:3677550a8bef8e01c67cb615407dc8a69d32f4e36017033cd6f71a831c99d5de",
"zh:3c2fb4c14bfd43cf20ee25d0068ce09f1d48758408b8f1c88a096cea243612b3",
"zh:5577543322003693c4fe24a69ed0d47e58f867426fd704fac94cf5c16d3d6153",
"zh:6771f09d76ad01ffc04baa3bce7a3eed09f6a8a949274ffbd9d11756a58a4329",
"zh:7a57b79d304d17cf52ee3ddce91679f6b4289c5bdda2e31b763bf7d512e542d9",
"zh:815fb027e17bfe754b05367d20bd0694726a95a99b81e8d939ddd44e2b1f05a9",
"zh:a3d67db5ec0f4e9750eb19676a9a1aff36b0721e276a4ba789f42b991bf5951c",
"zh:cd67ff33860ad578172c19412ce608ba818e7590083197df2b793f870d6f50a3",
"zh:fbe0835055d1260fb77ad19a32a8726248ba7ac187f6c463ded90737b4cea8e6",
]
}
provider "registry.terraform.io/hashicorp/template" {
version = "2.2.0"
hashes = [
"h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=",
"zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386",
"zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53",
"zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603",
"zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16",
"zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776",
"zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451",
"zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae",
"zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde",
"zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d",
"zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2",
]
}

21
src/acm.tf Normal file
View file

@ -0,0 +1,21 @@
# SSL Certificate
resource "aws_acm_certificate" "ssl_certificate" {
provider = aws.acm_provider
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
validation_method = "EMAIL"
#validation_method = "DNS"
tags = var.common_tags
lifecycle {
create_before_destroy = true
}
}
# Uncomment the validation_record_fqdns line if you do DNS validation instead of Email.
resource "aws_acm_certificate_validation" "cert_validation" {
provider = aws.acm_provider
certificate_arn = aws_acm_certificate.ssl_certificate.arn
#validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

170
src/cloudfront.tf Normal file
View file

@ -0,0 +1,170 @@
# Cloudfront distribution for main s3 site.
resource "aws_cloudfront_distribution" "www_s3_distribution" {
origin {
domain_name = aws_s3_bucket.www_bucket.website_endpoint
origin_id = "S3-www.${var.bucket_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = ["www.${var.domain_name}"]
custom_error_response {
error_caching_min_ttl = 0
error_code = 404
response_code = 200
response_page_path = "/404.html"
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-www.${var.bucket_name}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 31536000
default_ttl = 31536000
max_ttl = 31536000
compress = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.1_2016"
}
tags = var.common_tags
}
# Cloudfront S3 for redirect to www.
resource "aws_cloudfront_distribution" "root_s3_distribution" {
origin {
domain_name = aws_s3_bucket.root_bucket.website_endpoint
origin_id = "S3-.${var.bucket_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
aliases = [var.domain_name]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-.${var.bucket_name}"
forwarded_values {
query_string = true
cookies {
forward = "none"
}
headers = ["Origin"]
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.1_2016"
}
tags = var.common_tags
}
# Devup Cloudfront
resource "aws_cloudfront_distribution" "devup_s3_distribution" {
origin {
domain_name = aws_s3_bucket.devup_bucket.website_endpoint
origin_id = "S3-devup.${var.bucket_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = ["devup.${var.domain_name}"]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-devup.${var.bucket_name}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 31536000
default_ttl = 31536000
max_ttl = 31536000
compress = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.1_2016"
}
tags = var.common_tags
}

25
src/providers.tf Normal file
View file

@ -0,0 +1,25 @@
terraform {
required_version = "~> 0.14"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "yourdomain-terraform"
key = "prod/terraform.tfstate"
region = "eu-west-1"
}
}
provider "aws" {
region = "eu-west-1"
}
provider "aws" {
alias = "acm_provider"
region = "us-east-1"
}

48
src/route53.tf Normal file
View file

@ -0,0 +1,48 @@
# Route 53 for domain
resource "aws_route53_zone" "main" {
name = var.domain_name
tags = var.common_tags
}
resource "aws_route53_record" "root-a" {
zone_id = aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.root_s3_distribution.domain_name
zone_id = aws_cloudfront_distribution.root_s3_distribution.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "www-a" {
zone_id = aws_route53_zone.main.zone_id
name = "www.${var.domain_name}"
type = "A"
alias {
name = aws_cloudfront_distribution.www_s3_distribution.domain_name
zone_id = aws_cloudfront_distribution.www_s3_distribution.hosted_zone_id
evaluate_target_health = false
}
}
# Uncomment the below block if you are doing certificate validation using DNS instead of Email.
#resource "aws_route53_record" "cert_validation" {
# for_each = {
# for dvo in aws_acm_certificate.ssl_certificate.domain_validation_options : dvo.domain_name => {
# name = dvo.resource_record_name
# record = dvo.resource_record_value
# type = dvo.resource_record_type
# zone_id = aws_route53_zone.main.zone_id
# }
# }
#
# allow_overwrite = true
# name = each.value.name
# records = [each.value.record]
# ttl = 60
# type = each.value.type
# zone_id = each.value.zone_id
#}

33
src/s3.tf Normal file
View file

@ -0,0 +1,33 @@
# S3 bucket for website.
resource "aws_s3_bucket" "www_bucket" {
bucket = "www.${var.bucket_name}"
acl = "public-read"
policy = templatefile("templates/s3-policy.json", { bucket = "www.${var.bucket_name}" })
cors_rule {
allowed_headers = ["Authorization", "Content-Length"]
allowed_methods = ["GET", "POST"]
allowed_origins = ["https://www.${var.domain_name}"]
max_age_seconds = 3000
}
website {
index_document = "index.html"
error_document = "404.html"
}
tags = var.common_tags
}
# S3 bucket for redirecting non-www to www.
resource "aws_s3_bucket" "root_bucket" {
bucket = var.bucket_name
acl = "public-read"
policy = templatefile("templates/s3-policy.json", { bucket = var.bucket_name })
website {
redirect_all_requests_to = "https://www.${var.domain_name}"
}
tags = var.common_tags
}

View file

@ -0,0 +1,12 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::${bucket}/*"
}
]
}

6
src/terraform.tfvars Normal file
View file

@ -0,0 +1,6 @@
domain_name = "yourdomain.com"
bucket_name = "yourdomain.com"
common_tags = {
Project = "yourdomain"
}

13
src/variables.tf Normal file
View file

@ -0,0 +1,13 @@
variable "domain_name" {
type = string
description = "The domain name for the website."
}
variable "bucket_name" {
type = string
description = "The name of the bucket without the www. prefix. Normally domain_name."
}
variable "common_tags" {
description = "Common tags you want applied to all components."
}