Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/pages/manage/reverse-proxy/access-logs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Denied L4 connections (blocked by access restrictions) are logged immediately wi

### Deny reasons

The following deny reasons can appear for both HTTP and L4 services:
The following deny reasons can appear for both HTTP and L4 services, except the `appsec_*` reasons, which are HTTP-only since AppSec does not inspect L4 traffic:

| Reason | Description |
|--------|-------------|
Expand All @@ -70,10 +70,26 @@ The following deny reasons can appear for both HTTP and L4 services:
| `crowdsec_captcha` | The client IP has a CrowdSec captcha decision |
| `crowdsec_throttle` | The client IP has a CrowdSec throttle decision |
| `crowdsec_unavailable` | CrowdSec enforce mode is active but the bouncer has not completed its initial sync (fail-closed) |
| `appsec_ban` | The AppSec engine flagged the request and returned a ban remediation |
| `appsec_captcha` | The AppSec engine flagged the request and returned a captcha remediation, which the proxy treats as a denial |
| `appsec_unavailable` | AppSec enforce mode is active but the engine could not produce a verdict: unreachable, timed out, it rejected the call, or it answered with something that is not a remediation (fail-closed). The last case usually means the configured URL is not the AppSec endpoint. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

All CrowdSec decision types (ban, captcha, throttle) result in a connection denial in enforce mode. The proxy does not serve captcha challenges or apply rate limiting: the decision type is recorded for informational purposes only.

When CrowdSec is in **observe** mode, the verdict appears in the log metadata but the deny reason field is empty (the connection is allowed). In the dashboard, these entries render with an observe-mode badge on the reason cell and show the underlying decision type (ban, captcha, throttle, unavailable) on hover. This lets you audit what CrowdSec would block without affecting traffic. For a self-test workflow, see [Testing the integration](/selfhosted/maintenance/crowdsec#testing-the-integration).
When a request was inspected on headers and URI but not on its body, the `appsec_body_bypass` metadata key records why:

| Value | Meaning |
|---|---|
| `oversize` | The body exceeded the configured cap. Never inspected on a truncated prefix, since a partial body changes the verdict in both directions. |
| `budget_exhausted` | The proxy-wide body-buffering allowance was fully committed to other in-flight requests. |
| `upgrade` | A protocol-upgrade handshake, which carries no body. |
| `disabled` | Body forwarding is turned off for this proxy (`NB_PROXY_CROWDSEC_APPSEC_MAX_BODY_BYTES=-1`). |

A clean verdict on a request carrying one of these is not evidence that the payload was examined.

AppSec verdicts follow the same pattern as CrowdSec ones, under the `appsec_verdict` metadata key. Because AppSec inspects the request rather than the client address, an `appsec_*` entry tells you which request was flagged, not which IP is known bad.

When CrowdSec or AppSec is in **observe** mode, the verdict appears in the log metadata but the deny reason field is empty (the connection is allowed). In the dashboard, these entries render with an observe-mode badge on the reason cell and show the underlying decision type (ban, captcha, throttle, unavailable) on hover. This lets you audit what CrowdSec would block without affecting traffic. For a self-test workflow, see [Testing the integration](/selfhosted/maintenance/crowdsec#testing-the-integration).

## Use cases

Expand Down
38 changes: 35 additions & 3 deletions src/pages/manage/reverse-proxy/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,43 @@ CrowdSec decisions include different remediation types (ban, captcha, throttle).
CrowdSec is only available when the proxy cluster has CrowdSec configured. If the cluster does not support CrowdSec, the option will not appear in the Access Control tab. For self-hosted deployments, see the [CrowdSec setup guide](/selfhosted/maintenance/crowdsec) to enable it.
</Note>

### CrowdSec AppSec (WAF)

Where IP reputation asks whether the *client* is known bad, [AppSec](https://docs.crowdsec.net/docs/appsec/intro) asks whether the *request* is an attack. It is a separate component of the CrowdSec Security Engine: the proxy mirrors each HTTP request to it, and the engine answers with a verdict based on its rule set (virtual patching for known CVEs, generic exploit detection, and optionally the OWASP Core Rule Set).

AppSec applies to HTTP services only. TCP, UDP, and TLS services forward opaque byte streams with no requests to inspect, so the setting is rejected for those modes.

AppSec operates in one of three modes per service:

| Mode | Behavior |
|------|----------|
| **Off** | Requests are not inspected (default). |
| **Enforce** | Flagged requests are rejected with `403`. If the AppSec endpoint is unreachable, times out, or rejects the call, requests are denied (fail-closed). |
| **Observe** | Flagged requests are recorded in [access logs](/manage/reverse-proxy/access-logs) with an observe-mode badge and still forwarded. Use this to evaluate the rules against real traffic before enforcing. |

Inspection is synchronous: every request to an inspected service waits for the engine's verdict, with a 200 ms timeout by default. Requests arriving over the WireGuard overlay are inspected too, unlike the country and IP-reputation checks, because request content is just as meaningful there.

What gets mirrored is the request envelope, the client's headers, and the request body up to 64 KB. Bodies larger than the cap are inspected on headers and URI only, never on a truncated prefix, since a partial body changes the verdict in both directions. Body buffering also draws on a proxy-wide allowance shared with the agent-network request capture, so a burst of large bodies cannot become an out-of-memory lever; requests that arrive once it is committed are inspected on headers and URI only. Whenever the body is skipped, for that reason or any other, the access log records [`appsec_body_bypass`](/manage/reverse-proxy/access-logs) so a clean verdict is never mistaken for "the payload was examined". WebSocket upgrade requests are inspected on headers only.

Credentials are withheld from the engine the same way they are withheld from backends. The proxy's session cookie, the values of any header-auth headers, the OIDC `session_token`, and the `password` / `pin` fields of the proxy's own login form are replaced with a placeholder before mirroring, whether they arrive in the body, the query string, a cookie, or a header. Only the credential value is replaced, so the surrounding form, query, and cookie jar stay fully inspectable. Everything else the client sent is mirrored as-is, since that is what the rules match on.

<Note>
AppSec is only available when the proxy cluster has an AppSec endpoint configured, which is independent of IP reputation: a cluster can have one, both, or neither. If the cluster does not support AppSec, the option will not appear in the Access Control tab. It is currently available for self-hosted and bring-your-own-proxy deployments; support on NetBird Cloud clusters is coming. For setup, see the [CrowdSec setup guide](/selfhosted/maintenance/crowdsec#app-sec-waf-request-inspection).
</Note>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<Note>
**Recommended rollout.** Start with **Observe** on AppSec while IP reputation runs in **Enforce**. Out-of-band rules such as the OWASP CRS never block, but their alerts feed a CrowdSec scenario that produces ordinary IP ban decisions, which the IP-reputation check already enforces. That combination protects the service while you check the access logs for false positives, before switching AppSec to **Enforce**.
</Note>

### Combining restrictions with authentication

Access restrictions and authentication methods are independent layers:

1. **Connection arrives** at the proxy.
2. **Access restrictions** are evaluated first: IP CIDRs, then country, then CrowdSec. If the connection is blocked at any layer, it is rejected with no further processing.
3. **Authentication** is evaluated next (for HTTP services): SSO, password, PIN, or header auth.
4. If both layers pass, the request is forwarded to the backend.
3. **AppSec inspection** runs next when enabled (HTTP services only). A flagged request is rejected with `403` in enforce mode, or logged and allowed through in observe mode.
4. **Authentication** is evaluated next (for HTTP services): SSO, password, PIN, or header auth.
5. If every layer passes, the request is forwarded to the backend.

This layered approach lets you, for example, restrict a service to your corporate IP ranges while still requiring SSO for identity verification. Or you can use access restrictions as the sole protection for an L4 service that cannot use browser-based auth.

Expand Down Expand Up @@ -346,7 +375,9 @@ To remove NetBird-Only Access, reopen the modal and click **Remove**. The servic
- Select countries in the **Blocked Countries** field to create a country blocklist.
5. To enable CrowdSec IP reputation (when available):
- Set the **CrowdSec IP Reputation** dropdown to **Enforce** or **Observe**.
6. Click **Save** (or **Save Changes** when editing).
6. To enable CrowdSec AppSec request inspection (when available, HTTP services only):
- Set the **CrowdSec AppSec (WAF)** dropdown to **Enforce** or **Observe**.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
7. Click **Save** (or **Save Changes** when editing).

<Note>
Access restrictions apply immediately to new connections. Existing connections that were established before the restriction was added are not affected until they reconnect.
Expand All @@ -361,6 +392,7 @@ Access restrictions are evaluated as a pipeline. Each layer can only further res
| 1. CIDR | Allowlist/blocklist by IP range | Stops here, country and CrowdSec are skipped |
| 2. Country | Allowlist/blocklist by geolocation | Stops here, CrowdSec is skipped |
| 3. CrowdSec | IP reputation against decision cache | Blocks (enforce) or logs (observe) |
| 4. AppSec | Request inspection by the WAF rule set | Blocks (enforce) or logs (observe) |

**Examples:**

Expand Down
117 changes: 114 additions & 3 deletions src/pages/selfhosted/maintenance/crowdsec.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {Note} from "@/components/mdx"

export const description = 'Enable CrowdSec IP reputation blocking for self-hosted NetBird Proxy deployments.'
export const description = 'Enable CrowdSec IP reputation blocking and AppSec (WAF) request inspection for self-hosted NetBird Proxy deployments.'

# CrowdSec IP Reputation

![CrowdSec IP Reputation Overview](/docs-static/img/selfhosted/maintenance/crowdsec-overview.png)

[CrowdSec](https://www.crowdsec.net) is an open-source security engine that combines local detection with a crowdsourced threat feed. Agents installed across the CrowdSec community share decisions about malicious IPs, and that shared intelligence is redistributed as blocklists that any CrowdSec instance can consume. NetBird Proxy integrates with CrowdSec to check every incoming client IP against a local decision cache and block connections from flagged addresses before they reach your services. For background on the wider CrowdSec platform, see the [CrowdSec documentation](https://docs.crowdsec.net).

This page covers how the integration works and how to operate it. For full setup instructions, see [Step 7: Enable CrowdSec IP reputation](/selfhosted/migration/enable-reverse-proxy#step-7-optional-enable-crowdsec-ip-reputation) in the Enable Reverse Proxy guide.
This page covers how the integration works and how to operate it. For full setup instructions, see [Step 7: Enable CrowdSec IP reputation](/selfhosted/migration/enable-reverse-proxy#step-7-optional-enable-crowd-sec-ip-reputation) in the Enable Reverse Proxy guide.

<Note>
If you're running the [quickstart script](/selfhosted/selfhosted-quickstart) for a fresh installation, it offers to enable CrowdSec automatically when you choose the built-in Traefik option and enable the proxy.
Expand Down Expand Up @@ -48,6 +48,117 @@ Observe-mode verdicts are recorded in the NetBird proxy access logs, not in the

Access restrictions are evaluated in a fixed order: CIDR, then country, then CrowdSec. A denial at any earlier layer short-circuits the rest, and CrowdSec can never relax a CIDR or country decision. See the [restriction evaluation order](/manage/reverse-proxy/authentication#restriction-evaluation-order) reference for the full precedence table and worked examples.

## AppSec (WAF) request inspection

IP reputation asks whether the client is known bad. [AppSec](https://docs.crowdsec.net/docs/appsec/intro) asks whether the request is an attack, matching each HTTP request against a rule set: virtual patches for known CVEs, generic exploit detection, and optionally the OWASP Core Rule Set. It is a separate listener on the same Security Engine, so it needs its own configuration but reuses the bouncer API key.

Unlike the decision stream, this check is a blocking call per request: the proxy mirrors the request to the engine and waits for the verdict, with a 200 ms timeout by default. AppSec applies to HTTP services only.

### Enable it on the engine

The AppSec listener only exists if an acquisition datasource defines it. Create `crowdsec/acquis.d/appsec.yaml` next to your compose file:

```yaml
source: appsec
listen_addr: 0.0.0.0:7422
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
```

Then install the rule collections. Both are required: `appsec-default` references `crowdsecurity/generic-*` and `crowdsecurity/experimental-*`, which only `appsec-generic-rules` provides, and the engine exits at startup if they are missing.

```bash
docker compose exec crowdsec cscli collections install \
crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules
docker compose restart crowdsec
```

Optionally add the OWASP Core Rule Set, which runs out-of-band (it never blocks the request, but its alerts feed a scenario that produces ordinary IP ban decisions):

```bash
docker compose exec crowdsec cscli collections install crowdsecurity/appsec-crs
```

Then add `crowdsecurity/crs` to the `appsec_configs` list and restart.

<Note>
On a fresh install the [quickstart script](/selfhosted/selfhosted-quickstart) writes the acquisition file and installs both required collections for you when you enable CrowdSec.
</Note>

### Point the proxy at it

Add the endpoint to `proxy.env` and restart the proxy. The bouncer API key is reused, since the AppSec component validates it against the LAPI:

```bash
NB_PROXY_CROWDSEC_APPSEC_URL=http://crowdsec:7422/
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Three optional settings are available:

| Variable | Default | Notes |
|---|---|---|
| `NB_PROXY_CROWDSEC_APPSEC_TIMEOUT` | `200ms` | Clamped to between `10ms` and `5s`. Inspection is synchronous, so this is added to the latency of every inspected request. |
| `NB_PROXY_CROWDSEC_APPSEC_MAX_BODY_BYTES` | `65536` | Capped at 8 MB. Set `-1` to inspect headers and URI only. |
| `NB_PROXY_CROWDSEC_APPSEC_MAX_CONCURRENT` | `256` | Inspections in flight toward the engine. Once reached, further requests are denied in enforce mode instead of queueing until they time out, so a saturated engine fails fast rather than parking a request each. Set `-1` to remove the bound. |
| `NB_PROXY_CAPTURE_BUDGET_BYTES` | `268435456` (256 MB) | Total body buffering in flight across the proxy, shared with the agent-network request capture. Requests arriving once it is committed are inspected on headers and URI only, recorded as `budget_exhausted`. |

Raise the throughput of the engine itself before enabling enforce widely. The `appsec` datasource processes requests with a single runner by default, which serializes inspection across every service on the listener; set `routines` in the acquisition file to the engine's core count:

```yaml
source: appsec
listen_addr: 0.0.0.0:7422
routines: 4
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
```

Setting the URL makes the proxy advertise the `supports_appsec` capability, which is what lets a service select an AppSec mode. Nothing is inspected until a service opts in.

Confirm the proxy picked it up:

```bash
docker compose logs proxy | grep -i appsec
```

```text
netbird-proxy | INFO proxy/server.go: CrowdSec AppSec inspection available at http://crowdsec:7422/
```

### Custom rules

Rules are engine-side configuration, not a NetBird setting. Drop a YAML file in `crowdsec/appsec-rules/`, where it registers as a local hub item and is loaded by the `name` inside it:

```yaml
name: myorg/no-wp-login
description: block wp-login probes
rules:
- zones: [URI]
transform: [lowercase]
match: {type: startsWith, value: /wp-login.php}
```

Reference it from an appsec-config that you add to `appsec_configs`, alongside the hub ones. See the [CrowdSec rule syntax](https://docs.crowdsec.net/docs/appsec/rules_syntax) reference for the available zones, transforms, and match operators, including importing legacy ModSecurity rules.

One listener carries one merged rule set: the protocol has no rule-set selector, so all services with AppSec enabled on a cluster share the same rules. To vary rules per service, either run a second acquisition datasource on another port for a separate cluster, or use `pre_eval` hooks that filter on `req.Host` to disable rules or change the remediation for specific domains.

### Testing it

With a service set to **enforce**, a request matching a virtual patch should be rejected with `403`:

```bash
curl -sk -o /dev/null -w '%{http_code}\n' 'https://<your-service>/.env'
```

The event log shows an `appsec_ban` deny reason. With the service set to **observe**, the same request reaches the backend and the verdict appears on the event entry with an observe-mode badge. To check the engine side directly:

```bash
docker compose exec crowdsec cscli alerts list
```

## Enroll with the CrowdSec Console (optional)

Enrolling your LAPI with the [CrowdSec Console](https://app.crowdsec.net) lets you view blocked IPs, manage scenarios, and opt into premium blocklists from a web UI. The quickstart script prompts for an enrollment key and registers it automatically. To enroll an existing deployment:
Expand All @@ -61,7 +172,7 @@ Enrollment is optional. The bouncer continues to sync the community blocklist wi

## Configuring services

Once CrowdSec is enabled on the proxy, the **CrowdSec IP Reputation** dropdown appears in the Access Control tab of each reverse proxy service. Set it to **Enforce** or **Observe** per service. See [reverse proxy authentication](/manage/reverse-proxy/authentication#crowdsec-ip-reputation) for configuration steps and [access logs](/manage/reverse-proxy/access-logs) for the verdict fields that appear in event logs.
Once CrowdSec is enabled on the proxy, the **CrowdSec IP Reputation** dropdown appears in the Access Control tab of each reverse proxy service, and the **CrowdSec AppSec (WAF)** dropdown appears when the AppSec endpoint is configured too. Set each to **Enforce** or **Observe** per service. A good rollout is AppSec in **Observe** with IP reputation in **Enforce**: out-of-band rules still produce ban decisions that the reputation check enforces, while you review the access logs for false positives. See [reverse proxy authentication](/manage/reverse-proxy/authentication#crowd-sec-ip-reputation) for configuration steps and [access logs](/manage/reverse-proxy/access-logs) for the verdict fields that appear in event logs.

## Testing the integration

Expand Down
Loading
Loading