syncro: finalize deploy-agent (installer URL = per-client vaulted secret, MSI)
Decoded the Syncro RMM installer URL from 4 client samples:
base64(v1-{customer_id}-{A}-48753-{policy_folder_id}). 48753 = our account id (constant);
customer_id + policy_folder_id are API-readable; A is an unguessable per-client security token
NOT exposed anywhere in the API -> the URL is NOT constructible and must be harvested per client
and vaulted. Installer is an MSI -> deploy uses msiexec /qn /norestart.
deploy-agent now reads clients/<slug>/syncro-agent-installer (credentials.installer_url) from
the vault (cascades-tucson, grabb-durando, reliant, howard-test seeded) and pushes via GuruRMM.
Findings recorded in test-findings.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -741,46 +741,51 @@ curl -s "${BASE}/rmm_alerts?per_page=50&api_key=${API_KEY}" | tr -d '\000-\037'
|
||||
|
||||
#### Agent Deployment (`/syncro deploy-agent`) — hybrid via GuruRMM
|
||||
|
||||
Push the Syncro RMM agent to a machine. Syncro's API has **no** installer-download endpoint,
|
||||
so this is a **hybrid**: the per-policy-folder installer (which embeds the customer enrollment
|
||||
token) is obtained once from the Syncro GUI and vaulted; **GuruRMM (`/rmm`)** does the actual
|
||||
push (it runs PowerShell as SYSTEM on the endpoint). `/rmm diagnose` already *detects* a
|
||||
Syncro agent as a competitor-RMM check — this is the deploy half.
|
||||
Push the Syncro RMM agent to a machine. Syncro's API has **no** installer-download endpoint
|
||||
(verified: 0 of 116 paths), so this is a **hybrid**: the per-policy-folder installer URL is
|
||||
harvested once from the Syncro GUI and **vaulted**; **GuruRMM (`/rmm`)** does the push (runs as
|
||||
SYSTEM). `/rmm diagnose` already *detects* a Syncro agent — this is the deploy half.
|
||||
|
||||
**Prerequisites (one-time per customer):**
|
||||
1. In Syncro: **Assets & RMM → Downloads** (or the target Policy Folder) → copy the
|
||||
`SyncroSetup-<company>-<token>.exe` **download URL**. It is per-policy-folder — the token
|
||||
binds the installed agent to that customer + policy. Do NOT reuse one customer's URL for
|
||||
another.
|
||||
2. Vault it via the `vault` skill at `clients/<slug>/syncro-agent-installer.sops.yaml`
|
||||
(`credentials.installer_url`). Never hardcode the token URL.
|
||||
**The installer URL is a per-client SECRET and is NOT constructible.** Decoded (2026-07-10):
|
||||
`https://rmm.syncromsp.com/dl/msi/<b64>` where `<b64>` = base64 of
|
||||
`v1-{customer_id}-{A}-48753-{policy_folder_id}`. `48753` is OUR account id (constant across all
|
||||
clients); `customer_id` and `policy_folder_id` are API-readable — but **`A` is an unguessable
|
||||
~1.8-billion per-client security token that appears nowhere in the API** (checked customer +
|
||||
policy-folder objects). So you cannot build the URL; it must be harvested per client. Note the
|
||||
link is an **MSI** (`/dl/msi/`) → silent install is `msiexec /qn`, not the `.exe --console` path.
|
||||
|
||||
**Prerequisites (one-time per client):**
|
||||
1. In Syncro GUI: the client's Policy Folder → **Downloads** → copy the `.../dl/msi/<token>` URL.
|
||||
2. Vault it (it embeds the secret `A`): `bash .claude/skills/vault/scripts/vault-helper.sh new
|
||||
clients/<slug>/syncro-agent-installer --kind generic --name "Syncro RMM Installer - <Client>"
|
||||
--set installer_url=<URL> --set customer_id=<N> --set policy_folder_id=<N>`. Already vaulted:
|
||||
`cascades-tucson`, `grabb-durando`, `reliant`, `howard-test`.
|
||||
|
||||
**Deploy workflow:**
|
||||
1. Resolve the target host in GuruRMM (`/rmm` — resolve hostname → UUID; confirm `os_type`
|
||||
is `windows` and it is `is_connected`). Confirm the customer↔host mapping with the user.
|
||||
2. Preview the exact install command + target, get explicit confirmation.
|
||||
3. Dispatch via GuruRMM (`command_type: powershell`, SYSTEM context). The agent installs
|
||||
**silently by default**; `--console` runs it non-interactively and `--allow-force-reboot`
|
||||
permits the reboot Syncro triggers if it must update .NET first:
|
||||
1. Read the URL: `bash .claude/scripts/vault.sh get-field clients/<slug>/syncro-agent-installer
|
||||
credentials.installer_url`. If the client has no entry, STOP and have the URL harvested first.
|
||||
2. Resolve the target host in GuruRMM (`/rmm` — hostname → UUID; confirm `os_type` windows +
|
||||
`is_connected`). Confirm the client↔host mapping with the user.
|
||||
3. Preview the exact command + target; get explicit confirmation. **deploy-agent is
|
||||
outward-facing (installs software, may reboot) — always confirm first.**
|
||||
4. Dispatch via GuruRMM (`command_type: powershell`, SYSTEM). Download the MSI and install silent:
|
||||
|
||||
```powershell
|
||||
$url = '<vaulted SyncroSetup URL>'
|
||||
$exe = "$env:TEMP\SyncroSetup.exe"
|
||||
Invoke-WebRequest -Uri $url -OutFile $exe -UseBasicParsing
|
||||
Start-Process -FilePath $exe -ArgumentList '--console','--allow-force-reboot' -Wait
|
||||
$url = '<vaulted installer_url>'
|
||||
$msi = "$env:TEMP\SyncroSetup.msi"
|
||||
Invoke-WebRequest -Uri $url -OutFile $msi -UseBasicParsing
|
||||
Start-Process msiexec.exe -ArgumentList '/i',"`"$msi`"",'/qn','/norestart' -Wait
|
||||
```
|
||||
(Refs: Syncro deploy docs `docs.syncrosecure.com/agents-alerts-automations/deploy-an-endpoint`;
|
||||
community "Command Line install".) `--allow-force-reboot` CAN reboot the endpoint — treat
|
||||
deploy-agent as an outward-facing action: confirm reboot tolerance with the user first.
|
||||
4. **Verify** the agent enrolled: after ~2-3 min, either re-query GuruRMM output, or confirm
|
||||
the new asset appears under the customer via `GET /customer_assets?customer_id=N&query=<host>`
|
||||
and/or that "Syncro" shows in the endpoint's `installed_applications`.
|
||||
5. Bot alert (RMM write → `[RMM]`/`[DEPLOY]` routes to #dev-alerts; Syncro-side note to
|
||||
#bot-alerts if an asset record was created).
|
||||
(`.exe` variant, if a client's link is `/dl/exe/`: `Start-Process $exe -ArgumentList
|
||||
'--console','--allow-force-reboot' -Wait`.)
|
||||
5. **Verify enrollment:** after ~2-3 min, confirm the new asset appears —
|
||||
`GET /customer_assets?customer_id=N&query=<host>` — and/or "Syncro" shows in the endpoint's
|
||||
`installed_applications`.
|
||||
6. Bot alert (RMM write → `[RMM]`/`[DEPLOY]` routes to #dev-alerts).
|
||||
|
||||
**[VERIFY before first production use]** Confirm `--console --allow-force-reboot` against the
|
||||
current Syncro installer on one ACG-internal test machine before rolling to a customer — the
|
||||
switches are stable historically but the installer is versioned.
|
||||
**[VERIFY before first production use]** Confirm the `msiexec /qn /norestart` silent behavior on
|
||||
RMM-TEST-MACHINE (Howard Test) before rolling to a customer. Full detail:
|
||||
`.claude/standards/syncro/test-findings.md`.
|
||||
|
||||
#### Customers
|
||||
|
||||
|
||||
@@ -5,6 +5,30 @@ Verified behaviors/gotchas discovered while testing `/syncro` against the live t
|
||||
Each entry is a rule the skill must not re-learn. Critical ones are also folded into
|
||||
`syncro.md` (operating manual) and `api-reference.md` (breadth reference).
|
||||
|
||||
## 2026-07-10 — Phase 2 prep: RMM installer URL (structure decoded)
|
||||
|
||||
**The Syncro RMM installer URL is a base64 token that partly encodes API-derivable IDs.**
|
||||
A working install link is `https://rmm.syncromsp.com/dl/msi/<b64>` where `<b64>` base64-decodes
|
||||
to `v1-{customer_id}-{A}-{B}-{policy_folder_id}`. For Howard Test:
|
||||
`djEt...` → `v1-36118743-1819842526-48753-5092561` (customer_id 36118743, policy_folder_id
|
||||
5092561 — both pulled from the API; `A=1819842526`, `B=48753` are NOT on the customer or
|
||||
policy-folder objects). Host is `rmm.syncromsp.com` (Kabuto/RMM), separate from the PSA API.
|
||||
|
||||
- **RESOLVED (4-client sample):** `B=48753` is CONSTANT (our Syncro account id). `A` VARIES per
|
||||
client (1819842526 / 1610167344 / 1552910968 / 1520216784) and is **not** `kabuto_customer_id`
|
||||
nor anywhere in the customer/policy-folder objects — an unguessable per-client security token
|
||||
(prevents URL forgery from sequential customer_ids). **Therefore the URL is NOT constructible**
|
||||
— it must be harvested per client and vaulted as a secret. `customer_id` + `policy_folder_id`
|
||||
ARE API-readable, but `A` is the blocker. Vaulted so far: cascades-tucson, grabb-durando,
|
||||
reliant, howard-test at `clients/<slug>/syncro-agent-installer.sops.yaml`
|
||||
(`credentials.installer_url`). `deploy-agent` reads from there.
|
||||
- **Installer is an MSI** (`/dl/msi/`), so silent deploy uses `msiexec /i <file>.msi /qn
|
||||
/norestart` (NOT the `.exe --console` path). Syncro also offers a `.exe` (`--console
|
||||
--allow-force-reboot`); the download link determines which. Verify the exact MSI silent
|
||||
behavior on RMM-TEST-MACHINE before any customer push.
|
||||
- The token embeds an enrollment secret → it is a **credential**: vault it (per client), never
|
||||
commit plaintext. `policy_folder_id` comes from `GET /policy_folders?customer_id=N`.
|
||||
|
||||
## 2026-07-10 — Phase 1 (PSA CRUD)
|
||||
|
||||
**Emailing an invoice/estimate returns 200 "Email sent" even when it delivered to NOBODY.**
|
||||
|
||||
Reference in New Issue
Block a user