Static blog with S3 and Cloudfront using Terraform
Intro
Using Amazon S3 and CloudFront is a nice combination for static web hosting, which after hand, was pretty easy to set up.
Terraform
Terraform is a nice tool which lets us provision infrastructure using code. So
instead of clicking around in the AWS console, we run terraform apply
and it
creates infrastructure defined the the terraform files.
First thing i did was creating a S3 bucket and DynamoDB table to store the
terraform state in. For this, i used Cloudposse tfstate-backend
module. This was
rather simple, in main.tf
at root directory of the terraform project, insert:
1module "terraform_state_backend" {
2 source = "cloudposse/tfstate-backend/aws"
3 version = "0.38.1"
4 name = "TF_State_s3bucket_name"
5 attributes = ["state"]
6
7 terraform_backend_config_file_path = "."
8 terraform_backend_config_file_name = "backend.tf"
9 force_destroy = false
10}
and after:
terraform init
, which downloads the terraform modules and providers.terraform apply
, which creates state bucket and DynamoDB lock table (along with other specified resources). State is still stored locally.- Running
terraform init -force-copy
moves our state to the S3 backend.
S3
Creating a bucket in terraform:
1resource "aws_s3_bucket" "bkt" {
2 bucket = var.domain
3
4 tags = {
5 Name = ".."
6 Environment = ".."
7 # Tag = "field"
8 }
9}
10
11resource "aws_s3_bucket_acl" "pub-acl" {
12 bucket = aws_s3_bucket.bkt.id
13 acl = "public-read"
14}
15
16
17resource "aws_s3_bucket_website_configuration" "s3_bkt_web_conf" {
18 bucket = aws_s3_bucket.bkt.id
19 index_document {
20 suffix = "index.html"
21 }
22
23 error_document {
24 key = "error.html"
25 }
26}
Nothing too complex here. Made it public so people can access it (and CloudFront). Might look into origin access identity (OAI) later.
Amazon Certificate Manager (ACM)
To use SSL/TLS with CloudFront, we need an ACM certificate. This needs to be in
the us-east-1 region and since my default region is set to eu-north-1, i needed
to create a new provider for terraform, so i added this in the main.tf
file:
1provider "aws" {
2 alias = "east_1_provider"
3 region = "us-east-1"
4}
Then use the provider inside the aws_acm_certificate
resource like so:
1resource "aws_acm_certificate" "cert" {
2 provider = aws.east_1_provider
3 domain_name = var.domain
4 validation_method = "DNS"
5
6 tags = {
7 Description = "Certificate for ${var.domain}"
8 }
9
10}
I initially created this in the web console, but was easily importable into terraform with its ARN:
1terraform import aws_acm_certificate.cert "arn:aws:acm:us-east-1:012345678901:certificate/11111111-2222-3333-aaaa-111111111111"
CloudFront
Ran into issues here. When specifying the domain_name
you’re supposed to use
the website endpoint and not the S3 bucket itself. When changing the
domain_name
to the website endpont, i ran into a error:
1aws_cloudfront_distribution.s3_distribution: InvalidArgument: The parameter
2Origin DomainName does not refer to a valid S3 bucket.
The problem was that i did not specify the custom_origin_config
block, which
was supposedly required.
In the viewer_certificate
block, we specify our ACM certificate ARN created earlier.
1resource "aws_cloudfront_distribution" "s3_distribution" {
2 origin {
3 domain_name = aws_s3_bucket_website_configuration.s3_bkt_web_conf.website_endpoint
4 origin_id = "myWebsiteS3"
5 custom_origin_config {
6 http_port = "80"
7 https_port = "443"
8 origin_protocol_policy = "http-only"
9 origin_ssl_protocols = ["TLSv1.2"]
10 }
11 }
12 restrictions {
13 geo_restriction {
14 restriction_type = "none"
15 }
16 }
17
18 enabled = true
19 is_ipv6_enabled = true
20 default_root_object = "index.html"
21 viewer_certificate {
22 acm_certificate_arn = aws_acm_certificate.cert.arn
23 ssl_support_method = "sni-only"
24 }
25
26
27 aliases = ["cederqvist.dev"]
28
29 default_cache_behavior {
30 allowed_methods = ["GET", "HEAD"]
31 cached_methods = ["GET", "HEAD"]
32 target_origin_id = "myWebsiteS3"
33
34 cache_policy_id = data.aws_cloudfront_cache_policy.policy.id
35 viewer_protocol_policy = "redirect-to-https"
36 min_ttl = 0
37 default_ttl = 7200
38 max_ttl = 86400
39 }
40
41 price_class = "PriceClass_200"
42
43 tags = {
44 Description = "CDN for ${var.domain}"
45 }
46
47}
48
49data "aws_cloudfront_cache_policy" "policy" {
50 id = var.cache_optimized_policy
51}
Route53 records
For route53 records, i did these manually.. as i had lots of records already created and did not have the energy for importing them into terraform, maybe i’ll do this later.
To route the traffic to my CloudFront distribution, i created an aliased A type record pointing to my CloudFront distribution. Also created AAAA record for IPV6, because no reason not to.