WAF + CDN rollout across a multi-brand, multi-country domain portfolio
Migrating every public-facing brand domain across four countries onto CloudFront + AWS WAF — geo-fencing, OWASP managed rules, and centralized logging — as a scheduled, reversible cut-over that never touched how internal traffic reached the same backends.
Overview
Goal: put AWS WAF in front of every public web and API endpoint across the brand portfolio to protect against OWASP Top-10 threats, geo-block traffic from high-risk jurisdictions, and centralize logging and monitoring through CloudWatch — without disturbing how internal users and services already reach the same backends via an internal load balancer.
[ External users / bots ] ──► [ Amazon CloudFront ] ◄── ACM wildcard certs per brand
│ (*.brand1.com, *.brand2.se, ...)
[ AWS WAF ] ──► [ OpenSearch — logging ]
│
[ Internal load balancer ] ◄── internal users, unchanged path
│
[ Ingress / EKS workloads ]
Infrastructure is managed as a single reusable Terraform module, so every brand domain gets the same WAF ruleset, logging configuration, and certificate handling rather than a hand-rolled one-off per domain.
1. Geo-fencing policy
Traffic is blocked from a list of countries aligned with EU sanctions guidance and the EU's published list of jurisdictions of concern — a mix of comprehensively sanctioned states and countries flagged for elevated fraud/AML risk in the financial sector specifically. This is public policy guidance, not company-specific data, and is reviewed periodically rather than hard-coded permanently, since sanctions guidance changes:
2. Domain scope
Every public brand domain across the portfolio was brought into scope, each with its own ACM wildcard certificate in us-east-1 (a CloudFront requirement) and its own set of subdomains routed through CloudFront + WAF. The full domain-by-domain scope for this rollout — domain names below are illustrative brand placeholders, not the real portfolio:
brand1.com — primary consumer brand
| Subdomains routed through CloudFront + WAF | |||
|---|---|---|---|
| agreement | anet | api | eid |
| id | invoice | minesider-alt | minasidor |
| my | portal | prod-cms | provenir |
| loanapp | sso | core-dev.partner | core.partner |
| *.core.partner | *.core.test-partner | www | |
brand2.se — domestic brand + internal tooling
| Subdomains routed through CloudFront + WAF | |||
|---|---|---|---|
| api | confluence | dev-gate | dev-sftp |
| invoices | gate | git | grafana |
| loanapp | nugetrepo | octopus | pmm-flow |
| dev-filetransfer | sonarqube | srv | test-confluence |
| test-gate | www | ||
Remaining brand domains — international markets
| Brand domain | Subdomains routed through CloudFront |
|---|---|
| brand3.fi | www, *.brand3.fi |
| brand4.no | www, *.brand4.no |
| brand5.no | www, *.brand5.no |
| brand6.no | www, www.nettbank, *.brand6.no |
| brand7.no | www, flexicredit |
Seven brand domains across four countries, roughly fifty individual and wildcard subdomains in total — every one of them backed by the same reusable Terraform module, the same WAF ruleset, and the same CloudWatch logging target, so onboarding domain fifty felt identical to onboarding domain one.
3. DEV/TEST as a dress rehearsal
Before touching a single production record, the same Terraform module was applied to dev-* and test-* subdomains and left running for a full test cycle:
- AWS managed rule groups plus custom rules tested against real application traffic patterns to confirm nothing legitimate gets blocked
- Geo-blocking confirmed with traffic simulated from both allowed and blocked regions
- Rate-limiting and IP allow-listing verified against known internal and partner ranges
- Application functionality tested end to end to confirm the WAF doesn't block legitimate traffic
- CloudWatch log groups and alarms confirmed to be receiving and triggering correctly
Additional recommendations worked through before production cut-over: simulating realistic traffic patterns to validate allowed vs. blocked requests, cross-region/edge-case testing for geo-blocking, integration tests against production-like clients to verify headers and redirects, and a final CloudWatch monitoring pass to confirm logs and alarms trigger as expected. DEV/TEST was treated explicitly as a dress rehearsal for exactly this reason — every issue found there is one that never reaches a customer.
4. Prerequisites & preparations
| Item | Detail | Est. time |
|---|---|---|
| Certificates | ACM wildcards requested and validated in us-east-1 for every brand domain — validation can take longer than expected, so this starts well ahead of cut-over | 10 min per domain (initiation) |
| Terraform backend | S3 + DynamoDB state lock ready and accessible | 10 min |
| DNS | Route 53 zones confirmed to exist for every domain in scope; records verified with dig/nslookup | 20 min |
| IAM roles | Terraform role verified for least privilege — create/modify access limited to WAF, CloudFront, Route 53, ACM, CloudWatch only | 5 min |
| Load balancer backup | Current internal load balancer configuration (in git) and public IP recorded | 5 min |
| DNS TTL adjustment (optional) | Lower TTL on non-alias records to 300s a few hours ahead of cut-over to speed propagation — CloudFront alias records propagate on their own and don't need manual TTL tuning | 10 min |
Preparations phase, end to end: roughly 1–2 hours, done well ahead of the actual cut-over window.
5. Cut-over steps
| # | Action | Responsible | Est. time |
|---|---|---|---|
| 1 | Update Terraform configuration to include the module, all WAF rules, CloudFront distributions, logging, and wildcard certs for every domain | Infra/App | 30 min |
| 2 | Review and approve the Terraform plan (terraform plan -out=tfplan) | Infra/App | 15 min |
| 3 | Apply the plan (terraform apply tfplan) to deploy WAF, CloudFront, CloudWatch, and prepare DNS records — no traffic cut over yet | Infra/App | 30 min |
| 4 | Confirm internal DNS still points to the internal load balancer, unaffected | Infra/App | 5 min |
| 5 | Update load balancer host-header routing if needed; remove any conflicting SNI/SSL termination ahead of the public cut-over | Infra/App | 20 min |
| 6 | Validate ACM wildcard certificates in both Terraform state and the AWS console | Infra/App | 5 min |
| 7 | DNS cut-over: update public Route 53 records to point to CloudFront, scheduled for the low-traffic window | Infra/App | 10 min |
| 8 | Functional testing: verify every domain resolves and behaves correctly via its CloudFront endpoint | Dev & NOC | within the overnight window |
| 9 | Security testing: verify WAF geo-blocking and rate-limiting rules fire against the live endpoint | Dev/NOC | 30 min |
| 10 | Logging validation: confirm the CloudWatch log group is receiving entries and alarms are functional | NOC | 30 min |
| 11 | Remove the public IP from the internal load balancer once verified, so all public traffic flows through CloudFront | Infra/App | 10 min |
6. Estimated timeline
| Phase | Duration |
|---|---|
| Preparations | 1–2 hours (ahead of cut-over window) |
| Production deployment (Terraform + load balancer + DNS) | ~1 hour, scheduled overnight |
| DNS propagation & verification | Overnight through the following morning |
| Post-cutover monitoring | ~2 hours, final verification the next morning |
7. Rollback plan
- Revert to the previous Terraform configuration and re-apply
- Restore the internal load balancer's public IP
- Confirm both internal and public DNS resolve correctly back to the load balancer
8. Communication
The wider IT organization is notified ahead of the scheduled cut-over window, including the expected start/end time and who to contact if something looks wrong during the overnight monitoring period — a reversible plan is only as good as people knowing it's happening.
Pre-go-live checklist
- Terraform plan reviewed and approved by a second engineer
- ACM certificates issued and validated for every domain in scope
- DNS records verified with
dig/nslookupbefore and after cut-over - Load balancer configuration backed up
- Terraform IAM role confirmed least-privilege
- CloudWatch logging and alarms confirmed functional
- Cut-over window and rollback plan communicated to the wider team in advance