# CrowdSec UI A lightweight, dependency-free web dashboard for monitoring and managing CrowdSec. **Stack**: Go 1.26 · `html/template` · Tailwind CSS (CDN) · Zero external Go packages --- ## Features | Feature | Transport | Requires | |---|---|---| | Decisions (list, add, delete) | LAPI REST | Machine credentials | | Alerts (list, delete) | LAPI REST | Machine credentials | | Live dashboard stats | LAPI REST (polled) | Machine credentials | | Bouncers (list, add, delete) | `cscli exec` | cscli binary | | Machines (list, validate, delete) | `cscli exec` | cscli binary | | Hub (collections/parsers/scenarios) | `cscli exec` | cscli binary | | Metrics | `cscli exec` | cscli binary | > All `cscli`-backed features degrade gracefully with a banner if the binary is unavailable. --- ## Quick Start ### 1. Register the UI as a CrowdSec machine ```bash # On the host running CrowdSec: sudo cscli machines add crowdsec-dashy --password your-password -a -f /etc/crowdsec/dashy_credentials.yaml # Or if CrowdSec is in Docker: docker exec crowdsec cscli machines add crowdsec-dashy --password your-password -a -f /etc/crowdsec/dashy_credentials.yaml ``` ### 2. Configure environment variables Copy or edit `docker-compose.yml` and set: ```yaml CROWDSEC_API_LOGIN: "crowdsec-dashy" CROWDSEC_API_PASSWORD: "your-password" # must match step 1 UI_USERNAME: "admin" UI_PASSWORD: "your-ui-password" # Basic Auth for the web UI UI_SESSION_SECRET: "32-char-random-string" ``` ### 3. Set up cscli bind-mount (for CLI features) ```bash # If CrowdSec is in Docker, extract cscli: docker cp crowdsec:/usr/local/bin/cscli /usr/local/bin/cscli chmod +x /usr/local/bin/cscli # The docker-compose.yml already bind-mounts it: # - /usr/local/bin/cscli:/usr/local/bin/cscli:ro ``` ### 4. Launch ```bash docker compose up -d ``` Open `http://localhost:8080` — browser will prompt for Basic Auth. --- ## Running Directly (no Docker) ```bash # Build go build -o crowdsec-dashy ./cmd/server # Set env vars export CROWDSEC_API_URL="http://localhost:8080" export CROWDSEC_API_LOGIN="crowdsec-dashy" export CROWDSEC_API_PASSWORD="your-password" export UI_USERNAME="admin" export UI_PASSWORD="your-ui-password" export UI_SESSION_SECRET="32-char-random-string" # Run ./crowdsec-dashy ``` --- ## Environment Variables | Variable | Default | Description | |---|---|---| | `PORT` | `:8080` | Listen address | | `CROWDSEC_API_URL` | `http://localhost:8080` | CrowdSec LAPI base URL | | `CROWDSEC_API_LOGIN` | *(required)* | Machine login (cscli machines add) | | `CROWDSEC_API_PASSWORD` | *(required)* | Machine password | | `CSCLI_PATH` | `/usr/local/bin/cscli` | Path to cscli binary | | `UI_USERNAME` | `admin` | Basic Auth username | | `UI_PASSWORD` | `changeme` | Basic Auth password — **change this** | | `UI_SESSION_SECRET` | *(required, ≥32 chars)* | HMAC signing key | | `POLL_INTERVAL_SEC` | `15` | Dashboard live-poll interval | --- ## Project Structure ``` crowdsec-dashy/ ├── cmd/server/main.go entrypoint ├── internal/ │ ├── config/config.go env-var loading │ ├── crowdsec/ │ │ ├── types.go all shared structs │ │ ├── lapi.go REST client (decisions, alerts) │ │ └── cli.go cscli exec wrapper │ ├── handlers/ │ │ ├── renderer.go template engine + PageData │ │ ├── dashboard.go │ │ ├── decisions.go │ │ ├── alerts.go │ │ ├── bouncers.go │ │ ├── machines.go │ │ ├── hub.go │ │ ├── metrics.go │ │ └── api.go JSON API for dashboard polling │ ├── middleware/middleware.go BasicAuth, Logger, SecureHeaders, Recovery │ └── router/router.go route wiring └── web/ ├── templates/layouts/base.html sidebar shell ├── templates/pages/ one file per page └── static/css/app.css all component styles static/js/app.js global JS static/js/dashboard.js stats polling ``` --- ## Adding a New Page 1. Create `internal/handlers/mypage.go` with a handler struct 2. Create `web/templates/pages/mypage.html` starting with `{{template "base" .}}` 3. Add to `SidebarNav` in `renderer.go` 4. Add `mux.Handle("/mypage", handlers.NewMyPageHandler(deps))` in `router.go` --- ## Security Notes - All routes protected by HTTP Basic Auth — put behind TLS (nginx/Caddy) in production - `cscli` args passed as a slice (never through a shell); strict allow-list of subcommands - LAPI JWT stored in memory only, never logged - Generated bouncer API keys shown exactly once, never stored by the UI - Security headers on every response: CSP, X-Frame-Options, X-Content-Type-Options - Server has explicit Read/Write/Idle timeouts --- ## Dependencies **Go**: zero external packages — stdlib only. **Frontend**: Tailwind CSS via CDN (CSS only, no JS framework), Google Fonts via CDN.