# CrowdSec WAF QuickStart for NGINX Ingress (Helm)

Deprecated and not recommended

This CrowdSec integration for `ingress-nginx` is being deprecated. `ingress-nginx` has known security issues, and upstream removed Lua support in version `1.12`, which this integration depends on.

Do not use this for new deployments. If you are already using it, plan a migration to another supported integration such as [Traefik](https://docs.crowdsec.net/docs/next/appsec/quickstart/traefik.md) or [Envoy Gateway](https://docs.crowdsec.net/docs/next/appsec/quickstart/envoy-gateway.md).

## Objectives[​](#objectives "Direct link to Objectives")

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:

* CrowdSec running in-cluster with the AppSec API listening on `7422`
* The ingress controller using the CrowdSec Lua plugin to forward requests for inspection
* Basic virtual patching rules blocking common web exploits

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

1. If you're new to the [AppSec Component](https://docs.crowdsec.net/docs/next/appsec/intro.md#introduction) or **W**eb **A**pplication **F**irewalls, start with the [Introduction](https://docs.crowdsec.net/docs/next/appsec/intro.md#introduction) for a better understanding.

2. It's assumed that you have already installed **CrowdSec [Security Engine](https://docs.crowdsec.net/docs/next/intro.md)**. For installation quickstart, refer to the [QuickStart guide](https://docs.crowdsec.net/u/getting_started/installation/kubernetes.md).

warning

A Lua-enabled controller is essential for CrowdSec's NGINX Ingress remediation, as it relies on the Lua plugin interface. Please use the `crowdsecurity/controller` image provided by CrowdSec (as specified in the values below). Note that the standard upstream controller removed Lua support in version 1.12 and therefore is not suitable for use as a CrowdSec remediation component.

Already have CrowdSec AppSec running?

If CrowdSec is already deployed with AppSec enabled in your cluster, skip to [Enable the CrowdSec Lua plugin on NGINX Ingress](#enable-the-crowdsec-lua-plugin-on-nginx-ingress).

## Deploy CrowdSec with AppSec enabled[​](#deploy-crowdsec-with-appsec-enabled "Direct link to Deploy CrowdSec with AppSec enabled")

### Helm repository[​](#helm-repository "Direct link to Helm repository")

Add or update the CrowdSec Helm repository:

SHCOPY

```
helm repo add crowdsec https://crowdsecurity.github.io/helm-charts
helm repo update
```

note

If CrowdSec is already deployed with Helm in this cluster, the repository entry is already present—you only need `helm repo update`.

### Update CrowdSec configuration[​](#update-crowdsec-configuration "Direct link to Update CrowdSec configuration")

Store the nginx bouncer key in a Kubernetes secret, following the same pattern used by the Envoy quickstart.

Create or update the secret used by CrowdSec LAPI:

crowdsec-keys.yaml

YAMLcrowdsec-keys.yamlCOPY

```
apiVersion: v1
kind: Secret
metadata:
  name: crowdsec-keys
  namespace: crowdsec
type: Opaque
stringData:
  BOUNCER_KEY_nginx_ingress_waf: "<choose-a-long-random-key>"
```

Apply it:

SHCOPY

```
kubectl apply -f crowdsec-keys.yaml
```

Add this to the CrowdSec `values.yaml` with the AppSec acquisition datasource (see the [AppSec datasource](https://docs.crowdsec.net/docs/next/log_processor/data_sources/appsec.md)) and the default [AppSec configuration](https://docs.crowdsec.net/docs/next/appsec/configuration.md):

values.yaml

YAMLvalues.yamlCOPY

```
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_nginx_ingress_waf
      valueFrom:
        secretKeyRef:
          name: crowdsec-keys
          key: BOUNCER_KEY_nginx_ingress_waf
```

warning

The Helm chart still enables the CrowdSec agent by default. If you do not want the agent, disable it explicitly.

Snippet to disable the agent

values.yaml

YAMLvalues.yamlCOPY

```
agent:
  enabled: false
```

note

Although this is the same bouncer key value, you need two `Secret` objects here: one in `crowdsec` and one in `ingress-nginx`. Kubernetes secrets are namespace-scoped, so the ingress controller cannot read a secret from the `crowdsec` namespace.

This YAML configuration snippet exposes the important configuration items:

* `listen_addr: 0.0.0.0:7422` exposes the AppSec API inside the cluster.
* `appsec_configs` loads the [AppSec configuration(s)](https://docs.crowdsec.net/docs/next/appsec/configuration.md) that define which rules are evaluated (in-band vs out-of-band).
* The two collections provide virtual patching and generic rule coverage.
* `lapi.env` forces the `nginx_ingress_waf` bouncer key from the `crowdsec-keys` Secret.

And now we apply the new configuration with:

SHCOPY

```
helm upgrade --install crowdsec crowdsec/crowdsec --namespace crowdsec --create-namespace -f crowdsec-appsec-values.yaml
```

### Confirm the pods are healthy:[​](#confirm-the-pods-are-healthy "Direct link to Confirm the pods are healthy:")

SHCOPY

```
kubectl -n crowdsec get pods
```

You should see `crowdsec-agent` pods, the `crowdsec-lapi` pod and the `crowdsec-appsec` pod in `Running` state.

## Enable the CrowdSec Lua plugin on NGINX Ingress[​](#enable-the-crowdsec-lua-plugin-on-nginx-ingress "Direct link to Enable the CrowdSec Lua plugin on NGINX Ingress")

Create the secret holding the same CrowdSec bouncer key in the `ingress-nginx` namespace:

crowdsec-ingress-bouncer-secret.yaml

YAMLcrowdsec-ingress-bouncer-secret.yamlCOPY

```
apiVersion: v1
kind: Secret
metadata:
  name: crowdsec-ingress-bouncer-secrets
  namespace: ingress-nginx
type: Opaque
stringData:
  api-key: "<same-value-as-BOUNCER_KEY_nginx_ingress_waf>"
```

Apply it:

SHCOPY

```
kubectl apply -f crowdsec-ingress-bouncer-secret.yaml
```

To extend the ingress controller with the CrowdSec plugin and point it to the AppSec API, create the file named `ingress-values.yaml`. You can read the entire file in the snippet below.

ingress-values.yaml

YAMLingress-values.yamlCOPY

```
controller:
  image:
    registry: docker.io
    image: crowdsecurity/controller
    tag: v1.14.3
    digest: sha256:9ab8791635f4cde9964ab2562fb8b15faf72fe0205f0fe288089a87e1455675d
  extraVolumes:
    - name: crowdsec-bouncer-plugin
      emptyDir: {}
  extraInitContainers:
    - name: init-clone-crowdsec-bouncer
      image: crowdsecurity/lua-bouncer-plugin:latest
      imagePullPolicy: IfNotPresent
      env:
        - name: API_URL
          value: "http://crowdsec-service.crowdsec.svc.cluster.local:8080"
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: crowdsec-ingress-bouncer-secrets
              key: api-key
        - name: BOUNCER_CONFIG
          value: "/crowdsec/crowdsec-bouncer.conf"
        - name: APPSEC_URL
          value: "http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422"
        - name: APPSEC_FAILURE_ACTION
          value: "ban"
        - name: APPSEC_CONNECT_TIMEOUT
          value: "100"
        - name: APPSEC_SEND_TIMEOUT
          value: "100"
        - name: APPSEC_PROCESS_TIMEOUT
          value: "1000"
        - name: ALWAYS_SEND_TO_APPSEC
          value: "false"
      command:
        - sh
        - -c
        - |
          sh /docker_start.sh
          mkdir -p /lua_plugins/crowdsec/
          cp -R /crowdsec/* /lua_plugins/crowdsec/
      volumeMounts:
        - name: crowdsec-bouncer-plugin
          mountPath: /lua_plugins
  extraVolumeMounts:
    - name: crowdsec-bouncer-plugin
      mountPath: /etc/nginx/lua/plugins/crowdsec
      subPath: crowdsec
  config:
    plugins: "crowdsec"
    lua-shared-dicts: "crowdsec_cache: 50m"
    server-snippet: |
      lua_ssl_trusted_certificate "/etc/ssl/certs/ca-certificates.crt";
      resolver local=on ipv6=off;
```

* `API_URL` targets the Local API service exposed by the Helm chart.
* `API_KEY` is read from the `crowdsec-ingress-bouncer-secrets` Secret in the `ingress-nginx` namespace.
* `APPSEC_URL` points to the AppSec service; keep the namespace in sync with your CrowdSec release.
* The plugin copies the Lua files from the init container into an `emptyDir` that is mounted at runtime.

Deploy or upgrade the ingress controller with the new values:

SHCOPY

```
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  -f crowdsec-ingress-values.yaml
```

CrowdSec Ingress-NGINX Remediation Configuration Explained

This `values.yaml` snippet integrates the CrowdSec remediation (Lua bouncer) into the `ingress-nginx` controller by injecting the Lua plugin, generating its configuration, and enabling it inside NGINX.

#### Controller Image Override[​](#controller-image-override "Direct link to Controller Image Override")

YAMLCOPY

```
controller:
  image:
    registry: docker.io
    image: crowdsecurity/controller
    tag: v1.14.3
    digest: sha256:9ab8791635f4cde9964ab2562fb8b15faf72fe0205f0fe288089a87e1455675d
```

The controller image is replaced with a CrowdSec-enabled build that includes the required Lua integration points.

#### Shared Volume for the Plugin[​](#shared-volume-for-the-plugin "Direct link to Shared Volume for the Plugin")

YAMLCOPY

```
extraVolumes:
  - name: crowdsec-bouncer-plugin
    emptyDir: {}
```

An emptyDir volume is used to hold the Lua bouncer plugin. It will be filled by an initContainer and mounted into the main controller.

#### InitContainer: Plugin Fetch and Configuration[​](#initcontainer-plugin-fetch-and-configuration "Direct link to InitContainer: Plugin Fetch and Configuration")

YAMLCOPY

```
extraInitContainers:
  - name: init-clone-crowdsec-bouncer
    image: crowdsecurity/lua-bouncer-plugin:latest
    env:
      - name: API_URL
        value: "http://crowdsec-service.crowdsec.svc.cluster.local:8080"
      - name: API_KEY
        valueFrom:
          secretKeyRef:
            name: crowdsec-ingress-bouncer-secrets
            key: api-key
      - name: BOUNCER_CONFIG
        value: "/crowdsec/crowdsec-bouncer.conf"
      - name: APPSEC_URL
        value: "http://crowdsec-appsec-service.crowdsec.svc.cluster.local:7422"
      - name: APPSEC_FAILURE_ACTION
        value: "ban"
      - name: APPSEC_CONNECT_TIMEOUT
        value: "100"
      - name: APPSEC_SEND_TIMEOUT
        value: "100"
      - name: APPSEC_PROCESS_TIMEOUT
        value: "1000"
      - name: ALWAYS_SEND_TO_APPSEC
        value: "false"
    command:
      - sh
      - -c
      - |
        sh /docker_start.sh
        mkdir -p /lua_plugins/crowdsec/
        cp -R /crowdsec/* /lua_plugins/crowdsec/
    volumeMounts:
      - name: crowdsec-bouncer-plugin
        mountPath: /lua_plugins
```

The initContainer generates the plugin configuration (crowdsec-bouncer.conf) from environment variables and copies the Lua plugin files into the shared volume so the main controller can load them.

#### Mounting the Plugin in NGINX[​](#mounting-the-plugin-in-nginx "Direct link to Mounting the Plugin in NGINX")

YAMLCOPY

```
extraVolumeMounts:
  - name: crowdsec-bouncer-plugin
    mountPath: /etc/nginx/lua/plugins/crowdsec
    subPath: crowdsec
```

This mounts the plugin files inside the directory where ingress-nginx expects Lua plugins.

#### NGINX Configuration to Enable the Plugin[​](#nginx-configuration-to-enable-the-plugin "Direct link to NGINX Configuration to Enable the Plugin")

TEXTCOPY

```
config:
  plugins: "crowdsec"
  lua-shared-dicts: "crowdsec_cache: 50m"
  server-snippet: |
    lua_ssl_trusted_certificate "/etc/ssl/certs/ca-certificates.crt"
    resolver local=on ipv6=off;
```

This snippet enables the crowdsec Lua plugin, aAllocates shared memory for caching LAPI/AppSec results and ensures Lua HTTPS validation and DNS resolution work properly.

#### Summary[​](#summary "Direct link to Summary")

This configuration:

* Injects the CrowdSec Lua bouncer plugin into ingress-nginx.
* Generates its configuration via an initContainer.
* Mounts it into NGINX so it is executed during request processing.
* Enables both LAPI enforcement and optional AppSec forwarding depending on settings.

note

After the rollout, you can optionally check that the right container is deployed with something like:

`kubectl -n ingress-nginx exec -ti <ingress-pod-name> -- find /etc/nginx/lua/plugins/crowdsec`

This should give you a bunch of crowdsec lua files.

## Testing the AppSec Component + Remediation Component[​](#testing-the-appsec-component--remediation-component "Direct link to Testing the AppSec Component + Remediation Component")

note

We're assuming the web server is installed on the same machine and is listening on port 80. Please adjust your testing accordingly if this is not the case.

if you try to access `http://localhost/.env` from a browser, your request will be blocked, resulting in the display of the following HTML page:

![appsec-denied](/assets/images/appsec_denied-6e67f77eebc0aafccfbb7304136ad33e.png)

We can also look at the metrics from `cscli metrics show appsec` on the appsec pod it will display:

* the number of requests processed by the AppSec Component
* Individual rule matches

Example Output

kubectl exec -n crowdsec -ti crowdsec-appsec-\<pod-name> -- cscli metrics show appsec

SHkubectl exec -n crowdsec -ti crowdsec-appsec-\<pod-name> -- cscli metrics show appsecCOPY

```
Appsec Metrics:
╭─────────────────┬───────────┬─────────╮
│  Appsec Engine  │ Processed │ Blocked │
├─────────────────┼───────────┼─────────┤
│ 127.0.0.1:7422/ │ 2         │ 1       │
╰─────────────────┴───────────┴─────────╯

Appsec '127.0.0.1:7422/' Rules Metrics:
╭─────────────────────────────────┬───────────╮
│             Rule ID             │ Triggered │
├─────────────────────────────────┼───────────┤
│ crowdsecurity/vpatch-env-access │ 1         │
╰─────────────────────────────────┴───────────╯
```

You can test and investigate further with [Stack Health-Check](https://docs.crowdsec.net/u/getting_started/health_check.md#trigger-crowdsec-test-scenarios) and [Appsec Troubleshooting guide](https://docs.crowdsec.net/docs/next/appsec/troubleshooting.md)

## Integration with the Console[​](#integration-with-the-console "Direct link to Integration with the Console")

If you haven't yet, follow the guide about [how to enroll your Security Engine in the Console](https://docs.crowdsec.net/u/getting_started/post_installation/console.md).

Once done, all your alerts, including the ones generated by the AppSec Component, appear in the Console:

![appsec-console](/assets/images/appsec_console-59b5f39cf3f7fc002e61539c0e866f23.png)

## Next steps[​](#next-steps "Direct link to Next steps")

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

As the next steps, you can:

* Monitor WAF alerts in the [CrowdSec Console](https://app.crowdsec.net).
* Review the [AppSec troubleshooting guide](https://docs.crowdsec.net/docs/next/appsec/troubleshooting.md) if you need to investigate or refine the deployment.
* Explore [WAF deployment strategies](https://docs.crowdsec.net/docs/next/appsec/advanced_deployments.md), [rules syntax](https://docs.crowdsec.net/docs/next/appsec/rules_syntax.md), [rule creation](https://docs.crowdsec.net/docs/next/appsec/create_rules.md), and [benchmarks](https://docs.crowdsec.net/docs/next/appsec/benchmark.md) to go further.
