Terraform Recipes: CloudFront distribution from an S3 bucket
In this new short series of articles, I want to share Terraform recipes to common tasks. These will be highly opinionated (as everything on this site is), but I believe that these are fairly close to the ideal approach.
Today’s goals 💪
- Create an S3 bucket to store static website assets in;
- Secure the bucket so that it is not accessible directly;
- Create a CloudFront distribution with the S3 bucket as an origin.
Creating the correct identity 🆔
Somewhat counter-intuitively perhaps, the first thing we should set up is the CloudFront Origin Access Identity that CloudFront will use to access the S3 bucket. Fortunately, this is also the most easy part.
###################################
# CloudFront Origin Access Identity
###################################
resource "aws_cloudfront_origin_access_identity" "gitbook" {
comment = "gitbook"
}
Setting up the permissions 💂♂️
With that in place, we can prepare a data
resource that will later be attached to the S3 bucket. As this is a data
resource, it will not create any actual AWS resources.
###################################
# IAM Policy Document
###################################
data "aws_iam_policy_document" "read_gitbook_bucket" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.gitbook.arn}/*"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.gitbook.iam_arn]
}
}
statement {
actions = ["s3:ListBucket"]
resources = [aws_s3_bucket.gitbook.arn]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.gitbook.iam_arn]
}
}
}
Creating the Bucket 🗑️
Finally, we’re ready to create the bucket and attach the correct access policy to it. While we’re at it, let’s also block any future public access to the bucket.
###################################
# S3
###################################
resource "aws_s3_bucket" "gitbook" {
bucket = "gitbook.milanvit.net"
acl = "public-read"
website {
index_document = "index.html"
}
}
###################################
# S3 Bucket Policy
###################################
resource "aws_s3_bucket_policy" "read_gitbook" {
bucket = aws_s3_bucket.gitbook.id
policy = data.aws_iam_policy_document.read_gitbook_bucket.json
}
###################################
# S3 Bucket Public Access Block
###################################
resource "aws_s3_bucket_public_access_block" "gitbook" {
bucket = aws_s3_bucket.gitbook.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = false
}
Spread out the word 🗣️
Finally, we can create the CloudFront distribution. Bear in mind that most changes to CloudFront take between 5-10 minutes to propagate.
There are two references to resources that we haven’t created in this article (web_acl_id
and the viewer_certificate
section), so feel free to delete the first one, and replace the content of the required viewer_certificate
section with cloudfront_default_certificate = true
.
The entire definition is quite lengthy, but in most cases, it really is mostly default values.
###################################
# CloudFront
###################################
resource "aws_cloudfront_distribution" "gitbook" {
enabled = true
default_root_object = "index.html"
aliases = [aws_s3_bucket.gitbook.bucket]
# Huh? Is the next line a spoiler of a future article?
web_acl_id = aws_waf_web_acl.gitbook.id
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.gitbook.bucket
viewer_protocol_policy = "redirect-to-https"
compress = true
min_ttl = 0
default_ttl = 5 * 60
max_ttl = 60 * 60
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
}
origin {
domain_name = aws_s3_bucket.gitbook.bucket_regional_domain_name
origin_id = aws_s3_bucket.gitbook.bucket
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.gitbook.cloudfront_access_identity_path
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
# Huh? Another spoiler?
acm_certificate_arn = aws_acm_certificate_validation.cf_gitbook.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2018"
}
}
And you should be good to go! Don’t forget to create a CNAME
DNS record that points to the value of aws_cloudfront_distribution.gitbook.domain_name
, unless you’re fine with the ugly subdomain names that CloudFront generates for you.
Let me know in the comments if you’d be interested in hearing how to correctly set up permissions for a new IAM user & configure CircleCI for automated deployments to the S3 bucket!