From dcb127e9e80a4bc0d9f19698967243c9debbe400 Mon Sep 17 00:00:00 2001 From: Jeffrey Smeets Date: Wed, 26 Aug 2026 17:00:34 +0200 Subject: [PATCH] crowdsec: add LAPI + AppSec and the Envoy ext_authz bouncer Detection is CAPI community blocklists plus AppSec inline WAF, not Envoy access-log parsing. Enforcement is gRPC ext_authz from Envoy Gateway. Changes from the draft that was held: Agent disabled rather than given an empty acquisition list. Chart 0.24.0 refuses to render the DaemonSet with acquisition: [] ("No acquisition or additionalAcquisition configured"), and with no log tailing the agent has nothing to do. The WAF collections moved to appsec.env, where they belong, and base-http-scenarios was dropped since it only feeds log parsing. AppSec registers with LAPI on its own, so it works without the agent. Bouncer values updated for chart 0.8.0 (the app already pinned 0.8.0 while the values were written against 0.6.3). Added waf.failOpen: true, whose chart default is false and would deny every request if AppSec were unreachable. SecurityPolicy now targets the whole gateway-public rather than the it-tools listener. EG v1.3.2 rejects targetRefs.sectionName on SecurityPolicy, and targeting the HTTPRoute instead would gate LAN traffic too since public routes also parent gateway-internal. Blanket coverage of the public gateway is what we want anyway, and failOpen keeps a bouncer outage from taking public apps down. envoyproxy-public.yaml is kept as documentation but not applied. EG v1.3.2 already defaults envoyService.externalTrafficPolicy to Local, verified live on all three gateway LB services, so the real client IP already reaches Envoy. Not wired into the envoy-gateway kustomization yet. That lands once the bouncer is up and healthy. --- .../referencegrant.yaml | 16 ++++++ infra/crowdsec-envoy-bouncer/values.yaml | 48 ++++++++++++++++ infra/crowdsec/crowdsec-namespace.yaml | 4 ++ infra/crowdsec/values.yaml | 57 +++++++++++++++++++ .../crowdsec/envoyproxy-public.yaml | 15 +++++ .../gateway-public-securitypolicy.yaml | 29 ++++++++++ 6 files changed, 169 insertions(+) create mode 100644 infra/crowdsec-envoy-bouncer/referencegrant.yaml create mode 100644 infra/crowdsec-envoy-bouncer/values.yaml create mode 100644 infra/crowdsec/crowdsec-namespace.yaml create mode 100644 infra/crowdsec/values.yaml create mode 100644 infra/envoy-gateway/crowdsec/envoyproxy-public.yaml create mode 100644 infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml diff --git a/infra/crowdsec-envoy-bouncer/referencegrant.yaml b/infra/crowdsec-envoy-bouncer/referencegrant.yaml new file mode 100644 index 0000000..85b43cf --- /dev/null +++ b/infra/crowdsec-envoy-bouncer/referencegrant.yaml @@ -0,0 +1,16 @@ +# Allows the SecurityPolicy (in the envoy-gateway namespace) to reference the +# bouncer Service in this (crowdsec) namespace as its ext_authz backend. +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: crowdsec-ext-auth-from-envoy-gateway + namespace: crowdsec +spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: envoy-gateway + to: + - group: "" + kind: Service + name: crowdsec-envoy-bouncer diff --git a/infra/crowdsec-envoy-bouncer/values.yaml b/infra/crowdsec-envoy-bouncer/values.yaml new file mode 100644 index 0000000..73ac91a --- /dev/null +++ b/infra/crowdsec-envoy-bouncer/values.yaml @@ -0,0 +1,48 @@ +# CrowdSec Envoy bouncer, chart ghcr.io/kdwils/charts/envoy-proxy-bouncer 0.8.0 +# gRPC ext_authz service that Envoy Gateway calls for every request on protected +# listeners. Checks the client IP against LAPI decisions and (WAF) forwards the +# request to CrowdSec AppSec for inline inspection. +# +# NOTE: this is a community project ("not tested in production, use at your own +# risk"). It is used behind failOpen: true (see the SecurityPolicy), so a bouncer +# outage lets traffic through rather than blocking your services. + +# Fixed name so the SecurityPolicy backendRef / ReferenceGrant can target it. +fullnameOverride: crowdsec-envoy-bouncer + +service: + type: ClusterIP + grpcPort: 8080 + httpPort: 8081 + +config: + server: + grpcPort: 8080 + httpPort: 8081 + logLevel: "info" + + # Envoy (public proxy) is the immediate hop and, with externalTrafficPolicy: + # Local on the LB service, presents the real client IP. If you later put another + # proxy in front (e.g. Cloudflare), set trustedProxies / trustedIPHeader so the + # bouncer reads the true client IP from X-Forwarded-For instead of banning the + # proxy. + trustedProxies: [] + exemptIPs: [] + + # IP-decision enforcement against LAPI (same namespace). + bouncer: + enabled: true + lapiURL: "http://crowdsec-service:8080" + apiKeySecretRef: + name: crowdsec-secrets + key: bouncer-key + + # WAF / AppSec inline inspection (same namespace). + waf: + enabled: true + appSecURL: "http://crowdsec-appsec-service:7422" + apiKeySecretRef: + name: crowdsec-secrets + key: bouncer-key + # Chart default is false, which would deny every request if AppSec is down. + failOpen: true diff --git a/infra/crowdsec/crowdsec-namespace.yaml b/infra/crowdsec/crowdsec-namespace.yaml new file mode 100644 index 0000000..36fcbc9 --- /dev/null +++ b/infra/crowdsec/crowdsec-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: crowdsec diff --git a/infra/crowdsec/values.yaml b/infra/crowdsec/values.yaml new file mode 100644 index 0000000..93e6beb --- /dev/null +++ b/infra/crowdsec/values.yaml @@ -0,0 +1,57 @@ +# CrowdSec core — chart crowdsecurity/crowdsec 0.24.0 (appVersion v1.7.8) +# Deploys: LAPI (decisions DB), log-processing agent, and AppSec (WAF) component. +# +# Detection strategy here = CAPI community blocklists + AppSec (inline WAF). +# We intentionally do NOT parse Envoy access logs (no reliable first-party +# Envoy parser); the kdwils bouncer forwards requests to AppSec instead. +# Remediation/enforcement is done by the separate `crowdsec-envoy-bouncer`. + +#=============================# +#= Local API (LAPI) =# +#=============================# +lapi: + # Pre-register the "envoy" bouncer. The CrowdSec image turns BOUNCER_KEY_ + # into a registered bouncer named on first start. The kdwils bouncer + # authenticates with this same key. + env: + - name: BOUNCER_KEY_envoy + valueFrom: + secretKeyRef: + name: crowdsec-secrets + key: bouncer-key + persistentVolume: + data: + enabled: true + storageClassName: longhorn + size: 2Gi + config: + enabled: true + storageClassName: longhorn + size: 100Mi + +#=============================# +#= Log-processing agent =# +#=============================# +agent: + # Disabled: detection comes from AppSec + CAPI, not log tailing. Chart 0.24.0 + # refuses to render the agent DaemonSet with an empty acquisition list anyway. + enabled: false + +#=============================# +#= AppSec (WAF) component =# +#=============================# +appsec: + enabled: true + # Hub collections installed into the AppSec pod at startup (the WAF rule sets). + env: + - name: COLLECTIONS + value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules" + acquisitions: + - source: appsec + listen_addr: "0.0.0.0:7422" + path: / + # Virtual-patching ruleset (CRS-based vpatch rules). Verify it loaded after + # first deploy with: cscli appsec-configs list / cscli appsec-rules list + appsec_config: crowdsecurity/virtual-patching + labels: + type: appsec diff --git a/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml b/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml new file mode 100644 index 0000000..532deff --- /dev/null +++ b/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml @@ -0,0 +1,15 @@ +# NOT APPLIED, deliberately left out of kustomization.yaml. +# EG v1.3.2 already defaults envoyService.externalTrafficPolicy to Local, so all +# three gateway LB services preserve the real client IP without this CR. Kept as +# documentation of that finding. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: eg-public-proxy + namespace: envoy-gateway +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + externalTrafficPolicy: Local diff --git a/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml b/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml new file mode 100644 index 0000000..bca2010 --- /dev/null +++ b/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml @@ -0,0 +1,29 @@ +# ext_authz for the whole PUBLIC gateway. Every public listener is checked +# against CrowdSec decisions and AppSec before the request reaches a backend. +# +# Scope is the entire Gateway because EG v1.3.2 rejects targetRefs.sectionName +# on SecurityPolicy ("this policy does not yet support the sectionName field"). +# Targeting the HTTPRoute instead is not an option: public routes also parent +# gateway-internal, so that would gate LAN traffic too. +# +# failOpen: true -> if the bouncer is unreachable, requests are allowed through +# (a bouncer outage must not take every public service down). +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: crowdsec-gateway-public + namespace: envoy-gateway +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-public + extAuth: + failOpen: true + grpc: + backendRefs: + - group: "" + kind: Service + name: crowdsec-envoy-bouncer + namespace: crowdsec + port: 8080