|
| 1 | +--- |
| 2 | +id: nginx-ingress |
| 3 | +title: QuickStart - NGINX Ingress (Helm) |
| 4 | +--- |
| 5 | + |
| 6 | +import Tabs from '@theme/Tabs'; |
| 7 | +import TabItem from '@theme/TabItem'; |
| 8 | +import UnderlineTooltip from '@site/src/components/underline-tooltip'; |
| 9 | + |
| 10 | +# CrowdSec WAF QuickStart for NGINX Ingress (Helm) |
| 11 | + |
| 12 | +## Objectives |
| 13 | + |
| 14 | +This quickstart shows how to deploy the CrowdSec AppSec component with the official Helm chart and protect workloads exposed through the Kubernetes [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/). At the end you will have: |
| 15 | + |
| 16 | +- CrowdSec running in-cluster with the AppSec API listening on `7422` |
| 17 | +- The ingress controller using the CrowdSec Lua plugin to forward requests for inspection |
| 18 | +- Basic virtual patching rules blocking common web exploits |
| 19 | + |
| 20 | +## Prerequisites |
| 21 | + |
| 22 | +Before you begin, make sure you have: |
| 23 | + |
| 24 | +- A working Kubernetes cluster (v1.25+ recommended) with `kubectl` access |
| 25 | +- [Helm 3](https://helm.sh/docs/intro/install/) installed locally |
| 26 | +- The [`ingress-nginx` Helm repository](https://artifacthub.io/packages/helm/ingress-nginx/ingress-nginx) available, or an existing controller that can be upgraded |
| 27 | +- Cluster-admin permissions to create namespaces, Deployments, Secrets and ConfigMaps |
| 28 | +- Internet access from the cluster nodes so the CrowdSec pod can download Hub content |
| 29 | + |
| 30 | +:::warning Lua-enabled controller required |
| 31 | +CrowdSec’s NGINX Ingress remediation relies on the Lua plugin interface. Use the `crowdsecurity/controller` image shipped by CrowdSec (included in the values below). The vanilla upstream controller dropped Lua support in v1.12. |
| 32 | +::: |
| 33 | + |
| 34 | +## Step 1 – Deploy CrowdSec with AppSec enabled |
| 35 | + |
| 36 | +1. Add or update the CrowdSec Helm repository: |
| 37 | + |
| 38 | + ```bash |
| 39 | + helm repo add crowdsec https://crowdsecurity.github.io/helm-charts |
| 40 | + helm repo update |
| 41 | + ``` |
| 42 | + |
| 43 | + :::note |
| 44 | + If CrowdSec is already deployed with Helm in this cluster, the repository entry is already present—you only need `helm repo update`. |
| 45 | + ::: |
| 46 | + |
| 47 | +2. Create `crowdsec-appsec-values.yaml` with the AppSec configuration: |
| 48 | + |
| 49 | + ```yaml title="crowdsec-appsec-values.yaml" |
| 50 | + appsec: |
| 51 | + enabled: true |
| 52 | + service: |
| 53 | + type: ClusterIP |
| 54 | + port: 7422 |
| 55 | + acquisitions: |
| 56 | + - listen_addr: 0.0.0.0:7422 |
| 57 | + source: appsec |
| 58 | + labels: |
| 59 | + type: appsec |
| 60 | + appsec_configs: |
| 61 | + - crowdsecurity/appsec-default |
| 62 | + config: |
| 63 | + cscli: |
| 64 | + setup: |
| 65 | + collections: |
| 66 | + - crowdsecurity/appsec-virtual-patching |
| 67 | + - crowdsecurity/appsec-generic-rules |
| 68 | + bouncers: |
| 69 | + - name: nginx_ingress_waf |
| 70 | + key: ${NGINX_INGRESS_BOUNCER_KEY} |
| 71 | + ``` |
| 72 | +
|
| 73 | + - `listen_addr: 0.0.0.0:7422` exposes the AppSec API inside the cluster. |
| 74 | + - The two collections provide virtual patching and generic rule coverage. |
| 75 | + - The chart bootstraps a bouncer named `nginx_ingress_waf` using the key you export locally. |
| 76 | + |
| 77 | +3. Install (or upgrade) the CrowdSec release: |
| 78 | + |
| 79 | + ```bash |
| 80 | + helm upgrade --install crowdsec crowdsec/crowdsec \ |
| 81 | + --namespace crowdsec \ |
| 82 | + --create-namespace \ |
| 83 | + -f crowdsec-appsec-values.yaml |
| 84 | + ``` |
| 85 | + |
| 86 | +4. Confirm the pods are healthy: |
| 87 | + |
| 88 | + ```bash |
| 89 | + kubectl -n crowdsec get pods |
| 90 | + ``` |
| 91 | + |
| 92 | + You should see both the `crowdsec` pod and the `crowdsec-appsec` pod in `Running` state. |
| 93 | + |
| 94 | +## Step 2 – Provide the bouncer key via environment variables |
| 95 | + |
| 96 | +The ingress controller authenticates against CrowdSec with a bouncer API key. Instead of invoking `cscli` manually, let the Helm chart create the bouncer by providing the key through an environment variable. |
| 97 | + |
| 98 | +1. Generate (or reuse) a strong key and export it in your shell: |
| 99 | + |
| 100 | + ```bash |
| 101 | + export NGINX_INGRESS_BOUNCER_KEY=$(openssl rand -hex 32) |
| 102 | + ``` |
| 103 | + |
| 104 | + :::tip |
| 105 | + Keep working in the same terminal so the variable remains available while you write both values files. If you already have a key, export it instead of generating a new one. |
| 106 | + ::: |
| 107 | + |
| 108 | +2. When you create `crowdsec-appsec-values.yaml`, ensure the `${NGINX_INGRESS_BOUNCER_KEY}` placeholder is expanded by your shell (for example with `cat <<EOF` or `envsubst`). During installation the chart registers the `nginx_ingress_waf` bouncer automatically, so no additional Kubernetes Secret is required. Repeat the same approach for `crowdsec-ingress-values.yaml` in the next step. |
| 109 | + |
| 110 | +## Step 3 – Enable the CrowdSec Lua plugin on NGINX Ingress |
| 111 | + |
| 112 | +Create `crowdsec-ingress-values.yaml` (from the same shell session so `${NGINX_INGRESS_BOUNCER_KEY}` is still defined) to extend the ingress controller with the CrowdSec plugin and point it to the AppSec API: |
| 113 | + |
| 114 | +```yaml title="crowdsec-ingress-values.yaml" |
| 115 | +controller: |
| 116 | + image: |
| 117 | + registry: docker.io |
| 118 | + image: crowdsecurity/controller |
| 119 | + tag: v1.13.2 |
| 120 | + digest: sha256:4575be24781cad35f8e58437db6a3f492df2a3167fed2b6759a6ff0dc3488d56 |
| 121 | + extraVolumes: |
| 122 | + - name: crowdsec-bouncer-plugin |
| 123 | + emptyDir: {} |
| 124 | + extraInitContainers: |
| 125 | + - name: init-clone-crowdsec-bouncer |
| 126 | + image: crowdsecurity/lua-bouncer-plugin:latest |
| 127 | + imagePullPolicy: IfNotPresent |
| 128 | + env: |
| 129 | + - name: API_URL |
| 130 | + value: "http://crowdsec-service.crowdsec.svc.cluster.local:8080" |
| 131 | + - name: API_KEY |
| 132 | + value: "${NGINX_INGRESS_BOUNCER_KEY}" |
| 133 | + - name: BOUNCER_CONFIG |
| 134 | + value: "/crowdsec/crowdsec-bouncer.conf" |
| 135 | + - name: APPSEC_URL |
| 136 | + value: "http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422" |
| 137 | + - name: APPSEC_FAILURE_ACTION |
| 138 | + value: "ban" |
| 139 | + - name: APPSEC_CONNECT_TIMEOUT |
| 140 | + value: "100" |
| 141 | + - name: APPSEC_SEND_TIMEOUT |
| 142 | + value: "100" |
| 143 | + - name: APPSEC_PROCESS_TIMEOUT |
| 144 | + value: "1000" |
| 145 | + - name: ALWAYS_SEND_TO_APPSEC |
| 146 | + value: "false" |
| 147 | + command: |
| 148 | + - sh |
| 149 | + - -c |
| 150 | + - | |
| 151 | + sh /docker_start.sh |
| 152 | + mkdir -p /lua_plugins/crowdsec/ |
| 153 | + cp -R /crowdsec/* /lua_plugins/crowdsec/ |
| 154 | + volumeMounts: |
| 155 | + - name: crowdsec-bouncer-plugin |
| 156 | + mountPath: /lua_plugins |
| 157 | + extraVolumeMounts: |
| 158 | + - name: crowdsec-bouncer-plugin |
| 159 | + mountPath: /etc/nginx/lua/plugins/crowdsec |
| 160 | + subPath: crowdsec |
| 161 | + config: |
| 162 | + plugins: "crowdsec" |
| 163 | + lua-shared-dicts: "crowdsec_cache: 50m" |
| 164 | + server-snippet: | |
| 165 | + lua_ssl_trusted_certificate "/etc/ssl/certs/ca-certificates.crt" |
| 166 | + resolver local=on ipv6=off; |
| 167 | +``` |
| 168 | + |
| 169 | +- `API_URL` targets the Local API service exposed by the Helm chart. |
| 170 | +- `API_KEY` reuses the `${NGINX_INGRESS_BOUNCER_KEY}` variable exported earlier so the ingress controller shares the same credential. |
| 171 | +- `APPSEC_URL` points to the AppSec service; keep the namespace in sync with your CrowdSec release. |
| 172 | +- The plugin copies the Lua files from the init container into an `emptyDir` that is mounted at runtime. |
| 173 | + |
| 174 | +Deploy or upgrade the ingress controller with the new values: |
| 175 | + |
| 176 | +<Tabs |
| 177 | + groupId="nginx-ingress-deploy" |
| 178 | + defaultValue="upgrade" |
| 179 | + values={[ |
| 180 | + { label: 'Upgrade existing release', value: 'upgrade' }, |
| 181 | + { label: 'Fresh install', value: 'install' }, |
| 182 | + ]} |
| 183 | +> |
| 184 | + <TabItem value="upgrade"> |
| 185 | + |
| 186 | +```bash |
| 187 | +helm upgrade ingress-nginx ingress-nginx/ingress-nginx \ |
| 188 | + --namespace ingress-nginx \ |
| 189 | + -f crowdsec-ingress-values.yaml |
| 190 | +``` |
| 191 | + |
| 192 | + </TabItem> |
| 193 | + <TabItem value="install"> |
| 194 | + |
| 195 | +```bash |
| 196 | +helm install ingress-nginx ingress-nginx/ingress-nginx \ |
| 197 | + --namespace ingress-nginx \ |
| 198 | + --create-namespace \ |
| 199 | + -f crowdsec-ingress-values.yaml |
| 200 | +``` |
| 201 | + |
| 202 | + </TabItem> |
| 203 | +</Tabs> |
| 204 | + |
| 205 | +After the rollout, verify that the controller pod lists the `crowdsec` plugin during startup: |
| 206 | + |
| 207 | +```bash |
| 208 | +kubectl -n ingress-nginx logs deploy/ingress-nginx-controller | grep crowdsec |
| 209 | +``` |
| 210 | + |
| 211 | +You should see log lines confirming the plugin was loaded and the remote AppSec endpoint was reached. |
| 212 | + |
| 213 | +## Step 4 – Validate the end-to-end flow |
| 214 | + |
| 215 | +1. Confirm the AppSec service is reachable from inside the cluster: |
| 216 | + |
| 217 | + ```bash |
| 218 | + kubectl -n ingress-nginx exec deploy/ingress-nginx-controller -- \ |
| 219 | + curl -s -o /dev/null -w '%{http_code}\n' \ |
| 220 | + http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422/ |
| 221 | + ``` |
| 222 | + |
| 223 | + The command should return `400` or `404`, indicating the service responded. |
| 224 | + |
| 225 | +2. Deploy a sample application and ingress (replace hostnames as needed): |
| 226 | + |
| 227 | + ```yaml title="demo.yaml" |
| 228 | + apiVersion: v1 |
| 229 | + kind: Namespace |
| 230 | + metadata: |
| 231 | + name: demo |
| 232 | + --- |
| 233 | + apiVersion: apps/v1 |
| 234 | + kind: Deployment |
| 235 | + metadata: |
| 236 | + name: whoami |
| 237 | + namespace: demo |
| 238 | + spec: |
| 239 | + replicas: 1 |
| 240 | + selector: |
| 241 | + matchLabels: |
| 242 | + app: whoami |
| 243 | + template: |
| 244 | + metadata: |
| 245 | + labels: |
| 246 | + app: whoami |
| 247 | + spec: |
| 248 | + containers: |
| 249 | + - name: whoami |
| 250 | + image: containous/whoami:v1.5.0 |
| 251 | + ports: |
| 252 | + - containerPort: 80 |
| 253 | + --- |
| 254 | + apiVersion: v1 |
| 255 | + kind: Service |
| 256 | + metadata: |
| 257 | + name: whoami |
| 258 | + namespace: demo |
| 259 | + spec: |
| 260 | + selector: |
| 261 | + app: whoami |
| 262 | + ports: |
| 263 | + - port: 80 |
| 264 | + targetPort: 80 |
| 265 | + --- |
| 266 | + apiVersion: networking.k8s.io/v1 |
| 267 | + kind: Ingress |
| 268 | + metadata: |
| 269 | + name: whoami |
| 270 | + namespace: demo |
| 271 | + annotations: |
| 272 | + kubernetes.io/ingress.class: nginx |
| 273 | + spec: |
| 274 | + rules: |
| 275 | + - host: whoami.example.test |
| 276 | + http: |
| 277 | + paths: |
| 278 | + - path: / |
| 279 | + pathType: Prefix |
| 280 | + backend: |
| 281 | + service: |
| 282 | + name: whoami |
| 283 | + port: |
| 284 | + number: 80 |
| 285 | + ``` |
| 286 | + |
| 287 | + ```bash |
| 288 | + kubectl apply -f demo.yaml |
| 289 | + ``` |
| 290 | + |
| 291 | +3. Trigger a benign request and a malicious probe: |
| 292 | + |
| 293 | + ```bash |
| 294 | + # Replace with the address that resolves to your ingress controller |
| 295 | + INGRESS_HOST=whoami.example.test |
| 296 | + curl -H "Host: $INGRESS_HOST" http://<load-balancer-ip>/ |
| 297 | + curl -H "Host: $INGRESS_HOST" http://<load-balancer-ip>/.env -i |
| 298 | + ``` |
| 299 | + |
| 300 | + The `.env` request should return `403 Forbidden` and a CrowdSec block page while the regular request succeeds. |
| 301 | + |
| 302 | +4. Inspect CrowdSec metrics and alerts: |
| 303 | + |
| 304 | + ```bash |
| 305 | + kubectl -n crowdsec exec "$CROWdSEC_POD" -c crowdsec -- cscli alerts list |
| 306 | + kubectl -n crowdsec exec "$CROWdSEC_POD" -c crowdsec -- cscli metrics show appsec |
| 307 | + ``` |
| 308 | + |
| 309 | + You should see the AppSec component processing traffic and banning the offending source. |
| 310 | + |
| 311 | +## Next steps |
| 312 | + |
| 313 | +- Add the [OWASP CRS](/appsec/advanced_deployments.mdx) to extend detection coverage. |
| 314 | +- Enable HTTPS termination and mTLS between the controller and CrowdSec for stronger transport security. |
| 315 | +- Configure alert forwarding to the [CrowdSec Console](https://app.crowdsec.net) for centralized visibility. |
| 316 | +- Automate regression tests for business paths to tune AppSec rules and minimize false positives. |
0 commit comments