Files
guru-connect/specs/native-remote-control/plan.md
Mike Swanson e3e95f8fa7
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
chore: sync repository to current working state
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>
2026-05-29 06:15:29 -07:00

11 KiB

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.

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.