diff --git a/src/pages/manage/reverse-proxy/access-logs.mdx b/src/pages/manage/reverse-proxy/access-logs.mdx index 508b721e4..e167bfb1b 100644 --- a/src/pages/manage/reverse-proxy/access-logs.mdx +++ b/src/pages/manage/reverse-proxy/access-logs.mdx @@ -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 | |--------|-------------| @@ -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. | 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 diff --git a/src/pages/manage/reverse-proxy/authentication.mdx b/src/pages/manage/reverse-proxy/authentication.mdx index 6095666c9..2f3d21aae 100644 --- a/src/pages/manage/reverse-proxy/authentication.mdx +++ b/src/pages/manage/reverse-proxy/authentication.mdx @@ -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. +### 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. + + +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). + + + + **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**. + + ### 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. @@ -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**. +7. Click **Save** (or **Save Changes** when editing). Access restrictions apply immediately to new connections. Existing connections that were established before the restriction was added are not affected until they reconnect. @@ -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:** diff --git a/src/pages/selfhosted/maintenance/crowdsec.mdx b/src/pages/selfhosted/maintenance/crowdsec.mdx index 8d376ae66..61fc36ee0 100644 --- a/src/pages/selfhosted/maintenance/crowdsec.mdx +++ b/src/pages/selfhosted/maintenance/crowdsec.mdx @@ -1,6 +1,6 @@ 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 @@ -8,7 +8,7 @@ export const description = 'Enable CrowdSec IP reputation blocking for self-host [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. 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. @@ -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. + + +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. + + +### 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/ +``` + +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:///.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: @@ -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 diff --git a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx index 61781360c..d567bae24 100644 --- a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx +++ b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx @@ -359,7 +359,62 @@ CrowdSec must be enabled individually on each service through the dashboard unde In **enforce** mode, if the bouncer has not completed its initial sync with the LAPI, all connections to that service will be denied. This is by design (fail-closed). If you want to avoid this during initial rollout, start with **observe** mode. -#### 7e. Enroll in CrowdSec Console (optional) +#### 7e. Enable AppSec request inspection (optional) + +The same Security Engine can also inspect HTTP requests for exploits (SQL injection, path traversal, known CVE probes). This is a second listener on the CrowdSec container with its own rule set, reusing the bouncer key from step 7b. It applies to HTTP services only. + +Create the acquisition file that defines the listener: + +```bash +mkdir -p crowdsec/acquis.d +cat > crowdsec/acquis.d/appsec.yaml <<'EOF' +source: appsec +listen_addr: 0.0.0.0:7422 +appsec_configs: + - crowdsecurity/appsec-default +labels: + type: appsec +EOF +``` + +Add the rule collections to the CrowdSec service in `docker-compose.yml`, so they are reinstalled if the container is recreated: + +```yaml + environment: + COLLECTIONS: crowdsecurity/linux crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules +``` + + +Both AppSec collections are required. The `appsec-default` config references `crowdsecurity/generic-*` and `crowdsecurity/experimental-*` rules, which only `appsec-generic-rules` provides, and the engine exits at startup if they are missing. + + +Recreate CrowdSec so it picks up the new environment and acquisition file, then confirm the listener is up: + +```bash +docker compose up -d --force-recreate crowdsec +docker compose logs crowdsec | grep -i "appsec listening" +``` + +Point the proxy at the endpoint by adding one line to `proxy.env` (the API key from step 7b is reused, since the AppSec component validates it against the LAPI): + +```bash +NB_PROXY_CROWDSEC_APPSEC_URL=http://crowdsec:7422/ +``` + +Restart the proxy and verify: + +```bash +docker compose up -d proxy +docker compose logs proxy | grep -i appsec +``` + +You should see `CrowdSec AppSec inspection available at http://crowdsec:7422/`. AppSec then appears as a per-service option under **Access Control** alongside IP reputation, off by default. For custom rules, tuning, and testing, see [AppSec (WAF) request inspection](/selfhosted/maintenance/crowdsec#app-sec-waf-request-inspection). + + +Start with **observe** rather than **enforce**. Inspection is synchronous and enforce mode fails closed, so a misconfigured or unreachable engine would deny requests to every service that enforces it. + + +#### 7f. Enroll in CrowdSec Console (optional) The CrowdSec Console gives you a web dashboard to monitor decisions, manage alerts, and subscribe to premium blocklists. Enrollment is optional: the community blocklists work without it. @@ -634,7 +689,7 @@ The proxy is configured entirely through environment variables (each one maps to ### IP reputation (CrowdSec) -See [Step 7: Enable CrowdSec IP reputation](#step-7-optional-enable-crowdsec-ip-reputation) for the full setup. +See [Step 7: Enable CrowdSec IP reputation](#step-7-optional-enable-crowd-sec-ip-reputation) for the full setup. | Variable | Required | Description | Default | |----------|----------|-------------|---------|