<#34 Windows osquery upgrades> Pull request opened...
# osctrl
g
#34 Windows osquery upgrades Pull request opened by javuto Windows osquery upgrades Companion to the osctrl change that lets osctrl answer Windows hosts. Builds on osquery upgrades (#31) and the gradual rollout support (#33). Problem osctrld refused to upgrade osquery on Windows. Three guards skipped it, and the Windows file-ownership check was a stub, so even with the guards removed the upgrade folder check would have failed. Windows fleets had to upgrade osquery by hand. Change osctrld now upgrades a Windows host to the environment target or its pin using the official osquery MSI. It runs the same flow as other platforms: resolve, download, check the digest and Authenticode signature, save the configuration, stop the service, run
msiexec
, restore the configuration, start osqueryd, confirm the new version is reported. No rollback. MSI refuses to install an older version over a newer one, so a failed Windows upgrade is not rolled back. It restores the configuration snapshot, starts osqueryd, waits for it, clears the pending marker and the stash, and records a rejection with the new reason
rollback_unavailable
. The host is not retried under the same request id. osctrl counts it as a rollback, so the rollout pauses. Target only. Windows does not download, stash or check the previous version's MSI. A server answer with a target and no
previous
is valid on Windows. On other platforms a missing previous is still an
invalid_resolution
skip.
msiexec
• Exit codes 3010 and 1641 (reboot pending) are not errors by themselves. If the new version is running, the upgrade succeeds. If the old binary is still installed, the upgrade is skipped as
install_failed
with the existing 6 hour backoff and is not recorded as a rejection. • It runs with a 10 minute timeout. A timeout, or a privilege failure with the version unchanged, is the same
install_failed
skip. • A clean exit is a success even if a child process holds the output pipe past the wait delay. Service settings. Before every osqueryd start after
msiexec
(success, skip, failure path and resume), osctrld checks that the
osqueryd
service starts automatically and that its command line still has
--flagfile=<configured flag file>
, and reapplies both if the MSI reset them. The command line is rewritten by a pure function that refuses flag file paths containing a double quote or ending in a backslash, and matches the flag name case-sensitively. Upgrade folder safety (Windows) • Ownership check: a path counts as trusted only when it is owned by Administrators (
S-1-5-32-544
) or SYSTEM (
S-1-5-18
). • The stash folder gets a protected access list with Administrators and SYSTEM only, because
snapshot.json
holds a copy of the enrollment secret. • The check runs in this order: real directory (junctions and symlinks are refused), trusted owner, then the access list. When an existing stash is read back, the access list is re-applied best-effort, so a failure there never blocks restoring the configuration. Crash and reboot recovery. On Windows, every resume case that would roll back goes through the failure path above. A pending upgrade whose old version is still installed and never took effect is forgotten without a rejection, after restoring what is possible and reapplying the service settings. amd64 only for now. The MSI upgrade path supports amd64 only. On any other architecture the upgrade is skipped as
target_unavailable
with the usual backoff and nothing is downloaded.
osctrld install
is unchanged and still fetches the x64 MSI on Windows ARM64. Reporting.
rollback_unavailable
is added to the reported reasons. Security and operational impact • The upgrade assumes osctrld runs elevated (SYSTEM or an administrator). Without that, the upgrade is skipped as
install_failed
. • The enrollment secret copy in the upgrade stash is no longer readable by ordinary users on Windows. • No change on Linux and macOS. Every new branch is gated on the Windows platform. • Older osctrld releases still refuse Windows. A new osctrld talking to an older osctrl is safe, because the older osctrl answers
unsupported_platform
for Windows. Validation •
go test ./... -count=1
, and
go test -race ./cmd/osctrld
•
GOOS=windows go vet ./...
,
GOOS=darwin go vet ./...
and
GOOS=windows GOARCH=arm64 go vet ./...
•
gofmt -l cmd
•
golangci-lint run ./...
and `GOOS=windows golangci-lint run ./...`: 0 issues • New tests cover: • the pure logic:
trustedOwnerSID
, the access list string,
msiExitResult
, and the service command-line parser and updater •
runMSI
end to end with a fake process: success, a non-zero exit, a timeout, and a lingering child • the Windows upgrade path end to end through the existing test seams: target-only success, 3010 with the new and old version, a privilege failure, a service settings error, an access list error, and the ARM64 skip • the failure path: config restored, osqueryd running,
rollback_unavailable
recorded, no second
msiexec
, no retry under the same request id • resume after a crash in each state • Linux and macOS still requiring a previous version Not tested on a real Windows host The Windows-only code (the access list and owner lookup, and the
svc/mgr
service update) is compile-checked only. Please run these on a real host: • an upgrade on a host enrolled by the quick-add script and on one with an MSI-created service: configuration preserved, service flag file and start type preserved, new version reported • a forced failure that reports
rollback_unavailable
with osqueryd still running on its old configuration • a freshly created stash folder passing the ownership check under an elevated administrator (if the check fails, the result is a safe
install_failed
skip) • the 3010 reboot-pending flow • what the MSI does to the quick-add script's hand-made service (it may drop `--flagfile`; the service settings check should repair it) •
go test ./cmd/osctrld/
on Windows Known gap: if
msiexec
times out, osctrld kills only the client. The Windows Installer service may finish the install later and reset the service, with nothing reported. This should block calling Windows upgrades production-ready until it is checked. Compatibility Deploy osctrl first or in either order. An osctrld that has Windows upgrades does nothing until osctrl answers a Windows host with a target. jmpsec/osctrld