CloudFront Terraform Configuration #
File and Folder Structure #
The file and folder structure of the Terraform project looks like this:
aws-cloudfront-terraform
├── certificate.tf
├── cloudfront.tf
├── outputs.tf
├── s3_bucket.tf
├── terraform.tf
└── variables.tf
Project Folder #
# Create Terraform project folder
TF_PROJECT_NAME=aws-cloudfront-terraform
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME
Terraform Provider #
- terraform.tf
# Terraform Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Provider AWS Region: us-east-1
provider "aws" {
region = "us-east-1"
}
Variables #
- variables.tf
# Bucket Name
variable "bucketname" {
description = "S3 Bucket Name"
type = string
default = "jkw-cf-dev"
}
# CloudFront Alias
variable "cf_alias" {
description = "CloudFront Alias Name"
type = string
default = "jkw-cf-dev.jklug.work"
}
## Managed Certificate
# Route 53 Hosted Zone ID
variable "route53_zone_id" {
description = "Route 53 Hosted Zone ID"
type = string
default = "Z05838622L1JHJDmyzone"
}
# Route 53 Domain Name
variable "domain_name" {
description = "Route 53 Domain Name"
type = string
default = "jkw-cf-dev.jklug.work"
}
Managed TLS Certificate #
The “aws_acm_certificate_validation” resources is necessary for the dependency of the CloudFront distribution. If the managed certificate is not yet validated, the creation of the CF distribution failes.
- certificate.tf
# AWS Managed TLS Certificate
resource "aws_acm_certificate" "managed_cert" {
domain_name = var.domain_name
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# Route 53 Entry for Certificate Validation
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.managed_cert.domain_validation_options : dvo.domain_name => dvo
}
zone_id = var.route53_zone_id
name = each.value.resource_record_name
type = each.value.resource_record_type
records = [each.value.resource_record_value]
ttl = 300
}
# Certificate Validation Status
resource "aws_acm_certificate_validation" "cert_validation" {
certificate_arn = aws_acm_certificate.managed_cert.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
S3 Bucket #
- s3_bucket.tf
# S3 Bucket
resource "aws_s3_bucket" "s3bucket" {
bucket = var.bucketname
tags = {
Name = "Example-Bucket"
}
}
# Bucket Ownership
resource "aws_s3_bucket_ownership_controls" "s3_ownership" {
bucket = aws_s3_bucket.s3bucket.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
object_ownership = "BucketOwnerEnforced"
Bucket owner has full control over uploaded objects in the S3 bucket
CloudFront Distribution #
- cloudfront.tf
# CloudFront Distribution
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = aws_s3_bucket.s3bucket.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.default.id
origin_id = aws_s3_bucket.s3bucket.id # Use bucket name as origin_id
}
enabled = true
is_ipv6_enabled = false
comment = "Some comment"
default_root_object = "index.html"
aliases = [var.cf_alias]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.s3bucket.id # Use bucket name as origin_id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https" # Redirect HTTP to HTTPS
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
price_class = "PriceClass_200" # Limit delivery to North America and Europe
# Geo Restrictions
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["AT", "DE"]
}
}
tags = {
Environment = "production"
}
# Wait until certificate is validated
depends_on = [aws_acm_certificate_validation.cert_validation]
# Managed TLS Certificate
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.managed_cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
# Route 53 CNAME Record for CloudFront
resource "aws_route53_record" "cloudfront_cname" {
zone_id = var.route53_zone_id
name = var.domain_name
type = "CNAME"
ttl = 300
records = [aws_cloudfront_distribution.s3_distribution.domain_name]
depends_on = [aws_cloudfront_distribution.s3_distribution]
}
## Permissions
# CloudFront AccessControl
resource "aws_cloudfront_origin_access_control" "default" {
name = "s3-oac"
description = "CloudFront access to S3"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# S3 Bucket Access Policy
resource "aws_s3_bucket_policy" "s3bucket_policy" {
bucket = aws_s3_bucket.s3bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.s3bucket.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.s3_distribution.arn
}
}
}]
})
}
Outputs #
- outputs.tf
# CloudFront Endpoint
output "cloudfront_endpoint" {
description = "CloudFront_Endpoint"
value = aws_cloudfront_distribution.s3_distribution.domain_name
}
# S3 Bucket ARN
output "s3bucket_arn" {
description = "S3 Bucket ARN"
value = aws_s3_bucket.s3bucket.arn
}
# Managed Certificate ARN
output "acm_certificate_arn" {
description = "Managed Certificate ARN"
value = aws_acm_certificate.managed_cert.arn
}
Apply Configuration #
Initialize Terraform Project #
This will download and install the AWS Terraform provider:
# Initialize the Terraform project
terraform init
Validate Configuration Files #
# Validates the syntax and structure of Terraform configuration files
terraform validate
# Shell output:
Success! The configuration is valid.
Plan the Deployment #
# Dry run / preview changes before applying them
terraform plan
Apply the Configuration #
The deployment takes about 8 minutes.
# Create network stack
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
Outputs:
acm_certificate_arn = "arn:aws:acm:us-east-1:012345678912:certificate/dd579a0e-4a3d-4004-8085-38071adb732f"
cloudfront_endpoint = "dlvw16p5rrfwk.cloudfront.net"
s3bucket_arn = "arn:aws:s3:::jkw-cf-dev"
Test the CloudFront Distribution #
S3 Bucket #
HTML File #
Create an index.html file:
- index.html
<!DOCTYPE html>
<html>
<head>
<title>jklug.work</title>
</head>
<body>
<h1>Hi there</h1>
<p>Some HTML content </p>
</body>
</html>
Upload HTML File to S3 Bucket #
Copy the index.html file into the S3 Bucket:
# Upload the index.html file
aws s3 cp index.html s3://jkw-cf-dev
Verify CloudFront #
# Curl the CloudFront domain name
curl https://jkw-cf-dev.jklug.work
# Shell output:
<!DOCTYPE html>
<html>
<head>
<title>jklug.work</title>
</head>
<body>
<h1>Hi there</h1>
<p>Some HTML content </p>
</body>
</html>
Cleanup #
Delete HTML File #
Remove the index.html file inside the S3 bucket, otherwise it can’t deleted:
# Delete the index.html file
aws s3 rm s3://jkw-cf-dev/index.html
Delete AWS Resources #
# Delete resources
terraform destroy -auto-approve
Links #
# Terraform Provider for AWS CloudFront Docu
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution