From b9818619b8f31b7f60b8730006c98da191e4251d Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:38:50 +0200 Subject: [PATCH 1/3] test: disable tests flaky under parallel stress (#2611) Repeated Bazel test targets with --runs_per_test at 10, 20, 50, 100, and 200 runs. The tests failed intermittently when multiple instances ran concurrently. This indicates timing, shared tracing state, socket readiness, or parallel-safety issues that need focused follow-up before re-enabling the tests. A normal non-repeated bazel test //... run passes with these tests ignored. Disabled tests: - sandbox_forward_foreground_fails_when_ssh_exits_before_listener_opens - sandbox_forward_background_terminates_owned_child_when_listener_never_opens - podman_socket_probe_accepts_successful_ping_response - podman_socket_probe_rejects_docker_ping_response - docker_socket_probe_accepts_successful_ping_response - docker_socket_probe_rejects_podman_ping_response - docker_socket_detection_returns_the_responsive_candidate - podman_socket_detection_returns_the_responsive_candidate - driver_watch_events_are_roots_and_store_operations_have_parents - reconcile_sweeps_are_roots_and_operations_have_parents - gateway_listeners_bind_ipv6_wildcard_and_ipv4_callback_on_same_port - watch_producer_releases_request_span_when_client_disconnects - expected_conflicts_leave_the_span_unmarked - store_spans_record_what_they_touched_as_attributes - store_operations_export_spans_with_parents - refresh_worker_ticks_are_roots_and_store_operations_have_parents Signed-off-by: Simon Scatton --- .../tests/sandbox_create_lifecycle_integration.rs | 2 ++ crates/openshell-core/src/config.rs | 6 ++++++ crates/openshell-server/src/compute/mod.rs | 2 ++ crates/openshell-server/src/gateway_listener.rs | 1 + crates/openshell-server/src/grpc/sandbox.rs | 1 + crates/openshell-server/src/persistence/tests.rs | 3 +++ crates/openshell-server/src/provider_refresh.rs | 1 + 7 files changed, 16 insertions(+) diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 5fa27234c4..8bacb76a2d 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -1738,6 +1738,7 @@ async fn sandbox_forward_background_tracks_owned_child_when_pid_discovery_fails( } #[tokio::test] +#[ignore = "flaky under concurrent test execution"] async fn sandbox_forward_foreground_fails_when_ssh_exits_before_listener_opens() { let server = run_server().await; let fake_ssh_dir = tempfile::tempdir().unwrap(); @@ -1768,6 +1769,7 @@ async fn sandbox_forward_foreground_fails_when_ssh_exits_before_listener_opens() } #[tokio::test] +#[ignore = "flaky under concurrent test execution"] async fn sandbox_forward_background_terminates_owned_child_when_listener_never_opens() { let server = run_server().await; let fake_ssh_dir = tempfile::tempdir().unwrap(); diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index daa867f16f..5d4cceeab3 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -1273,6 +1273,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn podman_socket_probe_accepts_successful_ping_response() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let socket_path = temp_dir.path().join("podman.sock"); @@ -1296,6 +1297,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn podman_socket_probe_rejects_docker_ping_response() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let socket_path = temp_dir.path().join("podman.sock"); @@ -1319,6 +1321,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn docker_socket_probe_accepts_successful_ping_response() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let socket_path = temp_dir.path().join("docker.sock"); @@ -1342,6 +1345,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn docker_socket_probe_rejects_podman_ping_response() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let socket_path = temp_dir.path().join("podman.sock"); @@ -1377,6 +1381,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn docker_socket_detection_returns_the_responsive_candidate() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let inactive_path = temp_dir.path().join("inactive.sock"); @@ -1418,6 +1423,7 @@ mod tests { #[cfg(unix)] #[test] + #[ignore = "flaky under concurrent test execution"] fn podman_socket_detection_returns_the_responsive_candidate() { let temp_dir = tempfile::tempdir().expect("create temp dir"); let inactive_path = temp_dir.path().join("inactive.sock"); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index ba7eff3e92..a1c33e49ff 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -6223,6 +6223,7 @@ mod tests { /// Driver watch events arrive on a background stream, so the store writes /// they trigger land outside the request that caused them. #[tokio::test] + #[ignore = "flaky under concurrent test execution"] async fn driver_watch_events_are_roots_and_store_operations_have_parents() { use crate::otel_tracing::test_exporter; @@ -6268,6 +6269,7 @@ mod tests { /// The reconciler runs on a timer with no inbound request, so without a /// span of its own each store call becomes its own anonymous trace. #[tokio::test] + #[ignore = "flaky under concurrent test execution"] async fn reconcile_sweeps_are_roots_and_operations_have_parents() { use crate::otel_tracing::test_exporter; diff --git a/crates/openshell-server/src/gateway_listener.rs b/crates/openshell-server/src/gateway_listener.rs index 1db4c6cbca..b42069d848 100644 --- a/crates/openshell-server/src/gateway_listener.rs +++ b/crates/openshell-server/src/gateway_listener.rs @@ -682,6 +682,7 @@ mod tests { #[tokio::test] #[cfg(target_os = "linux")] + #[ignore = "flaky under concurrent test execution"] async fn gateway_listeners_bind_ipv6_wildcard_and_ipv4_callback_on_same_port() { let probe = TcpListener::bind("[::1]:0") .await diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index d3cdddf41c..6921ea7591 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -2625,6 +2625,7 @@ mod tests { } #[tokio::test] + #[ignore = "flaky under concurrent test execution"] async fn watch_producer_releases_request_span_when_client_disconnects() { use crate::otel_tracing::test_exporter; use tokio_stream::StreamExt as _; diff --git a/crates/openshell-server/src/persistence/tests.rs b/crates/openshell-server/src/persistence/tests.rs index 9b6079cae4..6227eec297 100644 --- a/crates/openshell-server/src/persistence/tests.rs +++ b/crates/openshell-server/src/persistence/tests.rs @@ -35,6 +35,7 @@ async fn failed_store_calls_are_marked_on_the_span() { /// the span must stay clean — otherwise every lease a replica does not win, and /// every gateway restart, exports as a failure. #[tokio::test] +#[ignore = "flaky under concurrent test execution"] async fn expected_conflicts_leave_the_span_unmarked() { use crate::otel_tracing::test_exporter; @@ -78,6 +79,7 @@ async fn expected_conflicts_leave_the_span_unmarked() { /// Span names stay low-cardinality so they group across object types; what /// each call touched is carried as attributes. #[tokio::test] +#[ignore = "flaky under concurrent test execution"] async fn store_spans_record_what_they_touched_as_attributes() { use crate::otel_tracing::test_exporter; @@ -2182,6 +2184,7 @@ async fn membership_selector_escapes_adversarial_label_key() { /// so a trace decomposes an RPC into the storage work it did rather than /// bottoming out at the request boundary. #[tokio::test] +#[ignore = "flaky under concurrent test execution"] async fn store_operations_export_spans_with_parents() { use tracing::Instrument as _; diff --git a/crates/openshell-server/src/provider_refresh.rs b/crates/openshell-server/src/provider_refresh.rs index a03fdb0b17..77a8123297 100644 --- a/crates/openshell-server/src/provider_refresh.rs +++ b/crates/openshell-server/src/provider_refresh.rs @@ -1526,6 +1526,7 @@ mod tests { /// The worker ticks on a timer with no inbound request, so without a span /// of its own its store reads export as anonymous single-span traces. #[tokio::test] + #[ignore = "flaky under concurrent test execution"] async fn refresh_worker_ticks_are_roots_and_store_operations_have_parents() { use crate::otel_tracing::test_exporter; From 0e9a44cfa9e699dcc83dfc8e9d2042de02218d67 Mon Sep 17 00:00:00 2001 From: Polite_realism <119441270+politerealism@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:55:44 -0400 Subject: [PATCH 2/3] feat(build): add system CA root mode (#2324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(build): add system CA root mode Allow distro builds to use native trust stores for supervisor upstream TLS while keeping bundled Mozilla roots as the default. Avoid bundled root crates in system-ca-roots builds by using native-root TLS features and z3 0.20. Signed-off-by: Adam Miller * fix(build): keep CA root feature in telemetry-off verification The telemetry-off task uses --no-default-features which now disables bundled-ca-roots in addition to telemetry, triggering the compile_error guard. Re-enable bundled-ca-roots explicitly so the task verifies only telemetry compilation. Signed-off-by: Scott Burdine Signed-off-by: politerealism * fix(sdk): disable oauth2 default features to prevent webpki-roots leak The bare `oauth2 = "5"` dependency re-enabled default features (rustls-tls → reqwest/rustls-tls → webpki-roots), defeating the system-ca-roots feature gate. Mirror the CLI fix: disable defaults and enable only the `reqwest` feature. Signed-off-by: Quinn Burdine Signed-off-by: politerealism * refactor(build): simplify CA root selection to single feature toggle Replace mutually exclusive bundled-ca-roots / system-ca-roots features with a single bundled-ca-roots toggle. Disabling it implies system roots via rustls-native-certs, which is now a regular (non-optional) dependency. This fixes cargo --all-features and simplifies the distro build interface from --no-default-features --features system-ca-roots to just --no-default-features. Signed-off-by: Quinn Burdine Signed-off-by: politerealism * feat(build): add system-ca-roots convenience alias and fix verify task Add a system-ca-roots feature alias on openshell-sandbox that includes all other defaults (telemetry) except bundled-ca-roots, so distro builds can use --no-default-features --features system-ca-roots without manually re-adding unrelated defaults. Update the verify CI task to use the alias and scope checks to the sandbox package. Fix task description to use "build mode" terminology instead of implying a Cargo feature. Signed-off-by: Quinn Burdine Signed-off-by: politerealism * ci: fix system CA roots step name to use build mode terminology Signed-off-by: Quinn Burdine Signed-off-by: politerealism * refactor(sandbox): reorder features to place system-ca-roots alias near default Signed-off-by: Quinn Burdine Signed-off-by: politerealism * fix(proxy): unwrap Result from build_upstream_client_config in tests The function signature changed to return Result but the test call sites were not updated, causing type mismatch compilation errors in CI. Signed-off-by: Quinn Burdine Signed-off-by: politerealism --------- Signed-off-by: Adam Miller Signed-off-by: Scott Burdine Signed-off-by: politerealism Signed-off-by: Quinn Burdine Co-authored-by: Adam Miller --- .github/workflows/branch-checks.yml | 3 + Cargo.lock | 317 +++--------------- Cargo.toml | 9 +- architecture/build.md | 18 + crates/openshell-cli/Cargo.toml | 4 +- crates/openshell-core/Cargo.toml | 2 +- crates/openshell-sandbox/Cargo.toml | 13 +- crates/openshell-sdk/Cargo.toml | 2 +- crates/openshell-server/Cargo.toml | 2 +- .../openshell-supervisor-network/Cargo.toml | 7 +- .../src/l7/tls.rs | 94 ++++-- .../openshell-supervisor-network/src/run.rs | 2 +- .../src/upstream_proxy.rs | 4 +- tasks/rust.toml | 14 +- 14 files changed, 179 insertions(+), 312 deletions(-) diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index 3bc6944537..fccdfd1bb6 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -127,6 +127,9 @@ jobs: - name: Verify telemetry can be compiled out run: mise run rust:verify:telemetry-off + - name: Verify system CA roots build mode compiles and excludes bundled Mozilla roots + run: mise run rust:verify:system-ca-roots + - name: sccache stats if: always() run: | diff --git a/Cargo.lock b/Cargo.lock index c03be7c032..9f3f7dcdca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,18 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ "crypto-common 0.2.2", - "inout 0.2.2", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", + "inout", ] [[package]] @@ -44,7 +33,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ - "cipher 0.5.2", + "cipher", "cpubits", "cpufeatures 0.3.0", "zeroize", @@ -57,8 +46,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" dependencies = [ "aead", - "aes 0.9.2", - "cipher 0.5.2", + "aes", + "cipher", "ctr", "ghash", "subtle", @@ -138,7 +127,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -149,7 +138,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -751,28 +740,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" dependencies = [ "blowfish", - "pbkdf2 0.13.0", + "pbkdf2", "sha2 0.11.0", ] -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.11.1", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.2", - "shlex", - "syn 2.0.117", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -832,7 +803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" dependencies = [ "byteorder", - "cipher 0.5.2", + "cipher", ] [[package]] @@ -917,15 +888,6 @@ dependencies = [ "either", ] -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "capctl" version = "0.2.4" @@ -958,7 +920,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher 0.5.2", + "cipher", ] [[package]] @@ -979,15 +941,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1007,7 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", - "cipher 0.5.2", + "cipher", "cpufeatures 0.3.0", "rand_core 0.10.1", "zeroize", @@ -1027,16 +980,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.7", - "inout 0.1.4", -] - [[package]] name = "cipher" version = "0.5.2" @@ -1045,21 +988,10 @@ checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.2", - "inout 0.2.2", + "inout", "zeroize", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.6.1" @@ -1211,12 +1143,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - [[package]] name = "core-foundation" version = "0.10.1" @@ -1415,7 +1341,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher 0.5.2", + "cipher", ] [[package]] @@ -1514,12 +1440,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "delegate" version = "0.13.5" @@ -1624,7 +1544,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" dependencies = [ - "cipher 0.5.2", + "cipher", ] [[package]] @@ -1817,7 +1737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1895,7 +1815,6 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs", ] [[package]] @@ -2501,7 +2420,6 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.7", ] [[package]] @@ -2534,7 +2452,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -2758,15 +2676,6 @@ dependencies = [ "libc", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "inout" version = "0.2.2" @@ -3189,12 +3098,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libbz2-rs-sys" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" - [[package]] name = "libc" version = "0.2.189" @@ -3298,15 +3201,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rust2" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" -dependencies = [ - "sha2 0.10.9", -] - [[package]] name = "matchers" version = "0.2.0" @@ -3573,21 +3467,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "windows-sys 0.61.2", ] [[package]] @@ -3616,15 +3496,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.2.1" @@ -3651,17 +3522,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -4315,6 +4175,7 @@ dependencies = [ "regorus", "reqwest 0.12.28", "rustls 0.23.38", + "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", @@ -4333,7 +4194,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", - "webpki-roots 1.0.7", + "webpki-roots", ] [[package]] @@ -4605,16 +4466,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - [[package]] name = "pbkdf2" version = "0.13.0" @@ -4796,11 +4647,11 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca" dependencies = [ - "aes 0.9.2", + "aes", "aes-gcm", "cbc", "der 0.8.0", - "pbkdf2 0.13.0", + "pbkdf2", "rand_core 0.10.1", "scrypt", "sha2 0.11.0", @@ -4898,12 +4749,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -5100,7 +4945,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls 0.23.38", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5138,7 +4983,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -5401,7 +5246,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.7", ] [[package]] @@ -5526,14 +5370,14 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0" dependencies = [ - "aes 0.9.2", + "aes", "aws-lc-rs", "bitflags 2.11.1", "block-padding", "byteorder", "bytes", "cbc", - "cipher 0.5.2", + "cipher", "crypto-bigint", "ctr", "curve25519-dalek", @@ -5552,7 +5396,7 @@ dependencies = [ "ghash", "hex-literal", "hmac 0.13.0", - "inout 0.2.2", + "inout", "internal-russh-num-bigint", "keccak", "log", @@ -5564,7 +5408,7 @@ dependencies = [ "p384", "p521", "pageant", - "pbkdf2 0.13.0", + "pbkdf2", "pkcs1 0.8.0-rc.4", "pkcs5", "pkcs8 0.11.0", @@ -5675,7 +5519,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5755,7 +5599,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5805,7 +5649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" dependencies = [ "cfg-if", - "cipher 0.5.2", + "cipher", ] [[package]] @@ -5863,7 +5707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", - "pbkdf2 0.13.0", + "pbkdf2", "salsa20", "sha2 0.11.0", ] @@ -6274,7 +6118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6371,6 +6215,7 @@ dependencies = [ "once_cell", "percent-encoding", "rustls 0.23.38", + "rustls-native-certs", "serde", "serde_json", "sha2 0.10.9", @@ -6380,7 +6225,6 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.26.11", ] [[package]] @@ -6413,7 +6257,6 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sqlx-core", - "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.117", @@ -6452,7 +6295,6 @@ dependencies = [ "percent-encoding", "rand 0.8.6", "rsa 0.9.10", - "serde", "sha1 0.10.6", "sha2 0.10.9", "smallvec", @@ -6531,11 +6373,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" dependencies = [ "aead", - "aes 0.9.2", + "aes", "aes-gcm", "cbc", "chacha20", - "cipher 0.5.2", + "cipher", "ctr", "ctutils", "des", @@ -6769,7 +6611,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6805,7 +6647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6881,7 +6723,6 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -7396,12 +7237,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - [[package]] name = "typenum" version = "1.20.1" @@ -7768,15 +7603,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.7", -] - [[package]] name = "webpki-roots" version = "1.0.7" @@ -7818,7 +7644,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -8439,27 +8265,31 @@ dependencies = [ [[package]] name = "z3" -version = "0.19.15" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107cca65ed27d28b11f7c492298a51383333fd48ba6ebe49a432aba96162f678" +checksum = "80c4de445f5c9e3013703a6b8a40c80b4a64c925f0a19e7d0a23a7a9b70e854d" dependencies = [ "log", - "num", "z3-sys", ] [[package]] -name = "z3-sys" -version = "0.10.9" +name = "z3-src" +version = "416.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82b97329d02d87da6802ed9fda083f1b255d822ab13d5b1fb961196b58a69a1" +checksum = "f2af0c6527de39877cf55cb87f233016573eeeb7cf77afdc1469e4b32faef832" dependencies = [ - "bindgen", "cmake", +] + +[[package]] +name = "z3-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c18b0a91a13522d21b3414847667de2b2056a721a3edcb5b6ee6858352d58db4" +dependencies = [ "pkg-config", - "reqwest 0.12.28", - "serde_json", - "zip", + "z3-src", ] [[package]] @@ -8556,57 +8386,12 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "zip" -version = "8.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59" -dependencies = [ - "aes 0.8.4", - "bzip2", - "constant_time_eq", - "crc32fast", - "deflate64", - "flate2", - "getrandom 0.4.2", - "hmac 0.12.1", - "indexmap", - "lzma-rust2", - "memchr", - "pbkdf2 0.12.2", - "ppmd-rust", - "sha1 0.10.6", - "time", - "typed-path", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zlib-rs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" - [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index e3043dc322..ec582e60c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ rustls = { version = "0.23", default-features = false, features = ["std", "loggi rustls-pemfile = "2" rcgen = { version = "0.13", features = ["crypto", "pem"] } webpki-roots = "1" +rustls-native-certs = "0.8" # CLI clap = { version = "4.5", features = ["derive", "env"] } @@ -93,7 +94,7 @@ aws-config = { version = "1", default-features = false, features = ["rustls", "r aws-sdk-sts = { version = "1", default-features = false, features = ["rustls", "rt-tokio", "behavior-version-latest"] } # WebSocket -tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots"] } +tokio-tungstenite = { version = "0.26", default-features = false, features = ["connect", "rustls-tls-native-roots"] } # Clipboard (OSC 52) base64 = "0.22" @@ -121,10 +122,10 @@ url = "2" indexmap = "2" # Database -sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "sqlite", "migrate"] } +sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls-ring-native-roots", "postgres", "sqlite", "migrate", "macros"] } # Kubernetes -kube = { version = "0.90", features = ["runtime", "derive"] } +kube = { version = "0.90", default-features = false, features = ["client", "runtime", "derive", "rustls-tls"] } kube-runtime = "0.90" k8s-openapi = { version = "0.21.1", features = ["v1_26"] } @@ -132,7 +133,7 @@ k8s-openapi = { version = "0.21.1", features = ["v1_26"] } uuid = { version = "1.10", features = ["v4"] } # SMT solver (uses system libz3; enable z3/bundled via the prover's bundled-z3 feature for local dev without system z3) -z3 = "0.19" +z3 = "0.20" [workspace.lints.rust] unsafe_code = "warn" diff --git a/architecture/build.md b/architecture/build.md index d4a3769b92..47fb4a668d 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -39,6 +39,24 @@ are no-ops, so the data-model types stay available and dependent crates compile unchanged. The runtime `OPENSHELL_TELEMETRY_ENABLED` switch remains the way to disable telemetry in a default (telemetry-enabled) build. +Supervisor upstream TLS root-store selection is controlled by the +`bundled-ca-roots` Cargo feature (on by default). Default builds use Mozilla +roots through `webpki-roots` plus locally-installed CAs from the system bundle. +Building without `bundled-ca-roots` switches to the platform trust store via +`rustls-native-certs` and excludes bundled Mozilla root crates such as +`webpki-roots` and `webpki-root-certs` from the dependency graph. The +`system-ca-roots` feature alias on `openshell-sandbox` includes all other +defaults (currently `telemetry`) except `bundled-ca-roots`, so Linux +distribution builds (e.g. RPM) can use +`--no-default-features --features system-ca-roots` without manually re-adding +unrelated defaults. Other Rustls clients use native roots directly because that +already satisfies Linux distribution trust-store policy. + +The workspace uses `z3` versions whose `z3-sys` dependency keeps downloader +HTTP/TLS support behind explicit build features, so default system-Z3 builds do +not reintroduce bundled Mozilla roots. Release builds that need bundled Z3 +continue to opt in with `bundled-z3`. + ## Linux Runtime Environments OpenShell uses different Linux libc environments for different host artifacts. diff --git a/crates/openshell-cli/Cargo.toml b/crates/openshell-cli/Cargo.toml index d7b8fd502c..36d1c62a4d 100644 --- a/crates/openshell-cli/Cargo.toml +++ b/crates/openshell-cli/Cargo.toml @@ -46,7 +46,7 @@ bytes = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } hyper-util = { workspace = true } -hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging", "ring", "webpki-tokio"] } +hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging", "ring"] } rustls = { workspace = true } rustls-pemfile = { workspace = true } tokio-rustls = { workspace = true } @@ -63,7 +63,7 @@ tar = "0.4" tempfile = "3" # OIDC/Auth -oauth2 = "5" +oauth2 = { version = "5", default-features = false, features = ["reqwest"] } base64 = { workspace = true } # WebSocket (Cloudflare tunnel proxy) diff --git a/crates/openshell-core/Cargo.toml b/crates/openshell-core/Cargo.toml index 35a3732cf9..e138e1eee1 100644 --- a/crates/openshell-core/Cargo.toml +++ b/crates/openshell-core/Cargo.toml @@ -26,7 +26,7 @@ url = { workspace = true } ipnet = "2" base64 = { workspace = true } chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true } -reqwest = { workspace = true, features = ["blocking", "rustls-tls-webpki-roots"], optional = true } +reqwest = { workspace = true, features = ["blocking", "rustls-tls-native-roots"], optional = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } diff --git a/crates/openshell-sandbox/Cargo.toml b/crates/openshell-sandbox/Cargo.toml index 6a51635b14..94cbb4ad51 100644 --- a/crates/openshell-sandbox/Cargo.toml +++ b/crates/openshell-sandbox/Cargo.toml @@ -18,7 +18,7 @@ path = "src/main.rs" openshell-core = { path = "../openshell-core", default-features = false } openshell-ocsf = { path = "../openshell-ocsf" } openshell-policy = { path = "../openshell-policy" } -openshell-supervisor-network = { path = "../openshell-supervisor-network" } +openshell-supervisor-network = { path = "../openshell-supervisor-network", default-features = false } openshell-supervisor-middleware = { path = "../openshell-supervisor-middleware" } openshell-supervisor-middleware-builtins = { path = "../openshell-supervisor-middleware-builtins" } openshell-supervisor-process = { path = "../openshell-supervisor-process" } @@ -53,11 +53,14 @@ tracing-subscriber = { workspace = true } tracing-appender = { workspace = true } [features] -default = ["telemetry"] -## Compile in telemetry activity collection (forwards to openshell-core/telemetry). -## On by default; build with `--no-default-features` for a telemetry-free sandbox -## supervisor that never collects or forwards activity summaries. +default = ["telemetry", "bundled-ca-roots"] +## Convenience alias: all defaults except bundled CA roots. Use +## `--no-default-features --features system-ca-roots` to build a supervisor +## that uses the platform trust store with telemetry intact. +system-ca-roots = ["telemetry"] + telemetry = ["openshell-core/telemetry"] +bundled-ca-roots = ["openshell-supervisor-network/bundled-ca-roots"] [dev-dependencies] tempfile = "3" diff --git a/crates/openshell-sdk/Cargo.toml b/crates/openshell-sdk/Cargo.toml index a1016baec0..8d80beaa74 100644 --- a/crates/openshell-sdk/Cargo.toml +++ b/crates/openshell-sdk/Cargo.toml @@ -17,7 +17,7 @@ futures = { workspace = true } hyper = { workspace = true } hyper-util = { workspace = true } miette = { workspace = true } -oauth2 = "5" +oauth2 = { version = "5", default-features = false, features = ["reqwest"] } reqwest = { workspace = true } rustls = { workspace = true } serde = { workspace = true } diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 26568fd79c..e4a698c4c2 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -121,7 +121,7 @@ bundled-z3 = ["openshell-prover/bundled-z3"] test-support = [] [dev-dependencies] -hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "ring", "webpki-tokio"] } +hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "ring"] } rcgen = { version = "0.13", features = ["crypto", "pem"] } tokio-tungstenite = { workspace = true } futures-util = "0.3" diff --git a/crates/openshell-supervisor-network/Cargo.toml b/crates/openshell-supervisor-network/Cargo.toml index 3dead2b8be..58360aa56c 100644 --- a/crates/openshell-supervisor-network/Cargo.toml +++ b/crates/openshell-supervisor-network/Cargo.toml @@ -34,6 +34,7 @@ rcgen = { workspace = true } regorus = { version = "0.9", default-features = false, features = ["std", "arc", "glob"] } reqwest = { workspace = true } rustls = { workspace = true } +rustls-native-certs = { workspace = true } rustls-pemfile = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -47,7 +48,11 @@ tokio-rustls = { workspace = true } tower-mcp-types = { workspace = true } tracing = { workspace = true } uuid = { workspace = true } -webpki-roots = { workspace = true } +webpki-roots = { workspace = true, optional = true } + +[features] +default = ["bundled-ca-roots"] +bundled-ca-roots = ["dep:webpki-roots"] [dev-dependencies] openshell-supervisor-middleware-builtins = { path = "../openshell-supervisor-middleware-builtins" } diff --git a/crates/openshell-supervisor-network/src/l7/tls.rs b/crates/openshell-supervisor-network/src/l7/tls.rs index f7c923c690..2275a60d34 100644 --- a/crates/openshell-supervisor-network/src/l7/tls.rs +++ b/crates/openshell-supervisor-network/src/l7/tls.rs @@ -8,7 +8,7 @@ //! store, terminates TLS from the client (presenting dynamic certs per hostname), //! inspects the plaintext HTTP, then re-encrypts to upstream using real root CAs. -use miette::{IntoDiagnostic, Result}; +use miette::{IntoDiagnostic, Result, miette}; use rcgen::{CertificateParams, DnType, IsCa, KeyPair, KeyUsagePurpose}; use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; use rustls::{ClientConfig, ServerConfig}; @@ -180,7 +180,7 @@ pub async fn tls_terminate_client( Ok(tls_stream) } -/// Connect TLS to an upstream server, verifying against webpki-roots. +/// Connect TLS to an upstream server, verifying against the configured CA roots. /// /// Returns a TLS stream for re-encrypted upstream communication. pub async fn tls_connect_upstream( @@ -197,34 +197,75 @@ pub async fn tls_connect_upstream( Ok(tls_stream) } -/// Build a rustls `ClientConfig` with Mozilla + system root CAs for upstream connections. +/// Build a rustls `ClientConfig` using the configured CA root source. /// -/// `system_ca_bundle` is the pre-read PEM contents of the system CA bundle -/// (from [`read_system_ca_bundle`]). Pass the same string to [`write_ca_files`] -/// to avoid reading the bundle from disk twice. -pub fn build_upstream_client_config(system_ca_bundle: &str) -> Arc { +/// In `bundled-ca-roots` mode this uses Mozilla roots from `webpki-roots` overlaid +/// with any locally-installed CAs from `system_ca_bundle` (e.g. corporate or private +/// CAs added to `/etc/pki/ca-trust`). Duplicates with the Mozilla bundle are harmless. +/// +/// Without `bundled-ca-roots` this uses the platform/native trust store exclusively; +/// `system_ca_bundle` is ignored because the native store already reflects all +/// operator-installed trust anchors. +pub fn build_upstream_client_config(system_ca_bundle: &str) -> Result> { + let mut config = ClientConfig::builder() + .with_root_certificates(build_upstream_root_store(system_ca_bundle)?) + .with_no_client_auth(); + config.alpn_protocols = vec![b"http/1.1".to_vec()]; + + Ok(Arc::new(config)) +} + +fn build_upstream_root_store(system_ca_bundle: &str) -> Result { let mut root_store = rustls::RootCertStore::empty(); - root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - // System bundles typically overlap with webpki-roots (Mozilla roots); - // duplicates are harmless and ensure we also pick up any custom/corporate CAs. - let (added, ignored) = load_pem_certs_into_store(&mut root_store, system_ca_bundle); - if added > 0 { - tracing::debug!(added, "Loaded system CA certificates for upstream TLS"); + #[cfg(feature = "bundled-ca-roots")] + { + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + // Overlay system/corporate CAs so custom trust anchors are honoured in + // default upstream builds. Duplicates with webpki-roots are harmless. + let (added, ignored) = load_pem_certs_into_store(&mut root_store, system_ca_bundle); + if added > 0 { + tracing::debug!(added, "loaded system CA certificates for upstream TLS"); + } + if ignored > 0 { + tracing::warn!( + ignored, + "some system CA certificates could not be parsed and were ignored" + ); + } + } + + #[cfg(not(feature = "bundled-ca-roots"))] + { + let _ = system_ca_bundle; // native store already includes operator-installed CAs + add_native_roots(&mut root_store)?; } + + if root_store.is_empty() { + return Err(miette!("no TLS root certificates available")); + } + + Ok(root_store) +} + +#[cfg(not(feature = "bundled-ca-roots"))] +fn add_native_roots(root_store: &mut rustls::RootCertStore) -> Result<()> { + let native_certs = rustls_native_certs::load_native_certs(); + let cert_count = native_certs.certs.len(); + let (added, ignored) = root_store.add_parsable_certificates(native_certs.certs); + let ignored = ignored + native_certs.errors.len(); + if ignored > 0 { - tracing::warn!( - ignored, - "Some system CA certificates could not be parsed and were ignored" - ); + tracing::debug!(ignored, "ignored unparsable native root certificates"); } - let mut config = ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - config.alpn_protocols = vec![b"http/1.1".to_vec()]; + if added == 0 { + return Err(miette!( + "no usable native TLS root certificates found ({cert_count} loaded, {ignored} ignored)" + )); + } - Arc::new(config) + Ok(()) } /// Write CA certificate files for the sandbox trust store. @@ -234,8 +275,7 @@ pub fn build_upstream_client_config(system_ca_bundle: &str) -> Arc /// 2. Combined bundle: system CAs + sandbox CA (for `SSL_CERT_FILE` which replaces default) /// /// `system_ca_bundle` is the pre-read PEM contents of the system CA bundle -/// (from [`read_system_ca_bundle`]). Pass the same string to -/// [`build_upstream_client_config`] to avoid reading the bundle from disk twice. +/// (from [`read_system_ca_bundle`]). /// /// Returns `(ca_cert_path, combined_bundle_path)`. pub fn write_ca_files( @@ -266,6 +306,7 @@ pub fn write_ca_files( /// Returns `(added, ignored)` counts. Invalid or unparseable certificates /// are silently ignored, matching the behavior of /// `RootCertStore::add_parsable_certificates`. +#[cfg_attr(not(feature = "bundled-ca-roots"), allow(dead_code))] fn load_pem_certs_into_store( root_store: &mut rustls::RootCertStore, pem_data: &str, @@ -289,7 +330,7 @@ fn load_pem_certs_into_store( /// /// Returns the PEM contents of the first non-empty bundle found, or an empty /// string if none of the well-known paths exist. Call once and pass the result -/// to both [`write_ca_files`] and [`build_upstream_client_config`]. +/// to [`write_ca_files`]. pub fn read_system_ca_bundle() -> String { for path in SYSTEM_CA_PATHS { if let Ok(contents) = std::fs::read_to_string(path) @@ -299,7 +340,6 @@ pub fn read_system_ca_bundle() -> String { } } // No system bundle found — combined file will contain only the sandbox CA. - // This is acceptable since the proxy uses webpki-roots independently. String::new() } @@ -426,7 +466,7 @@ mod tests { #[test] fn upstream_config_alpn() { let _ = rustls::crypto::ring::default_provider().install_default(); - let config = build_upstream_client_config(""); + let config = build_upstream_client_config("").unwrap(); assert_eq!(config.alpn_protocols, vec![b"http/1.1".to_vec()]); } diff --git a/crates/openshell-supervisor-network/src/run.rs b/crates/openshell-supervisor-network/src/run.rs index 5047ad7bdb..3b5afbe993 100644 --- a/crates/openshell-supervisor-network/src/run.rs +++ b/crates/openshell-supervisor-network/src/run.rs @@ -217,7 +217,7 @@ pub async fn run_networking( // path injected by enrich_*_baseline_paths(), so no // explicit Landlock entry is needed here. - let upstream_config = build_upstream_client_config(&system_ca_bundle); + let upstream_config = build_upstream_client_config(&system_ca_bundle)?; let cert_cache = CertCache::new(ca); let state = Arc::new(ProxyTlsState::new(cert_cache, upstream_config)); ocsf_emit!( diff --git a/crates/openshell-supervisor-network/src/upstream_proxy.rs b/crates/openshell-supervisor-network/src/upstream_proxy.rs index 85e57c14c5..1d585caaf0 100644 --- a/crates/openshell-supervisor-network/src/upstream_proxy.rs +++ b/crates/openshell-supervisor-network/src/upstream_proxy.rs @@ -1654,10 +1654,10 @@ mod tests { // Trusted CA; the client config trusts it, and the fake upstream // server presents a leaf for SERVER_HOSTNAME signed by it. let ca = tls::SandboxCa::generate().unwrap(); - let client_config = tls::build_upstream_client_config(ca.cert_pem()); + let client_config = tls::build_upstream_client_config(ca.cert_pem()).unwrap(); let tls_state = Arc::new(tls::ProxyTlsState::new( tls::CertCache::new(ca), - tls::build_upstream_client_config(""), + tls::build_upstream_client_config("").unwrap(), )); // Fake upstream TLS server: accepts tunneled connections and completes diff --git a/tasks/rust.toml b/tasks/rust.toml index f035193fd8..c51fe4c054 100644 --- a/tasks/rust.toml +++ b/tasks/rust.toml @@ -42,6 +42,18 @@ run = [ # Guard: telemetry-free builds must contain no telemetry markers. "cargo build -p openshell-server --bin openshell-gateway --no-default-features", "tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-gateway", - "cargo build -p openshell-sandbox --bin openshell-sandbox --no-default-features", + "cargo build -p openshell-sandbox --bin openshell-sandbox --no-default-features --features bundled-ca-roots", "tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-sandbox", ] + +["rust:verify:system-ca-roots"] +description = "Verify system CA roots build mode compiles and excludes bundled Mozilla root crates" +run = [ + # Check that the sandbox compiles cleanly in system CA roots mode (all + # defaults except bundled-ca-roots). + "cargo check -p openshell-sandbox --all-targets --no-default-features --features system-ca-roots", + # Guard: webpki-roots must not appear in the dependency graph. + "bash -c 'if cargo tree -p openshell-sandbox -i webpki-roots --no-default-features --features system-ca-roots 2>/dev/null | grep -q webpki-roots; then echo \"ERROR: webpki-roots found in system CA roots build\" >&2; exit 1; fi'", + # Guard: webpki-root-certs must not appear either (webpki-roots re-exports it). + "bash -c 'if cargo tree -p openshell-sandbox -i webpki-root-certs --no-default-features --features system-ca-roots 2>/dev/null | grep -q webpki-root-certs; then echo \"ERROR: webpki-root-certs found in system CA roots build\" >&2; exit 1; fi'", +] From 9a00d4b2ab70f25194262112a93ce39280324e45 Mon Sep 17 00:00:00 2001 From: Gaizka Menendez Hernandez Date: Tue, 4 Aug 2026 18:23:56 +0100 Subject: [PATCH 3/3] docs(podman): document macOS socket path mismatch and dynamic lookup On macOS, Homebrew-installed Podman does not create the default socket path that the Podman driver probes. Document the OPENSHELL_PODMAN_SOCKET override and the podman machine inspect lookup in both the compute drivers reference and the debug-openshell-cluster skill. Fixes #1690 Signed-off-by: Gaizka Menendez Hernandez --- .../skills/debug-openshell-cluster/SKILL.md | 11 +++++++++- docs/reference/sandbox-compute-drivers.mdx | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md index d07bf1b6c8..b6118eec0f 100644 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ b/.agents/skills/debug-openshell-cluster/SKILL.md @@ -193,9 +193,18 @@ podman logs --tail=200 openshell status ``` +On macOS with Homebrew Podman, the API socket may not exist at the default +probe path. If `podman machine list` shows a running machine but the gateway +reports the socket as missing, resolve the dynamic path: + +```bash +podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}' +export OPENSHELL_PODMAN_SOCKET="$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')" +``` + Common findings: -- Podman socket unavailable: start or expose the user socket. +- Podman socket unavailable: start or expose the user socket. On macOS, also check the socket path mismatch described above. - Rootless networking unavailable: inspect Podman network configuration. - Sandbox image missing or pull denied: verify image reference and registry credentials. - Sandbox fails before readiness with an identity-resolution error: inspect the image's OCI `USER` and matching `/etc/passwd` and `/etc/group` entries, or explicitly set both process identity fields in policy. Root and missing identities are rejected. diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index 675ffaff6a..75e75ffc84 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -192,6 +192,26 @@ For maintainer-level implementation details, refer to the [Podman driver README] Select Podman with `compute_drivers = ["podman"]` in `[openshell.gateway]`. Configure Podman driver values such as `socket_path`, `network_name`, `supervisor_image`, `stop_timeout_secs`, `image_pull_policy`, `grpc_endpoint`, `host_gateway_ip`, `sandbox_ssh_socket_path`, `sandbox_pids_limit`, and `guest_tls_*` in `[openshell.drivers.podman]`. +### macOS Podman Socket Path + +On macOS, Homebrew-installed Podman does not create the default socket path +that the driver probes (`~/.local/share/containers/podman/machine/podman.sock`). +The actual API socket lives under `/var/folders/` in a path that macOS can +rotate after a reboot. + +If the gateway fails with `Podman socket not found; is podman machine running?` +while `podman machine list` shows a running machine, set the +`OPENSHELL_PODMAN_SOCKET` environment variable to the dynamic socket path: + +```shell +export OPENSHELL_PODMAN_SOCKET="$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')" +``` + +Add this to your shell profile or gateway launch environment so it resolves +correctly after each reboot. Alternatively, set `socket_path` in +`[openshell.drivers.podman]` to the current path, but note that the path may +change when macOS rotates `/var/folders/`. + Podman sandboxes default to a 45-second graceful stop window before Podman escalates from `SIGTERM` to `SIGKILL`. Set `stop_timeout_secs` in gateway config, or `OPENSHELL_STOP_TIMEOUT` for the standalone driver, when a local runtime needs a different teardown window. For proxy-required networks, the Podman driver also accepts the corporate egress proxy keys `https_proxy`, `no_proxy`, `proxy_auth_file`, `proxy_auth_allow_insecure`, and `proxy_connect_by_hostname`. The supervisor chains policy-approved TLS tunnels through the proxy with HTTP CONNECT instead of dialing destinations directly. See the [Gateway Configuration File](./gateway-config) reference for the full contract, including the cleartext-credential acknowledgement and the validated-IP CONNECT behavior.