Yellow Theme

Next start · November 2026

Notes / Engineering note

How we deliver · methodology

2026-09-03

Put CloudFront and WAF in front of Lambda Function URLs

AWS WAF cannot attach directly to a Lambda Function URL. Use CloudFront, a CloudFront-scoped web ACL in us-east-1, and a hostname you control.

Thesis: AWS WAF cannot associate a regional web ACL directly with a Lambda Function URL. Put Amazon CloudFront in front, use a CloudFront-scoped web ACL in us-east-1, and publish a branded hostname so your Content-Security-Policy never needs a wildcard for Function URL hosts.

What we tried first

Function URLs are attractive for a small POST/OPTIONS API: no API Gateway stage to babysit, IAM auth or public invoke, quick deploy. For a marketing-site form that posts JSON to the cloud, that shape is fine — until you ask for WAF.

AssociateWebACL against the Function URL ARN fails. Regional WAF expects Application Load Balancers, API Gateway REST/HTTP APIs, AppSync, Cognito user pools, and a few other resource types. Function URLs are not on that list.

The working shape

Browser
  │  HTTPS → intake.example.com
  ▼
CloudFront (custom domain + ACM in us-east-1)
  │  origin: Function URL (HTTPS-only)
  │  WAF: scope = CLOUDFRONT (must live in us-east-1)
  ▼
Lambda Function URL

# AuthType NONE still needs both permissions on the resource policy:
#   lambda:InvokeFunctionUrl
#   lambda:InvokeFunction

Three details matter in practice:

Why CSP cares

If the browser posts straight to a Function URL, teams often paper over the hostname with a CSP wildcard. That fingerprints the stack and widens the connect-src surface. A branded intake host is both cleaner for buyers reading your headers and stricter for what the page may call.

After cutover, verify with a simple header check on the marketing origin: connect-src should name the branded intake URL and any intentional analytics hosts — nothing broader.

Cost and ops notes

CloudFront plus WAF is not free. For a low-traffic form, treat it as a fixed control cost you turn on when the endpoint is public and worth defending — not as an afterthought once bots find the Function URL. Log WAF samples and CloudFront access logs to the same place you already watch for abuse.

Rate-based rules have AWS-side minimums; do not invent thresholds from memory — read the WAF documentation when you tune. Prefer starting with managed rule groups you understand, then add a conservative rate limit.

Two permissions for a public Function URL

With AuthType NONE, public callers still need both lambda:InvokeFunctionUrl and lambda:InvokeFunction on the resource policy. One without the other fails closed in surprising ways — CloudFront can look healthy while the origin rejects.

Checklist

Next in this series: WAF did not protect the raw Function URL · keep SSH closed until break-glass. How we deliver: methodology.

Engineering commentary only — not audit, legal, or certification advice.