Repository navigation
fix(supervisor-network): add Windows root certificates to the sandbox CA bundle - #5061
Conversation
… CA bundle On Windows, read_system_ca_bundle() probed only Linux PEM bundle paths and returned an empty string, so the combined ca-bundle.pem held only the sandbox CA. SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE and GIT_SSL_CAINFO name that file and replace a client's own trust, so clients that honor them could not verify a server whose TLS the proxy relays without terminating it, as for a `tls: skip` endpoint. On Windows, read the system bundle from the certificate store instead: the roots that rustls-native-certs loads (the current user's ROOT store, which includes the machine, Group Policy, enterprise and third-party roots), encoded as PEM. The combined bundle is then the platform roots followed by the sandbox CA, as on Linux. The Linux bundle paths are not consulted on Windows. In the default bundled-ca-roots build the proxy also adds these roots to its upstream trust, as it does with the distribution bundle on Linux. The bundle reaches the Windows sandbox boundary in the StartAgent control frame, which is limited to 1 MiB. If the store roots encode to more than 896 KiB, leave them out and log a warning, so that an oversized store or SSL_CERT_FILE gives the previous sandbox-CA-only bundle instead of failing every sandbox start. The currently valid roots of Microsoft's trusted root program encode to 823,019 bytes. Signed-off-by: Jeffrey Quesnelle <emozilla@nousresearch.com>
|
/ok to test |
@shailendra-nv, there was an error processing your request: See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/ |
|
/windows-ci |
Windows ARM64 CI: ✅ passedCommit
|
|
/ok to test fc00946 |
|
/ok to test 2019faa |
|
/windows-ci |
shailendra-nv
left a comment
There was a problem hiding this comment.
Reviewed head 2019faa77d031618fefbe8e8c7366301940b9b2a. Approving after Branch Checks, Windows MSVC x64/ARM64, and the managed native ARM64 report passed.
The original head hit the same unrelated SQLite concurrency failure twice on the GitHub ARM64 runner. Updating from windows brought in the existing repair from #5077. The PR diff remains the reviewed two-file CA-bundle fix; all fresh checks passed on the updated head.
Testing:
| Suite | Command | Total | Passed | Skipped | Failed |
|---|---|---|---|---|---|
| GitHub Windows x64 | mise run --skip-tools windows:test:x64 |
6,691 | 6,648 | 43 | 0 |
| GitHub Windows ARM64 | mise run --skip-tools windows:test:arm64 |
6,691 | 6,648 | 43 | 0 |
| Managed Windows ARM64 workspace | mise run --skip-tools windows:test:arm64 |
6,691 | 6,648 | 43 | 0 |
| Managed ARM64 focused configurations | mise run --skip-tools windows:test:unsupported:arm64 |
10 | 10 | 0 | 0 |
| All listed executions | — | 20,083 | 19,954 | 129 | 0 |
Counts include intentional focused reruns. Managed workspace check, release build and artifact packaging passed; GitHub Windows lint passed on both architectures. Optional E2E and Helm jobs were skipped by their configured filters.
Summary
On Windows, the combined CA bundle that OpenShell gives an MXC sandbox held only the sandbox CA. As a result, clients that honor
SSL_CERT_FILE,REQUESTS_CA_BUNDLE,CURL_CA_BUNDLEorGIT_SSL_CAINFOcould not verify any server behind atls: skipendpoint. Linux is unaffected because the supervisor there reads the distribution's bundle from one ofSYSTEM_CA_PATHS, in the sandbox's container or, for the VM driver, on the host; Windows keeps its roots in the certificate store, so those paths find nothing. This change builds the system part of the bundle from the Windows certificate store, so the bundle holds the platform roots followed by the sandbox CA, as it does on Linux.Related Issue
Closes #5060
Changes
read_system_ca_bundle()on Windows reads the roots thatrustls-native-certsloads and returns them as PEM. Those roots come from the current user'sROOTstore, which also holds the machine, Group Policy, enterprise and third-party roots; only time-valid roots usable for server authentication are returned. This is the same source the existingsystem-ca-rootsfeature uses, soSSL_CERT_FILEorSSL_CERT_DIRin the supervisor's own environment replaces the store, as it does there, and the certificates loaded from those paths are not filtered.SSL_CERT_FILEorSSL_CERT_DIRnames a path that cannot be read, the error is logged withwarn!and the bundle holds only the sandbox CA, as it does today.rustls-native-certs0.8.3 unwraps its per-certificate usage and validity queries, so a failure in one of them panics;system-ca-rootsbuilds already run that code.warn!, and the bundle again holds only the sandbox CA. The bundle reaches the sandbox boundary in theStartAgentcontrol frame, whichencode_framelimits to 1 MiB (MAX_CONTROL_FRAME_BYTES), so without this check an oversized store orSSL_CERT_FILEwould fail every sandbox start. Sizes are below.read_system_ca_bundle()andSYSTEM_CA_PATHSare unchanged; they are now compiled only for non-Windows targets.pem_encode_certificates()writes DER certificates asCERTIFICATEPEM blocks with 64-column base64 lines.read_system_ca_bundle()get the same roots. In the defaultbundled-ca-rootsbuild, the proxy adds them to its upstream trust and to the trust it uses for an HTTPS upstream proxy, as it does with the distribution bundle on Linux.pem_encode_certificates_round_trips_derandencode_system_ca_bundle_leaves_out_bundles_over_the_limitrun on every platform.windows_sandbox_bundle_includes_certificate_store_rootsruns on Windows. Onwindowswithout this fix it fails withthe bundle holds 0 certificates before the sandbox CA; the store has 47 roots.windows_system_ca_bundle_over_the_limit_is_left_outruns on Windows. It pointsSSL_CERT_FILEat one file under the limit and one over it.crates/openshell-driver-mxc/README.mdnow states what the injected trust variables contain on Windows, and the on-demand and size limits described below.Bundle sizes, from the encoder in this change:
CurrentUser\ROOTafter filteringcertutil -generateSSTFromWU), time-valid and marked for server authenticationGitHub-hosted Windows images import the whole program into
LocalMachine\Rootwhen they are built (Install-RootCA.ps1inactions/runner-images, used by thewindows-2025andwindows-11-arm64templates), so the Windows CI jobs run the store test with a bundle between the last two rows. The 896 KiB limit is above both and leaves about 110 KB of the frame for the agent spec, the policy and the provider environment.Limits, the same as on the
system-ca-rootspath:rustls-native-certsdoes not read theDisallowedstore. On the test host that store was empty.The bundle holds the Windows store and not also the Mozilla roots (
webpki-roots) that the proxy adds to its upstream trust in default builds. Linux works the same way: the bundle is the distribution's file, and the proxy's upstream trust is that file pluswebpki-roots. Using the store alone keeps the host's trust decisions in force for sandbox clients, so a root that an administrator removed or never deployed stays out.webpki-rootsholds trust anchors rather than certificates, so writing it as PEM would also need a new dependency (webpki-root-certs: 119 roots, 178,464 bytes of PEM). If maintainers prefer the proxy's view, appending those roots after the store roots on Windows is a small follow-up.Testing
https-cascenario runs insidewindows:test:mxc-networkbelow.All runs used Windows 11 build 26340.9767 (x64), with MXC 1.0.1 (
wxc-exec --probe: tierbase-container), against basewindows5d1bfbf. The Windows checks ran throughtasks/scripts/windows-msvc.ps1, which is the commandmise run --skip-tools windows:<task>executes.On commit fc00946:
cargo fmt --all -- --check: pass.mise run --skip-tools windows:lint:x64(windows-msvc.ps1 lint x86_64-pc-windows-msvc): pass (workspace clippy with-D warnings, e2e clippy, governance-interceptor check).cargo nextest run --profile ci -p openshell-supervisor-network --target x86_64-pc-windows-msvc: 1515 tests run, 1515 passed, 7 skipped.mise run --skip-tools windows:test:x64(windows-msvc.ps1 test-precommit x86_64-pc-windows-msvc): pass, 6634 tests run, 6634 passed, 41 skipped.cargo test -p openshell-supervisor-network --lib --target x86_64-pc-windows-msvc l7::tls:windows_sandbox_bundle_includes_certificate_store_rootsadded: 18 passed, 1 failed (the message above).windows_system_ca_bundle_over_the_limit_is_left_outadded: 20 passed, 1 failed withthe 1332060-byte bundle over the limit was kept.Linux (Ubuntu 24.04, WSL2), from a
git archiveof the commit:cargo fmt --all -- --check: pass.cargo clippy -p openshell-supervisor-network -p openshell-supervisor --all-targets -- -D warnings: pass.cargo check -p openshell-supervisor --all-targets --no-default-features --features system-ca-roots: pass.cargo nextest run --profile ci -p openshell-supervisor-network: 1560 passed (base: 1558; the difference is the two cross-platform tests).mise run --skip-tools windows:test:mxc-network(real MXC:pc_https_egress_reads_injected_ca_bundle, a terminated HTTPS request whose peer certificate is issued by the sandbox CA, pluspc_proxy_scopes_network_policy_to_socket_owner): fails on this host before any scenario runs, onwindowsand with this change alike, withharness error: gateway did not start within 30 s. The gateway log shows the listener bound.run-mxc-e2e.ps1waits for the port withGetActiveTcpListeners(), which keeps only sockets in theLISTENstate, and this build reports most listening sockets in other states.run-mxc-e2e.ps1, not part of this PR, the three scenarios behind this task (https-ca,socket-owner-allow,socket-owner-deny) pass on bothwindows5d1bfbf and this change. The changes: the port wait connects to the port, and the socket-owner scenarios start their batch file by name from the working directory (with only the first change,socket-owner-allowfails withparent cmd did not launch).https-capassed 5 of 6 on both;socket-owner-allow3 of 6 onwindowsand 5 of 6 with this change;socket-owner-deny6 of 6 on both. No attempt failed on a certificate or policy check.Live MXC sandbox, using the issue's reproduction script on base and head release builds, two runs each with the same results:
CURL_CA_BUNDLEtls: skipexample.com,curl.exe --cacert "%CURL_CA_BUNDLE%"schannel: the certificate chain is incompleteCloudflare TLS Issuing ECC CA 3OpenShell Sandbox CAOpenShell Sandbox CAThe same sandbox runs also covered the remaining bundle clients:
tls: skipendpoints, and on head they pass: Pythonrequests, Pythonsslwithcafile=%SSL_CERT_FILE%, andgit ls-remotewithhttp.sslBackend=openssl.fetchusesNODE_EXTRA_CA_CERTS, which adds to Node's own roots rather than replacing them, so it passes on both builds.Checklist
crates/openshell-driver-mxc/README.mdupdated