Some checks failed
Build and Test / Build Server (Linux) (push) Has been cancelled
Build and Test / Build Agent (Windows) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Build Summary (push) Has been cancelled
Run Tests / Test Server (push) Has been cancelled
Run Tests / Test Agent (push) Has been cancelled
Run Tests / Code Coverage (push) Has been cancelled
Run Tests / Lint and Format Check (push) Has been cancelled
Brings azcomputerguru/guru-connect up to the authoritative working copy that had been maintained in the claudetools monorepo: Phase 1 security and infrastructure (middleware, metrics, utils, token blacklist, deployment scripts, security audits) plus the native-remote-control integration spec. Preserves the repo .gitignore, .cargo, and server/static/downloads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# Native Remote Control — GC↔RMM Integration Contract & Embedded Viewer — Implementation Plan
|
|
|
|
> Spec created: 2026-05-29
|
|
> Status: not started
|
|
> Architecture: broker model — RMM orchestrates the separate GC agent, against a versioned
|
|
> integration contract that GC owns. Two independent products, kept in-sync by contract + capability
|
|
> discovery (NOT by shared pipelines).
|
|
> Repos: **GC** = guru-connect (standalone product, in claudetools repo) · **RMM** = guru-rmm (submodule).
|
|
|
|
## End-to-end flow (target behavior)
|
|
|
|
**Unattended:** tech clicks Remote Control on `AgentDetail` → RMM checks GC capabilities, (1)
|
|
pre-creates a GC session bound to the endpoint's `device_id` and mints a short-lived viewer token,
|
|
(2) commands the endpoint's RMM agent to ensure the GC agent is installed (checksum-verified) and
|
|
connected in persistent mode → RMM **embeds GC's viewer** in the dashboard (scoped iframe) pointed
|
|
at that session, native `guruconnect://` as fallback.
|
|
|
|
**Attended:** same, but RMM mints a support code on GC, the GC agent shows a consent prompt, and the
|
|
session starts only after the end user accepts.
|
|
|
|
The contract surface (Tasks 1-3) is GC's; the broker + embed (Tasks 7-10) is RMM's.
|
|
|
|
---
|
|
|
|
## Task 0: Commit this spec
|
|
|
|
```
|
|
git add projects/msp-tools/guru-connect/specs/native-remote-control/
|
|
git commit -m "spec: add native-remote-control shape spec"
|
|
```
|
|
Do not start Task 1 until this commit exists.
|
|
|
|
---
|
|
|
|
## Task 1 (GC): Define & version the integration contract — KEYSTONE
|
|
|
|
Files touched: `CONTRACT.md` (new, GC repo root or `docs/`), `server/src/main.rs` (routes `:254` `/health`,
|
|
`:300` `/api/version`), `server/src/api/` (new `integration.rs`), `server/src/middleware/` (integration auth).
|
|
|
|
- Author a semver'd `CONTRACT.md` documenting the GC integration surface (auth model, endpoints,
|
|
payloads, capability flags, viewer embed protocol, error envelope). This is the artifact both teams
|
|
keep "front of mind" — GC must not break a published version without a major bump.
|
|
- Create the `/api/integration/v1/` route namespace.
|
|
- `GET /api/integration/capabilities` (model on the existing public `/api/version` at
|
|
`releases.rs:76`) → `{ contract_version, features: { embedded_viewer, consent_prompt,
|
|
per_machine_keys, programmatic_sessions } }`. RMM reads this to version-gate.
|
|
- Add **server-to-server integration auth**: a single integration credential
|
|
(`CONNECT_INTEGRATION_KEY`, env/SOPS) required on all `/api/integration/v1/*` routes. Capabilities
|
|
endpoint may be unauthenticated (like `/api/version`) so RMM can probe before configuring.
|
|
- Error envelope per `api/response-format` (`detail`/`error_code`/`status_code`).
|
|
|
|
## Task 2 (GC): Per-machine agent keys
|
|
|
|
Files touched: `server/migrations/0XX_agent_keys.sql` (new), `server/src/db/agent_keys.rs` (new),
|
|
`server/src/relay/mod.rs` (`validate_agent_api_key` `:187`), `server/src/api/integration.rs`.
|
|
|
|
- Idempotent migration: `connect_agent_keys` (`id`, `agent_id`, `key_hash`, `created_at`, `revoked_at`).
|
|
Hashed keys only (model on RMM `enroll.rs` `generate_api_key`/`hash_api_key`).
|
|
- `POST /api/integration/v1/agents/:agent_id/keys` mints a per-machine key (plaintext once, store hash).
|
|
- `validate_agent_api_key()` accepts a valid DB per-machine key; shared `AGENT_API_KEY` env becomes a
|
|
deprecated fallback. Support `revoked_at`.
|
|
|
|
## Task 3 (GC): Programmatic session pre-create + viewer token
|
|
|
|
Files touched: `server/src/api/integration.rs`, `server/src/session/mod.rs` (`register_agent` `:95`),
|
|
`server/src/db/sessions.rs` (`create_session` `:22`), `server/src/auth/jwt.rs`, `server/migrations/`.
|
|
|
|
- `POST /api/integration/v1/sessions` — body `{ agent_id, mode }`. Pre-creates a session row +
|
|
in-memory slot keyed by `agent_id`, marked `is_managed`/`source="gururmm"`; returns `{ session_id }`.
|
|
When the GC agent later registers with that `agent_id`, `register_agent()` binds it to the
|
|
pre-created session instead of generating a new one.
|
|
- `POST /api/integration/v1/sessions/:id/viewer-token` — short-lived (~5 min), session-scoped viewer JWT.
|
|
- Add `is_managed BOOLEAN` / `source TEXT` to `connect_sessions` (idempotent migration).
|
|
- For attended, the broker reuses the existing `POST /api/codes` (`main.rs:382`); expose/document it
|
|
under the contract too.
|
|
|
|
## Task 4 (GC): Embedded session viewer
|
|
|
|
Files touched: `server/static/viewer.html`, `server/src/middleware/security_headers.rs:30,37-39`,
|
|
`server/src/main.rs` (per-route header layer for the viewer), `CONTRACT.md` (embed protocol).
|
|
|
|
- Add a **scoped framing allowlist** for the viewer route(s): `frame-ancestors <RMM dashboard origin>`
|
|
(from env, e.g. `CONNECT_EMBED_ALLOWED_ORIGINS`) and matching/relaxed `X-Frame-Options` ONLY on the
|
|
viewer path. Every other route keeps `frame-ancestors 'none'` (`:30`) — do not weaken globally.
|
|
- Add an **embed mode** to `viewer.html` (e.g. `?embed=1`): hide standalone chrome, accept the
|
|
session_id + viewer token from the host, and emit `postMessage` lifecycle events
|
|
(`viewer:connected`, `viewer:disconnected`, `viewer:error`, `viewer:resize`) for the RMM host to
|
|
react to. Document this embed protocol in `CONTRACT.md`.
|
|
|
|
## Task 5 (GC): Consent messages + attended prompt
|
|
|
|
Files touched: `proto/guruconnect.proto` (after `AdminCommand` `:286`),
|
|
`agent/src/session/mod.rs`, `agent/src/consent/mod.rs` (new) + `agent/src/tray/mod.rs`,
|
|
`server/src/relay/mod.rs`, `server/src/session/mod.rs`.
|
|
|
|
- Add `ConsentRequest { session_id, technician_name, reason }` (server→agent) and
|
|
`ConsentResponse { session_id, accepted }` (agent→server).
|
|
- GC agent: on `ConsentRequest` in attended mode, show a native consent dialog; Decline → session
|
|
refused + event logged. Unattended skips consent (gated by session `mode`).
|
|
- Emit `connect_session_events` for consent shown/accepted/declined. Expose `consent_prompt` in the
|
|
capabilities map (Task 1).
|
|
|
|
## Task 6 (GC): Session persistence / restart reconcile (robustness)
|
|
|
|
Files touched: `server/src/session/mod.rs` (`:81`), `server/src/db/sessions.rs`, `server/src/main.rs`.
|
|
|
|
- On startup, load active `connect_sessions` from DB into `SessionManager` so a relay restart does
|
|
not orphan managed sessions; reap stale rows. This satisfies the "robust" requirement.
|
|
|
|
## Task 7 (RMM): GC integration client (capability-aware) + config
|
|
|
|
Files touched: `server/src/connect_client.rs` (new), `server/src/config` (env wiring).
|
|
|
|
- Client for the GC `/api/integration/v1` contract: base URL (`CONNECT_SERVER_URL`) + integration key
|
|
(`CONNECT_INTEGRATION_KEY`), env/SOPS only.
|
|
- On startup / first use, call `GET /api/integration/capabilities`; **cache the contract version +
|
|
feature map** and version-gate RMM behavior off it (e.g. only offer attended consent if
|
|
`consent_prompt` is true). Log a `[WARNING]` if the GC contract version is newer/older than expected.
|
|
- Methods: `capabilities()`, `pre_create_session(device_id, mode)`, `mint_viewer_token(session_id)`,
|
|
`mint_support_code(technician)`, `provision_agent_key(device_id)`.
|
|
|
|
## Task 8 (RMM): Broker endpoint
|
|
|
|
Files touched: `server/src/api/remote_control.rs` (new), `server/src/api/mod.rs` (`:162` register),
|
|
`server/src/db/` + `server/migrations/0XX_remote_control_sessions.sql` (new, or extend `tech_sessions`),
|
|
reuse command dispatch `server/src/api/commands.rs:87-157`.
|
|
|
|
- `POST /api/agents/:agent_id/remote-control` — body `{ mode }`. Authz via `authorize_agent_access`.
|
|
Steps: resolve `device_id`+online → (via `connect_client`) ensure per-machine key, pre-create session,
|
|
attended→support code → dispatch launch command to the RMM agent (Task 9) → mint viewer token →
|
|
return `{ session_id, viewer_embed_url, viewer_native_url, mode, capabilities }`.
|
|
- Record a `remote_control_sessions` audit row (`agent_id`, `tech_id`, `connect_session_id`, `mode`,
|
|
`started_at`), mirroring the `tunnel_audit` pattern.
|
|
|
|
## Task 9 (RMM agent): Ensure-and-launch GC agent
|
|
|
|
Files touched: `agent/src/transport/websocket.rs` (`run_command` `:1050`, `execute_command` `:971`),
|
|
`agent/src/remote_control/mod.rs` (new), `agent/src/config.rs`, `agent/src/service.rs` (AppState parity).
|
|
|
|
- New launch path (Windows, `#[cfg(windows)]`): ensure the GC agent binary present; if missing/outdated,
|
|
download from the GC release channel and **verify SHA-256 before executing** (supply-chain guard).
|
|
Launch passing RMM `device_id` as the GC `agent_id`, the per-machine key, relay URL, and (attended)
|
|
the support code; unattended = persistent (no code).
|
|
- Non-Windows: working stub + `// TODO(platform): linux/macos — GC agent not available`
|
|
(per `gururmm/platform-parity`). Mirror any new `AppState` field into `service.rs`.
|
|
|
|
## Task 10 (RMM dashboard): Remote Control button + embedded viewer
|
|
|
|
Files touched: `dashboard/src/pages/AgentDetail.tsx` (`:1893-1931`), `dashboard/src/api/client.ts`
|
|
(`:293-310` pattern), `dashboard/src/components/RemoteControlPanel.tsx` (new).
|
|
|
|
- `remoteControlApi.start(agentId, mode)` → `POST /api/agents/:agent_id/remote-control`.
|
|
- "Remote Control" button on `AgentDetail` (enabled only when online + GC capabilities allow); on
|
|
success, render `RemoteControlPanel` embedding `viewer_embed_url` in a scoped iframe and wiring the
|
|
`postMessage` lifecycle events (Task 4). Native `viewer_native_url` offered as a fallback link.
|
|
ASCII markers in toasts/logs; no emojis.
|
|
|
|
## Task 11 (both): Contract tests in each pipeline
|
|
|
|
Files touched: GC `server/tests/integration_contract.rs` (new), RMM `server/tests/connect_contract.rs` (new).
|
|
|
|
- GC pipeline: a test asserting the `/api/integration/v1` surface + `capabilities` shape matches the
|
|
documented `CONTRACT.md` version (catches accidental breaking changes before release).
|
|
- RMM pipeline: a test (against a recorded/mock capabilities response) asserting the client correctly
|
|
version-gates and parses the contract. This is what keeps the independently-built products in-sync —
|
|
each pipeline independently fails if it drifts from the contract.
|
|
|
|
## Task 12: Verification
|
|
|
|
End-to-end (Windows endpoint, both agents installed):
|
|
|
|
- **Capability discovery:** RMM logs the GC contract version + feature map on startup; disabling a GC
|
|
feature flag hides the corresponding RMM affordance.
|
|
- **Embedded unattended:** Remote Control (unattended) on an online managed Windows endpoint → GC
|
|
viewer renders **inside the RMM dashboard** (iframe), screen + mouse/keyboard work, multi-monitor
|
|
switch works, no endpoint prompt. `postMessage` `viewer:connected` fires.
|
|
- **Attended:** end user sees the consent dialog (technician name); Accept → session; Decline → refused + logged.
|
|
- **Embedding security:** the GC viewer loads framed only from the RMM origin; any other origin is
|
|
refused (`frame-ancestors`); all non-viewer GC routes still return `frame-ancestors 'none'`.
|
|
- **Supply-chain guard:** corrupt the staged GC binary → agent refuses to launch (checksum mismatch in logs).
|
|
- **Standalone unaffected:** GC still builds, runs, and serves a normal (non-embedded) support session
|
|
with zero RMM present.
|
|
- **Robustness:** restart the GC relay mid-session → managed session reconciled from DB, not orphaned.
|
|
- **Audit:** `remote_control_sessions` (RMM) + `connect_session_events` (GC) show session, technician, mode, consent.
|
|
- **Contract tests:** both pipelines' contract tests pass; intentionally bumping the GC contract shape
|
|
without updating `CONTRACT.md`/RMM fails the relevant pipeline test.
|