diff --git a/Web-Dashboard.md b/Web-Dashboard.md new file mode 100644 index 0000000..b32d82c --- /dev/null +++ b/Web-Dashboard.md @@ -0,0 +1,158 @@ +# Web Dashboard + +The DECNET Web Dashboard is a React + TypeScript + Vite single-page app that +talks to the FastAPI backend exposed by the DECNET API server. It is the +operator-facing control surface for inspecting deckies, attackers, live logs, +bounties, and runtime configuration. + +Related: +[REST API](REST-API-Reference) - +[Environment variables](Environment-Variables) - +[Security & stealth](Security-and-Stealth). + +## Running + +Two processes are involved: the API server (FastAPI) and the web front-end +(static SPA with a thin `/api/*` reverse proxy). + +```bash +# Start the FastAPI backend (serves the REST API on DECNET_API_PORT). +decnet serve-api + +# Serve the Web Dashboard (static SPA). +decnet web \ + --host 0.0.0.0 \ + --web-port 8080 \ + --api-port 8000 +``` + +`decnet web` loads the pre-built bundle from `decnet_web/dist/` and refuses to +start if that directory is missing. The handler proxies any request whose path +starts with `/api/` to the backend on `--api-port`, so the SPA can use relative +URLs and no CORS round-trip is needed in the default topology. Add `--daemon` +to detach into the background. + +### Environment variables + +| Variable | Default | Purpose | +|---|---|---| +| `DECNET_WEB_HOST` | `127.0.0.1` | Bind address for the SPA server. | +| `DECNET_WEB_PORT` | `8080` | Port for the SPA server. | +| `DECNET_ADMIN_USER` | `admin` | Admin username seeded on first boot. | +| `DECNET_ADMIN_PASSWORD` | `admin` | Admin password seeded on first boot. | +| `DECNET_JWT_SECRET` | required | HS256 signing secret, must be at least 32 characters. | +| `DECNET_CORS_ORIGINS` | `http://:` | Comma-separated allowlist for backend CORS. | + +The CORS default resolves wildcard bind addresses (`0.0.0.0`, `::`) to +`localhost` when building the default origin. Override with something like: + +``` +DECNET_CORS_ORIGINS=http://192.168.1.50:9090,https://dashboard.example.com +``` + +### Admin credentials + +The literal pair `admin` / `admin` is a known-bad default. The web layer +refuses to start with those values outside pytest: set **both** +`DECNET_ADMIN_USER` and `DECNET_ADMIN_PASSWORD` to non-default values before +running `decnet web` or `decnet serve-api` in production. See the admin +credentials section in [Environment variables](Environment-Variables) for the +exact enforcement rules. + +On first boot the admin record is seeded with `must_change_password=True`, and +the frontend forces a password change on the next login. + +## Pages + +The routes are defined in `decnet_web/src/App.tsx`. All pages share the +`Layout` chrome (top bar with search + logout, left nav). + +- **Dashboard** (`/`, `Dashboard.tsx`) - Landing view. Summary tiles for decky + count, attacker count, recent log volume, and top-level activity charts. The + top-bar search box is threaded in as `searchQuery` so the dashboard can + filter its recent-events pane inline. +- **DeckyFleet** (`/fleet`, `DeckyFleet.tsx`) - Fleet control panel. Lists + every decky with status, IP, services, and mutation interval. Admin-only + controls let you redeploy from an INI payload, mutate or kill individual + deckies, and retune mutation intervals. Backed by the `fleet/` router group. +- **LiveLogs** (`/live-logs`, `LiveLogs.tsx`) - Streaming log tail across the + whole fleet. Events are parsed with `utils/parseEventBody.ts` to pull out + attacker IP, decky, service, and RFC 5424 structured data fields. An + artifact drawer (`ArtifactDrawer.tsx`) pops open when a log line references + an uploaded payload. +- **Attackers** (`/attackers`, `Attackers.tsx`) - Grouped view of observed + source IPs with hit counts, first/last seen, and targeted services. Click + through to the detail page. +- **AttackerDetail** (`/attackers/:id`, `AttackerDetail.tsx`) - Full + per-attacker timeline: every log line tied to that IP, captured credentials + and commands, any uploaded artifacts, and bounty status if applicable. +- **Config** (`/config`, `Config.tsx`) - Runtime configuration and user + management. Admins can view and edit the running INI config, reinitialize + the fleet, and create, update, or delete dashboard users. Non-admins cannot + reach this page; see "Server-side UI gating" below. +- **Bounty** (`/bounty`, `Bounty.tsx`) - Displays bounty offers and redemption + state attached to particular attacker behaviors. Read-only view over the + `bounty/` router group. + +## Authentication + +The backend exposes `POST /auth/login` (see +`decnet/web/router/auth/api_login.py`). The handler loads the user via a +cached lookup, verifies the password with bcrypt off the event loop, and +returns a `Token` payload: + +```json +{ + "access_token": "", + "token_type": "bearer", + "must_change_password": false +} +``` + +JWT details (`decnet/web/auth.py`): + +- Algorithm: **HS256**. +- Secret: `DECNET_JWT_SECRET` (must be at least 32 characters; short or + known-bad values abort startup). +- Lifetime: **1440 minutes** (24 hours). +- Claims: `uuid` (user UUID), `iat`, `exp`. + +The SPA stores the token in `localStorage` and decodes the `exp` claim on +boot to decide whether to show the login screen (`App.tsx`). + +### must_change_password flow + +When the login response carries `must_change_password: true` the SPA forces a +password-change dialog before any other page loads. Internally, the FastAPI +dependency `get_current_active_user` returns HTTP 403 for any protected route +while the flag is set, so the rest of the API is effectively gated until the +user calls `POST /auth/change-password`, which clears the flag via +`repo.update_user_password(..., must_change_password=False)`. + +## Server-side UI gating + +**Admin-only UI must be gated by the backend, not by client-side role +checks.** Every mutating or privileged endpoint under the `fleet/`, `config/`, +and `artifacts/` router groups is protected by the `require_admin` FastAPI +dependency (`decnet/web/dependencies.py`). The SPA may hide or disable admin +controls for UX, but that is a presentation convenience only - a non-admin +user who hand-crafts requests still gets HTTP 403 from the server. Do not +introduce features whose only access check is `if user.role === 'admin'` in +React; that check must exist on the server side as well. + +## Client build + +The front-end lives in `decnet_web/` and is a standard Vite project. + +```bash +cd decnet_web +npm install +npm run build # production bundle -> decnet_web/dist/ +npm run dev # hot-reload dev server (proxies /api/* to the backend) +``` + +`decnet web` expects the production bundle at `decnet_web/dist/` relative to +the installed package (`Path(__file__).parent.parent / "decnet_web" / "dist"` +in `decnet/cli.py`). If you are working on the SPA, rebuild before running +`decnet web`, or use `npm run dev` and point your browser at the Vite dev +server during development.