diff --git a/infra/main.tf b/infra/main.tf index 78e5054..5fef80e 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -53,6 +53,12 @@ variable "scanning" { default = true } +variable "domain" { + description = "Domain name for the site (e.g., everytab.site)" + type = string + default = "everytab.site" +} + # --- Data sources --- data "aws_ami" "al2023" { @@ -190,6 +196,96 @@ resource "aws_s3_bucket" "site" { bucket = "everytab-site" } +# --- ACM Certificate (must be us-east-1 for CloudFront) --- + +resource "aws_acm_certificate" "site" { + domain_name = var.domain + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } +} + +# This resource waits until the cert is validated (you must add the DNS record in Gandi first) +resource "aws_acm_certificate_validation" "site" { + certificate_arn = aws_acm_certificate.site.arn +} + +# --- CloudFront --- + +resource "aws_cloudfront_origin_access_control" "site" { + name = "everytab-site-oac" + origin_access_control_origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" +} + +resource "aws_cloudfront_distribution" "site" { + enabled = true + default_root_object = "index.html" + aliases = [var.domain] + price_class = "PriceClass_100" # US + Europe only (cheapest) + + origin { + domain_name = aws_s3_bucket.site.bucket_regional_domain_name + origin_id = "s3-site" + origin_access_control_id = aws_cloudfront_origin_access_control.site.id + } + + default_cache_behavior { + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "s3-site" + viewer_protocol_policy = "redirect-to-https" + compress = true + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } + + # Long cache for bundles, short for HTML/JS during development + min_ttl = 0 + default_ttl = 86400 # 1 day + max_ttl = 31536000 # 1 year + } + + viewer_certificate { + acm_certificate_arn = aws_acm_certificate_validation.site.certificate_arn + ssl_support_method = "sni-only" + minimum_protocol_version = "TLSv1.2_2021" + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } +} + +# S3 bucket policy: allow CloudFront OAC to read +resource "aws_s3_bucket_policy" "site" { + bucket = aws_s3_bucket.site.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { Service = "cloudfront.amazonaws.com" } + Action = "s3:GetObject" + Resource = "${aws_s3_bucket.site.arn}/*" + Condition = { + StringEquals = { + "AWS:SourceArn" = aws_cloudfront_distribution.site.arn + } + } + }] + }) +} + # --- RDS --- resource "aws_db_subnet_group" "main" { @@ -261,3 +357,29 @@ output "ssh_private_key" { output "ssh_command" { value = var.scanning ? "ssh -i everytab-key ec2-user@${aws_instance.main[0].public_ip}" : null } + +output "cloudfront_domain" { + value = aws_cloudfront_distribution.site.domain_name +} + +output "cloudfront_distribution_id" { + value = aws_cloudfront_distribution.site.id +} + +# DNS records to add in Gandi: +# 1. ACM certificate validation (one-time, add this CNAME then wait for validation) +# 2. Domain pointing to CloudFront (CNAME or ALIAS for bare domain) +output "acm_validation_records" { + value = { + for dvo in aws_acm_certificate.site.domain_validation_options : dvo.domain_name => { + type = dvo.resource_record_type + name = dvo.resource_record_name + value = dvo.resource_record_value + } + } +} + +output "dns_cname_target" { + description = "Point your domain to this CloudFront distribution (ALIAS/CNAME in Gandi)" + value = aws_cloudfront_distribution.site.domain_name +}