diff --git a/architecture/gateway.md b/architecture/gateway.md index 5442c1403..a7e8f1a00 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -358,6 +358,9 @@ table. by the operator or packaging layer. - Compute runtimes own the mechanics of starting workloads and injecting callback configuration. +- Docker-backed local gateways use Docker's `host-gateway` callback alias on + macOS and Docker Desktop-style runtimes. Native Linux Docker may expose an + additional bridge-gateway listener because the host can bind that bridge IP. - Gateway restarts recover persisted objects from storage, but live relay streams must be re-established by supervisors. - User-facing behavior changes must update published docs in `docs/`; this file diff --git a/crates/openshell-driver-docker/README.md b/crates/openshell-driver-docker/README.md index 434e70d13..96caa7b12 100644 --- a/crates/openshell-driver-docker/README.md +++ b/crates/openshell-driver-docker/README.md @@ -12,11 +12,12 @@ The gateway runs as a host process. The Docker driver creates one container per sandbox and starts the `openshell-sandbox` supervisor inside that container. The supervisor then creates the nested sandbox namespace for the agent process. -Docker containers currently use host networking. This lets a supervisor reach a -gateway bound to `127.0.0.1` without requiring a separate bridge listener, NAT -rule, or userland proxy. The container also receives -`host.openshell.internal -> 127.0.0.1` so local host services have a stable -OpenShell-owned name. +Docker containers join an OpenShell-managed bridge network. The driver injects +`host.openshell.internal` and `host.docker.internal` so supervisors have stable +names for reaching the gateway host. On Docker Desktop, Colima, Rancher +Desktop, OrbStack, and macOS-hosted gateways, those names use Docker's +`host-gateway` alias. On native Linux Docker, the gateway also binds the bridge +gateway IP so containers can call back to the host process. ## Container Contract @@ -26,7 +27,7 @@ contract: | Setting | Purpose | |---|---| | `user = "0"` | The supervisor needs root inside the container to prepare namespaces, mounts, Landlock, and seccomp. | -| `network_mode = "host"` | Lets the supervisor call back to loopback gateway endpoints. | +| `network_mode = openshell` | Places the supervisor on the managed Docker bridge network. | | `cap_add` | Grants supervisor-only capabilities required for namespace setup and process inspection. | | `apparmor=unconfined` | Avoids Docker's default profile blocking required mount operations. | | `restart_policy = unless-stopped` | Keeps managed sandboxes resumable across daemon or gateway restarts. | @@ -51,9 +52,12 @@ into the binary at compile time. The default Docker supervisor image is not ## Callback and TLS -`OPENSHELL_ENDPOINT` is injected from the gateway's configured gRPC endpoint -without rewriting. Because the container uses host networking, loopback -endpoints such as `http://127.0.0.1:8080` resolve to the host gateway. +`OPENSHELL_ENDPOINT` is injected from the gateway's configured gRPC endpoint. +When no endpoint is configured, the driver uses +`host.openshell.internal:` with the appropriate HTTP or HTTPS +scheme. Set `host_gateway_ip` only when the host has an explicit, locally +assigned address that containers should use for callbacks; package-managed +macOS gateways should leave it unset. For HTTPS endpoints, the server certificate must include the endpoint host as a subject alternative name. Docker sandboxes also need the client TLS bundle diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 010b8a042..33ed0edf5 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -1251,6 +1251,22 @@ fn docker_gateway_route( bridge_gateway_ip: IpAddr, port: u16, host_gateway_ip: Option, +) -> DockerGatewayRoute { + docker_gateway_route_for_host( + info, + bridge_gateway_ip, + port, + host_gateway_ip, + host_runtime_requires_host_gateway_alias(), + ) +} + +fn docker_gateway_route_for_host( + info: &SystemInfo, + bridge_gateway_ip: IpAddr, + port: u16, + host_gateway_ip: Option, + host_requires_host_gateway_alias: bool, ) -> DockerGatewayRoute { if let Some(host_alias_ip) = host_gateway_ip { return DockerGatewayRoute::Bridge { @@ -1259,7 +1275,7 @@ fn docker_gateway_route( }; } - if uses_host_gateway_alias(info) { + if host_requires_host_gateway_alias || uses_host_gateway_alias(info) { DockerGatewayRoute::HostGateway } else { DockerGatewayRoute::Bridge { @@ -1269,6 +1285,10 @@ fn docker_gateway_route( } } +fn host_runtime_requires_host_gateway_alias() -> bool { + cfg!(target_os = "macos") +} + /// Detect Docker Desktop and behaviourally compatible runtimes - Colima, /// Lima, Rancher Desktop, and `OrbStack` - that share Docker Desktop's routing /// constraint: the bridge gateway IP is reachable from inside containers but diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index b8759a794..7da6164ce 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -268,11 +268,12 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() { ..Default::default() }; - let route = docker_gateway_route( + let route = docker_gateway_route_for_host( &info, IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)), DEFAULT_SERVER_PORT, None, + false, ); assert_eq!( @@ -291,6 +292,25 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() { ); } +#[test] +fn docker_gateway_route_uses_host_gateway_when_host_runtime_requires_it() { + let info = SystemInfo { + operating_system: Some("Ubuntu 24.04 LTS".to_string()), + ..Default::default() + }; + + assert_eq!( + docker_gateway_route_for_host( + &info, + IpAddr::V4(Ipv4Addr::new(10, 89, 10, 1)), + DEFAULT_SERVER_PORT, + None, + true, + ), + DockerGatewayRoute::HostGateway + ); +} + #[test] fn docker_gateway_route_prefers_configured_host_gateway_ip() { let info = SystemInfo {