|
| 1 | +# =========================================== |
| 2 | +# LANDING PAGE - S3 + CloudFront + Route53 |
| 3 | +# =========================================== |
| 4 | +# Standalone terraform configuration for hosting the TF2-QuickServer |
| 5 | +# landing page on a private S3 bucket served via CloudFront with HTTPS. |
| 6 | +# |
| 7 | +# Usage: |
| 8 | +# cd terraform/landing-page |
| 9 | +# terraform init |
| 10 | +# terraform apply -var="hosted_zone_id=Z..." |
| 11 | +# =========================================== |
| 12 | + |
| 13 | +# =========================================== |
| 14 | +# RANDOM SUFFIX FOR UNIQUE BUCKET NAME |
| 15 | +# =========================================== |
| 16 | + |
| 17 | +resource "random_id" "bucket_suffix" { |
| 18 | + byte_length = 6 |
| 19 | +} |
| 20 | + |
| 21 | +# =========================================== |
| 22 | +# S3 BUCKET (PRIVATE) |
| 23 | +# =========================================== |
| 24 | + |
| 25 | +resource "aws_s3_bucket" "landing_page" { |
| 26 | + bucket = "${var.bucket_name}-${random_id.bucket_suffix.hex}" |
| 27 | + force_destroy = true |
| 28 | +} |
| 29 | + |
| 30 | +resource "aws_s3_bucket_public_access_block" "landing_page" { |
| 31 | + bucket = aws_s3_bucket.landing_page.id |
| 32 | + |
| 33 | + block_public_acls = true |
| 34 | + block_public_policy = true |
| 35 | + ignore_public_acls = true |
| 36 | + restrict_public_buckets = true |
| 37 | +} |
| 38 | + |
| 39 | +resource "aws_s3_bucket_versioning" "landing_page" { |
| 40 | + bucket = aws_s3_bucket.landing_page.id |
| 41 | + |
| 42 | + versioning_configuration { |
| 43 | + status = "Enabled" |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +resource "aws_s3_bucket_server_side_encryption_configuration" "landing_page" { |
| 48 | + bucket = aws_s3_bucket.landing_page.id |
| 49 | + |
| 50 | + rule { |
| 51 | + apply_server_side_encryption_by_default { |
| 52 | + sse_algorithm = "AES256" |
| 53 | + } |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +# =========================================== |
| 58 | +# LANDING PAGE FILES (UPLOAD) |
| 59 | +# =========================================== |
| 60 | + |
| 61 | +resource "aws_s3_object" "logo" { |
| 62 | + bucket = aws_s3_bucket.landing_page.id |
| 63 | + key = "assets/logo.png" |
| 64 | + source = "${path.module}/../../assets/logo.png" |
| 65 | + content_type = "image/png" |
| 66 | + cache_control = "max-age=604800" |
| 67 | + etag = filemd5("${path.module}/../../assets/logo.png") |
| 68 | +} |
| 69 | + |
| 70 | +resource "aws_s3_object" "index_html" { |
| 71 | + bucket = aws_s3_bucket.landing_page.id |
| 72 | + key = "index.html" |
| 73 | + source = "${path.module}/../../landing-page/index.html" |
| 74 | + content_type = "text/html; charset=utf-8" |
| 75 | + cache_control = "max-age=3600" |
| 76 | + etag = filemd5("${path.module}/../../landing-page/index.html") |
| 77 | +} |
| 78 | + |
| 79 | +resource "aws_s3_object" "script_js" { |
| 80 | + bucket = aws_s3_bucket.landing_page.id |
| 81 | + key = "script.js" |
| 82 | + source = "${path.module}/../../landing-page/script.js" |
| 83 | + content_type = "application/javascript" |
| 84 | + cache_control = "max-age=3600" |
| 85 | + etag = filemd5("${path.module}/../../landing-page/script.js") |
| 86 | +} |
| 87 | + |
| 88 | +resource "aws_s3_object" "style_css" { |
| 89 | + bucket = aws_s3_bucket.landing_page.id |
| 90 | + key = "style.css" |
| 91 | + source = "${path.module}/../../landing-page/style.css" |
| 92 | + content_type = "text/css" |
| 93 | + cache_control = "max-age=3600" |
| 94 | + etag = filemd5("${path.module}/../../landing-page/style.css") |
| 95 | +} |
| 96 | + |
| 97 | +# =========================================== |
| 98 | +# CLOUDFRONT ORIGIN ACCESS IDENTITY |
| 99 | +# =========================================== |
| 100 | + |
| 101 | +resource "aws_cloudfront_origin_access_identity" "landing_page" { |
| 102 | + comment = "OAI for ${var.domain_name}" |
| 103 | +} |
| 104 | + |
| 105 | +# =========================================== |
| 106 | +# S3 BUCKET POLICY (CLOUDFRONT OAI ONLY) |
| 107 | +# =========================================== |
| 108 | + |
| 109 | +resource "aws_s3_bucket_policy" "landing_page" { |
| 110 | + bucket = aws_s3_bucket.landing_page.id |
| 111 | + |
| 112 | + policy = jsonencode({ |
| 113 | + Version = "2012-10-17" |
| 114 | + Statement = [ |
| 115 | + { |
| 116 | + Sid = "CloudFrontOAIRead" |
| 117 | + Effect = "Allow" |
| 118 | + Principal = { |
| 119 | + AWS = aws_cloudfront_origin_access_identity.landing_page.iam_arn |
| 120 | + } |
| 121 | + Action = "s3:GetObject" |
| 122 | + Resource = "${aws_s3_bucket.landing_page.arn}/*" |
| 123 | + } |
| 124 | + ] |
| 125 | + }) |
| 126 | + |
| 127 | + depends_on = [aws_s3_bucket_public_access_block.landing_page] |
| 128 | +} |
| 129 | + |
| 130 | +# =========================================== |
| 131 | +# ACM CERTIFICATE (DNS VALIDATION) |
| 132 | +# =========================================== |
| 133 | + |
| 134 | +resource "aws_acm_certificate" "landing_page" { |
| 135 | + domain_name = var.domain_name |
| 136 | + validation_method = "DNS" |
| 137 | + |
| 138 | + lifecycle { |
| 139 | + create_before_destroy = true |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +resource "aws_route53_record" "cert_validation" { |
| 144 | + for_each = { |
| 145 | + for dvo in aws_acm_certificate.landing_page.domain_validation_options : dvo.domain_name => { |
| 146 | + name = dvo.resource_record_name |
| 147 | + record = dvo.resource_record_value |
| 148 | + type = dvo.resource_record_type |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + allow_overwrite = true |
| 153 | + name = each.value.name |
| 154 | + records = [each.value.record] |
| 155 | + ttl = 60 |
| 156 | + type = each.value.type |
| 157 | + zone_id = var.hosted_zone_id |
| 158 | +} |
| 159 | + |
| 160 | +resource "aws_acm_certificate_validation" "landing_page" { |
| 161 | + certificate_arn = aws_acm_certificate.landing_page.arn |
| 162 | + validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] |
| 163 | +} |
| 164 | + |
| 165 | +# =========================================== |
| 166 | +# CLOUDFRONT DISTRIBUTION |
| 167 | +# =========================================== |
| 168 | + |
| 169 | +data "aws_cloudfront_cache_policy" "caching_optimized" { |
| 170 | + name = "Managed-CachingOptimized" |
| 171 | +} |
| 172 | + |
| 173 | +resource "aws_cloudfront_distribution" "landing_page" { |
| 174 | + enabled = true |
| 175 | + is_ipv6_enabled = true |
| 176 | + http_version = "http2and3" |
| 177 | + default_root_object = "index.html" |
| 178 | + aliases = [var.domain_name] |
| 179 | + |
| 180 | + origin { |
| 181 | + domain_name = aws_s3_bucket.landing_page.bucket_regional_domain_name |
| 182 | + origin_id = "S3-LandingPage" |
| 183 | + |
| 184 | + s3_origin_config { |
| 185 | + origin_access_identity = aws_cloudfront_origin_access_identity.landing_page.cloudfront_access_identity_path |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + default_cache_behavior { |
| 190 | + allowed_methods = ["GET", "HEAD"] |
| 191 | + cached_methods = ["GET", "HEAD"] |
| 192 | + target_origin_id = "S3-LandingPage" |
| 193 | + |
| 194 | + cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id |
| 195 | + |
| 196 | + viewer_protocol_policy = "redirect-to-https" |
| 197 | + compress = true |
| 198 | + } |
| 199 | + |
| 200 | + price_class = "PriceClass_100" |
| 201 | + |
| 202 | + restrictions { |
| 203 | + geo_restriction { |
| 204 | + restriction_type = "none" |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + viewer_certificate { |
| 209 | + acm_certificate_arn = aws_acm_certificate_validation.landing_page.certificate_arn |
| 210 | + ssl_support_method = "sni-only" |
| 211 | + minimum_protocol_version = "TLSv1.2_2021" |
| 212 | + } |
| 213 | + |
| 214 | + depends_on = [ |
| 215 | + aws_s3_object.logo, |
| 216 | + aws_s3_object.index_html, |
| 217 | + aws_s3_object.script_js, |
| 218 | + aws_s3_object.style_css, |
| 219 | + ] |
| 220 | +} |
| 221 | + |
| 222 | +# =========================================== |
| 223 | +# ROUTE53 DNS RECORD |
| 224 | +# =========================================== |
| 225 | + |
| 226 | +resource "aws_route53_record" "landing_page" { |
| 227 | + zone_id = var.hosted_zone_id |
| 228 | + name = var.domain_name |
| 229 | + type = "A" |
| 230 | + |
| 231 | + alias { |
| 232 | + name = aws_cloudfront_distribution.landing_page.domain_name |
| 233 | + zone_id = aws_cloudfront_distribution.landing_page.hosted_zone_id |
| 234 | + evaluate_target_health = false |
| 235 | + } |
| 236 | +} |
0 commit comments