| new file mode 100644 |
| index 0000000..7a048ad |
| --- /dev/null |
| +++ b/infra/prod/status-domain.tf |
| @@ -0,0 +1,115 @@ |
| +# π€ status.elevenmessenger.com β the friendly front for the status bucket. |
| +# CloudFront + ACM rather than the router box on purpose: a status page must |
| +# not be served by the infrastructure it reports on, or it goes dark exactly |
| +# when it matters. DNS rides the DNSimple provider (product account 176940, |
| +# token via env DNSIMPLE_TOKEN/DNSIMPLE_ACCOUNT β never in state or files). |
| + |
| +# ACM certs used by CloudFront must live in us-east-1, wherever the viewer is. |
| +provider "aws" { |
| + alias = "us_east_1" |
| + region = "us-east-1" |
| + |
| + default_tags { |
| + tags = { |
| + project = "eleven" |
| + env = "prod" |
| + managed = "opentofu" |
| + } |
| + } |
| +} |
| + |
| +provider "dnsimple" {} |
| + |
| +locals { |
| + status_host = "status.elevenmessenger.com" |
| +} |
| + |
| +resource "aws_acm_certificate" "status" { |
| + provider = aws.us_east_1 |
| + domain_name = local.status_host |
| + validation_method = "DNS" |
| + |
| + lifecycle { |
| + create_before_destroy = true |
| + } |
| +} |
| + |
| +# One validation record per domain (here: one). trimsuffix strips the zone + |
| +# trailing dot ACM includes, because DNSimple wants the relative name. |
| +resource "dnsimple_zone_record" "status_validation" { |
| + for_each = { |
| + for dvo in aws_acm_certificate.status.domain_validation_options : dvo.domain_name => { |
| + name = trimsuffix(dvo.resource_record_name, ".elevenmessenger.com.") |
| + type = dvo.resource_record_type |
| + value = trimsuffix(dvo.resource_record_value, ".") |
| + } |
| + } |
| + |
| + zone_name = "elevenmessenger.com" |
| + name = each.value.name |
| + type = each.value.type |
| + value = each.value.value |
| + ttl = 300 |
| +} |
| + |
| +resource "aws_acm_certificate_validation" "status" { |
| + provider = aws.us_east_1 |
| + certificate_arn = aws_acm_certificate.status.arn |
| + validation_record_fqdns = [ |
| + for dvo in aws_acm_certificate.status.domain_validation_options : trimsuffix(dvo.resource_record_name, ".") |
| + ] |
| + |
| + depends_on = [dnsimple_zone_record.status_validation] |
| +} |
| + |
| +resource "aws_cloudfront_distribution" "status" { |
| + enabled = true |
| + comment = "eleven public status dashboards" |
| + aliases = [local.status_host] |
| + default_root_object = "index.html" |
| + price_class = "PriceClass_100" # EU+NA edges β where members are |
| + |
| + origin { |
| + # The bucket's REST endpoint: objects are public-read by design, so no |
| + # origin access control is needed β CloudFront is here for the name and |
| + # the cert, not for access mediation. |
| + domain_name = "${aws_s3_bucket.status.bucket}.s3.eu-west-1.amazonaws.com" |
| + origin_id = "status-bucket" |
| + } |
| + |
| + default_cache_behavior { |
| + target_origin_id = "status-bucket" |
| + viewer_protocol_policy = "redirect-to-https" |
| + allowed_methods = ["GET", "HEAD"] |
| + cached_methods = ["GET", "HEAD"] |
| + compress = true |
| + # AWS managed CachingOptimized policy: honors the origin's Cache-Control, |
| + # which the publisher sets deliberately (JSON 60s, pages 300s) β the edge |
| + # inherits the freshness contract instead of inventing its own. |
| + cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" |
| + } |
| + |
| + restrictions { |
| + geo_restriction { |
| + restriction_type = "none" |
| + } |
| + } |
| + |
| + viewer_certificate { |
| + acm_certificate_arn = aws_acm_certificate_validation.status.certificate_arn |
| + ssl_support_method = "sni-only" |
| + minimum_protocol_version = "TLSv1.2_2021" |
| + } |
| +} |
| + |
| +resource "dnsimple_zone_record" "status" { |
| + zone_name = "elevenmessenger.com" |
| + name = "status" |
| + type = "CNAME" |
| + value = aws_cloudfront_distribution.status.domain_name |
| + ttl = 300 |
| +} |
| + |
| +output "status_domain_url" { |
| + value = "https://${local.status_host}/" |
| +} |