<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Baltacov</title>
    <description>The latest articles on DEV Community by Alexey Baltacov (@alexeybaltacov).</description>
    <link>https://dev.to/alexeybaltacov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2826933%2Fab385ce6-4e2c-4759-a55c-4989e5e307cf.png</url>
      <title>DEV Community: Alexey Baltacov</title>
      <link>https://dev.to/alexeybaltacov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexeybaltacov"/>
    <language>en</language>
    <item>
      <title>Precision Canary Deployments for Static Content: Navigating High-Stakes Tech Migrations</title>
      <dc:creator>Alexey Baltacov</dc:creator>
      <pubDate>Wed, 06 May 2026 13:26:02 +0000</pubDate>
      <link>https://dev.to/aws-builders/precision-canary-deployments-for-static-content-navigating-high-stakes-tech-migrations-1nim</link>
      <guid>https://dev.to/aws-builders/precision-canary-deployments-for-static-content-navigating-high-stakes-tech-migrations-1nim</guid>
      <description>&lt;h2&gt;
  
  
  Precision Canary Deployments for Static Content: Beyond the "Big Bang" Release
&lt;/h2&gt;

&lt;p&gt;Big frontend migrations rarely fail because of one obvious bug.&lt;/p&gt;

&lt;p&gt;They fail because production is messy.&lt;/p&gt;

&lt;p&gt;A move from a legacy React SPA to a Next.js static build, a routing-model change, or a full redesign can look straightforward on paper because the site is "just static files." In production, the real risk is the combination of browser state, CDN caching, routing behavior, and customer sessions already in flight.&lt;/p&gt;

&lt;p&gt;That is where a &lt;strong&gt;precision canary&lt;/strong&gt; earns its keep.&lt;/p&gt;

&lt;p&gt;Instead of sending 50% of traffic to a new build and hoping for the best, you can start with &lt;strong&gt;0.1% or even 0.01%&lt;/strong&gt;, observe what happens under real conditions, and expand only when the evidence says you should.&lt;/p&gt;

&lt;p&gt;For engineers, that cuts blast radius. For executives, it turns a risky migration into a controlled rollout with a fast rollback path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executive summary
&lt;/h2&gt;

&lt;p&gt;A precision canary for static content does four important things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It keeps the &lt;strong&gt;initial risk very small&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It makes the routing decision &lt;strong&gt;before the cache lookup&lt;/strong&gt;, so stable and canary traffic stay separated.&lt;/li&gt;
&lt;li&gt;It avoids cookies by using an &lt;strong&gt;anonymous, deterministic routing key&lt;/strong&gt; such as &lt;strong&gt;JA4&lt;/strong&gt;, then &lt;strong&gt;JA3&lt;/strong&gt;, with a final fallback when neither is available.&lt;/li&gt;
&lt;li&gt;It allows &lt;strong&gt;instant rollback&lt;/strong&gt; by changing one edge decision instead of waiting for DNS behavior to settle.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why static-site migrations still fail in production
&lt;/h2&gt;

&lt;p&gt;Staging environments are useful, but they are not production. They do not contain months of real browser state, cached assets, half-complete sessions, or every unusual client combination that appears on the public internet.&lt;/p&gt;

&lt;p&gt;Here are the common ways large static-site releases fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hidden client state
&lt;/h3&gt;

&lt;p&gt;Real users arrive with old service workers, outdated cached files, old &lt;code&gt;localStorage&lt;/code&gt; data, and long-lived tabs. A new build can be technically correct and still break when it meets that real-world state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Session fragmentation
&lt;/h3&gt;

&lt;p&gt;If a user lands on the new version for one request and the old version for the next, state and routing assumptions can collide. The result is often a broken session, confusing UI behavior, or JavaScript errors that never appeared in testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Routing surprises
&lt;/h3&gt;

&lt;p&gt;A migration from hash routing to history-based routing changes the rules. A page that works during client-side navigation can still fail on refresh if the origin or edge does not return the expected fallback document.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Business impact arrives before technical certainty
&lt;/h3&gt;

&lt;p&gt;When a public site supports revenue, leads, sign-ins, or brand trust, the cost of a bad release is not just a higher bug count. It is lost conversion, increased support load, and rollback stress at the worst possible moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DNS weighting is not the best fit here
&lt;/h2&gt;

&lt;p&gt;A common first idea is to split traffic at the DNS layer with Route 53 weighted records.&lt;/p&gt;

&lt;p&gt;That can work in some scenarios, but it is a weak fit for high-stakes frontend migrations because it sits outside the request path, where the decision really needs to happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route 53 weighting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to understand&lt;/li&gt;
&lt;li&gt;Native AWS capability&lt;/li&gt;
&lt;li&gt;Fine for coarse traffic shifts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rollback is slower because DNS behavior is not instantly consistent everywhere&lt;/li&gt;
&lt;li&gt;It cannot easily support tester overrides with headers&lt;/li&gt;
&lt;li&gt;It does not naturally keep a single viewer pinned to a deterministic version during the migration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why CloudFront Functions is the better control point
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;CloudFront Functions&lt;/strong&gt;, the routing decision happens at the edge on the request itself.&lt;/p&gt;

&lt;p&gt;That gives you better operational control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant rollback&lt;/strong&gt; by changing edge logic or configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very fine rollout precision&lt;/strong&gt; using basis points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QA and executive preview paths&lt;/strong&gt; through explicit overrides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better routing consistency&lt;/strong&gt; without relying on cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Important trade-off: AWS notes that CloudFront Functions run on every viewer request, while Lambda@Edge origin-request logic runs only on cache misses. For highly cacheable workloads, Lambda@Edge can be more cost-efficient. For this article, the recommendation stays with CloudFront Functions because the main goal is &lt;strong&gt;fast, precise rollout control&lt;/strong&gt; during a risky migration, not minimizing function invocations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Architecture at a glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7a08791j7gu197mxtm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7a08791j7gu197mxtm4.png" alt="Precision canary architecture" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep &lt;strong&gt;one CloudFront distribution&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Define &lt;strong&gt;two origins&lt;/strong&gt;: stable and canary.&lt;/li&gt;
&lt;li&gt;Run a &lt;strong&gt;viewer-request CloudFront Function&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Decide which origin to use before the cache lookup.&lt;/li&gt;
&lt;li&gt;Add a small header such as &lt;code&gt;x-canary-origin&lt;/code&gt; so the cache key stays isolated between stable and canary objects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This matters because without cache isolation, CloudFront can serve a cached canary object to a stable user, or vice versa.&lt;/p&gt;

&lt;h2&gt;
  
  
  The routing principle: deterministic, anonymous, cookie-free
&lt;/h2&gt;

&lt;p&gt;For this use case, the goal is not personal identity. The goal is &lt;strong&gt;consistent traffic assignment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A good order of precedence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explicit header override&lt;/strong&gt; for QA or controlled previews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit query override&lt;/strong&gt; for one-off testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JA4 fingerprint&lt;/strong&gt; when available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JA3 fingerprint&lt;/strong&gt; if JA4 is not available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback hash&lt;/strong&gt; using &lt;code&gt;user-agent + viewer.ip&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gives you a cookie-free strategy that is still stable enough for production rollout control.&lt;/p&gt;

&lt;p&gt;This article assumes the site is always served over &lt;strong&gt;HTTPS&lt;/strong&gt;. That matters because JA4 and JA3 are derived from the TLS handshake and are only available on HTTPS requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical decision flow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ag8p1coe1nfqso5nan2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ag8p1coe1nfqso5nan2.png" alt="Decision flow for cookie-free canary routing" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why basis points matter
&lt;/h2&gt;

&lt;p&gt;Percentages are often too coarse.&lt;/p&gt;

&lt;p&gt;For a large migration, &lt;strong&gt;1% can already represent a meaningful amount of real traffic&lt;/strong&gt;. Using &lt;strong&gt;basis points (BPS)&lt;/strong&gt; gives you a much finer dial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 BPS = 0.01%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;10 BPS = 0.1%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100 BPS = 1%&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That lets you run a true smoke test in production before the rollout is visible to a meaningful share of customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudFront Function example
&lt;/h2&gt;

&lt;p&gt;The following function shows the core pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manual overrides first&lt;/li&gt;
&lt;li&gt;deterministic cookie-free routing second&lt;/li&gt;
&lt;li&gt;cache isolation always&lt;/li&gt;
&lt;li&gt;stable fallback when viewer fingerprints are missing&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: the sample below assumes JavaScript runtime 2.0 because origin selection helper methods require it.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudfront&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;OLD_ORIGIN_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin-production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;NEW_ORIGIN_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin-canary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Basis points: 1 = 0.01%, 10 = 0.1%, 100 = 1%&lt;/span&gt;
  &lt;span class="na"&gt;CANARY_BPS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ES5-safe deterministic 32-bit hash for bucketing&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stableHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2166136261&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pickRoutingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Prefer JA4, then JA3. Both are anonymous TLS-derived fingerprints.&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ja4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudfront-viewer-ja4-fingerprint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ja4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja4:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ja4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ja3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudfront-viewer-ja3-fingerprint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ja3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja3:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ja3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Final fallback: still deterministic, but less stable than JA4/JA3&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uaip:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyManualOverride&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;querystring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querystring&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="c1"&gt;// 1) QA header override&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;qaVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-qa-version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qaVersion&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qaVersion&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 2) One-off query override&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;forceCanary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove the test flag so it does not accidentally affect cache behavior&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forceCanary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;viewer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewer&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;useNewOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyManualOverride&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useNewOrigin&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;routingKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pickRoutingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stableHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routingKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;useNewOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CANARY_BPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Keep stable and canary cache entries isolated&lt;/span&gt;
  &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-canary-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;useNewOrigin&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useNewOrigin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectRequestOriginById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEW_ORIGIN_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectRequestOriginById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OLD_ORIGIN_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this code gets right
&lt;/h2&gt;

&lt;p&gt;This version is designed to align with the operational goals in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  It does not use cookies
&lt;/h3&gt;

&lt;p&gt;That keeps the mechanism lightweight and avoids introducing a separate persistence layer just to keep users on the same version.&lt;/p&gt;

&lt;h3&gt;
  
  
  It prefers the most stable anonymous signal available
&lt;/h3&gt;

&lt;p&gt;JA4 first, then JA3, gives you a better routing key than raw IP alone. When those fingerprints are unavailable, the function still behaves predictably.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Header-name note: AWS documentation presents these as &lt;code&gt;CloudFront-Viewer-JA4-Fingerprint&lt;/code&gt; and &lt;code&gt;CloudFront-Viewer-JA3-Fingerprint&lt;/code&gt;, but inside a CloudFront Functions event object you access them in lowercase as &lt;code&gt;cloudfront-viewer-ja4-fingerprint&lt;/code&gt; and &lt;code&gt;cloudfront-viewer-ja3-fingerprint&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  It keeps cache entries separated
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;x-canary-origin&lt;/code&gt; request header is the critical separator. Include this header in the cache key so stable and canary content never share the same cached object.&lt;/p&gt;

&lt;h3&gt;
  
  
  It supports controlled previews
&lt;/h3&gt;

&lt;p&gt;Executives, QA, or launch managers can view the canary safely using a header or a one-off query switch without changing the default rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Required distribution configuration
&lt;/h2&gt;

&lt;p&gt;The code is only part of the solution. The distribution settings matter just as much.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Two origins
&lt;/h3&gt;

&lt;p&gt;Define one origin for the current production build and one for the canary build.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Viewer-request CloudFront Function
&lt;/h3&gt;

&lt;p&gt;Associate the function with the cache behavior that serves the site.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Enable the CloudFront-generated fingerprint headers
&lt;/h3&gt;

&lt;p&gt;CloudFront needs to add the viewer fingerprint headers so the function can inspect them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CloudFront-Viewer-JA4-Fingerprint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CloudFront-Viewer-JA3-Fingerprint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These headers are available to CloudFront Functions and Lambda@Edge, but only for &lt;strong&gt;HTTPS&lt;/strong&gt; requests. AWS also notes that these TLS-related headers can be added to an &lt;strong&gt;origin request policy&lt;/strong&gt;, but not to a &lt;strong&gt;cache policy&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cache policy includes &lt;code&gt;x-canary-origin&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is what keeps stable and canary cache objects isolated.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Query-string handling is deliberate
&lt;/h3&gt;

&lt;p&gt;If you allow the &lt;code&gt;?canary=1&lt;/code&gt; switch for testing, either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove it in the function, as shown above, or&lt;/li&gt;
&lt;li&gt;make sure it is not part of the cache key&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. SPA or app-style routing fallback is explicit
&lt;/h3&gt;

&lt;p&gt;If the migration changes routing behavior, make sure refreshes and deep links return the correct document.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Validate viewer fingerprint availability in your environment
&lt;/h3&gt;

&lt;p&gt;JA3 and JA4 are excellent routing inputs when exposed to the function path, but you should validate their availability in your exact CloudFront setup before making them your only signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rollout plan that works for both engineers and executives
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3wx5ynbf31f5m2mwfb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3wx5ynbf31f5m2mwfb6.png" alt="Rollout stages for precision canary deployment" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A practical rollout plan looks like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: 0.1% smoke test
&lt;/h3&gt;

&lt;p&gt;Purpose: prove routing, cache isolation, and basic stability.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unexpected 404s&lt;/li&gt;
&lt;li&gt;JavaScript startup failures&lt;/li&gt;
&lt;li&gt;broken asset loading&lt;/li&gt;
&lt;li&gt;obvious support noise&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 2: 1% early validation
&lt;/h3&gt;

&lt;p&gt;Purpose: confirm that the new build survives real traffic patterns.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client-side error rates&lt;/li&gt;
&lt;li&gt;performance regressions&lt;/li&gt;
&lt;li&gt;login or session complaints&lt;/li&gt;
&lt;li&gt;routing edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 3: 5% confidence build
&lt;/h3&gt;

&lt;p&gt;Purpose: compare business and operational metrics against the stable path.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conversion rate&lt;/li&gt;
&lt;li&gt;bounce rate&lt;/li&gt;
&lt;li&gt;task completion&lt;/li&gt;
&lt;li&gt;error budgets&lt;/li&gt;
&lt;li&gt;support tickets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 4: 25% operational proof
&lt;/h3&gt;

&lt;p&gt;Purpose: verify team readiness, dashboards, alerts, and rollback confidence under broader load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 5: 100% cutover
&lt;/h3&gt;

&lt;p&gt;Promote only when the migration risk is low enough for the new path to become the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure during the canary
&lt;/h2&gt;

&lt;p&gt;For technical teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;404 rate by path&lt;/li&gt;
&lt;li&gt;JavaScript error rate&lt;/li&gt;
&lt;li&gt;asset load failures&lt;/li&gt;
&lt;li&gt;Core Web Vitals or equivalent performance signals&lt;/li&gt;
&lt;li&gt;origin error rate and CDN cache behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For business stakeholders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conversion or lead generation&lt;/li&gt;
&lt;li&gt;checkout or sign-in completion&lt;/li&gt;
&lt;li&gt;bounce rate&lt;/li&gt;
&lt;li&gt;support ticket volume&lt;/li&gt;
&lt;li&gt;incident count during the rollout window&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Important caveats
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JA3 and JA4 are not identity
&lt;/h3&gt;

&lt;p&gt;They are useful routing signals, not person-level identifiers. Their role here is to keep similar requests on the same side of the rollout decision, not to identify an individual user.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTPS matters
&lt;/h3&gt;

&lt;p&gt;JA3 and JA4 come from the TLS &lt;code&gt;Client Hello&lt;/code&gt;, so they are relevant only for HTTPS traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cache key is the safety line
&lt;/h3&gt;

&lt;p&gt;If you remember only one implementation detail, remember this one: &lt;strong&gt;do not mix stable and canary objects in the same cache key&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep rollback boring
&lt;/h3&gt;

&lt;p&gt;The best rollout is the one that can be reversed in seconds without debate. Edge-based selection gives you that option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;A canary deployment for static content is not just a safer release tactic.&lt;/p&gt;

&lt;p&gt;For major web migrations, it is a way to separate &lt;strong&gt;technical uncertainty&lt;/strong&gt; from &lt;strong&gt;business risk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By making the decision at the edge, using deterministic cookie-free routing, and isolating cache entries correctly, you can move from a fragile big-bang launch to a controlled rollout that both engineers and executives can support.&lt;/p&gt;

&lt;p&gt;That is the real value: fewer surprises, faster rollback, and better evidence before full cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgments
&lt;/h2&gt;

&lt;p&gt;Special thanks to &lt;a href="https://www.linkedin.com/in/moti-moskovich-1b5b635/" rel="noopener noreferrer"&gt;@Moti Moskovich&lt;/a&gt; for his contribution to this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/helper-functions-origin-modification.html" rel="noopener noreferrer"&gt;Amazon CloudFront helper methods for origin modification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html" rel="noopener noreferrer"&gt;Add CloudFront request headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html" rel="noopener noreferrer"&gt;CloudFront Functions event structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cache-key-understand-cache-policy.html" rel="noopener noreferrer"&gt;Understand cache policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html" rel="noopener noreferrer"&gt;Cache content based on request headers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>cloudfront</category>
      <category>s3</category>
      <category>canary</category>
    </item>
    <item>
      <title>Protect Your Web Site with AWS WAF and CloudFront</title>
      <dc:creator>Alexey Baltacov</dc:creator>
      <pubDate>Sun, 22 Mar 2026 12:58:57 +0000</pubDate>
      <link>https://dev.to/aws-builders/protect-your-web-site-with-aws-waf-and-cloudfront-2p8c</link>
      <guid>https://dev.to/aws-builders/protect-your-web-site-with-aws-waf-and-cloudfront-2p8c</guid>
      <description>&lt;h2&gt;
  
  
  Protect Your On-Premises Website with AWS WAF and Amazon CloudFront
&lt;/h2&gt;

&lt;p&gt;Protecting web applications—regardless of where they run—with a scalable, highly available, and cost-effective edge layer is a cornerstone of modern architecture. This article walks through a hands-on proof of concept (PoC) that leverages AWS WAF for security, CloudFront for global caching and performance, and a serverless pipeline for on-the-fly HTTP context modification.&lt;/p&gt;

&lt;p&gt;You’ll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhance availability and reduce origin load by caching static and dynamic content at the edge.&lt;/li&gt;
&lt;li&gt;Secure traffic with AWS WAF rules before it ever reaches CloudFront.&lt;/li&gt;
&lt;li&gt;Offload requests from the origin to minimize connectivity and costs.&lt;/li&gt;
&lt;li&gt;Modify any part of the HTTP request or response—including headers, query strings, and body—using a Lambda-powered proxy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sensitive data and IP addresses in screenshots have been blurred.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I understand this guide—demonstrating how to proxy and modify HTTP content—might resemble instructions used by phishing site creators. That was never my intention; these techniques are meant solely for legitimate security, performance, and reliability purposes.&lt;/p&gt;

&lt;p&gt;Traditional WAF appliances and CDNs can be expensive, rigid, and complex to manage—particularly when you need to retrofit existing on-premises applications. By combining AWS WAF and Amazon CloudFront, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edge Security:&lt;/strong&gt; AWS WAF blocks malicious requests at AWS’s global PoPs, shielding your origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Caching:&lt;/strong&gt; CloudFront serves cached content from locations closest to your users, reducing latency and origin hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin Offload:&lt;/strong&gt; Less network traffic and compute on your servers, lowering TCO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-the-Fly HTTP Manipulation:&lt;/strong&gt; Inject, strip, or rewrite headers and body content without touching origin code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll demonstrate these capabilities using a simple IP-lookup service (&lt;code&gt;noc.co.il&lt;/code&gt;) to keep the PoC lean and focused on edge mechanics.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Evaluating Origin Compatibility
&lt;/h2&gt;

&lt;p&gt;For this PoC, we chose a simple IP-lookup service (&lt;code&gt;noc.co.il&lt;/code&gt;) to isolate edge mechanics. Our goals were to verify that the origin would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Respond over HTTPS without client interruptions.&lt;/li&gt;
&lt;li&gt;Trust and reflect standard proxy headers (&lt;code&gt;X-Forwarded-For&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Parse header values as plain text (no validation), allowing us to spoof or inject values.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2.1 Initial Compatibility Test
&lt;/h3&gt;

&lt;p&gt;We first obtained the &lt;code&gt;curl&lt;/code&gt; command via the browser’s &lt;strong&gt;Copy as cURL&lt;/strong&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://noc.co.il/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: text/html'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compressed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this returned the expected HTML shell of the IP lookup page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="nt"&gt;--compressed&lt;/span&gt; https://noc.co.il/
HTTP/1.1 200 OK
Content-Type: text/html&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTF-8
...
&amp;lt;html&amp;gt;My IP: 203.0.113.10&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 Testing &lt;code&gt;X-Forwarded-For&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Next, we injected a fake IP via the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header to see if the service honors it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Forwarded-For: 198.51.100.24"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compressed&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://noc.co.il/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The response body included both our real IP and &lt;code&gt;198.51.100.24&lt;/code&gt;, confirming the header was trusted.&lt;/li&gt;
&lt;li&gt;No header validation errors occurred.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.3 Spoofing Non-existent Addresses
&lt;/h3&gt;

&lt;p&gt;To verify lack of validation, we added another bogus IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Forwarded-For: 198.51.100.24, 300.300.300.300"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://noc.co.il/ | &lt;span class="nb"&gt;tee &lt;/span&gt;response.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspecting &lt;code&gt;response.html&lt;/code&gt; in a browser showed the exact header text echoed in the page—demonstrating the origin parses the header as raw text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sidebar:&lt;/strong&gt; Because CloudFront automatically appends &lt;code&gt;X-Forwarded-For&lt;/code&gt;, this origin behavior is crucial for forwarding the true client IP downstream.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Solution Architecture Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Original Architecture
&lt;/h3&gt;

&lt;p&gt;In the initial approach, I fronted the origin directly with AWS WAF and CloudFront, without any additional proxy layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy3namj1ejohgjpvk7n9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy3namj1ejohgjpvk7n9.png" alt="Original architecture diagram" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Clients -&amp;gt; AWS WAF -&amp;gt; CloudFront -&amp;gt; Original Site (noc.co.il)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF:&lt;/strong&gt; Enforces IP allow-lists and managed security rules at AWS edge locations before traffic reaches CloudFront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront:&lt;/strong&gt; Distributes and caches content globally, forwarding only necessary headers to the origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Original Site:&lt;/strong&gt; The unmodified &lt;code&gt;noc.co.il&lt;/code&gt; endpoint, responding directly to CloudFront requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Final Architecture (With API Gateway Proxy)
&lt;/h3&gt;

&lt;p&gt;To overcome response-body limitations, I introduced an API Gateway/Lambda proxy between CloudFront and the origin. The Lambda function serves as a reverse proxy for dynamic HTML, while static assets like &lt;code&gt;favicon.ico&lt;/code&gt; are served directly from S3 via a separate behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz93gyapuhhipwo0kqac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz93gyapuhhipwo0kqac.png" alt="Final architecture with API Gateway proxy" width="800" height="752"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF:&lt;/strong&gt; Applies IP filters and managed rules at the AWS edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront:&lt;/strong&gt; Uses two cache behaviors:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/favicon.ico&lt;/code&gt; -&amp;gt; &lt;strong&gt;S3 origin&lt;/strong&gt; for static assets with optimized TTLs.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/*&lt;/code&gt; -&amp;gt; &lt;strong&gt;API Gateway&lt;/strong&gt; for dynamic HTML via the reverse-proxy Lambda.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway (REST API Reverse Proxy Mode):&lt;/strong&gt; Receives viewer requests for dynamic content and invokes the Lambda reverse-proxy function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lambda Function (Reverse Proxy):&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fetch:&lt;/strong&gt; Makes an HTTP request to &lt;code&gt;https://noc.co.il&lt;/code&gt; including path and query parameters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modify:&lt;/strong&gt; Reads and parses the HTML response body.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Return:&lt;/strong&gt; Sends the modified HTML back through API Gateway and CloudFront while preserving the original status code and headers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;S3 Origin:&lt;/strong&gt; Serves the custom &lt;code&gt;favicon.ico&lt;/code&gt; directly from edge caches, bypassing API Gateway.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Detailed Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Certificate Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Generate a Let’s Encrypt certificate for your custom domain (&lt;code&gt;noc.ittools.net&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9036rorh8x8wj3tb60bp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9036rorh8x8wj3tb60bp.png" alt="Certificate generation screenshot" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import it into ACM in &lt;code&gt;us-east-1&lt;/code&gt; for CloudFront.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm965xzy5smjod8e6j1ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm965xzy5smjod8e6j1ga.png" alt="ACM import screenshot" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 AWS WAF Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP Sets:&lt;/strong&gt; Define trusted IPv4/IPv6 addresses such as office and datacenter ranges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgr5f8ta7u0es0bhj3b7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgr5f8ta7u0es0bhj3b7.png" alt="AWS WAF IP set screenshot 1" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="" class="article-body-image-wrapper"&gt;&lt;img alt="AWS WAF IP set screenshot 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web ACL:&lt;/strong&gt; Allow traffic from the IP sets and set the default action to &lt;strong&gt;Block&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76vsnikwkasubyo4r5ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76vsnikwkasubyo4r5ok.png" alt="AWS WAF Web ACL screenshot" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rate-based rules (optional):&lt;/strong&gt; Throttle anomalous request spikes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.3 CloudFront Distribution
&lt;/h3&gt;

&lt;p&gt;Configure the CloudFront distribution with the cache behavior and origin request policy that match your proxy design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcox8stde414asnx98mu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcox8stde414asnx98mu.png" alt="CloudFront distribution screenshot" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attach your AWS WAF Web ACL to this distribution.&lt;/li&gt;
&lt;li&gt;Enable response headers policies to add HSTS, CSP, and custom headers at the edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.4 Reducing Origin Connectivity
&lt;/h3&gt;

&lt;p&gt;Caching at CloudFront dramatically cuts down on requests to your origin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static assets (images, CSS, JS) are served from edge caches for configurable TTLs.&lt;/li&gt;
&lt;li&gt;Dynamic content hits origin only on cache misses—adjustable via cache keys (headers, cookies, query strings).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This offload not only improves performance but reduces bandwidth and compute costs on your servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 Full HTTP Context Access and Modification
&lt;/h3&gt;

&lt;p&gt;Neither CloudFront Functions nor Lambda@Edge allow modifying response bodies. Additionally, since both CloudFront and API Gateway automatically append their own &lt;code&gt;X-Forwarded-For&lt;/code&gt; entries, we need a reverse proxy to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the original HTML from &lt;code&gt;https://noc.co.il&lt;/code&gt; while preserving query strings and path.&lt;/li&gt;
&lt;li&gt;Inject a custom &lt;code&gt;&amp;lt;link rel="icon" href="/favicon.ico" /&amp;gt;&lt;/code&gt; into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Strip the two trailing IP addresses added by CloudFront and API Gateway from any comma-separated IP lists embedded in the HTML body.&lt;/li&gt;
&lt;li&gt;Return the modified HTML through API Gateway and CloudFront while preserving the origin’s headers and status codes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To achieve this, configure API Gateway in REST API reverse-proxy mode and invoke a Lambda function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http.client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration
&lt;/span&gt;&lt;span class="n"&gt;REMOVE_IP_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REMOVE_IP_COUNT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;FAVICON_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAVICON_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/favicon.ico&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ORIGIN_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ORIGIN_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noc.co.il&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ORIGIN_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ORIGIN_PATH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1) Parse client request info
&lt;/span&gt;    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;httpMethod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;qs_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;query_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;qs_params&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="c1"&gt;# 2) Build the upstream path
&lt;/span&gt;    &lt;span class="n"&gt;origin_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ORIGIN_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;client_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;origin_path&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;query_str&lt;/span&gt;

    &lt;span class="c1"&gt;# 3) Copy headers except Host
&lt;/span&gt;    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# 4) Fetch from the real origin
&lt;/span&gt;    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HTTPSConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ORIGIN_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;origin_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getresponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;body_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 5) Decode HTML
&lt;/span&gt;    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 6) Inject favicon link right after &amp;lt;head&amp;gt;
&lt;/span&gt;    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&amp;lt;head\b[^&amp;gt;]*&amp;gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\1&amp;lt;link rel=\"icon\" href=\"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FAVICON_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" type=\"image/png\"&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 7) Remove the last N IPv4s from every comma-separated list
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;strip_trailing_ips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s*,\s*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;REMOVE_IP_COUNT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;REMOVE_IP_COUNT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:\d{1,3}\.){3}\d{1,3}(?:\s*,\s*(?:\d{1,3}\.){3}\d{1,3})+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;strip_trailing_ips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 8) Build the API Gateway Lambda-proxy response
&lt;/span&gt;    &lt;span class="n"&gt;out_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_bytes&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;isBase64Encoded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern gives you full control over outgoing responses without touching the origin code.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Final Results
&lt;/h2&gt;

&lt;p&gt;Below is a comparison of the original IP lookup page and the modified version, showing the injected favicon and response changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Original Site
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczczj4iqzkncmxyx9olq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczczj4iqzkncmxyx9olq.png" alt="Original site screenshot" width="748" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Modified Site
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0edtx7ug0zs2udpj9w0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0edtx7ug0zs2udpj9w0q.png" alt="Modified site screenshot" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. High Availability, Security, and CDN Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DDoS Mitigation:&lt;/strong&gt; AWS WAF integrates with AWS Shield at no extra cost for CloudFront distributions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Failover:&lt;/strong&gt; Edge caches can continue serving content even if the origin is briefly unreachable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Efficiency:&lt;/strong&gt; Pay only for WAF rule evaluations, Lambda invocations, and data transfer—no upfront hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.1 Amazon CloudFront as a CDN
&lt;/h3&gt;

&lt;p&gt;Amazon CloudFront is AWS’s global content delivery network, designed to accelerate both static and dynamic content by caching it at edge locations worldwide. Key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced latency:&lt;/strong&gt; Delivers content from the closest edge location to end users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable cache behaviors:&lt;/strong&gt; Control TTLs, forwarded headers, query strings, and cookies per path pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content support:&lt;/strong&gt; Forward dynamic requests to any HTTP origin and selectively cache or bypass content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security integrations:&lt;/strong&gt; AWS WAF, TLS termination at the edge, and Origin Access Control for private S3 buckets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalidation and versioning:&lt;/strong&gt; Invalidate cache on demand or use versioned URLs for instant updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay-as-you-go pricing:&lt;/strong&gt; Based on data transfer, request counts, and invalidation usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.2 Origin Failover with Multiple Origins
&lt;/h3&gt;

&lt;p&gt;To maximize availability, CloudFront supports &lt;strong&gt;Origin Groups&lt;/strong&gt;—a primary/secondary origin configuration that automatically fails over if the primary origin becomes unhealthy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary origin:&lt;/strong&gt; Your default origin, such as API Gateway or a custom origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary origin:&lt;/strong&gt; A fallback endpoint, such as another region, an on-premises endpoint, or a static S3 bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health checks:&lt;/strong&gt; CloudFront monitors the primary origin using configurable HTTP(S) health checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failover logic:&lt;/strong&gt; On health-check failure, requests are routed to the secondary origin with minimal latency impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the CloudFront distribution, create two origins.&lt;/li&gt;
&lt;li&gt;Define an origin group linking both origins and specify health-check parameters.&lt;/li&gt;
&lt;li&gt;Update cache behaviors to use the origin group as the target.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This failover capability ensures that even if your primary backend experiences downtime, CloudFront can seamlessly serve content from an alternate source.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;By fronting any website—on-premises or in the cloud—with AWS WAF and CloudFront, you achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhanced availability through aggressive edge caching and failover.&lt;/li&gt;
&lt;li&gt;Robust security with customizable WAF rules at every PoP.&lt;/li&gt;
&lt;li&gt;Minimal origin load via cache offload and selective forwarding.&lt;/li&gt;
&lt;li&gt;Complete HTTP context manipulation using CloudFront Functions, Lambda@Edge, or, in very specific cases, API Gateway and Lambda for dynamic transformations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Normally the API Gateway + Lambda part is not required, since many HTML body changes can be made on the origin web server directly and these workarounds are unnecessary.&lt;/p&gt;

&lt;p&gt;This architecture transforms legacy and modern applications alike into resilient, secure, and performant cloud-edge services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix A: Edge Compute Options — CloudFront Functions vs Lambda@Edge
&lt;/h2&gt;

&lt;p&gt;AWS CloudFront provides two serverless compute options at the edge—CloudFront Functions and Lambda@Edge—each tailored to different use cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiv83lo09rtuwbprcpcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiv83lo09rtuwbprcpcn.png" alt="Edge compute options screenshot" width="800" height="906"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Parallels to F5 iRules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F5 iRules allow TCP, UDP, and HTTP inspection and manipulation on traditional load balancers.&lt;/li&gt;
&lt;li&gt;CloudFront Functions provide similar lightweight header- and URL-based logic at the edge, executing JavaScript within milliseconds.&lt;/li&gt;
&lt;li&gt;Lambda@Edge extends these capabilities with richer runtimes and more advanced traffic steering and content transformation.&lt;/li&gt;
&lt;li&gt;Both CloudFront serverless options and F5 iRules enable granular control over HTTP flows, but CloudFront offloads compute to a global CDN with pay-as-you-go billing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront Functions cannot perform network calls or access request bodies.&lt;/li&gt;
&lt;li&gt;Lambda@Edge offers more runtime and compute but incurs higher latency and cost for short-lived tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This appendix clarifies compute options independently of the PoC’s specific Lambda reverse-proxy implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix B: Pricing Notes
&lt;/h2&gt;

&lt;p&gt;The original LinkedIn article includes AWS WAF and CloudFront pricing examples. Before publishing on DEV, verify current pricing in the official AWS pricing pages because service prices can change over time. The original article listed example monthly Web ACL, rule, request, data transfer, request, invalidation, and certificate price points. (&lt;a href="https://www.linkedin.com/pulse/protect-your-web-site-aws-waf-cloudfront-alexey-baltacov-60lgf/?trackingId=J10c9QI%2F7GB7OGQ44uQEVw%3D%3D" rel="noopener noreferrer"&gt;linkedin.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/waf/pricing/" rel="noopener noreferrer"&gt;AWS WAF pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudfront/pricing/" rel="noopener noreferrer"&gt;Amazon CloudFront pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/" rel="noopener noreferrer"&gt;Amazon CloudFront Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html" rel="noopener noreferrer"&gt;CloudFront origin failover&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-edge.html" rel="noopener noreferrer"&gt;Lambda@Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" rel="noopener noreferrer"&gt;CloudFront Functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>waf</category>
      <category>cloudfront</category>
      <category>security</category>
    </item>
    <item>
      <title>AWS WAF Rate Limiting Based on Origin Response</title>
      <dc:creator>Alexey Baltacov</dc:creator>
      <pubDate>Sun, 22 Mar 2026 11:24:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-waf-rate-limiting-based-on-origin-response-1h5j</link>
      <guid>https://dev.to/aws-builders/aws-waf-rate-limiting-based-on-origin-response-1h5j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllugmyu3dkpwg3wjzfv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllugmyu3dkpwg3wjzfv9.png" alt="AWS WAF Rate Limiting Based on Origin Response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You have a public website fronted by Amazon CloudFront that serves static files from S3. Customers access these files via direct URLs and must be able to download any file at any time without interference. At the same time, you want to stop malicious actors from crawling your entire bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Prevent automated scanning of all URLs while still allowing legitimate customers unlimited downloads of the specific files they need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint:&lt;/strong&gt; No user login or authentication. Files are freely downloadable, so you cannot simply gate them behind a sign-in flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Plain AWS WAF Rate Limiting Is Not Enough
&lt;/h2&gt;

&lt;p&gt;AWS WAF lets you define rate-limit rules keyed by source IP or by fingerprinting mechanisms such as JA3 and JA4. In theory, you could set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;low limit&lt;/strong&gt; such as 10 requests per minute, which blocks scanners effectively but risks blocking legitimate high-throughput customers.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;high limit&lt;/strong&gt;, which lets scanners creep through, especially if attackers distribute requests across IPs or devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is an uncomfortable trade-off: too low hurts real users, too high fails to stop attackers.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS WAF's View of Origin Responses and ATP Rules
&lt;/h2&gt;

&lt;p&gt;By default, custom AWS WAF rules only inspect request attributes. They do not know whether your origin returned &lt;code&gt;200 OK&lt;/code&gt; or &lt;code&gt;404 Not Found&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The only built-in AWS WAF rules that inspect responses are the &lt;strong&gt;Account Takeover Prevention (ATP)&lt;/strong&gt; managed rules. Those require you to map login fields and are designed for authentication endpoints, not static file downloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Lambda@Edge Alone Cannot Solve It
&lt;/h2&gt;

&lt;p&gt;Lambda@Edge runs per request and has no built-in shared global state. It cannot maintain counters across all executions, so by itself it cannot enforce a global request threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Hybrid Approach: WAF + Lambda@Edge
&lt;/h2&gt;

&lt;p&gt;You can combine WAF's global counting capabilities with Lambda@Edge's ability to modify HTTP responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Primary WAF Rate-Limit Rule (Soft Threshold)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; Rate-based statement, for example 10 requests per 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; &lt;code&gt;Count&lt;/code&gt; (not &lt;code&gt;Block&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom response header:&lt;/strong&gt; Insert &lt;code&gt;X-RateLimit-Exceeded: true&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS WAF prefixes custom header names with &lt;code&gt;x-amzn-waf-&lt;/code&gt;. So if you specify &lt;code&gt;X-RateLimit-Exceeded&lt;/code&gt;, downstream components will see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x-amzn-waf-x-ratelimit-exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Secondary WAF Rate-Limit Rule (Hard Threshold)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; Rate-based statement with a much higher threshold, for example 1,000 requests per 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; &lt;code&gt;Block&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately stops heavy-volume attacks at the WAF layer, prevents excessive Lambda@Edge invocations, and reduces cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Lambda@Edge Function (Origin Response Trigger)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; CloudFront Origin Response event
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// AWS WAF prefixes headers with x-amzn-waf-&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-amzn-waf-x-ratelimit-exceeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;429&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too Many Requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Rate Limit Reached&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Please try again later.&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Associate and Deploy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Attach your Web ACL, containing both rate-limit rules and any ATP group you choose to use, to the CloudFront distribution.&lt;/li&gt;
&lt;li&gt;Deploy the Lambda@Edge function through the CloudFront console or the AWS CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Legitimate Access
&lt;/h3&gt;

&lt;p&gt;Repeatedly fetch an existing file. The soft-limit counter will increment, but users will still receive the file until the hard threshold is crossed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scanning Attempts
&lt;/h3&gt;

&lt;p&gt;Request many non-existent URLs. Errors quickly hit the soft threshold, causing your custom &lt;code&gt;429&lt;/code&gt; response page. Extreme traffic volumes hit the hard threshold and are blocked at WAF before Lambda@Edge runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo video
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FtCMrJetjWo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of This Pattern
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precision:&lt;/strong&gt; Rate limits are tied to actual &lt;code&gt;Not Found&lt;/code&gt; or error responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience:&lt;/strong&gt; Legitimate customers getting &lt;code&gt;200 OK&lt;/code&gt; responses are not blocked unless they truly exceed your thresholds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Efficiency:&lt;/strong&gt; High-volume attacks are stopped before Lambda@Edge runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS-native design:&lt;/strong&gt; Uses AWS WAF, CloudFront, and Lambda@Edge without adding external state stores or proxy layers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/latest/APIReference/API_RateBasedStatement.html" rel="noopener noreferrer"&gt;AWS WAF Rate-based rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/latest/APIReference/API_CustomHTTPHeader.html" rel="noopener noreferrer"&gt;AWS WAF Custom HTTP headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/customizing-the-response-for-blocked-requests.html" rel="noopener noreferrer"&gt;AWS WAF Custom responses for Block actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-atp.html" rel="noopener noreferrer"&gt;AWS WAF Fraud Control ATP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on LinkedIn on May 8, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>waf</category>
      <category>cloudfront</category>
      <category>security</category>
    </item>
    <item>
      <title>Making Amazon Bedrock AgentCore Gateway Accessible (Only Through CloudFront)</title>
      <dc:creator>Alexey Baltacov</dc:creator>
      <pubDate>Mon, 16 Feb 2026 19:21:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/making-amazon-bedrock-agentcore-gateway-accessible-only-through-cloudfront-ha1</link>
      <guid>https://dev.to/aws-builders/making-amazon-bedrock-agentcore-gateway-accessible-only-through-cloudfront-ha1</guid>
      <description>&lt;p&gt;tags: aws, architecture, genai, security, AWScommunity, AWScommunityBuilders&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wp6js60t0avbdylojqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wp6js60t0avbdylojqo.png" alt="aws architecture genai cloud security" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Amazon Bedrock AgentCore Gateway makes it straightforward to expose GenAI-powered APIs.&lt;/p&gt;

&lt;p&gt;AWS provides an official pattern for attaching a custom domain using CloudFront:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Client -&amp;gt; CloudFront -&amp;gt; AgentCore Gateway&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Official documentation:&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-custom-domains.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-custom-domains.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For many use cases, this approach is sufficient.&lt;/p&gt;

&lt;p&gt;But what if the requirement is stronger?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This endpoint must only be reachable through a specific CloudFront distribution.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where architectural boundaries matter.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Hidden Exposure Problem
&lt;/h2&gt;

&lt;p&gt;Even when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront is placed in front&lt;/li&gt;
&lt;li&gt;AWS WAF is attached&lt;/li&gt;
&lt;li&gt;A custom domain is configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying AgentCore Gateway service endpoint remains publicly accessible.&lt;/p&gt;

&lt;p&gt;CloudFront functions as a reverse proxy, not a strict isolation boundary.&lt;/p&gt;

&lt;p&gt;AgentCore supports resource-based policies:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/resource-based-policies.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/resource-based-policies.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These policies primarily control &lt;strong&gt;who&lt;/strong&gt; can invoke the gateway (IAM principals, AWS accounts, and supported condition keys).&lt;/p&gt;

&lt;p&gt;This model works well for internal service-to-service communication.&lt;/p&gt;

&lt;p&gt;However, for public-facing APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;External consumers do not authenticate using IAM.&lt;/li&gt;
&lt;li&gt;Authentication typically relies on OAuth, JWT, API keys, or custom headers.&lt;/li&gt;
&lt;li&gt;The endpoint must remain internet-reachable for legitimate users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, even if legitimate traffic flows through CloudFront, the underlying Gateway endpoint may still be reachable directly from the internet unless additional controls are introduced.&lt;/p&gt;

&lt;p&gt;IAM-based restrictions do not remove that exposure because the service endpoint itself remains public.&lt;/p&gt;

&lt;p&gt;For environments with strict ingress isolation requirements, this becomes a critical gap.&lt;/p&gt;


&lt;h2&gt;
  
  
  Compare This to S3 + CloudFront (OAI / OAC)
&lt;/h2&gt;

&lt;p&gt;Amazon S3 provides a native enforcement primitive:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only CloudFront can access this bucket.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With CloudFront Origin Access Identity (OAI) or Origin Access Control (OAC), an S3 bucket can be made private and restricted to a specific CloudFront distribution.&lt;/p&gt;

&lt;p&gt;Example S3 bucket policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowCloudFrontAccessOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E123ABC456XYZ"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-private-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bucket is not public.&lt;/li&gt;
&lt;li&gt;Direct internet access fails.&lt;/li&gt;
&lt;li&gt;Only the CloudFront identity is permitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AgentCore does not currently provide an equivalent isolation construct.&lt;/p&gt;

&lt;p&gt;An additional boundary must therefore be introduced.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture for Enforcing CloudFront-Only Access
&lt;/h2&gt;

&lt;p&gt;To enforce CloudFront-only reachability, the following architecture can be implemented:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg0eu0w4u8cfh5jglszi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg0eu0w4u8cfh5jglszi.png" alt="Architecture for Enforcing CloudFront-Only Access" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public entry point: &lt;code&gt;ext-clients-api-57x9abc.mycompany.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Required Host header downstream: &lt;code&gt;gw-abc123.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Enforcing CloudFront-Only at the Network Layer
&lt;/h2&gt;

&lt;p&gt;The Application Load Balancer (ALB) is publicly addressable, but it does not need to be publicly reachable.&lt;/p&gt;

&lt;p&gt;Inbound traffic can be restricted using &lt;strong&gt;AWS Managed Prefix Lists for CloudFront&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/working-with-aws-managed-prefix-lists.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/vpc/latest/userguide/working-with-aws-managed-prefix-lists.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS maintains a managed prefix list containing CloudFront origin-facing IP ranges.&lt;/p&gt;

&lt;p&gt;Attach this managed prefix list to the ALB security group and allow inbound HTTPS only from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com.amazonaws.global.cloudfront.origin-facing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only CloudFront edge locations can establish TCP connections to the ALB.&lt;/li&gt;
&lt;li&gt;Direct internet traffic to the ALB is blocked at the security group level.&lt;/li&gt;
&lt;li&gt;IP spoofing cannot bypass the restriction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without CloudFront, the ALB is not reachable.&lt;/p&gt;

&lt;p&gt;This creates a true network enforcement boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AWS WAF and DDoS Protection Fit
&lt;/h2&gt;

&lt;p&gt;This design enables layered protection.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFront (Edge Layer)
&lt;/h3&gt;

&lt;p&gt;CloudFront is automatically protected by &lt;strong&gt;AWS Shield Standard&lt;/strong&gt;, which provides DDoS mitigation at the edge.&lt;/p&gt;

&lt;p&gt;AWS WAF at CloudFront can provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate-based rules (anti-abuse and scraping protection)&lt;/li&gt;
&lt;li&gt;IP reputation filtering&lt;/li&gt;
&lt;li&gt;Geo restrictions&lt;/li&gt;
&lt;li&gt;AWS Managed Rule Groups&lt;/li&gt;
&lt;li&gt;AWS Bot Control&lt;/li&gt;
&lt;li&gt;Account Takeover Prevention (ATP)&lt;/li&gt;
&lt;li&gt;Authentication field inspection&lt;/li&gt;
&lt;li&gt;Header validation&lt;/li&gt;
&lt;li&gt;Payload size limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Malicious traffic can be filtered before reaching the infrastructure.&lt;/p&gt;




&lt;h3&gt;
  
  
  ALB (Ingress Layer)
&lt;/h3&gt;

&lt;p&gt;AWS WAF can also be attached to the ALB to enforce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verification of a secret header injected by CloudFront&lt;/li&gt;
&lt;li&gt;Strict Host header validation&lt;/li&gt;
&lt;li&gt;Additional managed rule groups&lt;/li&gt;
&lt;li&gt;Authentication flow inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the ALB DNS name is discovered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct connection attempts fail due to managed prefix list restrictions.&lt;/li&gt;
&lt;li&gt;Spoofed requests fail due to header validation and WAF rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This results in three enforcement layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edge (Shield + WAF)&lt;/li&gt;
&lt;li&gt;Network (managed prefix list restriction)&lt;/li&gt;
&lt;li&gt;Application (ALB WAF + listener rules)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Technical Challenge: TLS vs Host Header
&lt;/h2&gt;

&lt;p&gt;When placing an ALB in front of AgentCore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The ALB must terminate TLS using a certificate for a custom domain, for example:&lt;br&gt;
&lt;code&gt;ext-clients-api-57x9abc.mycompany.com&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downstream routing may require a different HTTP &lt;code&gt;Host&lt;/code&gt; header, for example:&lt;br&gt;
&lt;code&gt;gw-abc123.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These belong to different protocol layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS SNI hostname controls certificate validation.&lt;/li&gt;
&lt;li&gt;HTTP Host header controls routing logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AgentCore does not support accepting arbitrary custom domains inbound.&lt;/p&gt;

&lt;p&gt;This mismatch must be resolved before traffic reaches AgentCore.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: CloudFront Origin Modification
&lt;/h2&gt;

&lt;p&gt;CloudFront Functions allow modification of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;domainName&lt;/code&gt; (origin connection hostname for TLS/SNI)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hostHeader&lt;/code&gt; (HTTP Host header sent to the origin)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudfront&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ORIGIN_DOMAIN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ext-clients-api-57x9abc.mycompany.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OVERRIDE_HOST_HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gw-abc123.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateRequestOrigin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ORIGIN_DOMAIN_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hostHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OVERRIDE_HOST_HEADER&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS validation against the ALB certificate.&lt;/li&gt;
&lt;li&gt;ALB routing based on the required Host header.&lt;/li&gt;
&lt;li&gt;External clients to interact only with the custom domain.&lt;/li&gt;
&lt;li&gt;AgentCore to remain accessible only through the controlled CloudFront -&amp;gt; ALB -&amp;gt; PrivateLink path.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Outcome
&lt;/h2&gt;

&lt;p&gt;This architecture provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom domain support&lt;/li&gt;
&lt;li&gt;Edge-level DDoS protection (Shield Standard)&lt;/li&gt;
&lt;li&gt;Advanced WAF protections (rate limiting, ATP, bot control, managed rules)&lt;/li&gt;
&lt;li&gt;Network-level CloudFront-only enforcement&lt;/li&gt;
&lt;li&gt;Ingress validation at ALB&lt;/li&gt;
&lt;li&gt;Private connectivity via VPC endpoint&lt;/li&gt;
&lt;li&gt;Strong ingress isolation for GenAI workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default AWS pattern is well suited for standard deployments.&lt;/p&gt;

&lt;p&gt;When strict CloudFront-only reachability is required, an architectural boundary is necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFront + managed prefix lists + ALB + PrivateLink + layered WAF provides that boundary.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>security</category>
      <category>agentcoregateway</category>
    </item>
    <item>
      <title>AWS WAF Intro Guide</title>
      <dc:creator>Alexey Baltacov</dc:creator>
      <pubDate>Thu, 06 Feb 2025 23:00:31 +0000</pubDate>
      <link>https://dev.to/alexeybaltacov/aws-waf-intro-guide-25a3</link>
      <guid>https://dev.to/alexeybaltacov/aws-waf-intro-guide-25a3</guid>
      <description>&lt;p&gt;With this guide you will not become an AWS WAF expert, but will get a bit of taste about its capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction and Overview of AWS WAF
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AWS Web Application Firewall (WAF)&lt;/strong&gt; is a managed security service designed to help protect your web applications or APIs from common web exploits and bots. AWS WAF lets you create rules to monitor (count), block, or allow traffic based on criteria such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP addresses&lt;/li&gt;
&lt;li&gt;HTTP headers&lt;/li&gt;
&lt;li&gt;URI paths&lt;/li&gt;
&lt;li&gt;Request size&lt;/li&gt;
&lt;li&gt;SQL injection attempts&lt;/li&gt;
&lt;li&gt;Cross-Site Scripting (XSS) patterns&lt;/li&gt;
&lt;li&gt;Request rate (rate-based rules)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Additionally&lt;/strong&gt;, when you use AWS WAF, you automatically get the protections of &lt;strong&gt;AWS Shield Standard&lt;/strong&gt; &lt;strong&gt;at no additional cost&lt;/strong&gt;. &lt;strong&gt;AWS Shield Standard&lt;/strong&gt; is a built-in DDoS protection service for AWS resources (including CloudFront and Route 53) that mitigates common network and transport layer attacks. Together, AWS WAF and AWS Shield Standard provide a layered defense approach: WAF focuses on Layer 7 (application layer) threats, while Shield Standard covers volumetric and SYN flood-type attacks (Layers 3 and 4).&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 Resources Protected by AWS WAF
&lt;/h2&gt;

&lt;p&gt;You can attach AWS WAF to the following AWS resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon CloudFront (for global distribution and caching)&lt;/li&gt;
&lt;li&gt;Amazon API Gateway (REST APIs)&lt;/li&gt;
&lt;li&gt;Application Load Balancer (ALB)&lt;/li&gt;
&lt;li&gt;AWS AppSync (GraphQL APIs)&lt;/li&gt;
&lt;li&gt;Amazon Cognito user pools&lt;/li&gt;
&lt;li&gt;AWS App Runner service&lt;/li&gt;
&lt;li&gt;AWS Verified Access instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, when you use &lt;strong&gt;CloudFront&lt;/strong&gt; with &lt;strong&gt;AWS WAF&lt;/strong&gt;, you can protect &lt;strong&gt;any external or internal origin behind CloudFront&lt;/strong&gt;, including &lt;strong&gt;on-premises servers or third-party cloud environments&lt;/strong&gt;. CloudFront acts as a secure reverse proxy, letting you apply WAF inspection rules to all inbound requests before they reach your origin, regardless of whether that origin is hosted inside or outside of AWS.&lt;/p&gt;

&lt;p&gt;Attaching WAF to these resources allows you to inspect and filter incoming requests based on rules you define (custom rules) or rules you enable from AWS Managed Rule Sets, all while benefiting from the baseline DDoS protections of AWS Shield Standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Applying AWS WAF: CloudFront vs. Other Resources
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2.1 AWS WAF with CloudFront
&lt;/h2&gt;

&lt;p&gt;When integrated with CloudFront, AWS WAF inspects and can block traffic at the edge, before it reaches your origin. Key advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global Edge Presence&lt;/strong&gt;: Requests are processed at CloudFront edge locations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN and Caching&lt;/strong&gt;: CloudFront’s CDN reduces latency and offloads your origin by caching content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure Reverse Proxy&lt;/strong&gt;: By placing CloudFront in front of your application (whether it’s hosted within AWS, on-premises, or another cloud), you effectively use it as a secure reverse proxy that can filter traffic globally.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Shield Standard&lt;/strong&gt;: Automatically protects CloudFront distributions (and other supported AWS resources) against most common DDoS attacks at no extra cost.&lt;br&gt;
If your application needs more nuanced logic than WAF alone can provide, you can extend functionality using:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CloudFront Functions&lt;/strong&gt;: Lightweight JavaScript functions for short-running tasks (up to 1ms) like inspecting headers or rewriting URIs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lambda@Edge&lt;/strong&gt;: A more robust solution if you need to read or modify request/response bodies, implement complex authentication flows, or handle sophisticated transformations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;_&lt;strong&gt;Important:&lt;/strong&gt; If AWS WAF blocks a request at the edge, CloudFront Functions and Lambda@Edge will not trigger for that request.&lt;br&gt;
_&lt;/p&gt;

&lt;h2&gt;
  
  
  2.2 AWS WAF with Regional Services
&lt;/h2&gt;

&lt;p&gt;AWS WAF can also operate at a regional level by attaching to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon API Gateway&lt;/strong&gt; (REST APIs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS AppSync&lt;/strong&gt; (GraphQL APIs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Cognito&lt;/strong&gt; user pools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS App Runner&lt;/strong&gt; service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Verified Access&lt;/strong&gt; instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is suitable for applications or microservices that do not require global edge distribution or caching. Key differences from the CloudFront approach include:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Latency and Caching
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront + WAF&lt;/strong&gt;: Security measures at the edge, lower latency, potential cost savings from caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional Services + WAF&lt;/strong&gt;: Traffic is inspected regionally without inherent CDN caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Scope and Customization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt; can use &lt;strong&gt;CloudFront Functions&lt;/strong&gt; or &lt;strong&gt;Lambda@Edge&lt;/strong&gt; for advanced transformations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional Services&lt;/strong&gt; can still use custom or managed WAF rules, but do not benefit from global caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Protecting External Origins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront + WAF&lt;/strong&gt; can protect &lt;strong&gt;non-AWS origins&lt;/strong&gt; by pointing the distribution to external servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional services&lt;/strong&gt; typically secure traffic within AWS unless you design a specific pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. AWS Managed Rules
&lt;/h2&gt;

&lt;p&gt;AWS Managed Rules are pre-configured, continuously updated rule sets provided by AWS to protect against a wide range of threats. By enabling these managed rules in your AWS WAF, you can quickly cover many common web attacks without having to define every pattern or behavior manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Coverage for OWASP Top 10
&lt;/h3&gt;

&lt;p&gt;Many AWS Managed Rules are designed to help address &lt;strong&gt;OWASP Top 10&lt;/strong&gt; vulnerabilities, such as:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Injection&lt;/strong&gt; (SQLi, NoSQL, Command Injection)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;AWSManagedRulesSQLiRuleSet&lt;/strong&gt; identifies and blocks SQL injection attempts.&lt;/li&gt;
&lt;li&gt;Common malicious payloads and known injection vectors are covered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Broken Authentication and Session Management
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Combined with &lt;strong&gt;Account Takeover Prevention (ATP)&lt;/strong&gt;, WAF can detect brute force or credential stuffing attempts on login endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Sensitive Data Exposure
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Managed Rules can flag suspicious payloads or unusual request patterns that might indicate data exfiltration (in conjunction with rate-based rules and SSL/TLS).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  XML External Entities (XXE)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Certain AWS Managed Rule Groups include checks for XML-based threats and malicious entity references.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Broken Access Control
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;While some aspects of access control are application-specific, AWS Managed Rules can detect attempts to bypass security controls using known exploit patterns, suspicious HTTP methods, or path tampering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Security Misconfiguration
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Common Rule Sets automatically update to address vulnerabilities in common frameworks and platforms, reducing the risk of misconfiguration exposure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cross-Site Scripting (XSS)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWSManagedRulesCommonRuleSet&lt;/strong&gt; includes signatures for detecting malicious scripts and XSS payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Insecure Deserialization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Some advanced Managed Rule Groups check for known malicious serialization formats and exploit payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Using Components with Known Vulnerabilities
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS regularly updates the Managed Rule Sets to address newly disclosed CVEs, helping you keep pace with emerging threats.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Insufficient Logging and Monitoring
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Although logging itself is not a “rule,” AWS WAF integrates with S3, CloudWatch, and Kinesis Firehose for monitoring. Suspicious requests are clearly tagged and logged, aiding your threat detection strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Examples of AWS Managed Rule Sets
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWSManagedRulesCommonRuleSet&lt;/strong&gt;: Covers a broad range of general vulnerabilities, including XSS, HTTP request smuggling, and some injection signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWSManagedRulesSQLiRuleSet&lt;/strong&gt;: Specifically targets SQL injection patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWSManagedRulesAmazonIpReputationList&lt;/strong&gt;: Blocks or labels requests from known malicious IP addresses or botnets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWSManagedRulesLinuxRuleSet&lt;/strong&gt; (or similar specialized sets): Target vulnerabilities specific to Linux-based systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.3 Complementing Managed Rules with Custom Logic
&lt;/h3&gt;

&lt;p&gt;While AWS Managed Rules provide excellent baseline coverage, you should &lt;strong&gt;monitor&lt;/strong&gt; traffic in “Count” mode initially to identify potential false positives and tune your rules. You can then add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom rules for application-specific logic (e.g., blocking certain user agents or disallowed query parameters).&lt;/li&gt;
&lt;li&gt;Rate-based rules to mitigate brute force or scraping attempts.&lt;/li&gt;
&lt;li&gt;Account Takeover Prevention (ATP) for deeper login endpoint protection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This &lt;strong&gt;layered approach&lt;/strong&gt; (managed rules plus custom refinements) helps ensure robust, OWASP-aligned protection without sacrificing application availability.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. AWS WAF Fraud Control – Account Takeover Prevention (ATP)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Overview of ATP
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Account Takeover Prevention (ATP)&lt;/strong&gt; is part of &lt;strong&gt;AWS WAF Fraud Control&lt;/strong&gt;, designed to detect and mitigate unauthorized login attempts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Analyzes Login Attempts&lt;/strong&gt;: Monitors login traffic to identify suspicious behavior (credential stuffing, brute force).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk-Based Scoring&lt;/strong&gt;: ATP assigns risk scores to login attempts based on factors like repeated login failures or IP reputation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with WAF&lt;/strong&gt;: You can create WAF rules to allow, count, block, or require CAPTCHA on requests flagged by ATP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By spotting anomalies in login patterns, ATP helps prevent account takeover incidents before they escalate.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Benefits of Using Email Addresses as Usernames with ATP
&lt;/h3&gt;

&lt;p&gt;When combining ATP with email-based usernames (your application should use usernames in email format), you gain several security and operational advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Easier Parsing and Validation: An email format (&lt;a href="mailto:username@domain.com"&gt;username@domain.com&lt;/a&gt;) is consistent, enabling simpler checks for malicious or disposable domains.&lt;/li&gt;
&lt;li&gt;Domain Intelligence: ATP or custom WAF rules can label or block logins from suspicious or disposable email domains.&lt;/li&gt;
&lt;li&gt;Reduced Guesswork: Attackers must guess valid email addresses, which can be less trivial than typical username formats like admin or test.&lt;/li&gt;
&lt;li&gt;Improved Logging: Email addresses provide a clearly identifiable username in the logs for investigating suspicious activity.&lt;/li&gt;
&lt;li&gt;Easier MFA and Recovery: Users often prefer logging in with an email address, simplifying password resets and integrating MFA flows.&lt;/li&gt;
&lt;li&gt;Leverage Compromised Credentials Databases:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;AWS obtains threat intelligence including email/password pairs from public data breaches to help detect credential stuffing or attempts using known compromised credentials.&lt;/li&gt;
&lt;li&gt;Because users commonly reuse email addresses across different services, ATP can cross-reference these compromised credential sets more effectively when your primary username field is an email.&lt;/li&gt;
&lt;li&gt;This synergy helps AWS WAF quickly flag suspicious login attempts, especially when an attacker attempts known leaked email/password pairs from security breaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Best Practices and Well-Architected Approach for Building Your Rule Base
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Start with Managed Rules, Then Layer Custom Rules
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline Protections (Managed Rules)&lt;/strong&gt;: Quickly address common threats (SQLi, XSS, bad bots) by enabling relevant AWS Managed Rule Sets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Rules&lt;/strong&gt;: Tailor rules to your application’s business logic (e.g., block suspicious headers, limit request sizes, or restrict certain paths).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-Based Rules&lt;/strong&gt;: Mitigate DDoS or brute-force attacks by capping requests from a single IP address over a specified period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS Challenge and Captcha&lt;/strong&gt;: Implement JS challenge and captcha in your application in order to reduce DDoS by automated browsers, CLI  and other tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.2 “Count” Mode + Labels, Then Block
&lt;/h3&gt;

&lt;p&gt;A major best practice is to start all rules in “Count” mode and add labels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure each rule to Count rather than block initially.&lt;/li&gt;
&lt;li&gt;Assign a label to requests that match the rule (e.g., SuspiciousSQLi, XSSAttempt).&lt;/li&gt;
&lt;li&gt;Observe logs to verify these matches are legitimate threats or anomalies.&lt;/li&gt;
&lt;li&gt;Implement a final, catch-all blocking rule that blocks requests carrying labels deemed malicious.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach helps reduce false positives while ensuring you have visibility into what would be blocked before actively cutting off traffic as well as centralized control over custom block page headers and responses&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Extend with CloudFront Functions or Lambda@Edge
&lt;/h3&gt;

&lt;p&gt;In cases where AWS WAF rules do not cover a specific use case or you need more dynamic logic, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront Functions&lt;/strong&gt;: Lightweight JavaScript for short-running tasks (up to 1 ms) like inspecting headers, rewriting URIs, or adding custom headers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda@Edge&lt;/strong&gt;: A more robust solution for reading/modifying request or response bodies, implementing complex authentication flows, or dynamic transformations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: If WAF blocks a request, CloudFront Functions or Lambda@Edge will not run for that request.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 Align with AWS Well-Architected Security Pillar
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Design your WAF rule base according to AWS’s Well-Architected Security Pillar:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identification&lt;/strong&gt;: Label and log events to understand potential threats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive Protection&lt;/strong&gt;: Use a combination of managed and custom rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least Privilege&lt;/strong&gt;: Only allow known safe patterns or domains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Deploy and manage WAF using Infrastructure as Code (CloudFormation, Terraform) for consistency and repeatability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Rule Groups and Rule Labels
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 Rule Groups
&lt;/h3&gt;

&lt;p&gt;A Rule Group is a container for one or more WAF rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Managed Rule Groups&lt;/strong&gt;: Quick to enable, provide coverage for broad classes of attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Rule Groups&lt;/strong&gt;: Let you organize your own rules by function (e.g., IP restrictions, user-agent blocks, country blocks).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6.2 Rule Labels
&lt;/h4&gt;

&lt;p&gt;When a request matches a rule, WAF can attach labels for tracking or later decision-making. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;SQLiDetected&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;BadBotTraffic&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;DisallowedCountry&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can create a final rule that blocks requests carrying specific labels. Labels also appear in WAF logs, helping you analyze and correlate different rule matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Custom Responses in WAF Rule Actions
&lt;/h2&gt;

&lt;p&gt;AWS WAF supports custom responses for Block actions. Instead of returning a generic 403 Forbidden, you can define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom HTTP Status&lt;/strong&gt; Code (e.g., 403, 404, 502, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Headers&lt;/strong&gt; (e.g., to convey an error reason to the client)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom HTML or text Body&lt;/strong&gt; for the response
This feature is useful when you want to provide a branded or instructive error page, or add extra troubleshooting information for the user. Since the request is already blocked, no further edge functions (CloudFront Functions or Lambda@Edge) will execute for that request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: You might choose to return a 403 status with a short HTML body:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "Name": "MyWebACL",&lt;br&gt;
  "Scope": "CLOUDFRONT",&lt;br&gt;
  "DefaultAction": { "Allow": {} },&lt;br&gt;
  "Rules": [&lt;br&gt;
    {&lt;br&gt;
      "Name": "BlockWithCustomResponse",&lt;br&gt;
      "Priority": 1,&lt;br&gt;
      "Statement": {&lt;br&gt;
        "ByteMatchStatement": {&lt;br&gt;
          "FieldToMatch": { "SingleHeader": { "Name": "x-bad-header" } },&lt;br&gt;
          "PositionalConstraint": "EXACTLY",&lt;br&gt;
          "SearchString": "bad-value",&lt;br&gt;
          "TextTransformations": [{ "Priority": 0, "Type": "NONE" }]&lt;br&gt;
        }&lt;br&gt;
      },&lt;br&gt;
      "Action": {&lt;br&gt;
        "Block": {&lt;br&gt;
          "CustomResponse": {&lt;br&gt;
            "ResponseCode": 403,&lt;br&gt;
            "CustomResponseBodyKey": "CustomHTMLResponse",&lt;br&gt;
            "ResponseHeaders": [&lt;br&gt;
              { "Name": "X-Custom-Error", "Value": "BlockedByWAF" }&lt;br&gt;
            ]&lt;br&gt;
          }&lt;br&gt;
        }&lt;br&gt;
      },&lt;br&gt;
      "VisibilityConfig": {&lt;br&gt;
        "SampledRequestsEnabled": true,&lt;br&gt;
        "CloudWatchMetricsEnabled": true,&lt;br&gt;
        "MetricName": "BlockWithCustomResponseRule"&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  ],&lt;br&gt;
  "CustomResponseBodies": {&lt;br&gt;
    "CustomHTMLResponse": {&lt;br&gt;
      "ContentType": "TEXT_HTML",&lt;br&gt;
      "Content": "&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Access Denied&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Your request has been blocked.&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;"&lt;br&gt;
    }&lt;br&gt;
  },&lt;br&gt;
  "VisibilityConfig": {&lt;br&gt;
    "SampledRequestsEnabled": true,&lt;br&gt;
    "CloudWatchMetricsEnabled": true,&lt;br&gt;
    "MetricName": "MyACLMetric"&lt;br&gt;
  }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this example, any request including a header &lt;strong&gt;x-bad-header&lt;/strong&gt;: &lt;strong&gt;bad-value&lt;/strong&gt; is blocked, returning a &lt;strong&gt;403&lt;/strong&gt; code with a custom HTML body.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Limitations
&lt;/h2&gt;

&lt;p&gt;While AWS WAF is powerful and flexible, you should be aware of some important constraints and limitations:&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule Capacity (WCU – WAF Capacity Units)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Each rule (or statement within a rule) consumes a certain number of WCUs based on its complexity. For example, ByteMatchStatement or IP match conditions generally have lower WCU usage, while Regex match statements and complex logical operators can use more.&lt;/li&gt;
&lt;li&gt;There is a maximum WCU limit for each Web ACL. If your rules exceed that limit, you cannot add them all to the same Web ACL. You may need to simplify your rules or optimize your rule groups.&lt;/li&gt;
&lt;li&gt;Monitor your rule WCU usage (in the AWS console or via the CLI) to ensure you stay within allowed capacity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Regex Complexity
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Very complex regex patterns can quickly use up available WCUs. Simplify your patterns or break them into multiple statements where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Logging Latency
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;WAF logs are almost real time when using Cloudwatch and typically appear with a short delay in S3 or Kinesis&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Regional vs. Global
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;WAF on CloudFront is global; on ALB, API Gateway, etc., it’s regional.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cost
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Each rule, managed rule group, and request passing through WAF contributes to your monthly bill. Monitor usage carefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Inspection Header Size
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;WAF regional inspects up to a certain size for request headers/body (e.g., first 16 KB). &lt;/li&gt;
&lt;li&gt;WAF Global (Cloudfront) supports up to 64 KB headers/body inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Requests exceeding this limit may not be fully inspected.&lt;/p&gt;

&lt;h4&gt;
  
  
  Blocking Halts Edge Functions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;If a request is blocked by WAF, CloudFront Functions or Lambda@Edge will not run for that request.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Practical Information about Working with Labels
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Defining Labels&lt;/strong&gt;: Specify labels in your WAF rule actions (console or CLI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combining Labels&lt;/strong&gt;: Multiple rules can label the same request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match on Labels&lt;/strong&gt;: A final rule can check if a request has any suspicious labels and block it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt;: Inspect WAF logs to see which labels are assigned and confirm the accuracy of your rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Protecting Resources Outside AWS Using CloudFront
&lt;/h2&gt;

&lt;p&gt;One advantage of &lt;strong&gt;CloudFront + WAF&lt;/strong&gt; is the ability to protect &lt;strong&gt;external (non-AWS) origins&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a CloudFront distribution that points to your on-premises or third-party cloud origin.&lt;/li&gt;
&lt;li&gt;Attach a WAF Web ACL to that distribution.&lt;/li&gt;
&lt;li&gt;Enable caching for static or cacheable content to reduce load on the external origin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Secure Reverse Proxy&lt;/strong&gt;: CloudFront, acting as a secure reverse proxy, inspects all traffic with WAF at the edge before forwarding it to your external servers.&lt;br&gt;
This setup provides global edge security and performance benefits even if your origin is not hosted on AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. AWS Managed Protections for OWASP Top 10
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The OWASP Top 10&lt;/strong&gt; is a standard awareness document outlining the most critical web application security risks. AWS WAF addresses several OWASP Top 10 vulnerabilities via AWS Managed Rules and additional features:&lt;/p&gt;

&lt;h4&gt;
  
  
  Injection
&lt;/h4&gt;

&lt;p&gt;(SQLi, NoSQL, Command Injection)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWSManagedRulesSQLiRuleSet&lt;/strong&gt; helps block SQL injection attempts.&lt;br&gt;
Custom rules can detect command injection patterns.&lt;br&gt;
Broken Authentication and Session Management&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ATP&lt;/strong&gt; helps mitigate brute force or credential stuffing.&lt;br&gt;
Rate-based rules can address repeated login attempts.&lt;br&gt;
Sensitive Data Exposure&lt;/p&gt;

&lt;p&gt;WAF can block suspicious payloads that may indicate data exfiltration.&lt;br&gt;
Combine with SSL/TLS for encrypted data in transit.&lt;/p&gt;

&lt;h4&gt;
  
  
  XML External Entities (XXE)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom or third-party&lt;/strong&gt; rules detect malicious XML patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Broken Access Control
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;WAF custom rules can enforce strict path or header-based controls.&lt;/li&gt;
&lt;li&gt;Labels help track role-based or token-based access attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Security Misconfiguration
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS Managed Rule Sets automatically update to address new vulnerabilities.&lt;/li&gt;
&lt;li&gt;Custom rules detect unusual HTTP methods or suspicious headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cross-Site Scripting (XSS)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;AWSManagedRulesCommonRuleSet&lt;/strong&gt; includes patterns for typical XSS vectors.&lt;/p&gt;

&lt;h4&gt;
  
  
  Insecure Deserialization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Advanced rule sets can detect malicious serialization formats.&lt;/li&gt;
&lt;li&gt;Using Components with Known Vulnerabilities&lt;/li&gt;
&lt;li&gt;Regular updates to AWS Managed Rules help protect against newly disclosed CVEs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Insufficient Logging and Monitoring
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;WAF logging (to S3, CloudWatch, or Kinesis) provides robust, centralized monitoring.&lt;/li&gt;
&lt;li&gt;Combine with Athena or OpenSearch for advanced log analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. Pricing Model
&lt;/h2&gt;

&lt;h3&gt;
  
  
  12.1 AWS WAF Pricing
&lt;/h3&gt;

&lt;p&gt;AWS WAF pricing typically includes four main components:&lt;/p&gt;

&lt;h4&gt;
  
  
  Web ACLs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You pay a monthly charge for each web ACL that you create (e.g., around USD $5 per month per web ACL, depending on the region).&lt;/li&gt;
&lt;li&gt;Rules&lt;/li&gt;
&lt;li&gt;Each rule (custom or managed) in your web ACL adds a small monthly cost - often USD $1 per rule per month.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Rule Groups
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enabling an AWS Managed Rule Group or a third-party rule group from AWS Marketplace can incur a monthly subscription (for the group itself), plus the cost for each rule inside the group if applicable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Request Volume
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You pay for each million web requests inspected by AWS WAF. The typical rate is around USD $0.60–$1.00 per million requests (exact cost varies by region).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Important:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Prices &lt;strong&gt;vary&lt;/strong&gt; by AWS Region.&lt;/li&gt;
&lt;li&gt;Third-party managed rule groups may have higher subscription costs than AWS-managed rule sets.&lt;/li&gt;
&lt;li&gt;Overuse of large or complex rules can drive up monthly costs. Monitor usage carefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  12.2 CloudFront Pricing
&lt;/h3&gt;

&lt;p&gt;Amazon CloudFront has its own separate billing model. Main cost factors include:&lt;/p&gt;

&lt;h4&gt;
  
  
  Data Transfer Out
&lt;/h4&gt;

&lt;p&gt;You pay for data transferred from CloudFront edge locations to your users. Rates vary by geographic region and volume.&lt;/p&gt;

&lt;h4&gt;
  
  
  Requests
&lt;/h4&gt;

&lt;p&gt;You pay for HTTPS/HTTP requests served by CloudFront. Pricing can be on the order of USD $0.01 per 10,000 requests in many regions.&lt;/p&gt;

&lt;h4&gt;
  
  
  CloudFront Functions / Lambda@Edge
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront Functions&lt;/strong&gt;: Billed by the number of function invocations (e.g., $0.10 per 1 million invocations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda@Edge&lt;/strong&gt;: Billed by the number of requests plus compute time (GB-seconds).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimization Tip: Caching effectively in CloudFront can drastically reduce the number of origin fetches, which lowers data transfer costs and can also reduce Lambda@Edge invocations. However, caching does not reduce the number of WAF inspections, as every client request that hits CloudFront is still processed by WAF (unless previously blocked or allowed by another mechanism).&lt;/p&gt;

&lt;h2&gt;
  
  
  13. AWS WAF vs. Incapsula (Imperva) vs. Cloudflare vs. AWS WAF with CloudFront
&lt;/h2&gt;

&lt;p&gt;Below is a high-level comparison table, including a separate row for AWS WAF with CloudFront:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91imq2jtn7utk431nte0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91imq2jtn7utk431nte0.png" alt=" " width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Takeaways:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS WAF (Regional Services): Ideal for apps that don’t require global edge caching or are purely regional.&lt;/li&gt;
&lt;li&gt;AWS WAF with CloudFront: Global edge distribution, built-in CDN and caching, extends protection to external origins. Additionally, combining AWS WAF with CloudFront Functions and Lambda@Edge provides extended customization flexibility, allowing deep control over each and every HTTP parameter within the session, from rewriting headers to injecting custom logic for advanced routing or security checks.&lt;/li&gt;
&lt;li&gt;Incapsula &amp;amp; Cloudflare: Third-party global security/CDN solutions with their own pricing, feature sets, and potential integration complexities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  14. Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AWS WAF&lt;/strong&gt; is a powerful, flexible solution for safeguarding &lt;strong&gt;both AWS-hosted and external web applications&lt;/strong&gt;. With managed rules, custom rules, labels, and a layered “count then block” strategy, you can tailor your defenses while minimizing false positives. Integrating &lt;strong&gt;CloudFront&lt;/strong&gt; adds global edge coverage, caching, and the option to use &lt;strong&gt;CloudFront Functions&lt;/strong&gt; or &lt;strong&gt;Lambda@Edge&lt;/strong&gt; for advanced logic.&lt;/p&gt;

&lt;p&gt;Furthermore, &lt;strong&gt;AWS Shield Standard&lt;/strong&gt; is included at no additional cost, protecting your AWS resources from common network and transport-layer DDoS attacks, while WAF focuses on application-layer threats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account Takeover Prevention (ATP)&lt;/strong&gt; further boosts security by analyzing login attempts for suspicious behavior, making it especially effective when users log in with email addresses. This consistency aids in detection and labeling of potentially malicious traffic and benefits from AWS’s threat intelligence on compromised credentials from public data breaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Implementation Steps&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable AWS Managed Rules for immediate protection against common attacks, including OWASP Top 10 vulnerabilities.&lt;/li&gt;
&lt;li&gt;Add Custom Rules to address your application’s unique security needs.&lt;/li&gt;
&lt;li&gt;Leverage Rule Labels and test in “Count” mode before enforcing blocks.&lt;/li&gt;
&lt;li&gt;Adopt a Final Blocking Rule that triggers on suspicious labels (e.g., SQLiDetected, ATPHighRisk).&lt;/li&gt;
&lt;li&gt;Use CloudFront as a secure reverse proxy to protect both AWS and external origins, taking advantage of edge caching.&lt;/li&gt;
&lt;li&gt;Implement ATP to mitigate unauthorized login attempts, benefiting from standardized email-based usernames and compromised credential checks.&lt;/li&gt;
&lt;li&gt;Monitor Limitations such as WAF Capacity Units (WCU), inspection header size, and overall request volume to ensure your rules function effectively at scale.&lt;/li&gt;
&lt;li&gt;Use Custom Responses in block actions for branded or instructive error pages.&lt;/li&gt;
&lt;li&gt;Leverage AWS Shield Standard for built-in DDoS protection, covering the network and transport layers at no extra cost.&lt;/li&gt;
&lt;li&gt;Manage Costs by monitoring the number of web ACLs, rules, and request volume. Optimize with CloudFront caching to reduce origin fetches.&lt;/li&gt;
&lt;li&gt;Regularly Review OWASP Top 10 and confirm your AWS Managed Rule Sets (and custom rules) align with emerging vulnerabilities.
By following these best practices and leveraging AWS WAF’s advanced features, you can establish a well-architected, scalable, and cost-effective defense against evolving threats- both within and beyond the AWS ecosystem.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
