From c286a29b9dcf9cbc8b25c6ff5ab2079c349b9606 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Tue, 2 Jun 2026 09:54:19 -0700 Subject: [PATCH] spec: SPEC-016 resolve all 5 open questions (enrollment design decisions) Fold the 2026-06-02 interview decisions into SPEC-016: - Installer wrapper: ship BOTH signed .exe and signed MSI per site - cak_ at-rest storage: DPAPI-machine-encrypted blob in a SYSTEM-ACL'd location - Fingerprint: hex (7F2A), deliberately unlike RMM word-codes - machine_uid: per-tenant scope + hardware-derived salt (survives re-image, separates distinct boxes) + collision-gated activation (template-cloned VMs sharing a hardware UUID drop to pending + alert, need dashboard confirm) - Attended support-code path: unchanged (filename-based, already signing-safe) Open Questions section -> Resolved decisions + a short Remaining-for-planning list (exact hardware salt signal set, WiX/MSI authoring approach). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/specs/SPEC-016-zero-touch-enrollment.md | 88 ++++++++++++++------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/docs/specs/SPEC-016-zero-touch-enrollment.md b/docs/specs/SPEC-016-zero-touch-enrollment.md index 3345475..db8f227 100644 --- a/docs/specs/SPEC-016-zero-touch-enrollment.md +++ b/docs/specs/SPEC-016-zero-touch-enrollment.md @@ -64,17 +64,33 @@ signed PE. ### Included in v1 (CORE) -1. **`machine_uid` — deterministic machine identity.** Derive a stable id from the Windows - `MachineGuid` (`HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid`), independent of the - config-file `agent_id`. (Shared root with SPEC-004; whichever lands first owns the impl, - the other consumes it.) Used as the dedup key for register/move. +1. **`machine_uid` — deterministic machine identity (hardware-salted, per-tenant).** Derive + a stable id from the Windows `MachineGuid` + (`HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid`) **salted with stable hardware + signals** (SMBIOS UUID / motherboard + disk serial), independent of the config-file + `agent_id`. Hardware-derived salt is deliberate: it **survives an OS reinstall/re-image + on the same hardware** (so the row is reused — the re-image dedup goal) while keeping + distinct physical boxes distinct (a per-install *random* salt would break re-image dedup + and is rejected). Uniqueness is scoped **per-tenant** — dedup key `(tenant_id, + machine_uid)` — so the same hardware legitimately present in two tenants stays two + independent rows. (Shared root with SPEC-004; whichever lands first owns the impl, the + other consumes it.) Used as the dedup key for register/move. + + **Collision-gated activation.** The residual collision case is VMs/templates that share a + hardware UUID (some hypervisors clone the SMBIOS UUID). When the server detects a + `machine_uid` collision (a seemingly-different endpoint resolving to an existing uid), the + endpoint does **not** auto-activate: it drops to a **pending** state, fires an alert, and + an operator must confirm in the dashboard that the collided endpoint may activate. This is + the one deliberate exception to auto-approve (see item 6). 2. **Per-site enrollment key + fingerprint.** - Long (≥256-bit) server-generated secret per site, stored **hashed** (Argon2id, same as `cak_`/passwords), never recoverable in plaintext after issue. - - A non-secret **fingerprint** = monotonic version + short derived code, rendered - `vN (XXXX)` (e.g. `v3 (7F2A)`), shown in the dashboard, baked into the installer - filename, and reported by the agent at enrollment. + - A non-secret **fingerprint** = monotonic version + short derived code in **hex**, + rendered `vN (XXXX)` (e.g. `v3 (7F2A)`), shown in the dashboard, baked into the + installer filename, and reported by the agent at enrollment. Hex is deliberate — + **not** the RMM word-style code (`GREEN-FALCON`) — so GuruConnect and GuruRMM + artifacts are never visually conflated. - **Rotate** regenerates the secret and bumps the version; old installers are rejected for *new* enrollments; existing agents (holding `cak_`) are unaffected. @@ -102,15 +118,18 @@ signed PE. byte-identical for everyone. - Per-site customization (labels + enrollment key + fingerprint) is delivered to the endpoint **at install time** via a signing-safe channel — NOT appended to the signed - PE. v1 mechanism: a small **signed wrapper/bootstrapper** (or signed MSI) that carries - the site config, lays down the signed agent, and writes the site config to the - protected config location. Decision to lock in planning: wrapper-exe vs MSI - (see Open Questions). + PE. **v1 produces BOTH a signed bootstrapper `.exe` and a signed MSI per site** + (ScreenConnect parity — manual installs grab the `.exe`, GPO/Intune fleet pushes take + the MSI), both wrapping the same sign-once agent and writing the site config to the + protected config location. The two differ only in packaging (bootstrapper stub vs. WiX + bundle); both are signed. - **Deprecate the append path** in `downloads.rs` for managed installs (keep only for attended/support-code if still needed), eliminating the signature-invalidation defect. -6. **Auto-approve posture.** A self-registered machine is live and controllable - immediately (ScreenConnect parity). The new-enrollment alert is the tripwire. +6. **Auto-approve posture (with collision-gate exception).** A self-registered machine is + live and controllable immediately (ScreenConnect parity); the new-enrollment alert is the + tripwire. The **one** exception is a detected `machine_uid` collision (item 1), which + gates the endpoint to **pending** until an operator confirms it in the dashboard. ### Explicitly out of scope (ANTICIPATED — reserve room, do NOT build in v1) @@ -161,8 +180,11 @@ authorized site + issued `cak_`. v1 only ships the per-site-embedded-key door. credential, per-machine revocation). Compromise of an enrollment key is recovered by rotating one site — no fleet-wide re-key. - **Enrollment keys stored hashed** (Argon2id); plaintext shown once at issue/rotate. -- **`cak_` at rest on the endpoint** must be SYSTEM-only (HKLM SYSTEM ACL or DPAPI-machine) - so a non-admin user can't read it. +- **`cak_` at rest on the endpoint** is stored as a **DPAPI-machine-encrypted blob inside a + SYSTEM-ACL'd location** (HKLM value or `ProgramData` file) — both layers: the SYSTEM ACL + stops non-admin users reading it, and DPAPI-machine encryption makes a copied file/export + inert off the box. (Local admin/SYSTEM can always recover it; that is accepted — blast + radius of one leaked `cak_` is a single, independently-revocable machine.) - **`machine_uid` binding** is the spoof-guard SPEC-004 wants: a `cak_` is bound to a `machine_uid`; a different box presenting another box's `cak_` is detectable. - **Authorization model** for moves/enrolls is possession-of-destination-key in v1 @@ -195,16 +217,28 @@ authorized site + issued `cak_`. v1 only ships the per-site-embedded-key door. - **Relationship to v2 phases:** sits with the Phase-1 secure-session-core (per-agent keys + identity) and feeds Phase-2 dashboard work. -## Open questions +## Resolved decisions (2026-06-02, Mike) -1. **Wrapper shape:** signed standalone bootstrapper `.exe` vs. signed **MSI** for the - per-site installer. MSI gives clean install/uninstall + GPO/Intune deploy; bootstrapper - is lighter. Lock in planning. -2. **`cak_` storage:** HKLM SYSTEM-ACL registry value vs. DPAPI-machine-protected file — - pick one for the protected store. -3. **Fingerprint code style:** raw hex (`7F2A`) vs. the RMM-house word style - (`GREEN-FALCON`). Cosmetic; pick for operator readability. -4. **Cross-tenant `machine_uid` collisions** (same hardware imaged across tenants) — scope - `machine_uid` uniqueness per tenant, not globally. -5. **Attended (support-code) path:** confirm whether the append-based `download_support` - path is retained as-is or also migrated off appending. +1. **Wrapper shape — BOTH.** v1 ships a signed bootstrapper `.exe` *and* a signed MSI per + site (ScreenConnect offers both; manual installs use the `.exe`, GPO/Intune fleet pushes + use the MSI). Same sign-once agent inside each. +2. **`cak_` storage — BOTH layers.** DPAPI-machine-encrypted blob stored in a SYSTEM-ACL'd + location. Non-admins can't read it; a stolen copy is inert off the box. +3. **Fingerprint — hex (`7F2A`).** Deliberately *not* the RMM word-code style, so the two + products' artifacts are never visually conflated. +4. **`machine_uid` — per-tenant scope, hardware-derived salt, collision-gated.** Dedup key + `(tenant_id, machine_uid)`; salt from stable hardware signals (survives same-hardware + re-image, separates distinct boxes); detected collisions (e.g. template-cloned VMs + sharing a hardware UUID) drop to pending + alert and require dashboard confirmation to + activate. +5. **Attended (support-code) path — unchanged.** `download_support` is filename-based + (`GuruConnect-.exe`), not append-based, so renaming never breaks the signature — + it is already signing-safe. Only the managed `download_agent` append path is retired. + +## Remaining for planning + +- Exact stable-hardware signal set for the salt (SMBIOS UUID alone vs. + motherboard/disk + serial) and hypervisor behavior matrix (which hypervisors duplicate the SMBIOS UUID on + clone → exercise the collision-gate). +- MSI authoring approach (WiX) and whether per-site config rides as a per-site MSI vs. a + base MSI + property/transform.