CrowdSec WAF QuickStart for Envoy Gateway
Objectives
This quickstart shows how to deploy the CrowdSec AppSec Component in Kubernetes and protect workloads exposed through Envoy Gateway using an external authorization service.
At the end, you will have:
- CrowdSec running in-cluster with the AppSec API listening on
7422 - A CrowdSec-compatible Envoy external auth service running in the cluster. This is the service Envoy calls during external authorization checks; it queries CrowdSec LAPI and AppSec, then tells Envoy whether the request should be allowed or denied.
SecurityPolicyresources attached to yourHTTPRouteobjects. In Envoy Gateway, aSecurityPolicyis the custom resource that enables external authorization for aGatewayorHTTPRouteand points Envoy to the auth backend.HTTPRouteobjects exposing your applications. In Gateway API, anHTTPRoutedefines how HTTP requests for specific hostnames or paths are matched and forwarded to your backendService.- Virtual patching and generic AppSec rules inspecting requests before they reach your backends
Prerequisites
-
If you're new to the AppSec Component or Web Application Firewalls, start with the Introduction.
-
It is assumed that you already have:
- A working CrowdSec Security Engine installation. For a Kubernetes install quickstart, refer to /u/getting_started/installation/kubernetes.
- A working Envoy Gateway installation with the Gateway API CRDs and an
accepted
GatewayClass. - Existing
Gateway/HTTPRouteresources exposing your applications.
Store the Envoy bouncer key in a Kubernetes secret
For Envoy Gateway, a practical approach is to choose a fixed key, store it in a
Kubernetes secret, and force BOUNCER_KEY_envoy from lapi.env with
valueFrom.secretKeyRef.
Create or update the secret used by CrowdSec LAPI:
apiVersion: v1
kind: Secret
metadata:
name: crowdsec-keys
namespace: crowdsec
type: Opaque
stringData:
ENROLL_KEY: "<your-existing-enroll-key>"
BOUNCER_KEY_envoy: "<choose-a-long-random-key>"
Apply it:
kubectl apply -f crowdsec-keys.yaml
Then reference BOUNCER_KEY_envoy from the CrowdSec Helm values:
lapi:
env:
- name: BOUNCER_KEY_envoy
valueFrom:
secretKeyRef:
name: crowdsec-keys
key: BOUNCER_KEY_envoy
Apply the CrowdSec release again:
helm upgrade --install crowdsec crowdsec/crowdsec \
--namespace crowdsec \
--create-namespace \
-f crowdsec-values.yaml
Deploy CrowdSec with AppSec enabled
Add this to the CrowdSec values.yaml to enable the AppSec acquisition
datasource and load the default AppSec configuration:
agent:
acquisition:
- namespace: envoy-gateway-system
podName: envoy-default-*
poll_without_inotify: true
program: envoy
env:
- name: COLLECTIONS
value: yanis-kouidri/envoy
- name: DEBUG
value: "true"
appsec:
acquisitions:
- appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
listen_addr: 0.0.0.0:7422
path: /
source: appsec
enabled: true
env:
- name: COLLECTIONS
value: crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules
lapi:
env:
- name: BOUNCER_KEY_envoy
valueFrom:
secretKeyRef:
key: BOUNCER_KEY_envoy
name: crowdsec-keys
Apply or upgrade the CrowdSec release:
helm upgrade --install crowdsec crowdsec/crowdsec \
--namespace crowdsec \
--create-namespace \
-f crowdsec-values.yaml
Verify the CrowdSec pods:
kubectl -n crowdsec get pods
kubectl -n crowdsec get svc crowdsec-service crowdsec-appsec-service
You should see:
crowdsec-lapiinRunningcrowdsec-appsecinRunningcrowdsec-serviceexposing port8080crowdsec-appsec-serviceexposing port7422
Deploy the Envoy external auth bouncer
- Helm-Based Installation
- Without Helm
For the Helm-based install, keep the API key in a Kubernetes Secret and
reference that secret from a user-managed values.yaml file.
Create the secret holding the CrowdSec bouncer key:
apiVersion: v1
kind: Secret
metadata:
name: crowdsec-envoy-bouncer-secrets
namespace: envoy-gateway-system
type: Opaque
stringData:
api-key: "<same-value-as-BOUNCER_KEY_envoy>"
Apply it:
kubectl apply -f crowdsec-envoy-bouncer-secret.yaml
Then create a values file:
config:
bouncer:
lapiURL: http://crowdsec-service.crowdsec.svc.cluster.local:8080
apiKeySecretRef:
name: crowdsec-envoy-bouncer-secrets
key: api-key
waf:
enabled: true
appSecURL: http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422
apiKeySecretRef:
name: crowdsec-envoy-bouncer-secrets
key: api-key
securityPolicy:
create: true
gatewayName: shared-public
gatewayNamespace: envoy-gateway-system
Install the chart and create a SecurityPolicy:
helm install crowdsec-envoy-bouncer oci://ghcr.io/kdwils/charts/envoy-proxy-bouncer \
--namespace envoy-gateway-system \
--create-namespace \
-f envoy-bouncer-values.yaml
If you only want to deploy the bouncer and manage SecurityPolicy objects
manually, omit the securityPolicy.* settings.
Keep the bouncer API key in a Kubernetes secret in the namespace where your
Envoy Gateway infrastructure runs. In this example, that namespace is
envoy-gateway-system.
apiVersion: v1
kind: Secret
metadata:
name: crowdsec-envoy-bouncer-secrets
namespace: envoy-gateway-system
type: Opaque
stringData:
api-key: "<same-value-as-BOUNCER_KEY_envoy>"
Apply the secret:
kubectl apply -f crowdsec-envoy-bouncer-secret.yaml
Then create the bouncer objects directly:
apiVersion: apps/v1
kind: Deployment
metadata:
name: crowdsec-envoy-bouncer
namespace: envoy-gateway-system
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: crowdsec-envoy-bouncer
template:
metadata:
labels:
app.kubernetes.io/name: crowdsec-envoy-bouncer
spec:
containers:
- name: crowdsec-envoy-bouncer
image: msabban/envoy-proxy-crowdsec-bouncer:latest
imagePullPolicy: Always
ports:
- name: grpc
containerPort: 8080
protocol: TCP
env:
- name: ENVOY_BOUNCER_BOUNCER_ENABLED
value: "true"
- name: ENVOY_BOUNCER_BOUNCER_APIKEY
valueFrom:
secretKeyRef:
name: crowdsec-envoy-bouncer-secrets
key: api-key
- name: ENVOY_BOUNCER_BOUNCER_APIURL
value: http://crowdsec-service.crowdsec.svc.cluster.local:8080
- name: ENVOY_BOUNCER_WAF_ENABLED
value: "true"
- name: ENVOY_BOUNCER_WAF_APIKEY
valueFrom:
secretKeyRef:
name: crowdsec-envoy-bouncer-secrets
key: api-key
- name: ENVOY_BOUNCER_WAF_APPSECURL
value: http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422
- name: ENVOY_BOUNCER_SERVER_GRPCPORT
value: "8080"
- name: ENVOY_BOUNCER_SERVER_HTTPPORT
value: "8081"
- name: ENVOY_BOUNCER_SERVER_LOGLEVEL
value: info
---
apiVersion: v1
kind: Service
metadata:
name: crowdsec-envoy-bouncer
namespace: envoy-gateway-system
spec:
selector:
app.kubernetes.io/name: crowdsec-envoy-bouncer
ports:
- name: grpc
port: 8080
targetPort: grpc
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: crowdsec-envoy-bouncer
namespace: envoy-gateway-system
spec:
from:
- group: gateway.envoyproxy.io
kind: SecurityPolicy
namespace: "<app-namespace>"
to:
- group: ""
kind: Service
name: crowdsec-envoy-bouncer
Apply it:
kubectl apply -f crowdsec-envoy-bouncer.yaml
This path gives you full control over the generated objects, but you must keep
the Deployment, Service, and ReferenceGrant aligned yourself.
Verify the rollout:
kubectl -n envoy-gateway-system rollout status deploy/crowdsec-envoy-bouncer
Attach Envoy SecurityPolicy resources to HTTPRoutes
Envoy Gateway external auth is configured through
gateway.envoyproxy.io/v1alpha1 SecurityPolicy resources.
Attach them at the HTTPRoute level:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: crowdsec-ext-auth
namespace: "<app-namespace>"
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: "<app-route>"
extAuth:
failOpen: true
grpc:
backendRefs:
- group: ""
kind: Service
name: crowdsec-envoy-bouncer
namespace: envoy-gateway-system
port: 8080
Apply them:
kubectl apply -f app-securitypolicy.yaml
Allow cross-namespace references
The Helm chart can create the required ReferenceGrant for you. In the example
above, this is handled by:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: crowdsec-ext-auth-backend
namespace: envoy-gateway-system
spec:
from:
- group: gateway.envoyproxy.io
kind: SecurityPolicy
namespace: default
to:
- group: ""
kind: Service
name: crowdsec-envoy-bouncer-envoy-proxy-bouncer
If you do not let the chart manage ReferenceGrant, you must create an
equivalent ReferenceGrant manually in envoy-gateway-system.
Validate the deployment
First validate the AppSec backend directly, before testing through Envoy:
kubectl -n crowdsec port-forward svc/crowdsec-appsec-service 7422:7422
curl -i -X POST http://127.0.0.1:7422/ \
-H 'x-crowdsec-appsec-ip: 198.51.100.10' \
-H 'x-crowdsec-appsec-uri: /.env' \
-H 'x-crowdsec-appsec-host: www.example.com' \
-H 'x-crowdsec-appsec-verb: GET' \
-H 'x-crowdsec-appsec-api-key: <same-value-as-BOUNCER_KEY_envoy>'
Expected result:
HTTP/1.1 403 Forbidden- a new
crowdsecurity/vpatch-env-accesstrigger in AppSec / CrowdSec logs
If this direct test does not return 403, the issue is not Envoy-to-bouncer
connectivity yet. Check that:
crowdsecurity/appsec-virtual-patchingis installed and loaded- the AppSec API key matches the Envoy bouncer key registered in LAPI
crowdsec-appsecis healthy and listening on7422
Check the bouncer:
kubectl -n envoy-gateway-system get pods -l app.kubernetes.io/name=crowdsec-envoy-bouncer
kubectl -n envoy-gateway-system logs deploy/crowdsec-envoy-bouncer
Check the policies:
kubectl -n <app-namespace> describe securitypolicy crowdsec-ext-auth
Check LAPI and AppSec:
kubectl -n crowdsec logs deploy/crowdsec-lapi --since=2m
kubectl -n crowdsec logs deploy/crowdsec-appsec --since=2m
Healthy signs include:
- the Envoy bouncer logging
initial decision sync complete crowdsec-lapireturning200on/v1/decisions/streamto the Envoy bouncer- no recent
invalid API keymessages incrowdsec-appsec kubectl -n app-a describe securitypolicy crowdsec-ext-authshowing the policy as accepted
Then validate the full Envoy path:
curl -i --resolve www.example.com:80:<gateway-ip> http://www.example.com/.env
This request should only be blocked if the attached HTTPRoute actually
matches /.env. If your route only matches a narrower prefix such as /api,
/app, or /get, Envoy will not run ext-auth for /.env on that route.
Make sure the protected HTTPRoute matches the paths you want WAF to inspect.
To verify route matching:
kubectl -n app-a get httproute app-a-route -o yaml
For a broad catch-all test route, use a PathPrefix of /.
If Envoy still cannot reach the bouncer, check the service wiring directly:
kubectl -n envoy-gateway-system get svc crowdsec-envoy-bouncer
kubectl -n envoy-gateway-system get endpointslice -l kubernetes.io/service-name=crowdsec-envoy-bouncer
kubectl -n envoy-gateway-system describe referencegrant crowdsec-envoy-bouncer
The crowdsec-envoy-bouncer Service must have at least one ready endpoint on
port 8080, and the ReferenceGrant must allow references from every
application namespace that attaches a SecurityPolicy.
Roll from failOpen: true to failOpen: false
Start with:
extAuth:
failOpen: true
This allows requests through if the bouncer is unavailable during rollout.
Once the following are true:
- the bouncer pod is
Running crowdsec-lapishows successfulenvoy-proxy-bouncerdecision pullscrowdsec-appsecshows no new auth or timeout errors- your applications still answer correctly through Envoy
switch to:
extAuth:
failOpen: false
This makes Envoy fail closed and turns the external auth service into an enforcement point instead of a best-effort filter.
Important Notes
1. Attach SecurityPolicy to HTTPRoutes, not just to the Gateway
For Envoy Gateway, route-level attachment is the safer pattern for this integration, especially if you later want to handle multiple applications or CAPTCHA endpoints independently.
2. Cross-namespace bouncer references still require ReferenceGrant
If your bouncer Service is in envoy-gateway-system and your applications
live in other namespaces, ReferenceGrant is required. The Helm chart can
create it, but the requirement itself does not go away.
3. Use failOpen: true during rollout
If you apply a fail-closed external auth policy before the bouncer is healthy,
Envoy will start rejecting traffic with 403
4. Check image architecture support
The community bouncer image is published for amd64. For other architectures you will need a custom build.
Next steps
- Tune rules and configurations: /appsec/configuration.md and /appsec/configuration_rule_management.md
- Troubleshoot AppSec behavior: /appsec/troubleshooting.md
- Learn more about remediation components: /u/bouncers/intro