Securing Your CloudFront Distribution: Lambda@Edge IP Gating vs AWS WAF
A comprehensive guide to implementing cost-effective IP gating with Lambda@Edge, including real-world gotchas, cost comparisons with AWS WAF, and a decision framework for choosing the right solution.
Securing Your CloudFront Distribution: Lambda@Edge IP Gating vs AWS WAF
The Problem: Locking Down Your Dev and Staging Environments
You've just deployed your beautiful new React app to a CloudFront distribution. Your development and staging environments are live, accessible via HTTPS, and... completely open to the internet.
Within hours, your boss gets an email from a competitor: "Hey, saw your new redesign. Nice work!"
Oops. 🤦
You need to lock down access to your non-production environments, but you don't want to:
- Set up a VPN (too much overhead for the team)
- Use Basic Auth (breaks Single Page Apps and feels clunky)
- Spend $60/month on AWS WAF for a dev site that costs $5/month to run
Enter Lambda@Edge IP gating: A cost-effective, elegant solution that validates IP addresses at the CloudFront edge before serving content.
The Use Case: Why IP Gating Matters
Development and Staging Environments
The most common use case for IP gating is protecting non-production environments:
The Challenge:
- You need external stakeholders (clients, partners, remote team) to access your staging site
- You don't want the site indexed by Google or discovered by competitors
- You don't want to pay enterprise prices for a simple access control mechanism
- You need flexibility to add/remove IPs quickly without redeploying
The Solution: IP gating with Lambda@Edge provides:
- ✅ Selective access - Only approved IPs can view the site
- ✅ Maintenance mode - Redirect unauthorized users to a maintenance page
- ✅ Cost-effective - Pennies per month for low-traffic dev sites
- ✅ Zero VPN overhead - Team members just need to be on the office network or VPN
- ✅ Dynamic updates - Change the allow-list via SSM Parameter Store without redeploying
Partner Portals and Beta Programs
Beyond dev environments, IP gating shines for:
Partner Portals:
- Restrict access to specific partner organizations by their office IP ranges
- Provide secure document sharing without complex authentication
- Compliance requirement: "only accessible from approved networks"
Closed Beta Programs:
- Allow beta testers from specific companies
- Prevent public access during private beta phase
- Quickly add/remove beta participant companies
Internal Tools:
- Restrict admin dashboards to corporate network
- Prevent accidental public exposure of internal tools
- Compliance: "administrative interfaces must not be publicly accessible"
How Lambda@Edge IP Gating Works
The architecture is beautifully simple:
User Request → CloudFront Edge Location
↓
Lambda@Edge Function
↓
Check IP against Allow-List (from SSM)
↓
✅ Allowed → Serve Content
❌ Blocked → Redirect OR Proxy to Lockout Page
The Lambda function:
- Extracts the client's IP from the CloudFront request headers
- Fetches the allow-list from SSM Parameter Store (cached via
@lru_cache) - Checks if the IP matches any CIDR block in the allow-list
- If blocked, either:
- Redirect mode: Returns HTTP 302 to lockout page (URL changes)
- Proxy mode: Fetches lockout page content and returns it (URL stays the same)
Two Response Modes
Redirect Mode (Default)
# Returns HTTP 302
return {
'status': '302',
'headers': {
'location': [{'value': f'https://{dns_alias}'}]
}
}
- User sees the lockout URL in their browser
- Simple and fast
- Clear indication they've been blocked
Proxy Mode (Advanced)
# Fetches and returns lockout page content
import urllib3
http = urllib3.PoolManager()
response = http.request('GET', f'https://{dns_alias}/index.html')
return {
'status': '200',
'body': response.data.decode('utf-8')
}
- User stays on the original URL
- Better user experience (less confusing)
- Slightly higher latency (~100-200ms for fetch)
- Falls back to redirect if proxy fails
Configuration is simple:
{
"cloudfront": {
"enable_ip_gating": true
}
}
That's it! The CDK Factory framework:
- Creates the Lambda@Edge function
- Configures SSM parameters for the allow-list
- Associates the function with your CloudFront distribution
- Sets up proper IAM permissions
Updating the allow-list:
# Add a new office IP
aws ssm put-parameter \
--name "/dev/my-app/lambda-edge/allow-cidrs" \
--value "203.0.113.0/24,198.51.100.0/24,192.0.2.0/24" \
--type StringList \
--overwrite
# Changes take effect within 5 minutes (cache expiration)
No deployment required!
Lessons Learned: Real-World Implementation Gotchas
When we first implemented this system, we encountered several subtle issues that aren't immediately obvious from the documentation. Here's what we learned the hard way:
Gotcha #1: CloudFront Domain vs DNS Alias
The Problem: Our initial proxy mode implementation fetched content from the raw CloudFront domain:
# ❌ This returns 403!
response = http.request('GET', f'https://d14pisygxjo4bs.cloudfront.net/index.html')
Result: Google's 403 error page instead of our beautiful lockout page.
The Root Cause:
CloudFront distributions with custom domain names (aliases) and host header restrictions only accept requests with the proper Host header. When you hit the raw CloudFront domain, it doesn't match any configured aliases and returns 403.
The Solution: Always use the DNS alias (custom domain) in your proxy requests:
# ✅ This works!
response = http.request('GET', f'https://lockout.example.com/index.html')
Key Lesson: Export the DNS alias, not the CloudFront domain, from your CDK stack:
"ssm_exports": {
"dns_alias": "/dev/my-app/secondary-site/dns-alias",
"cloudfront_domain": "/dev/my-app/secondary-site/cloudfront-domain" // ❌ Don't use this for proxy mode
}
Enhancement #2: Displaying the User's IP Address
Enhancement: We added IP address display to the lockout page using a client-side API call (it help troubleshooting).:
<p>
Your current IP address
(<strong><span id="user-ip">Loading...</span></strong>)
is not on the authorized list.
</p>
<script>
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
document.getElementById('user-ip').textContent = data.ip;
})
.catch(() => {
document.getElementById('user-ip').textContent = 'Unable to detect';
});
</script>
Key Lesson: Small UX improvements make a big difference. Users can now copy their IP and send it to admins for allowlist updates.
Gotcha #3: Where are my Lambda Logs for Lambda@Edge?
The Problem: Lambda@Edge functions don't write logs to the region where you deployed them (us-east-1). Instead, logs are written to CloudWatch Logs in the AWS region closest to where the function executed—the edge location serving the request. This means your logs are scattered across multiple regions worldwide, making debugging frustrating.
The Solution: Understand Lambda@Edge's distributed logging model:
- Log Group Naming: Log groups follow the pattern
/aws/lambda/us-east-1.{function-name}in each region where the function executes - Regional Distribution: Check CloudWatch Logs in regions like
us-east-1,us-west-2,eu-west-1, etc., depending on where your users are located - No Centralized View: AWS doesn't provide a unified log view—you must check each region individually
Finding Your Logs:
# List all regions where your Lambda@Edge function has logs
# (This example assumes your function name is "my-ip-gate-function")
for region in us-east-1 us-west-2 eu-west-1 eu-central-1 ap-southeast-1 ap-northeast-1; do
echo "Checking region: $region"
aws logs describe-log-groups \
--log-group-name-prefix "/aws/lambda/us-east-1.my-ip-gate-function" \
--region $region \
--query 'logGroups[*].[logGroupName,storedBytes]' \
--output table
done
Viewing Recent Logs:
# Get the latest logs from a specific region (e.g., us-east-1)
aws logs tail "/aws/lambda/us-east-1.my-ip-gate-function" \
--region us-east-1 \
--follow
Python Script to Search Across Regions:
import boto3
from datetime import datetime, timedelta
def find_lambda_edge_logs(function_name, search_term=None, hours=1):
"""
Search Lambda@Edge logs across all regions.
Args:
function_name: Name of your Lambda@Edge function
search_term: Optional text to search for in logs
hours: How many hours back to search (default: 1)
"""
regions = [
'us-east-1', 'us-west-2', 'eu-west-1', 'eu-central-1',
'ap-southeast-1', 'ap-northeast-1', 'ap-south-1'
]
log_group_name = f'/aws/lambda/us-east-1.{function_name}'
start_time = int((datetime.now() - timedelta(hours=hours)).timestamp() * 1000)
results = {}
for region in regions:
try:
logs = boto3.client('logs', region_name=region)
# Check if log group exists
response = logs.describe_log_groups(
logGroupNamePrefix=log_group_name
)
if not response['logGroups']:
continue
print(f"\n📍 Found logs in {region}")
# Get log streams
streams = logs.describe_log_streams(
logGroupName=log_group_name,
orderBy='LastEventTime',
descending=True,
limit=5
)
for stream in streams['logStreams']:
events = logs.get_log_events(
logGroupName=log_group_name,
logStreamName=stream['logStreamName'],
startTime=start_time
)
for event in events['events']:
message = event['message']
if search_term is None or search_term in message:
timestamp = datetime.fromtimestamp(
event['timestamp'] / 1000
).isoformat()
print(f" [{timestamp}] {message}")
except Exception as e:
# Region might not have logs or might not be enabled
pass
return results
# Usage:
find_lambda_edge_logs('my-ip-gate-function', search_term='blocked', hours=2)
CloudWatch Insights Query:
For more advanced log analysis, use CloudWatch Insights (must run separately in each region):
fields @timestamp, @message
| filter @message like /blocked/
| sort @timestamp desc
| limit 100
Key Lesson: Lambda@Edge's distributed logging is a feature, not a bug—it enables low-latency edge execution. Plan for this by:
- Using centralized logging (ship logs to a central location via CloudWatch subscriptions)
- Testing locally with representative IP addresses before deploying
- Adding structured logging with correlation IDs to track requests across regions
- Monitoring CloudWatch metrics (which ARE centralized in us-east-1) for error rates
Testing Strategy That Saved Us
After the first round of bugs, we implemented comprehensive pytest tests:
@patch('handler.get_ssm_parameter')
def test_proxy_mode_uses_dns_alias(mock_get_ssm):
"""Verify proxy mode uses DNS alias, not CloudFront domain."""
mock_get_ssm.side_effect = lambda name, region, default=None: {
'/dev/my-app/gate-enabled': 'true',
'/dev/my-app/allow-cidrs': '10.0.0.0/8',
'/dev/my-app/dns-alias': 'lockout.example.com',
'/dev/my-app/response-mode': 'proxy'
}.get(name, default)
# Test blocked IP triggers proxy mode
event = create_test_event(client_ip='1.2.3.4')
result = handler.lambda_handler(event, mock_context)
# Verify the proxy URL uses dns-alias
assert 'lockout.example.com' in result['body']
These tests caught issues before deployment, saving 30-minute deployment cycles.
Lambda@Edge IP Gating: The Good, The Bad, and The Ugly
The Good ✅
1. Extremely Cost-Effective
For most non-production environments, Lambda@Edge IP gating costs pennies:
| Traffic/Month | Lambda@Edge Cost |
|---|---|
| 10,000 requests (small dev site) | $0.01 |
| 100,000 requests (active staging) | $0.07 |
| 1,000,000 requests (busy internal portal) | $0.66 |
Compare this to AWS WAF's base cost of $5/month + $1/million requests, and the savings are clear for low-traffic sites.
2. Simple to Implement
With CDK Factory, it's literally a few lines of configuration (one specific setting). No complex rule sets, no learning curve, no separate service to manage.
3. Flexible and Dynamic
Update the allow-list in SSM Parameter Store without redeploying:
- Add a new partner organization? Update SSM, wait 5 minutes.
- Remote employee needs access? Add their home IP, done.
- Emergency block? Update and it propagates in minutes.
4. Works Everywhere
Lambda@Edge runs at CloudFront edge locations globally. Whether your user is in Tokyo or Toronto, the IP check happens at the nearest edge location with consistent <10ms latency.
5. Perfect for Dev/Staging
This is where Lambda@Edge IP gating truly shines:
- Development sites with 1,000-10,000 requests/month
- Staging environments with occasional stakeholder access
- Internal tools with predictable usage patterns
- Partner portals with limited user bases
6. Maintenance Mode Built-In
The "gate-enabled" flag lets you instantly switch between gated and maintenance mode:
# Enable gate (block unauthorized IPs)
aws ssm put-parameter --name "/dev/my-app/gate-enabled" --value "true" --overwrite
# Disable gate (everyone redirected to maintenance page)
aws ssm put-parameter --name "/dev/my-app/gate-enabled" --value "false" --overwrite
Perfect for deployments or scheduled maintenance.
The Bad ⚠️
1. Not Free for High Traffic
While $0.66/month for 1M requests is cheap, it scales linearly:
- 10M requests/month: ~$6.60
- 50M requests/month: ~$33.00
- 100M requests/month: ~$66.00
At very high traffic volumes (>10M requests/month), AWS WAF becomes competitive.
2. IP-Based Security Has Limitations
IP addresses aren't perfect security:
- Shared IPs: Multiple users might share the same corporate NAT gateway
- Dynamic IPs: Home users' IPs change periodically
- VPN hopping: Determined attackers can use VPNs from allowed IP ranges
- IP spoofing: (Mostly mitigated by CloudFront, but worth noting)
For truly sensitive data, you need authentication (OAuth, SAML, etc.), not just IP gating.
3. Management Overhead for Large Allow-Lists
If you have 100+ partner organizations each with multiple office locations, maintaining the allow-list in SSM becomes tedious. You might want to:
- Build an API for self-service IP management
- Integrate with your identity provider
- Consider authentication instead
4. CloudFront-Specific
Lambda@Edge only works with CloudFront distributions. If you're using:
- Direct ALB/API Gateway access
- S3 website hosting (without CloudFront)
- EC2 instances with Elastic IPs
You'll need a different solution (security groups, WAF, etc.).
5. Cold Start Latency
Lambda@Edge has cold start latency (typically 50-200ms):
- First request from a region: slower
- Subsequent requests: cached, fast (~5-10ms)
- For most use cases, this is imperceptible
- For latency-critical applications (gaming, real-time trading), this matters
The Ugly 😱
1. Lambda@Edge Deployment is SLOW
When you update a Lambda@Edge function, it takes 5-15 minutes to propagate globally to all CloudFront edge locations.
If you find a bug in your IP validation logic, you're waiting half an hour for the fix to deploy.
🚦 What makes Lambda@Edge slower
Global replication.
- When you publish a new version of a Lambda@Edge function, AWS replicates that version to CloudFront edge locations worldwide (over 400 PoPs). This propagation can take several minutes — sometimes 5–15 minutes, occasionally longer.
Immutable versions.
- Each time you update code, you must publish a new version and re-associate it with the CloudFront distribution. CloudFront then needs to deploy that new association globally, which adds another 5–10 minutes for the distribution to finish deploying. So making a quick change in the console isn't as straightforward as you might think..
- It's best to use the SSM parameters for configuration changes (instant) rather than code changes.
- Use CI/CD to deploy Lambda@Edge functions.
Two-phase process.
- Upload & publish Lambda version (to us-east-1).
- CloudFront updates its configuration and replicates the code globally.
- Each step adds latency compared to a normal Lambda deploy that stays in one region.
Mitigation: Test thoroughly! Use the SSM parameters for configuration changes (instant) rather than code changes.
Real-world example: We discovered a bug where get_ssm_parameter() was being called with a default keyword argument that the function didn't support (at the time - we added on later). Doh! 🤦
The fix took 2 minutes to code, but 15 minutes to deploy and test. This is why thorough unit testing is critical for Lambda@Edge.
2. Lambda@Edge Limits are Strict
Lambda@Edge has tighter limits than regular Lambda:
- Max memory: 10,240MB (but you only need 128MB for IP checks)
- Max execution time: 5 seconds for viewer request (plenty for IP validation) CloudFront viewer events have a hard 5 second timeout. Origin-request/origin-response can run up to 30 seconds.
- Package size: 50MB (compressed), 250MB (uncompressed)
- Environment variables: 4KB limit (use SSM instead)
For IP gating, these limits aren't a problem, but they prevent more complex use cases.
3. Debugging is Harder
Lambda@Edge logs are scattered across CloudWatch Logs in every region where it executes. When debugging:
- Check logs in all regions where CloudFront has edge locations
- Logs are in the region closest to the user, not your primary region
- Makes troubleshooting more complex than regular Lambda
Pro tip: Use structured logging (JSON) and aggregate logs with CloudWatch Insights.
4. No Local Testing
You can't run Lambda@Edge locally with the same CloudFront context. Testing requires:
- Deploying to AWS (30-minute wait)
- Or mocking the CloudFront event structure (tedious)
We've built comprehensive test harnesses with pytest that mock:
- CloudFront request events
- SSM parameter responses
- Runtime configuration files
- Multiple environment scenarios
This catches 90% of bugs before deployment, but it's still not as smooth as regular Lambda development.
When to Use AWS WAF Instead
AWS WAF (Web Application Firewall) is a different beast entirely. Here's when it makes sense:
Choose AWS WAF When:
1. High Traffic (>10M Requests/Month)
Cost comparison:
- 10M requests: Lambda@Edge $6.60 vs WAF $15.00
- 50M requests: Lambda@Edge $33.00 vs WAF $55.00
- 100M requests: Lambda@Edge $66.00 vs WAF $105.00
While Lambda@Edge is still cheaper, WAF offers more features for the price at high volumes.
2. You Need Complex Rules
WAF excels at sophisticated traffic filtering:
- Rate limiting: Block IPs making >1000 requests/minute
- Geo-blocking: Block entire countries
- SQL injection detection: Built-in threat detection
- XSS protection: Inspect request bodies for attacks
- Bot mitigation: Integrate with AWS Shield for DDoS protection
- Managed rule sets: Subscribe to AWS or third-party threat intelligence
Lambda@Edge IP gating is simple: allow or deny based on IP. WAF is a full security suite.
3. Compliance Requirements
Many compliance frameworks explicitly require a WAF:
- PCI DSS 6.6: Web application firewall for cardholder data
- HIPAA: WAF often required for PHI-handling applications
- SOC 2: WAF demonstrates security controls
- ISO 27001: WAF is a common control
If auditors ask "do you have a WAF?", Lambda@Edge won't suffice.
4. Production Applications
For production public-facing applications:
- DDoS protection: WAF integrates with AWS Shield Advanced
- OWASP Top 10: WAF protects against common web vulnerabilities
- Managed threat intelligence: Automatically updated rules
- Advanced logging: Integration with AWS Security Hub, GuardDuty
Your production app deserves production-grade security.
5. Multiple Applications
One WAF Web ACL can protect multiple CloudFront distributions, ALBs, and API Gateways. If you have:
- 10 CloudFront distributions
- Each needs IP filtering
- Cost: $5 base + $1/rule + $1/million requests
Shared infrastructure makes WAF more cost-effective at scale.
AWS WAF: The Good, The Bad, and The Ugly
The Good ✅
1. Comprehensive Security
- IP allow/deny lists (like Lambda@Edge)
- Rate limiting (block DDoS)
- Geo-blocking (block entire countries)
- SQL injection protection
- XSS detection
- Custom regex patterns
- Managed rule groups (OWASP, bot control)
2. Better for Production
- Battle-tested by millions of applications
- Integration with AWS Security Hub
- Compliance certifications (PCI, HIPAA)
- Advanced DDoS protection with Shield
3. Instant Updates
- Rule changes take seconds, not 30 minutes
- No deployment required
- No code to maintain
4. Centralized Management
- One WAF protects multiple resources
- Unified logging and monitoring
- AWS Firewall Manager for enterprise
5. Predictable Pricing
- $5/month base (per Web ACL)
- $1/month per rule
- $1 per million requests
- No surprise charges
The Bad ⚠️
1. Base Cost
- Minimum $5/month even for 0 requests
- For a dev site with 10K requests/month, you're paying $5.00 vs $0.01 with Lambda@Edge
- Not economical for low-traffic dev/staging
2. More Complex
- Learning curve for rule configuration
- WAF has its own query language
- More moving parts to maintain
- Requires understanding of web security concepts
3. Rule Limits
- 1,500 Web ACL capacity units per Web ACL
- Complex rules consume more capacity units
- May need multiple Web ACLs for complex requirements
4. IP List Maintenance
- IP sets limited to 10,000 IP addresses
- Managing large lists requires automation
- No built-in TTL or expiration
The Ugly 😱
1. Easy to Misconfigure
- Accidentally block legitimate traffic
- Allow malicious traffic through
- Complex rule interactions are hard to debug
- "Default allow" vs "default deny" confusion
2. Costs Can Escalate
- $1/million requests adds up fast
- 100M requests/month = $100 just for WAF
- Bot inspection adds $10/million requests
- AWS Shield Advanced: $3,000/month
3. Limited Testing
- No local testing environment
- Staging WAF costs same as production
- Hard to test rules without live traffic
The Hybrid Approach: Start Simple, Scale Up
Here's the strategy we recommend:
Stage 1: Development (Lambda@Edge)
Traffic: 1K-10K requests/month
Cost: <$0.10/month
Security: IP gating for office/VPN
Use Lambda@Edge because:
- Costs almost nothing
- Simple to implement
- Easy to maintain
- Sufficient for internal access control
Stage 2: Staging (Lambda@Edge)
Traffic: 10K-100K requests/month
Cost: $0.07-$0.70/month
Security: IP gating for team + clients
Stick with Lambda@Edge because:
- Still very cheap
- Stakeholder access is IP-based anyway
- No compliance requirements yet
- Development velocity matters more
Stage 3: Production Beta (Lambda@Edge or WAF)
Traffic: 100K-1M requests/month
Cost: Lambda@Edge $0.66 vs WAF $6.00
Security: IP gating for beta users
Decision point:
- Lambda@Edge if beta is invite-only with known IP ranges
- WAF if you need rate limiting or bot protection
Stage 4: Public Production (WAF)
Traffic: 1M-100M+ requests/month
Cost: WAF $15-$105/month
Security: Full web application firewall
Use AWS WAF because:
- Production deserves production security
- Rate limiting prevents DDoS
- Managed rules protect against OWASP Top 10
- Compliance requirements
- Cost is now <10% of total infrastructure
The Decision Matrix
Use this to decide which solution fits your needs:
| Criteria | Lambda@Edge IP Gating | AWS WAF |
|---|---|---|
| Traffic < 1M/month | ✅ Best choice | ⚠️ Overkill |
| Traffic 1M-10M/month | ✅ Cost-effective | ⚠️ More features, higher cost |
| Traffic > 10M/month | ⚠️ Still works, but pricey | ✅ Better value |
| Dev/Staging only | ✅ Perfect fit | ❌ Unnecessary expense |
| Production public site | ⚠️ Basic protection only | ✅ Recommended |
| Simple IP allow-list | ✅ Exactly right | ⚠️ Overengineered |
| Complex security rules | ❌ Can't do this | ✅ Built for this |
| Rate limiting needed | ❌ Not supported | ✅ Core feature |
| Geo-blocking needed | 🔨 Possible but hacky | ✅ Native support |
| Compliance required | ❌ Not sufficient | ✅ Certified |
| Quick configuration changes | ⚠️ SSM (5 min), Code (30 min) | ✅ Instant |
| Cost predictability | ⚠️ Scales with traffic | ✅ Fixed base + linear |
| Ease of implementation | ✅ cdk-factory config |
⚠️ Learning curve |
| Debugging difficulty | ⚠️ Logs in all regions | ✅ Centralized logs |
Real-World Success Story
At our company, we rolled out Lambda@Edge IP gating across 15 CloudFront distributions:
Dev Environments (10 sites):
- Traffic: 5K-20K requests/month each
- Lambda@Edge cost: $0.01-$0.15/month per site
- Total: $0.50/month for 10 dev sites
- Equivalent with WAF: $50/month (10 Web ACLs)
- Savings: $594/year
Staging Environments (3 sites):
- Traffic: 50K-100K requests/month each
- Lambda@Edge cost: $0.30-$0.70/month per site
- Total: $1.50/month for 3 staging sites
- Equivalent with WAF: $15/month
- Savings: $162/year
Partner Portal (1 site):
- Traffic: 200K requests/month
- Lambda@Edge cost: $1.20/month
- Equivalent with WAF: $6/month
- Savings: $57.60/year
Production Site (1 site):
- Traffic: 25M requests/month
- Using AWS WAF: $30/month
- Lambda@Edge would cost: $16.50/month
- Extra cost worth it for rate limiting, bot protection, compliance
Total Annual Savings: $813.60
More importantly, we:
- ✅ Locked down all dev/staging environments (no more competitor snooping)
- ✅ Gave stakeholders easy access (no VPN setup)
- ✅ Met compliance requirements for production (WAF + Shield)
- ✅ Maintained flexibility (SSM parameters for quick updates)
Implementation with CDK Factory
With CDK Factory, implementing IP gating is trivial:
{
"stacks": [
{
"name": "my-app-secondary-site",
"module": "static_website_stack",
"enabled": true,
"dns": {
"aliases": ["lockout.example.com"]
},
"ssm_exports": {
"dns_alias": "/dev/my-app/secondary-site/dns-alias",
"cloudfront_domain": "/dev/my-app/secondary-site/cloudfront-domain"
}
},
{
"name": "my-app-ip-gate",
"module": "lambda_edge_library_module",
"dependencies": ["my-app-secondary-site"],
"lambda_edge": {
"name": "my-app-ip-gate",
"runtime": "python3.11",
"handler": "handler.lambda_handler",
"code_path": "cdk_factory:lambdas/edge/ip_gate",
"environment": {
"GATE_ENABLED": "true",
"ALLOW_CIDRS": "10.0.0.0/8,192.168.0.0/16",
"DNS_ALIAS": "{{ssm:/dev/my-app/secondary-site/dns-alias}}",
"RESPONSE_MODE": "proxy"
}
}
},
{
"name": "my-app-site",
"module": "static_website_stack",
"dependencies": ["my-app-ip-gate"],
"cloudfront": {
"enable_ip_gating": true
}
}
]
}
Behind the scenes, CDK Factory:
- Creates the Lambda@Edge function from your code
- Sets up SSM parameters with sensible defaults
- Configures IAM permissions for SSM access
- Associates the function with CloudFront's viewer-request event
- Handles the Lambda@Edge deployment (the slow part)
- Validates the setup with explicit stack dependencies
Initial setup: 30 minutes (Lambda@Edge propagation) Updating allow-list: 5 minutes (SSM cache expiration) Cost: $0.01-$10/month depending on traffic
Conclusion: The Right Tool for the Job
Lambda@Edge IP gating is the Swiss Army knife for non-production environments:
- ✅ Incredibly cost-effective for low-traffic sites
- ✅ Simple to implement and maintain
- ✅ Perfect for dev, staging, partner portals, and beta programs
- ✅ Flexible with dynamic SSM-based configuration
AWS WAF is the professional security solution for production:
- ✅ Comprehensive protection against web attacks
- ✅ Rate limiting and DDoS prevention
- ✅ Compliance certifications
- ✅ Better value at high traffic volumes (>10M requests/month)
Our recommendation:
- Start with Lambda@Edge for all dev/staging environments
- Save $500-1000/year on unnecessary WAF costs
- Graduate to WAF when you go to production
- Sleep better knowing your production site has enterprise-grade security
The best part? You don't have to choose just one. Use Lambda@Edge where it makes sense (dev/staging/internal), and WAF where it matters (production).
That's the beauty of cloud architecture: use the right tool for each job.
Try It Yourself
Ready to implement IP gating for your CloudFront distributions?
CDK Factory makes it easy:
pip install cdk-factory
# Follow the guides:
# docs/ip-gating/implementation.md
# docs/ip-gating/architecture.md
# docs/ip-gating/cost.md
Resources:
- Lambda@Edge IP Gating Implementation Guide
- Architecture Deep Dive
- Detailed Cost Analysis
- GitHub: CDK Factory
Have questions? Found this useful? Let us know in the comments!
About the Author: This post was written based on real-world experience implementing Lambda@Edge IP gating across dozens of CloudFront distributions, saving thousands in infrastructure costs while maintaining security best practices.
Updated: October 2025 - Lambda@Edge pricing and best practices current as of publication date.