Skip to content

Isolate Docker and podman sessions on dedicated per-session networks - #122

Open
xai wants to merge 6 commits into
mainfrom
feat/per-session-networks
Open

xai wants to merge 6 commits into
mainfrom
feat/per-session-networks

Conversation

@xai

@xai xai commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

What it does

Sessions previously used the engine's default bridge, where unrelated
containers could connect directly to services listening inside a session,
even when published host ports were bound to 127.0.0.1.

Each Docker or podman session now gets a dedicated bridge network, whether
network access is restricted or unrestricted. The network stays with a stopped
background session and is removed when the session is stopped. Orphaned
networks are pruned after an hour if they have no session container or
attached endpoints. enclave ps --json exposes each session's network name
and subnet.

Published ports still work on host loopback. Containers on other bridges can
no longer connect directly to session listeners; deliberately attaching a
container to the session network remains possible. Restricted sessions still
use the gateway's DNS and outbound policy.

Podman requires netavark. Older netavark versions fall back to weaker
isolation with a warning: other isolated sessions are blocked, but containers
on non-isolated podman networks can still connect. Docker Desktop can also
make a host-loopback-published service reachable from another container via
host.docker.internal; services that need protection must authenticate
clients.

How to test

Run these steps on a Docker host from a project where Enclave is configured to
run a tool. If both Docker and podman are installed, select Docker as Enclave's
default backend first; ps and stop do not accept --backend.

  1. Start a background session that publishes a test port:
    enclave --backend docker --background --name net-review -p 13000:3000.
    Check enclave ps --json: the session should have a non-null network
    with a name and subnet, and docker network inspect <network-name> should
    find it. Copy that network name and get its attached endpoint's IPv4
    address (the gateway in restricted mode, the tool in unrestricted mode):

    NETWORK='paste the network name from enclave ps --json'
    SESSION_CIDR=$(docker network inspect "$NETWORK" --format '{{range .Containers}}{{.IPv4Address}}{{end}}')
    SESSION_IP=${SESSION_CIDR%/*}
  2. In another terminal, start a server inside that session, using the
    container name printed by the start command:
    docker exec -it <session-container> python3 -m http.server 3000 --bind 0.0.0.0.
    curl http://127.0.0.1:13000 on the host should succeed. From a separate
    container on Docker's default bridge, run
    docker run --rm --network bridge busybox:1.36 wget -T 3 -O - "http://$SESSION_IP:3000".
    It should fail. Avoid ports 8080/8443, which the gateway proxy already
    listens on.

  3. Stop the session with enclave stop net-review. Its network should no
    longer appear in docker network ls.

  4. Repeat with --allow-all-network when starting the session. The dedicated
    network, host access, cross-bridge isolation, and teardown should behave
    the same way without a gateway.

If testing podman, select Podman as Enclave's default backend, then repeat
with --backend podman, podman exec, and podman network inspect.
The commands to derive the session ip might vary.

On a netavark release that supports strict isolation, a container on another
podman bridge should not reach the session's bridge IP. On older netavark,
expect the weaker-isolation warning instead.

Follow-ups

None.

Breaking changes

  • This PR introduces breaking changes and has been coordinated with maintainers.

Podman hosts still using the CNI network backend can no longer start Enclave
sessions and must switch to netavark. Podman already
deprecated CNI in favor of netavark.

Review checklist

@xai xai added the bug Something isn't working label Sep 29, 2026
@xai

xai commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

@EclipseSourceAI

@EclipseSourceAI EclipseSourceAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Autonomous AI review.

This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.

Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.

To get an updated review after pushing changes, a maintainer may re-request a review from this account.

Running in Eclipse Enclave, submitted via review-guard-mcp

This PR moves every Docker and podman session off the default bridge and onto its own labeled bridge network, in both restricted mode (the gateway attaches) and unrestricted mode (the tool container attaches). The core is internal/backend/docker/network.go, which handles create/reuse with an ownership check, removal by ID with retries while the session's own containers settle, GC of orphaned networks at start and in cleanup --ephemeral, and a new network field in ps --json. On podman it requires netavark with isolate=strict, falling back to isolate=true with a warning on older netavark.

Where maintainers should focus:

  • Scope. A large share of the ~3k lines is gateway concurrency hardening, not networking: ReconcileStale with a one-minute grace period, ID-based gateway stop/remove, and RunDetached returning the ID on failure. The session-start lock already serializes starts of the same name, so part of this doesn't guard against a real race and changes retry behavior after a killed start. Splitting it into its own PR would make both halves easier to review.
  • Behavior changes that aren't listed as breaking. A failing docker info now aborts the start. Linux --bridge-port users with a host firewall need a new rule for every session.
  • Duplication in network.go. The same ownership check appears in several places, and the copies have already started to drift.
  • Start latency. GC now runs on every session start and inspects each old managed network individually.

ps --json gains an additive network field, and cli-reference.md documents it.

Comment thread internal/gateway/gateway.go Outdated
Comment thread internal/backend/docker/network.go Outdated
Comment thread internal/backend/docker/network.go
Comment thread internal/backend/docker/network.go Outdated
Comment thread internal/backend/docker/docker.go Outdated
Comment thread internal/backend/docker/docker.go Outdated
Comment thread internal/backend/docker/network.go
Comment thread docs/networking.md
@xai
xai requested a review from EclipseSourceAI September 29, 2026 07:47

@EclipseSourceAI EclipseSourceAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Autonomous AI review.

This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.

Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.

To get an updated review after pushing changes, a maintainer may re-request a review from this account.

Running in Eclipse Enclave, submitted via review-guard-mcp

Only one commit landed since my last review (1cde0bb, "fix: tighten session resource cleanup and startup recovery"). It's a thorough pass that addresses every thread I opened last time:

  • The gateway grace period is gone; ReconcileStale now removes an owned orphan unconditionally, relying on the session-start lock for safety instead of a time window.
  • The repeated network-ownership label check in network.go is consolidated into sessionNetworkOwnedBy / sessionNetworkOwner, used consistently across create, remove, GC, and ps --json fill paths.
  • dockerSessionRuntimeExists now delegates to dockerSessionContainerExists instead of duplicating the inspect logic.
  • GC no longer inspects each stale network's owner individually; it does one ContainerList call and checks against a set.
  • warnInsecureDockerConfig takes the already-fetched SystemInfo instead of re-fetching it under a misleading name.
  • The gateway ownership check in docker.go now uses the exported gateway.ContainerOwnedBy instead of a second, looser check.
  • The podman isolate fallback and its error matcher: the matcher is now scoped to the exact ParseBool message instead of any string containing "isolate", with a regression test (TestUnsupportedIsolateValueRejectsUnrelatedErrors) and comment explaining it's a different (CLI-side, not netavark-side) failure mode than the one I originally flagged.
  • docs/networking.md now has a "Migrating from the default bridge" section covering the docker0 firewall rule change and the new hard dependency on docker info/podman info succeeding at startup.

Build and the touched package tests (internal/gateway, internal/backend/docker, internal/docker) pass locally. I didn't find anything new to flag in this commit, so no inline comments this round. The remaining open items from my first review (the gateway concurrency hardening being out of scope for a networking PR, and start latency more generally) weren't touched by this commit and still stand.

These previous comments can be resolved as they are now handled:

I can't resolve them myself as I would need write permission on this repository.

@xai
xai marked this pull request as ready for review September 29, 2026 15:32
xai added 6 commits October 1, 2026 22:13
Podman drives the Docker backend but rejects Docker bridge options and
reports network IDs, subnets, and endpoints differently. Use engine-specific
options, normalize inspect data, and resolve the ID after creation because
Podman prints the network name instead.

Netavark validates isolation when a container attaches, so a create-time
fallback alone cannot detect an unsupported value. Choose isolate=strict
from netavark 1.7 onward; otherwise use isolate=true and warn that containers
on non-isolated networks can still reach the session.

Query attached containers when Podman omits the endpoint map. Never remove
a network after a failed query or while foreign endpoints remain, and allow
this session's endpoints time to disappear during asynchronous teardown.

Share stale-gateway reconciliation between pre-start and startup, preserving
young running gateways that may belong to a concurrent start. Expose stale
network pruning through cleanup --ephemeral as well as start-time GC.
Scope cleanup --ephemeral network pruning to the current project and tool
unless --all is selected. Remove containers through backend teardown before
pruning networks so the same invocation removes their networks too.

Capture the gateway's immutable ID and network before removing the session,
then stop the gateway and remove the network in that order. Podman cannot
remove the gateway while another container still joins its namespaces, and
stopping it by name after session removal could hit a concurrent replacement.
Leave gateway and network untouched if session removal fails for any reason
other than an already-missing container.
Classify network-not-found messages per stderr line because Docker 29 can
append an exit-status line after the daemon message.

Batch inspection may retain successfully inspected networks when another
network disappeared between listing and inspection. Malformed output, other
engine failures, and successful responses with missing objects must remain
errors rather than silently becoming partial results or missing networks.
…r by bare name

The gateway runs with AutoRemove, so after a failed start the engine has
already freed the name; a force-remove by name could only ever hit the
gateway of a concurrent start of the same session. Use the ID Docker prints
before starting, and otherwise inspect the name and remove only a non-running
container carrying this session's labels.
@xai
xai force-pushed the feat/per-session-networks branch from 1cde0bb to 8f828f8 Compare October 1, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants