311 lines
11 KiB
Markdown
311 lines
11 KiB
Markdown
|
|
# 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 all `hub` commands hit the database directly and are **not** exposed through the REST API. Any Docker deployment must bind-mount the `cscli` binary 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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/stats` every 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:
|
||
|
|
1. On startup, `lapi.Login()` POSTs credentials to `/v1/watchers/login` → JWT
|
||
|
|
2. JWT stored in memory, refreshed on 401 response
|
||
|
|
3. 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 Mono` for data/numbers/badges, `IBM Plex Sans` for 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 structure
|
||
|
|
- `crowdsec/types.go` — all structs
|
||
|
|
- `crowdsec/lapi.go` — HTTP client, login, decisions, alerts
|
||
|
|
- `crowdsec/cli.go` — exec wrapper, JSON parsing for bouncers/machines/hub/metrics
|
||
|
|
- `middleware/middleware.go` — BasicAuth + existing middleware
|
||
|
|
- `router/router.go` skeleton
|
||
|
|
- `base.html` — sidebar shell + Tailwind config
|
||
|
|
|
||
|
|
### Phase 2 — Dashboard + API
|
||
|
|
- `handlers/api.go` — `/api/v1/stats`, `/api/v1/health`
|
||
|
|
- `handlers/dashboard.go` + `dashboard.html`
|
||
|
|
- `static/js/dashboard.js` — polling, counter animation, sparklines
|
||
|
|
|
||
|
|
### Phase 3 — Decisions & Alerts
|
||
|
|
- `handlers/decisions.go` + `decisions.html`
|
||
|
|
- `handlers/alerts.go` + `alerts.html`
|
||
|
|
- `static/js/tables.js` — filter, sort, bulk-select, confirm modal
|
||
|
|
|
||
|
|
### Phase 4 — Bouncers & Machines
|
||
|
|
- `handlers/bouncers.go` + `bouncers.html`
|
||
|
|
- `handlers/machines.go` + `machines.html`
|
||
|
|
- Graceful degradation UI pattern
|
||
|
|
|
||
|
|
### Phase 5 — Hub & Metrics
|
||
|
|
- `handlers/hub.go` + `hub.html`
|
||
|
|
- `handlers/metrics.go` + `metrics.html`
|
||
|
|
|
||
|
|
### Phase 6 — Docker + Polish
|
||
|
|
- `Dockerfile` (multi-stage: build → scratch/alpine)
|
||
|
|
- `docker-compose.yml` with bind-mount examples
|
||
|
|
- Error page handler
|
||
|
|
- README with setup instructions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Docker Compose Example
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
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
|
||
|
|
- [ ] `cscli` exec: 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.MaxBytesReader` on 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`.
|