Make the one-line install survive Defender, and approval leave a PC that works
All three found testing the bootstrap installer on Windows. - The published command used the irm-pipe-iex form, which Microsoft Defender detects as Trojan:Win32/Commando.A!ml and kills before it runs, every time, on a default install. The command now downloads the script to a file, runs it, and removes it in a finally block, which Defender does not object to. The UI also offers the script as a plain download for a tech who would rather save it. - Approving a request only linked the asset, so a PC whose pending reason was "unknown hostname" - which is every PC installed this way - kept getting 403 on every call afterwards, because its computer record had no hostname to match the credential against. Approval now stamps the hostname when the record has none, refuses with a 409 naming both when the record has a different one, and overwrites only when the admin confirms. The Credentials tab offers that confirmation. - A hostname that resolves to no record now says so instead of reading as an impostor, while still refusing the request. - While waiting for approval the agent retried on the 15-minute telemetry cadence, so a PC approved immediately enrolled a quarter of an hour later and the installer always ended on "still pending". It now retries about every minute for the first ten minutes, then every five to thirty minutes, then every fifteen, with jitter. A rate limit or server error still uses the slower backoff.
This commit is contained in:
@@ -621,9 +621,9 @@ request row per hostname and serial (case-insensitive); repeated calls update it
|
||||
| Status | Body | Agent action |
|
||||
|---|---|---|
|
||||
| 201 | `{"status":"enrolled","credential":"sdbe_...","assetid":123}` | store, switch headers, no restart |
|
||||
| 202 | `{"status":"pending","requestid":45,"reason":"serial mismatch"}` | retry every 15 min with jitter |
|
||||
| 202 | `{"status":"pending","requestid":45,"reason":"serial mismatch"}` | retry about every 60 s for the first 10 min, then every 5 min to 30 min, then every 15 min indefinitely, with jitter |
|
||||
| 403 `ENROLL_NONCE_MISMATCH` | error envelope | an approved request belongs to a different nonce; keep retrying, an admin must reject and re-approve |
|
||||
| other | error envelope | back off 1, 5, 15 min (cap) |
|
||||
| other | error envelope | back off 1, 5, 15 min (cap); a 429 or 5xx uses this backoff, not the pending schedule |
|
||||
|
||||
Auto-issue happens only when `plugin.endpoints.autoenroll` is true, the shared
|
||||
key authenticated the call, the hostname matches exactly one computer, the
|
||||
@@ -645,6 +645,14 @@ All use the standard error envelope (`data.error.code`).
|
||||
| 401 | `SHARED_KEY_REFUSED` | shared key sent, but this PC is enrolled or the shared key is switched off | wake enrollment; use the credential |
|
||||
| 403 | `ASSET_MISMATCH` | the credential's asset differs from the hostname/assetid named in the request | log; do not retry the same request |
|
||||
|
||||
`ASSET_MISMATCH` also covers the case where the hostname the agent sends is on
|
||||
NO computer record at all, which is a data problem on the server (an enrolled PC
|
||||
whose computer record has no hostname) rather than a PC claiming to be another
|
||||
one. The code and status are the same, so nothing changes for the agent, but the
|
||||
message says which of the two it is and names the hostname. An admin fixes it by
|
||||
setting the hostname on the enrolled computer; approving an enroll request does
|
||||
that on its own (9.7).
|
||||
|
||||
Routes that authenticate this way: `POST /api/endpoints/collect` (same handler
|
||||
as `/api/collector/endpoints`), `GET /api/endpoints/monitors/resolved`,
|
||||
`GET /api/endpoints/commands/next`, `POST /api/endpoints/commands/<id>/result`,
|
||||
@@ -729,7 +737,16 @@ Read needs `endpoints.fleet`; writes need the admin role and are audited.
|
||||
in state `pending`; `counts.pending` is the number of pending enroll requests,
|
||||
including requests that match no computer (unknown hostname) and requests from
|
||||
PCs blocked from re-enrolling, so it equals the pending entries in `requests`.
|
||||
- `POST /api/endpoints/enrollment/requests/<id>/approve` `{"assetid": n}`
|
||||
- `POST /api/endpoints/enrollment/requests/<id>/approve`
|
||||
`{"assetid": n, "sethostname": false}`. The approval also makes sure the
|
||||
computer CARRIES the request's hostname, because the credential is bound by
|
||||
hostname afterwards (9.3): a computer with none is stamped with it
|
||||
(`hostnameresult` `set`), a computer already carrying the same one is left
|
||||
alone (`unchanged`), and a computer carrying a DIFFERENT hostname is refused
|
||||
with **409** naming both plus `details.hostnameconflict`, until the same call
|
||||
is repeated with `"sethostname": true` (`overwritten`). Both changes are
|
||||
audited against `Computer`. Approving without this left a one-off PC 403ing
|
||||
on every call after it enrolled.
|
||||
- `POST /api/endpoints/enrollment/requests/<id>/reject`
|
||||
- `POST /api/endpoints/enrollment/assets/<assetid>/revoke` (un-enroll). Optional
|
||||
body `{"blockreenroll": true, "reason": "rollback"}` (`reason` <= 64 chars,
|
||||
@@ -774,9 +791,28 @@ One-line install and hands a technician one command, which is run in an
|
||||
ELEVATED PowerShell on a PC with no agent:
|
||||
|
||||
```
|
||||
powershell -ExecutionPolicy Bypass -c "irm <baseurl>/api/endpoints/siteagent/bootstrap.ps1?t=<token> | iex"
|
||||
powershell -ExecutionPolicy Bypass -c "try { Invoke-WebRequest -UseBasicParsing '<baseurl>/api/endpoints/siteagent/bootstrap.ps1?t=<token>' -OutFile 'C:\Windows\Temp\shopdb-bootstrap.ps1'; & 'C:\Windows\Temp\shopdb-bootstrap.ps1' } finally { Remove-Item 'C:\Windows\Temp\shopdb-bootstrap.ps1' -Force -ErrorAction SilentlyContinue }"
|
||||
```
|
||||
|
||||
Download then run, never `irm ... | iex`: Microsoft Defender scores the
|
||||
piped-to-iex shape as `Trojan:Win32/Commando.A!ml` and kills the process before
|
||||
a line of it runs (reproduced 5 of 5 on a default install, no ASR rules), and
|
||||
every bay runs Defender. The shape of the line is load-bearing too:
|
||||
|
||||
- a literal temp path and no `$` anywhere, because pasting into an
|
||||
already-running PowerShell would otherwise expand `$p` and `$env:TEMP` in the
|
||||
PARENT and hand the child a broken line - as written it means the same in
|
||||
cmd.exe and in PowerShell;
|
||||
- single quotes for every inner literal, so the outer `-c "..."` is never
|
||||
closed early;
|
||||
- `try`/`finally`, so the dropped file is removed even when the script exits
|
||||
non-zero or the download itself fails. The wrapper's own exit code is the
|
||||
removal's, so read the script's printed result rather than `%ERRORLEVEL%`.
|
||||
|
||||
The same URL is offered in the UI as a **Download .ps1** link, for a tech who
|
||||
would rather save the file and run it. Fetching it does not spend a use either
|
||||
way.
|
||||
|
||||
The token is only an authorization to FETCH the installer. The package it
|
||||
serves carries no shared key, whatever `plugin.endpoints.sharedkeyallowed`
|
||||
says, so the PC enrolls by the normal 9.2 path and waits in Pending until an
|
||||
@@ -785,7 +821,9 @@ admin approves it.
|
||||
Admin routes (admin role, audited):
|
||||
|
||||
- `POST /api/endpoints/siteagent/bootstrap/tokens` `{"hours": 24, "maxuses": 25}`
|
||||
(`hours` 1-168, `maxuses` 1-500). Returns `{"secret", "command", "token"}`.
|
||||
(`hours` 1-168, `maxuses` 1-500). Returns
|
||||
`{"secret", "command", "scripturl", "token"}`; `scripturl` is the same token
|
||||
URL the command downloads, for the UI's Download .ps1 link.
|
||||
The secret is in the response exactly once; only its sha256 and a 12-character
|
||||
`tokenprefix` are stored.
|
||||
- `GET /api/endpoints/siteagent/bootstrap/tokens` - prefix, created, expires,
|
||||
|
||||
Reference in New Issue
Block a user