Skip to content

CloudFront

CloudFront is the only public path to S3. Browsers, mobile clients, and any backend reading assets all hit a CloudFront distribution; S3 is never exposed directly. There is one distribution per environment, both defined in cloudfront.tf.

Distributions

Environment Public alias Origin bucket
dev assets-dev.tomoda.life tomoda-assets-dev
prod assets.tomoda.life tomoda-assets-prod

The alias is set on the distribution itself via the aliases argument and matched against the ACM certificate's domain_name. If the alias and cert ever diverge (e.g. someone renames the asset subdomain in s3.tf's local.assets_subdomain), CloudFront will refuse to serve TLS for the requested host.

Origin Access Control

CloudFront talks to S3 with an OAC, not the legacy OAI. Defined in cloudfront.tf:

resource "aws_cloudfront_origin_access_control" "default" {
  name                              = "${local.bucket_name}-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

signing_behavior = "always" means every origin request CloudFront issues is signed with SigV4 using the distribution's identity. S3's bucket policy (see S3) checks the AWS:SourceArn of the requesting distribution against an exact match, so an attacker who knew the bucket name but not the distribution ARN still cannot read objects.

Cache behaviour

The single default cache behaviour applies to all paths:

default_cache_behavior {
  allowed_methods        = ["GET", "HEAD", "OPTIONS"]
  cached_methods         = ["GET", "HEAD"]
  viewer_protocol_policy = "redirect-to-https"
  min_ttl                = 0
  default_ttl            = 3600   # 1 hour
  max_ttl                = 86400  # 24 hours

  forwarded_values {
    query_string = false
    cookies { forward = "none" }
  }
}

Implications:

  • HTTP is redirected to HTTPS at the edge — there is no plaintext path to any asset.
  • Query strings and cookies are stripped before the origin lookup, so two requests to …/avatar.jpg?v=1 and …/avatar.jpg?v=2 share a cache key. If the backend ever needs cache-busting, it must do so via a new filename, not a query parameter.
  • OPTIONS is allowed for CORS preflight but not cached.
  • TTLs: objects without an explicit Cache-Control header live at the edge for 1 hour; ones that ship Cache-Control: max-age=… may live up to 24 hours.

Edge distribution

price_class = "PriceClass_All"

PriceClass_All enables every CloudFront edge location globally — North America, Europe, Asia, South America, Oceania, Africa, Middle East. Chosen because tomoda's user base spans Asia (primary), North America, and Europe. If usage consolidates and non-Asia egress becomes wasteful, drop to PriceClass_100 (US, Canada, Europe only).

TLS

viewer_certificate {
  acm_certificate_arn      = aws_acm_certificate.cert.arn
  ssl_support_method       = "sni-only"
  minimum_protocol_version = "TLSv1.2_2021"
}
  • The cert is the one provisioned by aws_acm_certificate.cert — see ACM. It must be in us-east-1 regardless of where the rest of the stack runs, because CloudFront is a global service whose control plane lives there.
  • TLSv1.2_2021 is the floor: TLS 1.2 with the modern cipher suite list, TLS 1.3 supported.
  • sni-only — clients that do not send SNI cannot connect (any modern browser and HTTP client does).

Defaults worth knowing

  • default_root_object = "index.html" — a request to https://assets.tomoda.life/ returns index.html from the bucket root, not a directory listing. There is currently no index.html uploaded for either environment; this is a no-op until something writes one.
  • is_ipv6_enabled = true — the distribution accepts AAAA queries.
  • geo_restriction.restriction_type = "none" — no country-level blocking. If that ever changes (regulatory, abuse), it goes here.

DNS

assets[-dev].tomoda.life resolves to <distribution-id>.cloudfront.net via a Cloudflare CNAME, configured in cloudflare.tf. Cloudflare does not proxy this record (proxied = false), so the edge cache is CloudFront alone — there is no double-CDN. See Cloudflare for the full DNS layering.

Operational notes

  • Propagation: any change to a CloudFront distribution takes 3–5 minutes to roll out to all edges. terraform apply blocks until the state is Deployed.
  • Cache invalidation: not automated. If a bad object needs immediate removal, issue a manual aws cloudfront create-invalidation --paths '/path/to/object'.