Skip to main content
Version: Next

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.
  • SecurityPolicy resources attached to your HTTPRoute objects. In Envoy Gateway, a SecurityPolicy is the custom resource that enables external authorization for a Gateway or HTTPRoute and points Envoy to the auth backend.
  • HTTPRoute objects exposing your applications. In Gateway API, an HTTPRoute defines how HTTP requests for specific hostnames or paths are matched and forwarded to your backend Service.
  • Virtual patching and generic AppSec rules inspecting requests before they reach your backends

Prerequisites

  1. If you're new to the AppSec Component or Web Application Firewalls, start with the Introduction.

  2. 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 / HTTPRoute resources exposing your applications.

This integration currently relies on a community Envoy external auth bouncer, not on a first-party CrowdSec remediation component.

The upstream project used in this guide is:

  • kdwils/envoy-proxy-crowdsec-bouncer

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:

crowdsec-keys.yaml
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:

crowdsec-values.yaml
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:

crowdsec-values.yaml
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-lapi in Running
  • crowdsec-appsec in Running
  • crowdsec-service exposing port 8080
  • crowdsec-appsec-service exposing port 7422

Deploy the Envoy external auth bouncer

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:

crowdsec-envoy-bouncer-secret.yaml
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:

envoy-bouncer-values.yaml
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.

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:

app-securitypolicy.yaml
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-access trigger 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-patching is installed and loaded
  • the AppSec API key matches the Envoy bouncer key registered in LAPI
  • crowdsec-appsec is healthy and listening on 7422

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-lapi returning 200 on /v1/decisions/stream to the Envoy bouncer
  • no recent invalid API key messages in crowdsec-appsec
  • kubectl -n app-a describe securitypolicy crowdsec-ext-auth showing 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-lapi shows successful envoy-proxy-bouncer decision pulls
  • crowdsec-appsec shows 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