11 KiB
CrowdSec Dashy — Project Plan
Pure Go · No NPM · Tailwind CDN · Zero third-party Go packages (except SQLite if needed)
1. Architecture Overview
Data Access — Two Layers
| Layer | What it covers | Auth |
|---|---|---|
CrowdSec LAPI (REST, net/http) |
Decisions CRUD, Alerts read/delete, health check | Machine login + password → JWT |
cscli exec (os/exec) |
Bouncers, Machines, Hub (collections/parsers/scenarios), Metrics | Binary path (mounted volume in Docker) |
Critical finding:
cscli bouncers add/delete/list,machines add/delete/list, and allhubcommands hit the database directly and are not exposed through the REST API. Any Docker deployment must bind-mount thecsclibinary or the app must gracefully degrade those sections.
Deployment Modes
Mode A — Sidecar Docker (recommended)
crowdsec container ←→ crowdsec-dashy container
- shared Docker network (LAPI at http://crowdsec:8080)
- /usr/local/bin/cscli bind-mounted read-only from host
- CROWDSEC_API_LOGIN / PASSWORD as machine credentials
Mode B — Host binary
- Direct localhost:8080 LAPI
- cscli already on PATH
No SQLite needed
All state lives in CrowdSec's own database. No local DB required.
2. Environment Variables
PORT=:8080 # UI listen address
CROWDSEC_API_URL=http://localhost:8080 # LAPI base URL
CROWDSEC_API_LOGIN=crowdsec-dashy # machine login (create with cscli machines add)
CROWDSEC_API_PASSWORD=changeme # machine password
CSCLI_PATH=/usr/local/bin/cscli # path to cscli binary
UI_USERNAME=admin # basic-auth for the UI itself
UI_PASSWORD=changeme # basic-auth for the UI itself
UI_SESSION_SECRET=random32chars # HMAC session signing key
POLL_INTERVAL_SEC=15 # dashboard live-poll interval
3. Project Structure
crowdsec-dashy/
├── go.mod (module crowdsec-dashy, go 1.22)
├── Dockerfile
├── docker-compose.yml
│
├── cmd/server/
│ └── main.go entrypoint, env config, server init
│
├── internal/
│ ├── config/
│ │ └── config.go env-var loading, validation
│ │
│ ├── crowdsec/
│ │ ├── types.go Decision, Alert, Bouncer, Machine, Hub*, Metrics structs
│ │ ├── lapi.go REST client: login, decisions CRUD, alerts, health
│ │ └── cli.go cscli wrapper: bouncers, machines, hub, metrics
│ │
│ ├── handlers/
│ │ ├── renderer.go template engine + PageData (sidebar nav)
│ │ ├── dashboard.go GET /
│ │ ├── decisions.go GET/POST /decisions, POST /decisions/delete
│ │ ├── alerts.go GET /alerts, POST /alerts/delete
│ │ ├── bouncers.go GET/POST /bouncers
│ │ ├── machines.go GET/POST /machines
│ │ ├── hub.go GET /hub, POST /hub/install, POST /hub/remove
│ │ ├── metrics.go GET /metrics-ui
│ │ └── api.go JSON API: /api/v1/stats, /api/v1/decisions, etc.
│ │
│ ├── middleware/
│ │ └── middleware.go BasicAuth, SecureHeaders, Logger, Recovery
│ │
│ └── router/
│ └── router.go route wiring
│
└── web/
├── templates/
│ ├── layouts/
│ │ └── base.html sidebar shell (not top-nav)
│ └── pages/
│ ├── dashboard.html
│ ├── decisions.html
│ ├── alerts.html
│ ├── bouncers.html
│ ├── machines.html
│ ├── hub.html
│ ├── metrics.html
│ └── error.html
└── static/
├── css/app.css component classes, sidebar, badges, tables
└── js/
├── app.js global: sidebar toggle, flash dismiss
├── dashboard.js SSE/polling for live stats + sparklines
└── tables.js client-side filter/sort, confirm-delete modal
4. Pages & Features
4.1 Dashboard (/)
- Stat cards: Active Bans, Alerts (24h), Bouncers connected, Machines registered
- Live-update: JS polls
/api/v1/statsevery N seconds, updates counters in-place - Recent activity: Last 10 alerts table (scenario, IP, date, origin)
- Recent bans: Last 10 decisions table
- CrowdSec version + health badge (green/red dot)
4.2 Decisions (/decisions)
- Table: IP/Range, Type (ban/captcha), Scenario, Origin, Duration, Expires At
- Filters: type, origin, scope (IP / Range / Country)
- Actions: Delete individual (POST /decisions/delete/{id}), Bulk delete (checked rows)
- Add Decision form: IP, scope, type, duration (PRG pattern)
- Pagination (server-side, offset/limit passed to LAPI)
4.3 Alerts (/alerts)
- Table: ID, Scenario, IP, Country, ASN, Date, Decisions count
- Click row → detail drawer (all events in alert, full metadata)
- Delete individual alert, bulk delete selected
- Filter by scenario, IP, date range (client-side JS)
4.4 Bouncers (/bouncers)
- Table: Name, IP, Last Seen, Version, Type, Valid
- Add bouncer (runs
cscli bouncers add <name>, shows generated API key once — modal) - Delete bouncer (POST with confirm)
- Status badge: green (valid) / red (revoked)
- Graceful degradation: if cscli unavailable, show inline warning banner
4.5 Machines (/machines)
- Table: Name, IP, Last Update, Version, Auth Type, Status, Last Heartbeat
- Add machine (
cscli machines add) - Validate pending machine (
cscli machines validate) - Delete machine
- Graceful degradation: cscli required
4.6 Hub (/hub)
- Tabbed view: Collections | Parsers | Scenarios | Postoverflows
- Each tab: list installed (with version), list available to install
- Install / Remove actions → POST → runs
cscli hub install/remove - Update all button (
cscli hub update && cscli hub upgrade) - Graceful degradation: cscli required
4.7 Metrics (/metrics-ui)
- Parsed output of
cscli metrics(acquisition, parsers, scenarios, LAPI) - Tables grouped by category: Acquisition Sources, Parser Stats, Scenario Stats, LAPI Stats
- Auto-refresh toggle (polls every 30s)
- Graceful degradation: cscli required
5. Internal API (/api/v1/...)
Used by frontend JS — all return JSON, all protected by same BasicAuth.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/stats |
Dashboard summary counts |
| GET | /api/v1/decisions |
Paginated decisions (query: page, limit, type, scope) |
| DELETE | /api/v1/decisions/{id} |
Delete one decision |
| GET | /api/v1/alerts |
Paginated alerts |
| DELETE | /api/v1/alerts/{id} |
Delete one alert |
| GET | /api/v1/health |
LAPI health + cscli availability |
6. Authentication
The UI sits behind HTTP Basic Auth (middleware.BasicAuth) — simple and works in Docker behind a reverse proxy with TLS.
The app itself authenticates to CrowdSec LAPI as a machine (not a bouncer) to get full read+write access to decisions and alerts.
Flow:
- On startup,
lapi.Login()POSTs credentials to/v1/watchers/login→ JWT - JWT stored in memory, refreshed on 401 response
- All LAPI requests carry
Authorization: Bearer <jwt>
7. UI Design
Aesthetic: Industrial/utilitarian dark — think terminal output meets ops dashboard. Not a generic admin panel.
- Layout: Fixed left sidebar (240px), collapsible on mobile. Content area with header bar.
- Palette: Near-black surface (
#080b10), blue-grey panels (#0f1520), electric cyan accent (#00d4ff), threat-red (#ff3b3b), safe-green (#00e676) - Typography:
JetBrains Monofor data/numbers/badges,IBM Plex Sansfor labels/headings - Tables: Monospaced IP addresses, color-coded type badges (ban=red, captcha=amber, custom=blue)
- Sidebar nav: Icon + label, active state left-border accent, subtle hover
- Stat cards: Large mono number, label, delta indicator, thin accent top-border
- No external chart libs: Sparklines drawn with inline SVG
<polyline>from JS
8. Build Phases
Phase 1 — Foundation
go.mod, config, folder structurecrowdsec/types.go— all structscrowdsec/lapi.go— HTTP client, login, decisions, alertscrowdsec/cli.go— exec wrapper, JSON parsing for bouncers/machines/hub/metricsmiddleware/middleware.go— BasicAuth + existing middlewarerouter/router.goskeletonbase.html— sidebar shell + Tailwind config
Phase 2 — Dashboard + API
handlers/api.go—/api/v1/stats,/api/v1/healthhandlers/dashboard.go+dashboard.htmlstatic/js/dashboard.js— polling, counter animation, sparklines
Phase 3 — Decisions & Alerts
handlers/decisions.go+decisions.htmlhandlers/alerts.go+alerts.htmlstatic/js/tables.js— filter, sort, bulk-select, confirm modal
Phase 4 — Bouncers & Machines
handlers/bouncers.go+bouncers.htmlhandlers/machines.go+machines.html- Graceful degradation UI pattern
Phase 5 — Hub & Metrics
handlers/hub.go+hub.htmlhandlers/metrics.go+metrics.html
Phase 6 — Docker + Polish
Dockerfile(multi-stage: build → scratch/alpine)docker-compose.ymlwith bind-mount examples- Error page handler
- README with setup instructions
9. Docker Compose Example
version: "3.9"
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
volumes:
- crowdsec-data:/var/lib/crowdsec/data
- crowdsec-config:/etc/crowdsec
networks:
- cs-net
crowdsec-dashy:
build: .
ports:
- "8080:8080"
environment:
CROWDSEC_API_URL: http://crowdsec:8080
CROWDSEC_API_LOGIN: crowdsec-dashy
CROWDSEC_API_PASSWORD: changeme
CSCLI_PATH: /usr/local/bin/cscli
UI_USERNAME: admin
UI_PASSWORD: changeme
volumes:
# Bind-mount cscli from the host for CLI features
- /usr/local/bin/cscli:/usr/local/bin/cscli:ro
networks:
- cs-net
depends_on:
- crowdsec
networks:
cs-net:
volumes:
crowdsec-data:
crowdsec-config:
10. Security Checklist
- All routes behind BasicAuth middleware
- LAPI credentials in env only, never in templates or logs
cscliexec: args passed as slice (no shell injection), strict allow-list of commands- All POST forms validate inputs server-side before exec/API call
- PRG pattern on all state-changing POSTs
http.MaxBytesReaderon all POST handlers- Security headers middleware (CSP, X-Frame-Options, etc.)
- Server timeouts (Read/Write/Idle)
- Template buffer pattern (render to bytes.Buffer first)
- JWT token never logged
- Generated bouncer API keys shown exactly once, never stored by the UI
11. go.mod
module crowdsec-dashy
go 1.22
Zero external Go dependencies. All CrowdSec communication via stdlib net/http + os/exec.