
CrowdSec Envoy Proxy Bouncer
A lightweight CrowdSec bouncer for Envoy Proxy using the ext_authz filter.
Features
- Block malicious IPs streamed via CrowdSec decisions
- Bouncer metrics reporting
- Request inspection via CrowdSec AppSec
- CAPTCHA challenges for suspicious IPs with support for:
- Google reCAPTCHA v2
- Cloudflare Turnstile
How It Works
The bouncer subscribes to decisions from CrowdSec via the Stream API and processes each request through multiple stages:
- IP Extraction: Determines the real client IP from forwarded headers, respecting trusted proxy configuration.
- Bouncer Check: Checks CrowdSec decision cache for IP-based decisions (ban, captcha, allow). Updates to cached decisions are real-time from the Stream API.
- WAF Analysis: If no blocking decision, forwards request to CrowdSec AppSec for analysis.
- Decision Application: Applies the final decision:
- Allow: Request proceeds normally
- Ban/Deny: Returns 403 Forbidden
- Captcha: Creates session and redirects to challenge page
When a captcha decision is made:
- CrowdSec or WAF returns "captcha" action for suspicious request
- Bouncer creates session and redirects to
/captcha/challenge?session=<id>
- User completes CAPTCHA and submits to
/captcha/verify
- On success, IP is cached (15 minutes by default) and user redirected to original URL
Configuration
The bouncer can be configured using:
- Configuration file (YAML or JSON)
- Environment variables
Configuration File
Create a config.yaml file:
server:
grpcPort: 8080 # Port for gRPC (Envoy ext_authz)
httpPort: 8081 # Port for HTTP (CAPTCHA endpoints)
logLevel: "info"
trustedProxies:
- 192.168.0.1
- 2001:db8::1
- 10.0.0.0/8
- 100.64.0.0/10
bouncer:
enabled: true
metrics: false
lapiURL: "http://crowdsec:8080"
apiKey: "<lapi-key>"
cacheCleanupInterval: "5m" # How often to clean up expired cache entries (optional)
waf:
enabled: true
appSecURL: "http://appsec:7422"
apiKey: "<lapi-key>"
captcha:
enabled: true
provider: "recaptcha" # Options: recaptcha, turnstile
siteKey: "<your-captcha-site-key>"
secretKey: "<your-captcha-secret-key>"
callbackURL: "https://yourdomain.com" # Base URL for captcha callbacks
# If the bouncer is hosted at https://my-domain.com the callbackURL should be https://my-domain.com
sessionDuration: "5m" # How long captcha verification is valid
cacheCleanupInterval: "5m" # How often to clean up expired cache entries (optional)
Run with config file:
envoy-proxy-bouncer serve --config config.yaml
Environment Variables
All configuration options can be set via environment variables using the prefix ENVOY_BOUNCER_ and replacing dots with underscores:
# Server configuration
export ENVOY_BOUNCER_SERVER_GRPCPORT=8080
export ENVOY_BOUNCER_SERVER_HTTPPORT=8081
export ENVOY_BOUNCER_SERVER_LOGLEVEL=debug
# Deprecated - use GRPCPORT instead
export ENVOY_BOUNCER_SERVER_PORT=8080
# Bouncer configuration
export ENVOY_BOUNCER_BOUNCER_ENABLED=true
export ENVOY_BOUNCER_BOUNCER_APIKEY=your-lapi-bouncer-api-key
export ENVOY_BOUNCER_BOUNCER_LAPIURL=http://crowdsec:8080
export ENVOY_BOUNCER_BOUNCER_TICKERINTERVAL=5s
export ENVOY_BOUNCER_BOUNCER_METRICS=false
export ENVOY_BOUNCER_BOUNCER_CACHECLEANUPINTERVAL=5m
# Trusted proxies (comma-separated)
export ENVOY_BOUNCER_TRUSTEDPROXIES=192.168.0.1,10.0.0.0/8
# WAF configuration
export ENVOY_BOUNCER_WAF_ENABLED=true
export ENVOY_BOUNCER_WAF_APPSECURL=http://appsec:7422
export ENVOY_BOUNCER_WAF_APIKEY=your-appsec-api-key
# CAPTCHA configuration
export ENVOY_BOUNCER_CAPTCHA_ENABLED=true
export ENVOY_BOUNCER_CAPTCHA_PROVIDER=recaptcha
export ENVOY_BOUNCER_CAPTCHA_SITEKEY=your-captcha-site-key
export ENVOY_BOUNCER_CAPTCHA_SECRETKEY=your-captcha-secret-key
export ENVOY_BOUNCER_CAPTCHA_CALLBACKURL=https://yourdomain.com
export ENVOY_BOUNCER_CAPTCHA_SESSIONDURATION=5m
export ENVOY_BOUNCER_CAPTCHA_CACHECLEANUPINTERVAL=5m
Configuration Precedence
The configuration is loaded in the following order (last wins):
- Default values
- Configuration file
- Environment variables
Required Configuration
A minimal configuration requires:
When bouncer is enabled:
bouncer.apiKey
bouncer.lapiURL
When WAF is enabled:
When CAPTCHA is enabled:
captcha.provider
captcha.siteKey
captcha.secretKey
captcha.callbackURL
Note on API keys:
- A key must be generated on your CrowdSec LAPI (with
cscli bouncers add <name>). You can use this key for both bouncer.apiKey and waf.apiKey.
Default Values
server:
grpcPort: 8080 # ext_authz grpc port
httpPort: 8081 # Only used when captcha is enabled
logLevel: "info"
bouncer:
enabled: false
metrics: false
tickerInterval: "10s"
cacheCleanupInterval: "5m"
waf:
enabled: false
captcha:
enabled: false
sessionDuration: "5m"
cacheCleanupInterval: "5m"
CAPTCHA Configuration
The bouncer supports CAPTCHA challenges as an alternative to immediately blocking suspicious IPs. When enabled, the bouncer runs dual servers:
- gRPC server (default port 8080): Handles Envoy ext_authz requests
- HTTP server (default port 8081): Serves CAPTCHA challenge and verification endpoints
Supported Providers
CAPTCHA Endpoints
When CAPTCHA is enabled, the HTTP server exposes these endpoints:
GET /captcha/challenge?session=<id>: Displays the CAPTCHA challenge page
POST /captcha/verify: Verifies CAPTCHA response and redirects user
Setup Instructions
- Register with CAPTCHA provider and obtain site key and secret key
- Configure the bouncer with your CAPTCHA credentials
- Update Envoy configuration to allow access to the HTTP server endpoints
- Set up CrowdSec scenarios to use
captcha remediation instead of ban
Usage
Install the binary
go install github.com/kdwils/envoy-proxy-bouncer@latest
Usage:
envoy-proxy-bouncer [command]
Available Commands:
bounce Test if an IP should be bounced or not
completion Generate the autocompletion script for the specified shell
help Help about any command
serve serve the envoy gateway bouncer
version envoy-proxy-bouncer version
Flags:
--config string config file (json or yaml)
-h, --help help for envoy-proxy-bouncer
-t, --toggle Help message for toggle
Use "envoy-proxy-bouncer [command] --help" for more information about a command.
Start the Bouncer
envoy-proxy-bouncer serve
Check an IP address against LAPI
envoy-proxy-bouncer bounce -i 192.168.1.1,10.0.0.1
Metrics
The bouncer can report metrics to CrowdSec's dashboard including:
- Total requests processed
- Number of requests bounced
These are opt-in and can be enabled by setting metrics: true in the bouncer config.
Metrics can be viewed using cscli
cscli metrics
Deploying
This project is tested in Kubernetes clusters with Envoy Gateway. For other environments, please open an issue if you encounter problems.
⚠️ Breaking Changes:
SecurityPolicy Configuration 09-21-25
Starting from version 0.2.0, SecurityPolicies are no longer created at the Gateway level due to limitations with redirect flows for CAPTCHA functionality. Individual HTTPRoutes cannot be excluded from gateway-level policies, which breaks the bouncer's redirect mechanism.
SecurityPolicies applied at the gateway level for the bouncer will cause infinite redirects.
Migration Required: SecurityPolicies must now be created at the HTTPRoute level per namespace. See the SecurityPolicy Configuration section below for examples.
Kubernetes
The bouncer can be deployed in a Kubernetes cluster alongside Envoy Gateway. See examples/deploy/README.md for a flat YAML example.
There is also a manifest that can be referenced in my homelab repo.
Helm
Add the Helm repository:
helm repo add envoy-proxy-bouncer https://kdwils.github.io/envoy-proxy-crowdsec-bouncer
helm repo update
Install the chart:
helm install bouncer envoy-proxy-bouncer/envoy-proxy-bouncer \
--set config.bouncer.enabled=true \
--set config.bouncer.apiKey=<lapi-key> \
--set config.bouncer.lapiURL=<your-crowdsec-host>:<port> \
--set config.trustedProxies=<your-trusted-proxies>
For cross-namespace SecurityPolicy access, enable the ReferenceGrant:
helm install bouncer envoy-proxy-bouncer/envoy-proxy-bouncer \
--set config.bouncer.enabled=true \
--set config.bouncer.apiKey=<lapi-key> \
--set config.bouncer.lapiURL=<your-crowdsec-host>:<port> \
--set referenceGrant.create=true \
--set referenceGrant.fromNamespaces="{media,argocd,blog}"
SecurityPolicy Configuration
SecurityPolicies must be created at the HTTPRoute level to ensure proper functionality with CAPTCHA redirects. Create a SecurityPolicy for each namespace that contains HTTPRoutes you want to protect.
Creating security policies
- Namespace: Create the SecurityPolicy in the same namespace as your HTTPRoutes
- Service Name: Update the service name to match your bouncer deployment
- Service Namespace: Ensure the namespace matches where the bouncer is deployed
- Port: Use port 8080 for the gRPC ext_authz service
- Target Multiple Routes: You can target multiple HTTPRoutes in the same SecurityPolicy
If an a set of HTTPRoutes exist like so:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: plex
namespace: media
spec:
hostnames:
- plex.my-domain.com
parentRefs:
- name: my-gateway
namespace: envoy-gateway-system
rules:
- backendRefs:
- name: plex
port: 32400
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: overseerr
namespace: media
spec:
hostnames:
- overseerr.my-domain.com
parentRefs:
- name: my-gateway
namespace: envoy-gateway-system
rules:
- backendRefs:
- name: overseerr
port: 80
Then the following security policy could then be created to apply to them:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: media
namespace: media
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: overseer
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: plex
extAuth:
grpc:
backendRefs:
- group: ""
kind: Service
name: envoy-proxy-bouncer
port: 8080
namespace: envoy-gateway-system
ReferenceGrant Configuration
When SecurityPolicies are created in different namespaces than the bouncer service, a ReferenceGrant is required to allow cross-namespace access. The Helm chart can automatically create this ReferenceGrant.
Example ReferenceGrant configuration in values.yaml:
referenceGrant:
create: true
fromNamespaces:
- media
- argocd
- blog
- vaultwarden
This creates a ReferenceGrant that allows SecurityPolicies from the specified namespaces to reference the bouncer service.
Migration from Gateway-Level Policies
If you were previously using gateway-level SecurityPolicies:
- Remove any existing gateway-level SecurityPolicies that target the bouncer
- Create namespace-specific SecurityPolicies targeting individual HTTPRoutes
- Ensure CAPTCHA endpoints (
/captcha/*) are accessible and not protected by the bouncer
Acknowledgements: