Tuesday, August 19, 2025

Serverless Hosted Website MyBlog.com (www.twtech-blog.com) on AWS | Overview.

Serverless Hosted Website MyBlog.com (www.twtech-blog.com) on AWS - Overview.

Scope:

  • Intro,
  • High-level architecture,
  • Request flow,
  • Core AWS resources (Sample Terraform snippets),
  • Security headers (use AWS Managed policy or twtech function),
  • Sample CI/CD (GitHub Actions),
  • Optional “dynamic” features (still serverless),
  • Security hardening checklist,
  • Performance tips,
  • Local DX & content model,
  • What twtech ends up with,
  • Insights.

Intro:

    • To host a serverless website like MyBlog.com, www.twtech-blog.com on AWS, twtech needs to use a combination of serverless services, primarily Amazon S3 for storage, Amazon CloudFront for content delivery, and Amazon Route 53 for domain management. 
    • This architecture provides a scalable, cost-effective, and secure solution without managing traditional servers

This gives twtech:

    •  Global performance,
    •  Zero servers to patch,
    •  Strong security,
    •  Easy CI/CD path.

High-level architecture

        Authoring/build: 
    • Markdown static site generator (Hugo, Astro, Next.js static export) in GitHub.
        Storage: 
    • Private S3 bucket for origin (myblog-origin-<env>), Object Ownership: Bucket owner enforced, block all public access.
        Delivery: 
    • CloudFront with Origin Access Control (OAC) to read from S3.
        TLS & DNS:
    • ACM (us-east-2) cert for myblog.com and www.twtech-blog.com, Route 53 hosted zone for DNS.
        Functions at the edge (optional but recommended):
    •    CloudFront Function for wwwapex redirects, trailing slash/clean URL handling, cache-key normalization, security headers.
        WAF (optional): 
    • AWS WAF managed rules + rate limiting.
        CI/CD: 
    • GitHub Actions (Jenkins or CodePipeline) build upload changed objects to S3 create CloudFront invalidation.
        Observability: 
    • CloudFront standard logs to S3 (or real-time logs to Kinesis), CloudWatch alarms on 4xx/5xx spikes.

Result: 

    •  www.myblog.com and www.twtech-blog.com serve immutable static assets via CloudFront; S3 never goes public; no servers.

Request flow

     1. User hits https://twtech-blog.com.
2. CloudFront (TLS) runs a lightweight viewer request function to enforce https, do redirects and headers.
3. If cache miss, CloudFront fetches from S3 using OAC; S3 bucket policy allows only that distribution.
4. CloudFront caches by path/ETag; you control TTLs and versioning (e.g., /assets/app.8f2c.js = long TTL).

Core AWS resources (Sample Terraform snippets)

    • Assumes twtech already has a Route 53 public hosted zone for www.twtech.blog.com.

# 1) ACM certificates (in us-east-2)

provider "aws" {
  region = "us-east-2"
}
resource "aws_acm_certificate" "myblog" {
  domain_name       = "twtwch-blog.com"
  validation_method = "DNS"
  subject_alternative_names = ["www.twtech-blog.com"]
}
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.myblog.domain_validation_options :
    dvo.domain_name => {
      name  = dvo.resource_record_name
      type  = dvo.resource_record_type
      value = dvo.resource_record_value
    }
  }
  zone_id = data.aws_route53_zone.main.zone_id
  name    = each.value.name
  type    = each.value.type
  records = [each.value.value]
  ttl     = 60
}
resource "aws_acm_certificate_validation" "twtech-blog" {
  certificate_arn         = aws_acm_certificate.twtech-blog.arn
  validation_record_fqdns = [for r in aws_route53_record.cert_validation : r.fqdn]
}

# 2) S3 origin (private)

provider "aws" { region = var.region }
resource "aws_s3_bucket" "origin" {
  bucket = "twtech-blog-origin-${var.env}"
}
resource "aws_s3_bucket_ownership_controls" "owner" {
  bucket = aws_s3_bucket.origin.id
  rule { object_ownership = "BucketOwnerEnforced" }
}
resource "aws_s3_bucket_public_access_block" "block" {
  bucket                  = aws_s3_bucket.origin.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.origin.id
  versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "sse" {
  bucket = aws_s3_bucket.origin.id
  rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } }
}

# 3) CloudFront + OAC(Origin Access Control)

resource "aws_cloudfront_origin_access_control" "oac" {
  name                              = "twtech-blog-oac"
  description                       = "OAC for S3 origin"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}
resource "aws_cloudfront_distribution" "cdn" {
  enabled             = true
  is_ipv6_enabled     = true
  price_class         = "PriceClass_100"
  aliases             = ["myblog.com", "www.twtech-blog.com"]
  default_root_object = "index.html"
  origins {
    domain_name = aws_s3_bucket.origin.bucket_regional_domain_name
    origin_id   = "s3-myblog", "www.twtech-blog.com"
    origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
  }
  default_cache_behavior {
    target_origin_id       = "s3-myblog", "www.twtech-blog.com"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET","HEAD"]
    cached_methods         = ["GET","HEAD"]
    compress               = true
    cache_policy_id        = data.aws_cloudfront_cache_policy.caching_optimized.id
    response_headers_policy_id = data.aws_cloudfront_response_headers_policy.security_headers.id
  }
  viewer_certificate {
    acm_certificate_arn            = aws_acm_certificate_validation.twtech-blog.certificate_arn
    ssl_support_method             = "sni-only"
    minimum_protocol_version       = "TLSv1.2_2021"
  }
  restrictions { geo_restriction { restriction_type = "none" } }
  logging_config {
    include_cookies = false
    bucket          = aws_s3_bucket.logs.bucket_domain_name
    prefix          = "cloudfront/"
  }
}
# Bucket policy allowing only this CloudFront distribution (OAC)
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket_policy" "allow_cf" {
  bucket = aws_s3_bucket.origin.id
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Sid       = "twtechAllowCloudFrontOAC"
      Effect    = "Allow"
      Principal = { Service = "cloudfront.amazonaws.com" }
      Action    = ["s3:GetObject"]
      Resource  = "${aws_s3_bucket.origin.arn}/*"
      Condition = {
        StringEquals = {
          "AWS:SourceArn" = aws_cloudfront_distribution.cdn.arn
        }
      }
    }]
  })
}

# Data sources (for brevity):

data "aws_route53_zone" "main" { name = "twtech-blog.com." }
data "aws_cloudfront_cache_policy" "caching_optimized" {
  name = "Managed-CachingOptimized"
}
data "aws_cloudfront_response_headers_policy" "security_headers" {
  name = "Managed-SecurityHeadersPolicy"
}

# 4) DNS (Route 53)

resource "aws_route53_record" "apex" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "twtechapp.com"
  type    = "A"
  alias {
    name                   = aws_cloudfront_distribution.cdn.twtechapp.com
    zone_id                = aws_cloudfront_distribution.cdn.hosted_zone_id
    evaluate_target_health = false
  }
}
resource "aws_route53_record" "www" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "www"
  type    = "A"
  alias {
    name                   = aws_cloudfront_distribution.cdn.twtechapp.com
    zone_id                = aws_cloudfront_distribution.cdn.hosted_zone_id
    evaluate_target_health = false
  }
}

# 5) Logs bucket (optional but recommended)

resource "aws_s3_bucket" "logs" {
  bucket = "twtech-blog-logs-${var.env}"
  force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "logs_block" {
  bucket                  = aws_s3_bucket.logs.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Edge logic (clean URLs, redirects, headers)

  • CloudFront Function (Viewer Request) — www apex & trailing slash normalization

function handler(event) {
  var req = event.request;
  var host = req.headers.host.value;
  // Force apex (remove www)
  if (host === 'www.twtech-blog.com') {
    return {
      statusCode: 301,  // Moved Permanently
      statusDescription: 'Moved Permanently',
      headers: { location: { value: 'https://twtech-blog.com' + req.uri } }
    };
  }
  // Add trailing slash for bare directory paths (optional; adapt for SSG)
  if (!req.uri.includes('.') && !req.uri.endsWith('/')) {
    req.uri += '/';
  }
  return req;
}


Security headers (use AWS Managed policy or twtech function)

    •  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
    •  Content-Security-Policy (tailor to your assets)
    •  X-Content-Type-Options: nosniff
    •  Referrer-Policy: no-referrer-when-downgrade
    •  Permissions-Policy: camera=(), geolocation=(), microphone=()

Sample CI/CD (GitHub Actions)

.github/workflows/twtech-deploy.yml

name: Deploy twtech-Blog
on:
  push:
    branches: [ main ]
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - name: Install & build
        run: |
          npm ci
          npm run build
      - name: Configure AWS creds (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/twech-blog-deploy
          aws-region: us-east-2
      - name: Sync to S3
        run: |
          aws s3 sync ./dist s3://twtech-blog-origin-prod/ --delete --only-show-errors
      - name: Create CloudFront invalidation (changed paths)
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/*"

NB:

  • For heavy traffic, prefer content hashing in filenames and avoid broad invalidations—only invalidate HTML routes (e.g., /index.html, /posts/*).

SPA (Single-Page Applications) vs. static routes

 Pure static (Hugo/Astro SSG): 
    • Each route has a real file; easiest cache story.
 SPA (React/Vue): 
    • Configure error response mapping:
      •   In CloudFront, add a Custom Error Response for 404200 with response page index.html, cache TTL small (e.g., 0–60s) to avoid caching misses as 404s.
 Hybrid (Next.js static export): 
    • Similar to SSG; keep /api/* out of this distribution.

Optional “dynamic” features (still serverless)

        Contact form / newsletter signup: 
    • API Gateway + Lambda → SES/Third-party; add reCAPTCHA/Turnstile.
        Comments / likes: 
    • API Gateway + Lambda + DynamoDB (or a hosted comments SaaS).
        Search: 
    • Prebuild Lunr/Minisearch index at build time and ship as static JSON; or use Algolia.

Security hardening checklist

    • S3 block public access; no bucket website endpoint.
    •  Access only via OAC; bucket policy denies anything else.
    •  TLS v1.2_2021 minimum on CloudFront; HSTS enabled.
    •  WAF with AWS Managed Core, IP reputation lists; optional bot control; throttle / and /feed.xml.
    •   Least-privilege deploy role (can s3:PutObject, s3:DeleteObject on the one bucket; cloudfront:CreateInvalidation on the one distribution).
    • Logs on: CloudFront to S3; set lifecycle to transition to Glacier after N days.
    •  CSP tuned to twtech asset domains.
    •  Immutability: hash-named assets with long TTL (e.g., 1 year; Cache-Control: public,max-age=31536000,immutable), short TTL for HTML (e.g., 60s).

Performance tips

    •  Image optimization: Preprocess at build (Sharp/imaginary) → multiple sizes + srcset. Or CloudFront + Lambda@Edge/Object Lambda for on-the-fly thumbs.
    •  Compression: Ensure Content-Encoding: gzip/br for text assets (most builders do this); CloudFront will serve as-is.
    •  HTTP/2 & IPv6: Already enabled via CloudFront.
    •  Edge cache policies: Use Managed-CachingOptimized for assets; split a separate behavior for /assets/* with long TTL.

Cost pointers (rough mental model)

    •  S3 storage + requests: Pennies unless twtech store GB-TBs.
    •  CloudFront egress: Main cost driver; but cheaper than S3 direct and faster.
    •  WAF: Fixed monthly + request-based.
    •  ACM/Route 53: Cert is free; DNS is pennies/month.

NB:

twtech needs to check region’s current pricing: set Budgets + alerts.

Local DX & content model

    • Use MDX/Markdown with frontmatter; generate RSS/Atom feed.xml and sitemap.xml.
    •  Add a drafts folder that’s excluded in prod builds.
    •  If twtech wants a GUI CMS without servers: Git-based CMS (e.g., Netlify CMS / Decap CMS) authenticating via GitHub OAuth; the site remains static.

What twtech ends up with

    • twtech-blog.com globally fast, HTTPS-only, fully cached.
    • Zero public S3, no servers to patch.
    • One-click (or push-to-main) deploys with invalidations.
    • Solid security posture with headers, WAF, and logs.

Insights:

CloudFront + OAC

    • Amazon CloudFront Origin Access Control (OAC) is a security feature that enables CloudFront distributions to securely access private content stored in Amazon S3 buckets or AWS Lambda function URLs.
    • It acts as a gatekeeper, ensuring that twtech S3 content or Lambda function URL can only be accessed through the associated CloudFront distribution, preventing direct access and enhancing security. 

Benefits of using CloudFront OAC

·        Enhanced Security: 

    • OAC implements robust security practices, including using IAM service principals for authentication, employing short-term credentials, and frequently rotating credentials. 
    • This strengthens the overall security posture of your CloudFront distribution and offers better protection against potential threats.

·        Restricted Direct Access: 

    • OAC prevents users from bypassing CloudFront and accessing your S3 content or Lambda functions directly through their URLs. 
    • This ensures that all requests go through CloudFront, allowing you to leverage its security features like AWS WAF and DDoS protection.

·        Reduced Costs: 

    • Serving data directly from S3 can be more expensive than serving it through CloudFront due to data transfer costs. 
    • By restricting direct access with OAC, twtech can optimize costs by ensuring that all content is delivered through CloudFront's global network.

·        Granular Control with IAM: 

    • OAC uses IAM service principals and resource-based policies, enabling more granular control over access permissions. 
    • twtech can define specific rules for different distributions and S3 objects, enhancing its security management capabilities.

·        Expanded HTTP Method Support

    • Unlike its predecessor, Origin Access Identity (OAI), OAC supports a wider range of HTTP methods including GET, PUT, POST, PATCH, DELETE, OPTIONS, and HEAD.

·        SSE-KMS Support: 

    • OAC seamlessly integrates with S3 server-side encryption with AWS Key Management Service (SSE-KMS) and allows both downloading and uploading of encrypted S3 objects.

·        Future-Proof for All AWS Regions

    • OAC is designed to work with all current and future AWS Regions, whereas OAI support is limited to regions launched before December 2022.

·        Simplified Management: 

    • OAC offers a streamlined approach to managing access to your S3 buckets, providing a more intuitive and efficient configuration process compared to OAI. 

CloudFront OAC use cases

·        Secure Static Websites: 

    • Host twtech static website content (HTML, CSS, images, etc.) in an S3 bucket and use CloudFront with OAC to serve it securely, preventing direct access to the S3 bucket.

·        Secure Content Delivery: 

    • Deliver private content, such as videos or other media files, from S3 to authenticated users through CloudFront while restricting direct S3 access.

·        Secure Lambda Function URLs

    • Use OAC to authenticate access to your AWS Lambda function URLs, ensuring that only requests coming through a specific CloudFront distribution can invoke them.

·        Enhance Web Application Security

    • By forcing traffic through CloudFront, twtech can leverage AWS WAF and Shield to protect its applications from common web exploits and DDoS attacks. 

Configuring CloudFront OAC

1.     Grant CloudFront Permission

    • Ensure that the CloudFront service principal has permission to access the target S3 bucket or Lambda function URL. 
    • This is typically achieved by updating the S3 bucket policy or Lambda function URL's resource-based policy to allow access from the CloudFront service principal, conditional on the request being on behalf of the specific distribution with OAC enabled.

2.     Creating an OAC (Origin Access Control):

1.     Navigate to the CloudFront console, expand Security in the navigation tab, and choose Origin Access.

2.     Choose Create Control Setting, provide a name and (optionally) a description, select the Origin Type (S3 or Lambda), and choose Create. Leaving the default "Sign requests" behavior is generally recommended.

3.     Attach OAC to Distribution:

0  Open the CloudFront console and choose the distribution you want to add the OAC to.

1.     Go to the Origins tab, select the S3 origin or Lambda function URL you want to configure with OAC, and click Edit.

2.     Choose Origin Access Control Settings, select the OAC you created in the previous step, and click Save Changes.

4.     Update S3 Bucket Policy (If using S3): If twtech origin is an S3 bucket, ensure the bucket policy is updated to allow access to the CloudFront IAM service principal, specifically for the CloudFront distribution associated with the OAC. The CloudFront console will often provide a sample policy statement after the OAC is created. 

Migrating from OAI to OAC (the Contemporary approach)

    • While AWS recommends using OAC for new distributions and migrating existing OAI-based distributions for its enhanced security and features, CloudFront currently supports both. 
    • If twtech is migrating from Origin Access Identity (OAI) to Origin Access Control (OAC), it can do so easily by updating its CloudFront distribution settings and ensuring its S3 bucket policy (if applicable) is updated to grant permissions to both OAI and OAC during the transition to avoid downtime.



No comments:

Post a Comment

Amazon EventBridge | Overview.

Amazon EventBridge - Overview. Scope: Intro, Core Concepts, Key Benefits, Link to official documentation, Insights. Intro: Amazon EventBridg...