Terraform files
This commit is contained in:
parent
f34931256c
commit
445397df40
10 changed files with 455 additions and 1 deletions
91
README.md
91
README.md
|
@ -1,2 +1,91 @@
|
||||||
# terraform-s3-static-website
|
# 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
37
src/.terraform.lock.hcl
Executable 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
21
src/acm.tf
Normal 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
170
src/cloudfront.tf
Normal 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
25
src/providers.tf
Normal 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
48
src/route53.tf
Normal 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
33
src/s3.tf
Normal 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
|
||||||
|
}
|
12
src/templates/s3-policy.json
Normal file
12
src/templates/s3-policy.json
Normal 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
6
src/terraform.tfvars
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
domain_name = "yourdomain.com"
|
||||||
|
bucket_name = "yourdomain.com"
|
||||||
|
|
||||||
|
common_tags = {
|
||||||
|
Project = "yourdomain"
|
||||||
|
}
|
13
src/variables.tf
Normal file
13
src/variables.tf
Normal 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."
|
||||||
|
}
|
Loading…
Reference in a new issue