Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Darling/PerformanceMonitor.Darling.Service/darling.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,12 @@
// (loopback-only HTTP, today's behavior). When present with a non-loopback "listen", the web host binds the
// LAN interface behind an in-app CIDR check and a token gate: a browser presents the token ONCE via
// ?token=, which is exchanged for an HMAC-signed session cookie (then the token is stripped from the URL).
// LOOPBACK stays TOKENLESS even while exposed (the surface is read-only). Fail-closed: any missing
// precondition (token / valid allowFrom / managed mode) keeps it loopback-only. There is deliberately NO
// TLS (a browser over plain HTTP on a trusted LAN); the MITM control is a TLS REVERSE PROXY in front of the
// port, same as MCP -- so this is a trusted-LAN opt-in, NEVER internet-exposed.
// LOOPBACK is exempt from the CIDR check ONLY -- while exposed it presents the token like anyone else
// (#1649: the Custom Views composer is a write path, so a tokenless local process could mutate views).
// A dashboard with NO "network" block registers no auth middleware at all and stays tokenless.
// Fail-closed: any missing precondition (token / valid allowFrom / managed mode) keeps it loopback-only.
// There is deliberately NO TLS (a browser over plain HTTP on a trusted LAN); the MITM control is a TLS
// REVERSE PROXY in front of the port, same as MCP -- a trusted-LAN opt-in, NEVER internet-exposed.
// "enabled"/"port" here are the SEED; after first start they live in the control plane and the Viewer's
// Settings toggles them LIVE (the service starts/stops/rebinds the endpoint within seconds - no restart).
// The "network" exposure block below stays file-defined and restart-only; the Viewer toggle still stops an
Expand Down
8 changes: 5 additions & 3 deletions Darling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Nothing is installed on the monitored SQL Servers by either edition beyond two l

## Quick Start

> **First time, on a box with nothing on it?** [**`docs/uat-onboarding.md`**](../docs/uat-onboarding.md) is the ordered procedure from a downloaded zip to a running service and whichever of the three surfaces you need — the WPF viewer, the web dashboard, or MCP — with a proof point at every step. This section, and the rest of this document, is the reference it links back into.

### Prerequisites

- **Windows** for the service host (Windows-service lifetime, DPAPI password protection) and for the viewer (WPF). Monitored servers can be SQL Server 2016–2025, Azure SQL Managed Instance, AWS RDS for SQL Server, or Azure SQL Database.
Expand Down Expand Up @@ -490,7 +492,7 @@ The embedded read-only **web dashboard** — a browser view of the monitoring st

Once enabled, open `http://localhost:5153/` in a browser on the service host. Like the MCP server, `enabled`/`port` here are the file SEED; after first start they live in the control plane and the Viewer's Settings toggles them LIVE (the service starts/stops/rebinds the dashboard within seconds — no restart). If the port is already in use at startup, the web host logs an error and retries on a calm cadence; collection is unaffected.

**What you see.** The dashboard opens on a **Fleet Overview**: a card per enabled server with a status dot, six per-metric health bands (CPU, threads, memory, blocking, deadlocks, collectors), and its last collection time — all banded server-side, so the browser only renders (a server that has never reported shows an amber "Awaiting first collection", never a red offline). Above the cards a worst-first "Needs attention" list surfaces the servers to look at, or an all-healthy line when there is nothing to chase. Click a card to **drill into one server**: an overview, wait stats with a trend for the heaviest wait, active queries, a CPU chart, memory and file-I/O trends, and collection health — the same collected data the viewer shows, over inline charts. A fleet-wide **Alert History** page (with a server filter box) rounds out phase 1. It is a read-only view — no settings, no write paths, no live-server queries — and refreshes every 60 seconds (pausing while the tab is hidden). The frontend ships fully self-contained (no CDN, no fonts, no remote anything), so it works on an air-gapped host with no internet access.
**What you see.** The dashboard opens on a **Fleet Overview**: a card per enabled server with a status dot, six per-metric health bands (CPU, threads, memory, blocking, deadlocks, collectors), and its last collection time — all banded server-side, so the browser only renders (a server that has never reported shows an amber "Awaiting first collection", never a red offline). Above the cards a worst-first "Needs attention" list surfaces the servers to look at, or an all-healthy line when there is nothing to chase. Click a card to **drill into one server**: an overview, wait stats with a trend for the heaviest wait, active queries, a CPU chart, memory and file-I/O trends, and collection health — the same collected data the viewer shows, over inline charts. A fleet-wide **Alert History** page (with a server filter box) rounds out phase 1, and a **Custom Views** page hosts the composer for the saved dashboards/notebooks in `config.custom_views` — the dashboard's one write path. Otherwise it is a read-only view — no settings, no live-server queries — and it refreshes every 60 seconds (pausing while the tab is hidden). The frontend ships fully self-contained (no CDN, no fonts, no remote anything), so it works on an air-gapped host with no internet access.

### peers

Expand Down Expand Up @@ -1167,12 +1169,12 @@ Add a `network` block to `web` (managed mode; `web.enabled` must be `true`):
}
```

When `listen` is a network address **and** a token is present **and** `allowFrom` is a valid CIDR, the web host binds that interface behind two gates: an **in-app CIDR check** on the remote address and an **access token**. A browser presents the token ONCE via `?token=` (open `http://192.168.1.205:5153/`, then paste it into the minimal login form, or append `?token=...` directly); the host validates it constant-time, sets an **HMAC-signed, HttpOnly, SameSite=Strict session cookie**, and 302-redirects to strip the token from the URL so it never lingers in history or a Referer header. Subsequent requests ride the cookie. **Loopback is always allowed tokenless, even while exposed** — unlike MCP, the read-only dashboard has no loopback-token requirement. An out-of-CIDR request is refused with **403**. The cookie signing key is per-process, so a service restart invalidates open sessions (just re-present the token). Prefer `encryptedToken` (a DPAPI blob from `--encrypt-password`); a plaintext `token` works for dev but is warned. Set the same scoped firewall rule for the web port:
When `listen` is a network address **and** a token is present **and** `allowFrom` is a valid CIDR, the web host binds that interface behind two gates: an **in-app CIDR check** on the remote address and an **access token**. A browser presents the token ONCE via `?token=` (open `http://192.168.1.205:5153/`, then paste it into the minimal login form, or append `?token=...` directly); the host validates it constant-time, sets an **HMAC-signed, HttpOnly, SameSite=Strict session cookie**, and 302-redirects to strip the token from the URL so it never lingers in history or a Referer header. Subsequent requests ride the cookie. **Loopback is exempt from the CIDR check only** — while the dashboard is exposed, a request from the box itself still has to present the token or a valid cookie. It used to pass tokenless on the grounds that the surface was read-only; the Custom Views composer's create / update / delete and `/api/compose/run` ended that, so in exposed mode the token *is* the loopback guard against SSRF and sandboxed local sockets ([#1649](https://github.com/erikdarlingdata/PerformanceMonitor/issues/1649)). A dashboard with **no** `network` block registers no auth middleware at all and stays genuinely tokenless. An out-of-CIDR request is refused with **403**, cookie or no cookie; an in-CIDR request with no credential gets the login form back as a **200**, not a 401. The cookie signing key is per-process, so a service restart invalidates open sessions (just re-present the token). Prefer `encryptedToken` (a DPAPI blob from `--encrypt-password`); a plaintext `token` works for dev but is warned. Set the same scoped firewall rule for the web port:

```
New-NetFirewallRule -DisplayName "Darling Web" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 5153 -RemoteAddress 192.168.1.0/24
```

**What a web token can reach.** The web dashboard is **read-only over the collected store** — it connects as the least-privilege `viewer` role and hosts no write paths and no live-server queries (no `analyze_server`, no plan re-execution). A token-holder can view everything collected, change nothing, and reach no monitored server — which is why loopback stays tokenless here while MCP's does not.
**What a web token can reach.** The web dashboard is **read-only over the collected store** — it connects as the least-privilege `viewer` role and hosts no live-server queries (no `analyze_server`, no plan re-execution). Its one write path is the Custom Views composer: INSERT/UPDATE/DELETE on `config.custom_views`, and the web host maps no other write route. (The `viewer` role holds one further narrow grant — INSERT/UPDATE/DELETE on `config.database_state_expected`, for the Viewer's per-database state-override editor ([#1986](https://github.com/erikdarlingdata/PerformanceMonitor/issues/1986)) — which no web route reaches, and which the WPF editor itself gates on the `has_table_privilege` read-only probe a `viewer`-role seat fails.) So a token-holder can read everything collected and author saved views, and reach no monitored server — but that write is why an exposed dashboard authenticates loopback too, exactly as MCP does.

**Web has no TLS either — same reverse-proxy control as MCP.** The token/cookie travels cleartext on the segment, so the in-app CIDR bounds *who can route to* the port but does not protect the wire. On an untrusted segment, put a TLS-terminating reverse proxy in front of the web port.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,12 @@ When a second Windows user on the same machine launches Lite, they see the share
1. Download **`PerformanceMonitorDarling-<version>.zip`** from the [latest release](https://github.com/erikdarlingdata/PerformanceMonitor/releases/latest) — the signed service and viewer with the bundled PostgreSQL + TimescaleDB runtime beside the service exe, so a from-zero install needs no database provisioning.
2. Copy `darling.sample.json` to `darling.json` and add your servers (and optional SMTP / webhook delivery). In managed mode the service unpacks and runs its own PostgreSQL — no external database to set up.
3. Run the service (console for a trial, or install it as a Windows service). It seeds the store and begins collecting on the same default cadences and retention horizons as a fresh Lite install.
4. Open the viewer and point it at the store to browse the fleet.
4. Start the **Darling Viewer**. It is in the same zip, under `viewer\`, and `install-darling.ps1` leaves a Desktop shortcut. On the service host there is nothing to point at anything: it finds the same `darling.json`, derives the store connection, and opens on the fleet. For a seat on another machine, run `--export-viewer-config` on the service host and copy the folder it writes.
5. Optionally turn on the two off-by-default surfaces: `--enable-web` serves the browser dashboard on port 5153, `--enable-mcp` serves the MCP endpoint on 5152. Both take effect live, no restart.

Configuration is a single JSON file with no schedule knobs. See the **[Darling operator guide](Darling/README.md)** for the full quick start, configuration reference, permissions, and operations.
**Never done this before?** [**docs/uat-onboarding.md**](docs/uat-onboarding.md) is the ordered path from a downloaded zip to all three surfaces — the WPF viewer, the web dashboard, and MCP — with the log line, HTTP response, or screen that proves each step worked, and the handful of things that reliably catch people out.

Configuration is a single JSON file with no schedule knobs. See the **[Darling operator guide](Darling/README.md)** for the configuration reference, permissions, and operations.

---

Expand Down
Loading
Loading