Stop the one-line install handing a standard user a way in

The bootstrap command staged its script into C:\Windows\Temp and then ran it
elevated. That directory grants Users create-file with CREATOR OWNER full
control, so somebody without admin rights can pre-plant the exact path the
elevated run is about to execute. It now stages under Program Files, creates the
directory with an explicit DACL when it is missing, and refuses to run a staged
file that is not owned by an admin principal. A failed hardening is loud rather
than silent: the run stops instead of carrying on with a file it could not
protect.

The token moved out of the query string into a header. In a query string it
lands verbatim in the reverse proxy and IIS logs, which is a credential written
to a file nobody thinks of as a secret store. The query form is still accepted,
because bays provisioned before this change are holding install commands that
use it, and it logs that it was used - without logging the token - so a site can
see who still needs a re-issued command before turning it off with
plugin.endpoints.bootstrapquerytoken.

Two smaller things this also closes:

A request with no token at all answered 400 naming the header, which told an
anonymous caller the state of that switch and contradicted the module's own
promise that anything unusable answers 404. No token is now 404. The 400 is kept
for a token genuinely presented in the query form while the query form is off,
which is a caller who needs to know their command is stale.

The rollout documentation was republishing the vulnerable one-liner verbatim, so
a tech following the runbook reinstated exactly what this fixes. Both documents
now carry the current command.

Also corrects the operator-facing copy for the token, which named a query
argument and a header that do not exist: it is ?t= and X-Endpoint-Bootstrap-Token,
per bootstrap.py's own constants. Anybody following the old text got a 404 with
no reason given.

Both switches added in this range are now declared with the other plugin
settings and reachable from their settings page. Read through Setting.get but
never declared, they were rows that did not exist until someone wrote one by
hand, which on an air-gapped box means the feature was unreachable.
This commit is contained in:
cproudlock
2026-09-18 13:30:08 -04:00
parent 3f80425f30
commit 987f5c0d0f
7 changed files with 934 additions and 81 deletions

View File

@@ -287,6 +287,15 @@ class EndpointsPlugin(BasePlugin):
making revocation an instant kill switch. `agentkeyprovisioned` records
that a UI-managed key has been minted so the settings page can render
the rotate/revoke state instead of the first-run generate state.
Every key an enforcement path reads belongs here, even when its default
is what the code already does with no row at all. Undeclared, the row
is only born when somebody writes it and the settings API has to guess
its type - which is how bootstrapquerytoken became a string row that
its boolean reader could never see turned off. Declared, the row is
seeded at install and enable (and by `flask plugin upgrade-all` on a
site that installed an earlier version), so the settings pages have
something to render and an operator has something to click.
"""
return [
{
@@ -318,6 +327,41 @@ class EndpointsPlugin(BasePlugin):
'approval when the hostname and serial number '
'match exactly one computer.',
},
{
'key': 'plugin.endpoints.revokeddeniessharedkey',
'value': 'false',
'valuetype': 'boolean',
'category': 'plugin.endpoints',
'description': 'Also refuse the shared fleet key to a PC whose '
'per-PC credential was revoked. Off (the '
'default) is what the fleet does today: a '
'revoked PC falls back to the shared key, so a '
'credential rotation and the documented revoke '
'plus blockreenroll rollback to an older agent '
'both keep reporting. Turning it on makes a '
'revoke an eviction - that PC reports nothing '
'and receives no commands until it enrolls '
'again - and it stops that rollback working. '
'Safe only once every PC holds its own '
'credential.',
},
{
'key': 'plugin.endpoints.bootstrapquerytoken',
'value': 'true',
'valuetype': 'boolean',
'category': 'plugin.endpoints',
'description': 'Accept a one-line install bootstrap token in '
'the ?t= query string as well as the '
'X-Endpoint-Bootstrap-Token header. On (the '
'default) '
'keeps the pasted install commands and the '
'Download .ps1 link working; the query form is '
'deprecated because the token lands in IIS and '
'proxy logs. Turn it off only after every '
'outstanding install command has been re-issued '
'- an in-flight one stops working the moment it '
'is off.',
},
{
'key': 'plugin.endpoints.agentkeyprovisioned',
'value': 'false',