Add Web-Dashboard page (Unit 12)
Covers decnet web / serve-web, env vars, admin credential enforcement, pages (Dashboard, DeckyFleet, LiveLogs, Attackers, AttackerDetail, Config, Bounty), JWT auth flow, must_change_password, server-side UI gating, client build, and CORS defaults.
158
Web-Dashboard.md
Normal file
158
Web-Dashboard.md
Normal file
@@ -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://<web_host>:<web_port>` | 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": "<JWT>",
|
||||||
|
"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.
|
||||||
Reference in New Issue
Block a user