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:
@@ -155,6 +155,23 @@ def asset_mismatch_response():
|
||||
http_code=403)
|
||||
|
||||
|
||||
def hostname_unrecorded_response(hostname):
|
||||
"""Refused, but say which of the two failures this actually is.
|
||||
|
||||
The credential is good and the hostname is simply on no computer record, so
|
||||
there is nothing to compare it with. That is a data problem on this server -
|
||||
an enrolled PC whose computer record carries no hostname - not a PC claiming
|
||||
to be another one. Same code and status, because nothing changes for the
|
||||
agent: it keeps retrying, and an admin has to set the hostname.
|
||||
"""
|
||||
return error_response(
|
||||
CODE_ASSET_MISMATCH,
|
||||
'No computer record carries the hostname {0}, so this credential '
|
||||
'cannot be matched to it. Set the hostname on the enrolled '
|
||||
'computer.'.format(hostname),
|
||||
http_code=403)
|
||||
|
||||
|
||||
def authenticate_agent():
|
||||
"""Authorize an agent request. Returns None when authorized, else a response.
|
||||
|
||||
@@ -190,14 +207,19 @@ def bind_hostname(hostname):
|
||||
"""The asset this request acts for. Returns (assetid, error_response).
|
||||
|
||||
Credential: the credential's asset; a hostname naming another asset is an
|
||||
ASSET_MISMATCH, and a missing hostname is fine.
|
||||
ASSET_MISMATCH, a hostname on no computer record at all is refused with the
|
||||
same code but its own message, and a missing hostname is fine.
|
||||
Shared key: the hostname's asset (None when absent or unknown, for the
|
||||
route to answer); an enrolled PC is refused.
|
||||
"""
|
||||
hostname = (hostname or '').strip()
|
||||
if is_credential_caller():
|
||||
if hostname and resolve_hostname(hostname) != g.agentassetid:
|
||||
return None, asset_mismatch_response()
|
||||
if hostname:
|
||||
named = resolve_hostname(hostname)
|
||||
if named is None:
|
||||
return None, hostname_unrecorded_response(hostname)
|
||||
if named != g.agentassetid:
|
||||
return None, asset_mismatch_response()
|
||||
return g.agentassetid, None
|
||||
|
||||
if not hostname:
|
||||
|
||||
Reference in New Issue
Block a user