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.
- Private
S3 bucket for origin (
myblog-origin-<env>), Object Ownership: Bucket owner enforced, block all public access.
- CloudFront with Origin Access Control (OAC) to read from S3.
- ACM (us-east-2)
cert for
myblog.comandwww.twtech-blog.com, Route 53 hosted zone for DNS.
- CloudFront Function for www↔apex redirects, trailing slash/clean URL handling, cache-key normalization, security headers.
- AWS WAF managed rules + rate limiting.
- GitHub Actions (Jenkins or CodePipeline) → build → upload changed objects to S3 → create CloudFront invalidation.
- CloudFront standard logs to S3 (or real-time logs to Kinesis), CloudWatch alarms on 4xx/5xx spikes.
Result:
andwww.myblog.comwww.twtech-blog.comserve 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-Blogon: 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.
- Configure error response mapping:
- In CloudFront, add a Custom Error Response
for
404→200with response pageindex.html, cache TTL small (e.g., 0–60s) to avoid caching misses as 404s.
- 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.
- API Gateway + Lambda + DynamoDB (or a hosted comments SaaS).
- 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:DeleteObjecton the one bucket;cloudfront:CreateInvalidationon 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/brfor 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.xmlandsitemap.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.comglobally 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