mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
0ac548bfce
Start-AutopilotMonitor.ps1 is the new stage 1 -- the file an administrator assigns in Intune. It carries no enrollment logic: it downloads the published installer, verifies its Authenticode signature, and runs it in the same process, passing through every remaining argument. Assigning the installer directly stays supported and unchanged. The point of the split is that the customer-held artifact stops changing. Today every bootstrap change forces customers to replace their Intune copy, so the fleet runs a distribution of script versions we do not control. It is also what makes script signing worth doing: signing a file that gets swapped by hand each release is a one-off gain, signing a loader that never changes is permanent. The loader is deliberately minimal, because it is the one piece that cannot be fixed remotely -- every guard placed in it would be frozen for years. All guards stay in stage 2. Verification pins the certificate SUBJECT, never a thumbprint: certificates get renewed and a deployed loader cannot be, so a thumbprint pin would strand the fleet at the next renewal. A hash oracle was rejected for a different reason -- it would have to be fetched from the same host as the script, so it could not survive a compromise of that host, while the expected publisher travels inside the loader. Fail-closed on verification, fail-soft on exit: nothing runs without a valid signature from us (running unverified code as SYSTEM is the risk being removed here), but the loader still exits 0, because a device without monitoring is acceptable and a failed enrollment step is not. The accepted cost is that a device which can never fetch or verify stays silent. Publisher changes: - every published script is signed (loader, installer, both -Dev renders, Test-ShouldBootstrapAgent, Grant-AutopilotMonitorAddOn), and signing is the LAST transformation: it covers exactly the CRLF/UTF-8 bytes Get-PublishBytes produced, so nothing may normalise or re-encode them afterwards. - signing is mandatory for a real publish. Only -DryRun may run unsigned, because the PR gate executes the publisher without any Azure login. - comparisons against a published copy strip the signature block first. A timestamped signature differs on every run, so without stripping the version-bump guard and the drift check would report a change for every unchanged script. - the bump guard became Assert-VersionBump and now covers the loader as well. - version.json gains loaderVersion. Additive, no consumer yet: stage 2 is current by construction, so only stage 1's version says anything about a customer's Intune copy. - -SignedOutputDir writes the exact uploaded bytes for the CI artifact, in a dry run too -- that is how a signed script gets inspected before it reaches anyone. publish-scripts.yml logs in with the signing identity first and only to fetch the Key Vault token (azure/login sets the default az account and the storage login has to be the one that stays), then publishes and offers the signed scripts as an artifact. build-agent.yml passes the same parameters on a stable cutover. Tests: 14 new Pester cases, including that a validly signed script from a FOREIGN publisher is refused -- that is what an attacker with any code-signing certificate can produce. Full bootstrap suite on Windows PowerShell 5.1: 43/43. Not covered: the MSI runner still downloads stage 2 without verifying it, and the customer docs still describe the direct assignment only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>