diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx index 356c16a36..a5d177535 100644 --- a/src/components/NavigationDocs.jsx +++ b/src/components/NavigationDocs.jsx @@ -614,6 +614,10 @@ export const docsNavigation = [ title: 'CrowdSec IP Reputation', href: '/selfhosted/maintenance/crowdsec', }, + { + title: 'CrowdSec Dashboard Protection', + href: '/selfhosted/maintenance/crowdsec-dashboard', + }, ], }, { diff --git a/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx b/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx new file mode 100644 index 000000000..219374217 --- /dev/null +++ b/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx @@ -0,0 +1,325 @@ +import { Warning } from '@/components/mdx' + +export const description = + 'Protect the self-hosted NetBird dashboard with CrowdSec AppSec and the Traefik CrowdSec bouncer plugin.' + +# Protect the dashboard with CrowdSec AppSec + +This guide shows how to protect a self-hosted NetBird dashboard with [CrowdSec AppSec](https://docs.crowdsec.net/docs/appsec/intro) when NetBird is deployed with the internal Traefik reverse proxy. + +The configuration reuses the CrowdSec LAPI container created by the NetBird quickstart when you enable the NetBird Proxy CrowdSec integration. NetBird Proxy and Traefik use separate bouncer keys against the same CrowdSec service. + +For the NetBird Proxy IP reputation integration, see [CrowdSec IP Reputation](/selfhosted/maintenance/crowdsec). + +## Prerequisites + +- A self-hosted NetBird deployment using the internal Traefik reverse proxy. +- NetBird Proxy enabled with CrowdSec IP reputation blocking. +- A Linux host reachable on TCP `80` and `443`, and UDP `3478`. +- A domain such as `netbird.example.com` pointing to the host. +- A wildcard record such as `*.netbird.example.com` pointing to the same host if you use NetBird Proxy. +- Docker with Compose v2. +- `curl`, `jq`, and `openssl`. + +Set these shell variables before running the commands in this guide: + +```bash +export NETBIRD_INSTALL_DIR="/path/to/netbird/install" +export NETBIRD_DOMAIN="netbird.example.com" +``` + +`NETBIRD_INSTALL_DIR` is the directory that contains the generated `docker-compose.yml`, `proxy.env`, `dashboard.env`, and `crowdsec/` files. + +## Deploy NetBird with Traefik and CrowdSec + +For a new deployment, follow the [Self-hosting Quickstart Guide](/selfhosted/selfhosted-quickstart). When the quickstart asks for deployment options, choose: + +```text +Reverse proxy: 0, Traefik +Enable proxy: y +Enable CrowdSec IP reputation blocking: y +``` + +Enabling CrowdSec creates a local `crowdsec` service, registers a `netbird-proxy` bouncer, and writes `NB_PROXY_CROWDSEC_API_URL` and `NB_PROXY_CROWDSEC_API_KEY` to `proxy.env`. + +If you already have a self-hosted NetBird deployment without NetBird Proxy, follow [Enable Reverse Proxy Feature](/selfhosted/migration/enable-reverse-proxy) first. + + + This CrowdSec dashboard protection setup requires the [combined container + setup](/selfhosted/migration/combined-container). If your deployment uses the + separate-container architecture (`management`, `signal`, `relay`, and + `coturn`), migrate to the combined container setup before enabling NetBird + Proxy. + + +## Add a dashboard bouncer key + +Create a dedicated bouncer key for the Traefik plugin and store it in the Compose project `.env` file: + +```bash +cd "$NETBIRD_INSTALL_DIR" +umask 077 +if ! grep -q '^CROWDSEC_DASHBOARD_BOUNCER_KEY=' .env 2>/dev/null; then + printf 'CROWDSEC_DASHBOARD_BOUNCER_KEY=%s\n' "$(openssl rand -hex 32)" >> .env +fi +``` + +Rerunning this command preserves the existing key. + +## Add CrowdSec AppSec acquisition + +Create the AppSec acquisition file: + +```bash +mkdir -p "$NETBIRD_INSTALL_DIR/crowdsec/acquis.d" +cat >"$NETBIRD_INSTALL_DIR/crowdsec/acquis.d/appsec.yaml" <<'EOF' +appsec_configs: + - crowdsecurity/appsec-default +labels: + type: appsec +listen_addr: 0.0.0.0:7422 +source: appsec +EOF +``` + +## Update docker-compose.yml + +Start from the `docker-compose.yml` generated by the quickstart with proxy CrowdSec enabled. The file already includes a `crowdsec` service, and `proxy.env` already points NetBird Proxy to `http://crowdsec:8080`. + +Under `services.traefik.command`, add the Traefik CrowdSec bouncer plugin static arguments: + +```yaml +services: + traefik: + command: + - '--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin' + - '--experimental.plugins.bouncer.version=v1.6.0' +``` + +If your generated Compose project network is named `netbird_netbird`, make sure Traefik uses that Docker network: + +```yaml +services: + traefik: + command: + - '--providers.docker.network=netbird_netbird' +``` + +Under `services.traefik.depends_on`, make Traefik wait until CrowdSec is healthy: + +```yaml +services: + traefik: + depends_on: + crowdsec: + condition: service_healthy +``` + +Under `services.dashboard.labels`, add the CrowdSec middleware to the existing dashboard router: + +```yaml +services: + dashboard: + labels: + - traefik.http.routers.netbird-dashboard.middlewares=netbird-dashboard-crowdsec@docker +``` + +In the same `services.dashboard.labels` list, define the middleware: + +```yaml +services: + dashboard: + labels: + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.enabled=true + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecMode=stream + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecLapiScheme=http + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecLapiHost=crowdsec:8080 + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecLapiKey=${CROWDSEC_DASHBOARD_BOUNCER_KEY} + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecAppsecEnabled=true + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecAppsecHost=crowdsec:7422 + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecAppsecFailureBlock=true + - traefik.http.middlewares.netbird-dashboard-crowdsec.plugin.bouncer.crowdsecAppsecUnreachableBlock=true +``` + +Under `services.netbird-server.labels`, add a higher-priority router for dashboard API and embedded IdP paths: + +```yaml +services: + netbird-server: + labels: + - traefik.http.routers.netbird-dashboard-api.rule=Host(`netbird.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/oauth2`)) + - traefik.http.routers.netbird-dashboard-api.entrypoints=websecure + - traefik.http.routers.netbird-dashboard-api.tls=true + - traefik.http.routers.netbird-dashboard-api.tls.certresolver=letsencrypt + - traefik.http.routers.netbird-dashboard-api.service=netbird-server + - traefik.http.routers.netbird-dashboard-api.middlewares=netbird-dashboard-crowdsec@docker + - traefik.http.routers.netbird-dashboard-api.priority=110 +``` + +Replace `netbird.example.com` with your NetBird domain. Traefik labels do not read the `NETBIRD_DOMAIN` shell variable used in the command examples. + + + Leave the NetBird Proxy TCP passthrough labels unchanged. Do not attach this + middleware to the `proxy-passthrough` TCP router. + + +Merge these fields into `services.crowdsec`. This adds the AppSec collections, registers the dashboard bouncer key, and updates the health check so CrowdSec reports healthy only after both LAPI and AppSec are available. Keep the generated fields that already exist in your Compose file, such as `image`, `container_name`, `restart`, `networks`, `labels`, `logging`, and `volumes`. + +The installer-created health check only verifies the CrowdSec Local API: + +```yaml +healthcheck: + test: ['CMD', 'cscli', 'lapi', 'status'] + interval: 10s + timeout: 5s + retries: 15 +``` + +Dashboard protection also needs the AppSec listener on port `7422`, so replace the installer-created health check with the one below. + +| Field | Purpose | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `crowdsecurity/linux` | Keeps the base Linux collection created by the installer. | +| `crowdsecurity/appsec-virtual-patching` | Adds virtual patching rules for common web application attack patterns. | +| `crowdsecurity/appsec-generic-rules` | Adds generic AppSec detection rules used by the AppSec engine. | +| `BOUNCER_KEY_netbird_dashboard` | Registers a separate Traefik dashboard bouncer key without reusing the NetBird Proxy bouncer key. | +| `healthcheck.test` | Replaces the installer LAPI-only check with a check for both LAPI and the AppSec listener on `7422`. | + +```yaml +services: + crowdsec: + ... + environment: + COLLECTIONS: "crowdsecurity/linux crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules" + BOUNCER_KEY_netbird_dashboard: "${CROWDSEC_DASHBOARD_BOUNCER_KEY}" + healthcheck: + test: ["CMD-SHELL", "cscli lapi status >/dev/null 2>&1 && bash -c '