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.

For remediation to work correctly, CrowdSec must see the real client IP in the Envoy access logs, and the bouncer must evaluate requests against that same IP.

If Envoy only logs an internal proxy, load balancer, or node IP, CrowdSec will create decisions for the wrong source and bouncing will not work as expected.

In Kubernetes, make sure the Envoy service configuration preserves source IPs, for example by setting externalTrafficPolicy: Local instead of a setup that hides the original client IP.

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
fullnameOverride: crowdsec-envoy-bouncer
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:

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.

Attaching the SecurityPolicy to an HTTPRoute is usually better than attaching it to the Gateway.

It keeps the policy scoped to one application route instead of every route on the shared entrypoint, which makes rollout, debugging, and multi-application setups easier to manage.

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

If you do not let the chart manage ReferenceGrant, you must create an equivalent ReferenceGrant manually in envoy-gateway-system.

Testing detection

Test the HTTP generic scenario

You can verify that CrowdSec is parsing Envoy logs correctly by triggering the crowdsecurity/http-generic-test dummy scenario.

  1. Access your service URL with this path: /crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl
curl -I http://<your-service-url>/crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl
  1. Confirm the alert has triggered for crowdsecurity/http-generic-test:
kubectl exec -n crowdsec -it $(kubectl get pods -n crowdsec -l k8s-app=crowdsec -l type=lapi -o name) -- cscli alerts list -s crowdsecurity/http-generic-test

If you trigger this scenario from a private ip, you won't see it trigger as it will be dismissed by the whitelist parser.

Test the AppSec generic scenario

If AppSec forwarding is enabled, the same probe path also lets you verify the crowdsecurity/appsec-generic-test dummy scenario.

  1. Access your service URL with this path: /crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl
curl -I http://<your-service-url>/crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl
  1. Confirm the alert has triggered for crowdsecurity/appsec-generic-test:
kubectl exec -n crowdsec -it $(kubectl get pods -n crowdsec -l k8s-app=crowdsec -l type=lapi -o name) -- cscli alerts list -s crowdsecurity/appsec-generic-test

If you trigger this scenario from a private ip, you won't see it trigger as it will be dismissed by the whitelist parser.

Important Notes

Attach SecurityPolicy to HTTPRoutes, not just to the Gateway

For Envoy Gateway, route-level attachment is the safer pattern for this integration. Attaching the policy at the Gateway level can apply external auth to every routed application behind that listener, which increases the blast radius of a bad policy, a broken backend reference, or an unhealthy bouncer. Attaching it to individual HTTPRoutes keeps the rollout explicit and incremental: you can protect only the routes that should use CrowdSec, leave other traffic untouched, and troubleshoot one application at a time.

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 ReferenceGrant has to correctly specify the crowdsec bouncer service.

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

Check image architecture support

The community bouncer image is published for amd64. For other architectures you will need a custom build.

Next steps

You are now running the AppSec Component on your CrowdSec Security Engine.

As the next steps, you can: