Proxy Middleware
A production-ready proxy middleware for the sillo ASGI framework.
Proxy Middleware
Section titled “Proxy Middleware”A production-ready proxy middleware for the sillo ASGI framework that handles applications running behind proxy servers, load balancers, and CDNs.
It automatically:
- Extracts real client IP addresses from
X-Forwarded-ForandForwardedheaders - Determines correct protocol from
X-Forwarded-Protoheaders - Handles
X-Forwarded-Hostfor proper host resolution - Provides security controls to prevent header spoofing
- Supports trusted proxy configuration with CIDR ranges
- Includes enhanced security middleware for stricter controls
Installation
Section titled “Installation”uv add sillo-contribQuick Start
Section titled “Quick Start”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
# Add basic proxy middlewareapp.use( Proxy( trusted_proxies=["192.168.1.0/24", "10.0.0.1"], trust_forwarded_headers=True ))
@app.get("/")async def home(request, response): # Get real client IP real_ip = getattr(request, 'client_ip', None) proxy_info = getattr(request, 'proxy_info', {})
return { "message": "Hello from behind proxy!", "client_ip": real_ip, "behind_proxy": proxy_info.get('trusted_proxy', False) }Common Proxy Scenarios
Section titled “Common Proxy Scenarios”Behind Nginx
Section titled “Behind Nginx”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
# Nginx typically sets X-Forwarded-* headersapp.use( Proxy( trusted_proxies=["127.0.0.1", "::1"], # Trust local proxy trust_forwarded_headers=True ))Behind AWS ELB/ALB
Section titled “Behind AWS ELB/ALB”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
# AWS load balancers use specific IP rangesapp.use( Proxy( trusted_proxies=[ "10.0.0.0/8", # Private IP range "172.16.0.0/12", # Docker bridge "192.168.0.0/16" # Local networks ] ))Behind Cloudflare
Section titled “Behind Cloudflare”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
# Cloudflare IPs are dynamic, use their rangesapp.use( Proxy( trusted_proxies=[ "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22", "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13", "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22" ] ))Configuration Options
Section titled “Configuration Options”ProxyMiddleware Options
Section titled “ProxyMiddleware Options”| Option | Type | Default | Description |
|---|---|---|---|
trusted_proxies | List[str] | [] | List of trusted proxy IP addresses/CIDRs |
trust_forwarded_headers | bool | True | Whether to trust X-Forwarded-* headers |
trust_forwarded_header | bool | True | Whether to trust Forwarded header |
preserve_host_header | bool | False | Whether to preserve original Host header |
store_proxy_info | bool | True | Store proxy information in request object |
TrustedProxyMiddleware Options
Section titled “TrustedProxyMiddleware Options”| Option | Type | Default | Description |
|---|---|---|---|
trusted_proxies | List[str] | Required | List of trusted proxy IP addresses/CIDRs |
require_https | bool | False | Require HTTPS when behind proxy |
max_forwards | int | 10 | Maximum number of proxy hops to allow |
Usage Examples
Section titled “Usage Examples”Basic Usage
Section titled “Basic Usage”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
app.use(Proxy())Trusted Proxy Configuration
Section titled “Trusted Proxy Configuration”from sillo import silloAppfrom sillo_contrib.proxy import Proxy
app = silloApp()
app.use( Proxy( trusted_proxies=[ "192.168.1.0/24", # Local network "10.0.0.1", # Specific proxy "2001:db8::1" # IPv6 proxy ], trust_forwarded_headers=True, store_proxy_info=True ))Enhanced Security
Section titled “Enhanced Security”from sillo import silloAppfrom sillo_contrib.proxy import TrustedProxyMiddleware
app = silloApp()
app.use( TrustedProxyMiddleware( trusted_proxies=["192.168.1.0/24"], require_https=True, # Require HTTPS when behind proxy max_forwards=5 # Limit proxy hops ))Accessing Proxy Information
Section titled “Accessing Proxy Information”@app.get("/debug")async def debug_info(request, response): proxy_info = getattr(request, 'proxy_info', {}) forwarded_for = getattr(request, 'x_forwarded_for', []) forwarded_header = getattr(request, 'forwarded_header', {})
return { "client_ip": getattr(request, 'client_ip', None), "proxy_info": proxy_info, "x_forwarded_for": forwarded_for, "forwarded_header": forwarded_header, "headers": dict(request.headers) }Supported Headers
Section titled “Supported Headers”Input Headers (Parsed)
Section titled “Input Headers (Parsed)”X-Forwarded-For: Client IP addresses (comma-separated)X-Forwarded-Proto: Original protocol (http/https)X-Forwarded-Host: Original hostX-Forwarded-Port: Original portForwarded: RFC 7239 compliant header
Output Headers (Added)
Section titled “Output Headers (Added)”X-Client-IP: Real client IP (added to response if different)
Security Considerations
Section titled “Security Considerations”Trusted Proxies
Section titled “Trusted Proxies”Always specify your trusted proxies explicitly:
# Good - explicit trusted proxiesapp.use(Proxy(trusted_proxies=["192.168.1.0/24"]))
# Bad - trusts all proxies (security risk)app.use(Proxy(trusted_proxies=[])) # Don't do this!Header Spoofing Prevention
Section titled “Header Spoofing Prevention”The middleware only processes proxy headers when the request comes from a trusted proxy IP, preventing header spoofing attacks.
# Example: Untrusted request with spoofed headers# Request from 203.0.113.1 (not in trusted_proxies)# Result: Headers are ignored, client_ip = 203.0.113.1HTTPS Enforcement
Section titled “HTTPS Enforcement”# Enforce HTTPS when behind proxyapp.use( TrustedProxyMiddleware( trusted_proxies=["10.0.0.0/8"], require_https=True ))Helper Functions
Section titled “Helper Functions”Parsing Headers
Section titled “Parsing Headers”from sillo_contrib.proxy import ( parse_x_forwarded_for, parse_forwarded_header, parse_x_forwarded_proto)
# Parse X-Forwarded-Forips = parse_x_forwarded_for("192.168.1.1, 10.0.0.1")# Returns: ["192.168.1.1", "10.0.0.1"]
# Parse Forwarded headerinfo = parse_forwarded_header('for=192.168.1.1; proto=https; host=example.com')# Returns: {"for": "192.168.1.1", "proto": "https", "host": "example.com"}IP Validation
Section titled “IP Validation”from sillo_contrib.proxy import is_trusted_proxy
# Check if IP is trustedis_trusted = is_trusted_proxy("192.168.1.100", ["192.168.1.0/24"])# Returns: True
# Check with multiple rangesis_trusted = is_trusted_proxy( "10.0.0.1", ["192.168.1.0/24", "10.0.0.0/8", "172.16.0.1"])Advanced Usage
Section titled “Advanced Usage”Custom Proxy Detection
Section titled “Custom Proxy Detection”from sillo_contrib.proxy import ProxyMiddleware
class CustomProxyMiddleware(ProxyMiddleware): def __init__(self, **kwargs): super().__init__(**kwargs)
def extract_client_ip(self, request): # Custom logic for extracting client IP custom_header = request.headers.get('X-Real-IP') if custom_header and self.is_trusted_proxy(request.client.host): return custom_header
return super().extract_client_ip(request)
app.use(CustomProxyMiddleware( trusted_proxies=["192.168.1.0/24"]))Logging Proxy Information
Section titled “Logging Proxy Information”import logging
@app.middlewareasync def log_proxy_info(request, response, call_next): proxy_info = getattr(request, 'proxy_info', {}) client_ip = getattr(request, 'client_ip', 'unknown')
if proxy_info.get('trusted_proxy'): logging.info(f"Request from {client_ip} via trusted proxy") else: logging.info(f"Direct request from {client_ip}")
return await call_next()Best Practices
Section titled “Best Practices”- Always specify trusted proxies - Never trust proxy headers from unknown sources
- Use CIDR ranges for better security than individual IPs
- Enable HTTPS enforcement when behind load balancers that terminate TLS
- Log proxy information for debugging and security monitoring
- Test with real proxy setups - Different proxies set different headers
- Monitor for header spoofing attempts in your logs
Example Production Configuration
Section titled “Example Production Configuration”from sillo import silloAppfrom sillo_contrib.proxy import TrustedProxyMiddleware
app = silloApp()
app.use( TrustedProxyMiddleware( trusted_proxies=[ "10.0.0.0/8", # Private networks "172.16.0.0/12", # Docker networks "192.168.0.0/16", # Local networks "127.0.0.1", # Localhost "::1" # IPv6 localhost ], require_https=True, max_forwards=3, store_proxy_info=True ))
# Add logging middleware to capture proxy info@app.middlewareasync def log_proxy_info(request, response, call_next): proxy_info = getattr(request, 'proxy_info', {}) if proxy_info.get('trusted_proxy'): logger.info(f"Request from {request.client_ip} via proxy")
return await call_next()Common Issues
Section titled “Common Issues”Incorrect Client IP
Section titled “Incorrect Client IP”If you’re seeing proxy IPs instead of real client IPs:
- Check that your proxy is sending the correct headers
- Verify your trusted_proxies configuration
- Ensure the middleware is added early in the chain
HTTPS/SSL Issues
Section titled “HTTPS/SSL Issues”If your app thinks it’s on HTTP when behind a TLS-terminating proxy:
- Ensure the proxy sets
X-Forwarded-Proto: https - Use
require_https=Truein TrustedProxyMiddleware - Check that the proxy is correctly configured
Host Header Issues
Section titled “Host Header Issues”If your app is getting the wrong host:
- Check
X-Forwarded-Hostheader from proxy - Consider setting
preserve_host_header=Trueif needed - Verify proxy configuration
Troubleshooting
Section titled “Troubleshooting”Debug Proxy Headers
Section titled “Debug Proxy Headers”@app.get("/debug/headers")async def debug_headers(request, response): return { "all_headers": dict(request.headers), "client_host": request.client.host, "client_ip": getattr(request, 'client_ip', None), "proxy_info": getattr(request, 'proxy_info', {}), "x_forwarded_for": request.headers.get('X-Forwarded-For'), "x_forwarded_proto": request.headers.get('X-Forwarded-Proto'), "x_forwarded_host": request.headers.get('X-Forwarded-Host'), "forwarded": request.headers.get('Forwarded') }Test Proxy Configuration
Section titled “Test Proxy Configuration”# Test script to verify proxy configurationimport requests
# Test direct requestresponse = requests.get("http://localhost:8000/debug/headers")print("Direct:", response.json())
# Test with proxy headersheaders = { "X-Forwarded-For": "203.0.113.1, 192.168.1.1", "X-Forwarded-Proto": "https", "X-Forwarded-Host": "example.com"}response = requests.get("http://localhost:8000/debug/headers", headers=headers)print("With headers:", response.json())Built with ❤️ by the @sillohq community.