[LXC] State-aware lifecycle management#633
Conversation
There was a problem hiding this comment.
Pull request overview
Adds state-aware lifecycle support for the Linux LXC backend by implementing StatefulSandboxBackend in lxc_common, wiring dispatch from lxc-exec, and exposing the new backend key/prefix in both Rust parsing/dispatch and the TypeScript SDK state-aware types/helpers.
Changes:
- Register
lxcas a state-aware backend prefix/key in the Rust dispatcher/config parser (with new unit tests). - Add
LxcStateAwareRunnerimplementingprovision/start/exec/stop/deprovisionfor LXC containers (best-effort iptables cleanup on teardown). - Extend the SDK state-aware type system + prefix map to support
lxc, including per-phase config/metadata typings.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/core/wxc_common/src/state_aware_dispatch.rs | Adds lxc sandboxId prefix routing + a unit test. |
| src/core/wxc_common/src/config_parser.rs | Accepts experimental.lxc as a known experimental backend key and adjusts key matching for containment: "lxc" (with test). |
| src/core/lxc/src/main.rs | Switches to load_mxc_request and routes state-aware requests to LxcStateAwareRunner. |
| src/core/lxc/Cargo.toml | Adds serde_json dependency for error envelope printing. |
| src/Cargo.lock | Lockfile update reflecting the new serde_json dependency. |
| src/backends/lxc/common/src/state_aware.rs | New LXC state-aware lifecycle implementation. |
| src/backends/lxc/common/src/lib.rs | Exposes the new state_aware module. |
| sdk/src/state-aware-types.ts | Adds lxc to state-aware backend union and defines LXC per-phase config + metadata types. |
| sdk/src/state-aware-helper.ts | Adds lxc prefix mapping and hoists containerId as a cross-cutting field. |
…) (AB#62953349) Provision/start/exec/stop/deprovision for the LXC backend, modeled on IsolationSessionRunner; reuses lxc CLI wrappers and one-shot lxc-attach PTY streaming. Registers the lxc wire key in Rust dispatch/parser and SDK state-aware routing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3b78bec0-e139-4cfd-9c10-092ef986d4f4
ded0b0d to
fabdfd0
Compare
… + narrow LXC start network type Restrict is_valid_container_name to the same character set and length bound (<=20 chars, alphanumeric/-/_) that NetworkIptablesManager::new uses to derive the per-container iptables chain name. This makes the container-name -> chain-name mapping an identity on valid names, so distinct names (e.g. 'a.b' vs 'ab', or names differing only past the 20th char) can no longer collide onto the same firewall chain and cross-tear-down each other's rules. Narrow LxcStartConfig.network to Omit<NetworkConfig, 'proxy'> so the SDK rejects network.proxy at compile time, matching the Rust runner which rejects it at start (apply_network_policy). Adds Rust + TypeScript tests for both. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aware_provision.json) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
Resolved conflicts: - src/core/lxc/src/main.rs: kept state-aware imports; dropped now-unused ScriptRunner - src/Cargo.lock: took main's lock, reconciled via cargo metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
Re-run GitHub Actions after a transient Hyperlight E2E network flake (hyperlight_networking live-HTTP cases timed out after 30s). No source changes; this empty commit only re-fires the pull_request workflows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
- Route Lxc state-aware dispatch through mxc_engine::run_state_aware so the lxc binary stays a thin CLI shim instead of hand-rolling the backend match. - Stop/destroy the container before tearing down its iptables rules in stop(), deprovision(), and the start() rollback, discovering the veth first so the FORWARD hook rule can still be deleted after the device is gone. Closes an unrestricted-egress window during teardown. - Clear lxc.mount.entry before reapplying filesystem mounts so a restart with a tightened policy no longer inherits the previous run's bind mounts (new LxcContainer::clear_config_item). - Kill the timed-out child's whole process group and bound the output drain in mxc_pty::run_with_pty so a leaked in-container process holding the pty open can no longer hang exec forever (new join_with_timeout helper). Adds unit/regression tests for each fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 Local test re-verification — 2026-07-17Re-ran the test suites locally at branch tip Windows host (
Linux (WSL2 Ubuntu-24.04,
Note: the |
Resolve conflict in wxc_common/src/state_aware_dispatch.rs: register both the `lxc` (this PR) and `wsb` (upstream microsoft#578) state-aware backend prefixes in backend_from_prefix, and keep both resolve_backend unit tests. Added `correlation_vector: None` to the lxc test to match the ParsedStateAwareRequest field introduced upstream (microsoft#624). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔀 Merge-conflict resolution — synced with
|
| container | ||
| .start() | ||
| .map_err(|e| MxcError::backend_error(format!("Failed to start container: {e}")))?; | ||
| if let Err(e) = apply_network_policy(&container, request, &mut logger) { |
There was a problem hiding this comment.
Container egress is still not filtered — the network policy fails open for the container's entire runtime. This is separate from the teardown-ordering fix in this update (which correctly closed the stop/deprovision window); this one is about the direction the chain is hooked, and it is untouched.
start() applies network enforcement here via apply_network_policy() -> NetworkIptablesManager::apply_firewall_rules(). The per-container chain filters on destination (-d ACCEPT/DROP for allowed/blocked hosts, then a default DROP/ACCEPT), but it is hooked into FORWARD with:
-I FORWARD -o <veth> -j <chain> (src/backends/lxc/common/src/network_iptables.rs:237)
In a bridged veth setup, container->internet egress enters the host FORWARD chain as -i , while internet->container replies are -o . Hooking on -o therefore applies the chain only to traffic heading INTO the container, never to traffic originating from it:
- Outbound SYN (-i veth): not matched -> leaves unfiltered; blocked_hosts, allowed_hosts, and the default DROP are all bypassed.
- Return SYN-ACK (-o veth): matched, but it's ESTABLISHED,RELATED -> ACCEPTed; and -d can't match a remote host on the return path anyway.
Net effect: default_network_policy=Block, blocked_hosts, and allowed_hosts are ineffective for egress; a container started with a deny-by-default policy can reach any destination for its whole lifetime, not just during teardown.
Two related fail-open paths in the same function: if discover_veth_interface() returns None it logs "Skipping FORWARD hook" but still sets rules_applied=true / returns Ok(true), so start() treats an unhooked container as success; and a non-Firewall/Both enforcement mode returns Ok(true) without applying anything.
Suggested fix: hook the chain on -i (network_iptables.rs:237) and change the cleanup -D FORWARD -o (network_iptables.rs:264) to -i, so the destination-based rules apply to container egress. Fail start (return Ok(false)/error) when the veth can't be discovered or the hook can't be installed, instead of proceeding with no enforcement. Add a test that sets deny-by-default and asserts the container cannot reach a non-allowlisted host.
…tate-aware-lifecycle # Conflicts: # sdk/node/src/state-aware-helper.ts # sdk/node/src/state-aware-types.ts # sdk/node/tests/unit/state-aware-types.test.ts # src/backends/lxc/common/src/filesystem_mounts.rs # src/core/wxc_common/src/state_aware_backend.rs
Linked work item: AB#62953349 — [LXC] State-aware lifecycle management
Summary
Implements the existing
StatefulSandboxBackendtrait for the LXC backend, modeled onIsolationSessionRunner.state_awaremodule in the LXC common crate;provision/start/exec/stop/deprovisionfully wired to the LXC CLI wrappers.execreuses thelxc-attachPTY streaming path; the returnedExecHandlewaiter yields the captured exit code.deprovisionperforms best-effort iptables cleanup.BACKEND_KEY = "lxc"in the Rust dispatcher/parser and the SDK prefix map (+ SDK state-aware types).Validation
cargo fmt --all -- --check;cargo clippy --target x86_64-unknown-linux-gnu -p lxc_common -D warnings— cleancargo test -p wxc_common— 397 passed;cargo test -p mxc-sdk— 32 passedcargo check --target x86_64-unknown-linux-gnu -p lxc_common --tests— passCoupling
Adds an SDK state-aware backend key and touches the shared dispatcher/parser, so merge-time reconciliation with the schema PR (AB#62830582) is expected.
Microsoft Reviewers: Open in CodeFlow