terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } backend "s3" { bucket = "gregh-terraform-state" key = "gregh-dev/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "gregh-terraform-locks" } } provider "aws" { region = "us-east-1" profile = "production" } # S3 bucket for static website resource "aws_s3_bucket" "website" { bucket = "gregh.dev" tags = { Name = "gregh.dev" Environment = "production" } } resource "aws_s3_bucket_public_access_block" "website" { bucket = aws_s3_bucket.website.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } resource "aws_s3_bucket_versioning" "website" { bucket = aws_s3_bucket.website.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "website" { bucket = aws_s3_bucket.website.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } # CloudFront Origin Access Control resource "aws_cloudfront_origin_access_control" "website" { name = "gregh-dev-oac" description = "OAC for gregh.dev" origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" } # S3 bucket policy to allow CloudFront access resource "aws_s3_bucket_policy" "website" { bucket = aws_s3_bucket.website.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "AllowCloudFrontServicePrincipal" Effect = "Allow" Principal = { Service = "cloudfront.amazonaws.com" } Action = "s3:GetObject" Resource = "${aws_s3_bucket.website.arn}/*" Condition = { StringEquals = { "AWS:SourceArn" = aws_cloudfront_distribution.website.arn } } } ] }) } # ACM Certificate for CloudFront (must be in us-east-1) resource "aws_acm_certificate" "website" { domain_name = "gregh.dev" subject_alternative_names = ["www.gregh.dev"] validation_method = "DNS" lifecycle { create_before_destroy = true } tags = { Name = "gregh.dev" } } # CloudFront distribution resource "aws_cloudfront_distribution" "website" { enabled = true is_ipv6_enabled = true comment = "gregh.dev static website" default_root_object = "index.html" price_class = "PriceClass_100" aliases = ["gregh.dev", "www.gregh.dev"] origin { domain_name = aws_s3_bucket.website.bucket_regional_domain_name origin_id = "S3-gregh.dev" origin_access_control_id = aws_cloudfront_origin_access_control.website.id } default_cache_behavior { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3-gregh.dev" viewer_protocol_policy = "redirect-to-https" compress = true forwarded_values { query_string = false cookies { forward = "none" } } min_ttl = 0 default_ttl = 3600 max_ttl = 86400 } custom_error_response { error_code = 404 response_code = 404 response_page_path = "/index.html" } custom_error_response { error_code = 403 response_code = 404 response_page_path = "/index.html" } restrictions { geo_restriction { restriction_type = "none" } } viewer_certificate { acm_certificate_arn = aws_acm_certificate.website.arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.2_2021" } tags = { Name = "gregh.dev" Environment = "production" } } # Route53 Hosted Zone resource "aws_route53_zone" "website" { name = "gregh.dev" tags = { Name = "gregh.dev" Environment = "production" } } # Route53 records for ACM validation resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.website.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = aws_route53_zone.website.zone_id } # ACM certificate validation resource "aws_acm_certificate_validation" "website" { certificate_arn = aws_acm_certificate.website.arn validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] } # Route53 A record for apex domain resource "aws_route53_record" "website_apex" { zone_id = aws_route53_zone.website.zone_id name = "gregh.dev" type = "A" alias { name = aws_cloudfront_distribution.website.domain_name zone_id = aws_cloudfront_distribution.website.hosted_zone_id evaluate_target_health = false } } # Route53 A record for www subdomain resource "aws_route53_record" "website_www" { zone_id = aws_route53_zone.website.zone_id name = "www.gregh.dev" type = "A" alias { name = aws_cloudfront_distribution.website.domain_name zone_id = aws_cloudfront_distribution.website.hosted_zone_id evaluate_target_health = false } } # Outputs output "nameservers" { value = aws_route53_zone.website.name_servers description = "Route53 nameservers - update these at your domain registrar" } output "s3_bucket_name" { value = aws_s3_bucket.website.id description = "Name of the S3 bucket" } output "cloudfront_distribution_id" { value = aws_cloudfront_distribution.website.id description = "CloudFront distribution ID" } output "cloudfront_domain_name" { value = aws_cloudfront_distribution.website.domain_name description = "CloudFront distribution domain name" }