| 1 | # π€ status.elevenmessenger.com β the friendly front for the status bucket. |
| 2 | # CloudFront + ACM rather than the router box on purpose: a status page must |
| 3 | # not be served by the infrastructure it reports on, or it goes dark exactly |
| 4 | # when it matters. DNS rides the DNSimple provider (product account 176940, |
| 5 | # token via env DNSIMPLE_TOKEN/DNSIMPLE_ACCOUNT β never in state or files). |
| 6 | |
| 7 | # ACM certs used by CloudFront must live in us-east-1, wherever the viewer is. |
| 8 | provider "aws" { |
| 9 | alias = "us_east_1" |
| 10 | region = "us-east-1" |
| 11 | |
| 12 | default_tags { |
| 13 | tags = { |
| 14 | project = "eleven" |
| 15 | env = "prod" |
| 16 | managed = "opentofu" |
| 17 | } |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | provider "dnsimple" {} |
| 22 | |
| 23 | locals { |
| 24 | status_host = "status.elevenmessenger.com" |
| 25 | } |
| 26 | |
| 27 | resource "aws_acm_certificate" "status" { |
| 28 | provider = aws.us_east_1 |
| 29 | domain_name = local.status_host |
| 30 | validation_method = "DNS" |
| 31 | |
| 32 | lifecycle { |
| 33 | create_before_destroy = true |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | # One validation record per domain (here: one). trimsuffix strips the zone + |
| 38 | # trailing dot ACM includes, because DNSimple wants the relative name. |
| 39 | resource "dnsimple_zone_record" "status_validation" { |
| 40 | for_each = { |
| 41 | for dvo in aws_acm_certificate.status.domain_validation_options : dvo.domain_name => { |
| 42 | name = trimsuffix(dvo.resource_record_name, ".elevenmessenger.com.") |
| 43 | type = dvo.resource_record_type |
| 44 | value = trimsuffix(dvo.resource_record_value, ".") |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | zone_name = "elevenmessenger.com" |
| 49 | name = each.value.name |
| 50 | type = each.value.type |
| 51 | value = each.value.value |
| 52 | ttl = 300 |
| 53 | } |
| 54 | |
| 55 | resource "aws_acm_certificate_validation" "status" { |
| 56 | provider = aws.us_east_1 |
| 57 | certificate_arn = aws_acm_certificate.status.arn |
| 58 | validation_record_fqdns = [ |
| 59 | for dvo in aws_acm_certificate.status.domain_validation_options : trimsuffix(dvo.resource_record_name, ".") |
| 60 | ] |
| 61 | |
| 62 | depends_on = [dnsimple_zone_record.status_validation] |
| 63 | } |
| 64 | |
| 65 | resource "aws_cloudfront_distribution" "status" { |
| 66 | enabled = true |
| 67 | comment = "eleven public status dashboards" |
| 68 | aliases = [local.status_host] |
| 69 | default_root_object = "index.html" |
| 70 | price_class = "PriceClass_100" # EU+NA edges β where members are |
| 71 | |
| 72 | origin { |
| 73 | # The bucket's REST endpoint: objects are public-read by design, so no |
| 74 | # origin access control is needed β CloudFront is here for the name and |
| 75 | # the cert, not for access mediation. |
| 76 | domain_name = "${aws_s3_bucket.status.bucket}.s3.eu-west-1.amazonaws.com" |
| 77 | origin_id = "status-bucket" |
| 78 | } |
| 79 | |
| 80 | default_cache_behavior { |
| 81 | target_origin_id = "status-bucket" |
| 82 | viewer_protocol_policy = "redirect-to-https" |
| 83 | allowed_methods = ["GET", "HEAD"] |
| 84 | cached_methods = ["GET", "HEAD"] |
| 85 | compress = true |
| 86 | # AWS managed CachingOptimized policy: honors the origin's Cache-Control, |
| 87 | # which the publisher sets deliberately (JSON 60s, pages 300s) β the edge |
| 88 | # inherits the freshness contract instead of inventing its own. |
| 89 | cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" |
| 90 | } |
| 91 | |
| 92 | restrictions { |
| 93 | geo_restriction { |
| 94 | restriction_type = "none" |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | viewer_certificate { |
| 99 | acm_certificate_arn = aws_acm_certificate_validation.status.certificate_arn |
| 100 | ssl_support_method = "sni-only" |
| 101 | minimum_protocol_version = "TLSv1.2_2021" |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | resource "dnsimple_zone_record" "status" { |
| 106 | zone_name = "elevenmessenger.com" |
| 107 | name = "status" |
| 108 | type = "CNAME" |
| 109 | value = aws_cloudfront_distribution.status.domain_name |
| 110 | ttl = 300 |
| 111 | } |
| 112 | |
| 113 | output "status_domain_url" { |
| 114 | value = "https://${local.status_host}/" |
| 115 | } |
| 116 | |