Architecture

How this site is hosted and secured

A static site with one small serverless function, deployed by Terraform from GitHub Actions. Everything a visitor touches goes through the CDN; nothing else is reachable from the internet.

Architecture diagramVisitors resolve the domain in Route 53 and connect over TLS to CloudFront. CloudFront adds security headers and serves static files from a private S3 bucket through Origin Access Control. Requests to /api/contact go to a throttled HTTP API, which invokes a Lambda function that sends mail through Amazon SES. GitHub Actions authenticates to AWS with OIDC to run Terraform, whose state is stored in a separate S3 bucket, and to publish the site files.AWS · us-east-1 · single accountVisitorbrowserRoute 53alias · CAA · DKIM · SPFCloudFrontACM cert · TLS 1.2+ · HTTP/3security headers policyedge function: redirectsS3 site bucketprivate · OAC onlyencrypted · versionedHTTP APIPOST /api/contactstage throttlingLambdavalidate · honeypotSESDKIM-signedInboxnever public1 DNS2 HTTPS/* OAC/api/*DeliveryGitHub ActionsPR: plan · main: applyPII scan gateIAM OIDC rolesshort-lived credsrepo + branch scopedTerraform stateown S3 bucketversioned · lockedOIDCplan/applysync files · invalidate cache
Solid arrows: request path. Dashed: deployment. Nothing outside the CDN is publicly reachable; the S3 website endpoint is not used.

Controls by layer

DNS

Route 53 hosted zone

  • Alias records to the CDN (no IPs to drift)
  • CAA records allow only Amazon to issue certificates
  • SPF, DKIM and DMARC for the mail domain

Edge / TLS

CloudFront + ACM certificate

  • TLS 1.2+ (TLSv1.2_2021 policy), HTTP → HTTPS redirect
  • HTTP/2 and HTTP/3
  • Edge function: www → apex redirect, clean URLs

Headers

Response headers policy

  • HSTS (2 years, includeSubDomains, preload)
  • Strict CSP: self-only scripts, no inline script, frame-ancestors none
  • nosniff, Referrer-Policy, Permissions-Policy, COOP/CORP

Origin

Private S3 bucket

  • Block Public Access on, ACLs disabled (bucket owner enforced)
  • Origin Access Control: bucket policy allows only this distribution
  • SSE encryption, versioning for instant rollback, TLS-only bucket policy

Contact API

HTTP API → Lambda → SES

  • Same-origin route through the CDN (no CORS, no extra CSP origin)
  • Stage throttling returns 429 on bursts; honeypot drops bots
  • Secret origin header so the API only answers the CDN
  • Lambda role can send only from the site domain identity; recipient never leaves the backend

IaC

Terraform

  • Remote state in a dedicated S3 bucket: versioned, encrypted, public access blocked, TLS-only
  • Native S3 state locking
  • Account-specific values kept out of git

CI/CD

GitHub Actions

  • OIDC federation – no long-lived AWS keys anywhere
  • PRs assume a read-only plan role; main uses an apply role behind a protected environment
  • Build fails if the PII scanner finds an email, phone or address

Want to verify? curl -sI https://jasonratcliffe.dev shows every header above. The infrastructure is written in Terraform – read about it on the blog.

guest@jasonratcliffe: ~