137 lines
6.0 KiB
Markdown
137 lines
6.0 KiB
Markdown
|
|
# CLAUDE.md
|
||
|
|
|
||
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
|
|
||
|
|
## Additional Instructions
|
||
|
|
- personal overrides: @~/.claude/CLAUDE.md
|
||
|
|
|
||
|
|
## Commands
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Build
|
||
|
|
go build -o crowdsec-dashy ./cmd/server
|
||
|
|
|
||
|
|
# Production build (static binary, stripped)
|
||
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o crowdsec-dashy ./cmd/server
|
||
|
|
|
||
|
|
# Run
|
||
|
|
./crowdsec-dashy
|
||
|
|
|
||
|
|
# Lint / vet
|
||
|
|
go vet ./...
|
||
|
|
|
||
|
|
# Tests (none yet — project is in early build phases)
|
||
|
|
go test ./...
|
||
|
|
|
||
|
|
# Docker
|
||
|
|
docker compose up -d
|
||
|
|
docker compose build
|
||
|
|
```
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
**Go 1.26. Zero external Go dependencies.** Stdlib only (`net/http`, `html/template`, `os/exec`, `encoding/json`).
|
||
|
|
|
||
|
|
### Data access — two parallel layers
|
||
|
|
|
||
|
|
| Layer | Package | Coverage |
|
||
|
|
|---|---|---|
|
||
|
|
| CrowdSec LAPI (REST) | `internal/crowdsec/lapi.go` | Decisions CRUD, Alerts, health |
|
||
|
|
| `cscli` exec wrapper | `internal/crowdsec/cli.go` | Bouncers, Machines, Hub, Metrics |
|
||
|
|
|
||
|
|
All shared structs (Decision, Alert, Bouncer, Machine, HubItem, MetricsSection, etc.) live in `internal/crowdsec/types.go`.
|
||
|
|
|
||
|
|
Bouncers, machines, hub, and metrics are **not** exposed via REST — they require `cscli` hitting the database directly. Features that need `cscli` check `Deps.CLIAvailable` and render an inline warning banner when the binary is absent.
|
||
|
|
|
||
|
|
### Package layout
|
||
|
|
|
||
|
|
```
|
||
|
|
cmd/server/main.go entrypoint — loads config, authenticates LAPI, starts HTTP server
|
||
|
|
internal/config/config.go env-var loading; CscliAvailable() stat-checks the binary path
|
||
|
|
internal/crowdsec/
|
||
|
|
types.go all shared structs
|
||
|
|
lapi.go REST client (JWT auth, auto-retry on 401)
|
||
|
|
cli.go cscli exec wrapper (strict allow-list)
|
||
|
|
internal/handlers/
|
||
|
|
renderer.go Renderer, PageData, Deps, SidebarNav
|
||
|
|
dashboard.go / api.go / ... one file per page + JSON API handler
|
||
|
|
internal/middleware/middleware.go BasicAuth, Logger, SecureHeaders, Recovery
|
||
|
|
internal/router/router.go constructs Deps, wires all routes
|
||
|
|
web/templates/layouts/base.html sidebar shell
|
||
|
|
web/templates/pages/*.html one file per page
|
||
|
|
web/static/css/app.css all component styles
|
||
|
|
web/static/js/dashboard.js live stat polling + sparklines
|
||
|
|
web/static/js/tables.js client-side filter/sort/bulk-select
|
||
|
|
```
|
||
|
|
|
||
|
|
### Request flow
|
||
|
|
|
||
|
|
```
|
||
|
|
HTTP request
|
||
|
|
→ middleware chain (BasicAuth → Logger → SecureHeaders → Recovery)
|
||
|
|
→ router.go (stdlib mux)
|
||
|
|
→ handler struct (receives Deps: Renderer, LAPI, CLI, CLIAvailable, PollInterval)
|
||
|
|
→ handler renders to bytes.Buffer first, then writes to ResponseWriter
|
||
|
|
```
|
||
|
|
|
||
|
|
### Dependency injection
|
||
|
|
|
||
|
|
`Deps` struct (`internal/handlers/renderer.go:246`) is constructed once in `router.New()` and passed to every handler constructor. Handlers are structs, not functions.
|
||
|
|
|
||
|
|
### Template system
|
||
|
|
|
||
|
|
`Renderer` (`internal/handlers/renderer.go`) parses all page templates at startup: each page in `web/templates/pages/*.html` is combined with `web/templates/layouts/base.html` into a named `*template.Template`. Templates are keyed by filename without extension (e.g. `"dashboard"`, `"decisions"`). All render calls go through `Renderer.Render(w, name, data)` using the buffer-then-write pattern.
|
||
|
|
|
||
|
|
Template functions available: `inc`, `dec`, `dict`, `decisionBadgeClass`, `originBadgeClass`, `hubStatusClass`, `safeHTML`, `boolIcon`, `truncate`, `join`.
|
||
|
|
|
||
|
|
### LAPI authentication
|
||
|
|
|
||
|
|
On startup, `lapi.Login()` POSTs to `/v1/watchers/login` → JWT stored in memory. All subsequent requests carry `Authorization: Bearer <jwt>`. On 401, `doJSON()` auto-retries with a fresh login once before failing.
|
||
|
|
|
||
|
|
### `cscli` exec security
|
||
|
|
|
||
|
|
All args passed as a slice (never through shell). Two allow-lists enforced before `exec.CommandContext`:
|
||
|
|
- `allowedSubcommands` (first arg): bouncers, machines, collections, parsers, scenarios, postoverflows, hub, metrics, version
|
||
|
|
- `allowedActions` (second arg): list, add, delete, install, remove, update, upgrade, validate
|
||
|
|
- All other user-supplied values validated against `safeArg` regex `^[a-zA-Z0-9_./:@\-]+$`
|
||
|
|
|
||
|
|
### POST pattern
|
||
|
|
|
||
|
|
All state-changing POST handlers use PRG (Post/Redirect/Get). Flash messages are attached to `PageData` via `WithFlash(type, msg)` and displayed on the redirected GET. All POST handlers must apply `http.MaxBytesReader` before parsing the body.
|
||
|
|
|
||
|
|
### Internal JSON API
|
||
|
|
|
||
|
|
Used by frontend JS — all return JSON, protected by the same BasicAuth middleware.
|
||
|
|
|
||
|
|
| Method | Path | Description |
|
||
|
|
|---|---|---|
|
||
|
|
| GET | `/api/v1/stats` | Dashboard summary counts |
|
||
|
|
| GET | `/api/v1/health` | LAPI health + cscli availability |
|
||
|
|
|
||
|
|
### Adding a new page
|
||
|
|
|
||
|
|
1. `internal/handlers/mypage.go` — handler struct with `ServeHTTP` or named methods
|
||
|
|
2. `web/templates/pages/mypage.html` — starts with `{{template "base" .}}`
|
||
|
|
3. Add `NavItem` to `SidebarNav` in `renderer.go`
|
||
|
|
4. `router.go` — `mux.Handle("/mypage", handlers.NewMyPageHandler(deps))`
|
||
|
|
|
||
|
|
### UI design
|
||
|
|
|
||
|
|
Dark industrial aesthetic. Palette: near-black surface `#080b10`, blue-grey panels `#0f1520`, cyan accent `#00d4ff`, threat-red `#ff3b3b`, safe-green `#00e676`. Fonts: `JetBrains Mono` for data, `IBM Plex Sans` for labels. All styles in `web/static/css/app.css`. No external JS frameworks — sparklines drawn with inline SVG `<polyline>`.
|
||
|
|
|
||
|
|
Dashboard live stats poll `/api/v1/stats` via `web/static/js/dashboard.js`. Client-side table filter/sort/bulk-select in `web/static/js/tables.js`.
|
||
|
|
|
||
|
|
### Environment variables
|
||
|
|
|
||
|
|
| Variable | Default | Notes |
|
||
|
|
|---|---|---|
|
||
|
|
| `PORT` | `:8080` | Listen address |
|
||
|
|
| `CROWDSEC_API_URL` | `http://localhost:8080` | LAPI base URL |
|
||
|
|
| `CROWDSEC_API_LOGIN` | *(required)* | Machine login |
|
||
|
|
| `CROWDSEC_API_PASSWORD` | *(required)* | Machine password |
|
||
|
|
| `CSCLI_PATH` | `/usr/local/bin/cscli` | Binary path; CLI features disabled if absent |
|
||
|
|
| `UI_USERNAME` | `admin` | Basic Auth username |
|
||
|
|
| `UI_PASSWORD` | `changeme` | Basic Auth password |
|
||
|
|
| `UI_SESSION_SECRET` | *(required, ≥32 chars)* | HMAC signing key |
|
||
|
|
| `POLL_INTERVAL_SEC` | `15` | Dashboard poll interval (seconds) |
|