Add isolated physical network access for apps - #6937
Conversation
|
This is AI assisted based on the design and discussion in home-assistant/architecture#1034. Currently it allows Apps which use host network to isolate. I've tested this with the ESPHome and Matter App. The Matter App required a small tweak in the startup script to not fetch the primary interface from Supervisor (rather use the I think I'd prefer to make IPv4 completely optional. Currently the MAC address is derrived from the IPv4 address, which seems to be a design which Docker used previously. However, I think I'd rather prefer it to just assign a random MAC. I am also not sure if we maybe should only support ipvlan. While macvlan is strictly more isolated, it seems not to work with WiFi really. If ipvlan gives all the isolation needs we have, maybe we can simply default to it instead 🤔 |
As discussed in home-assistant/architecture#1034, apps using host networking get no isolation at all, while the default internal network cannot serve use cases like custom multicast protocols or interface selection on multi-homed hosts. Allow users to assign apps with host networking an isolated endpoint on a physical interface instead: the container runs in bridge mode, attached to the internal hassio network (keeping ingress, API proxy and DNS plug-in communication unchanged) plus a Supervisor-managed macvlan network on the selected interface with a user-chosen static IPv4 and its own MAC address. The macvlan endpoint gets default route priority via GwPriority, which requires Docker 28; the feature is gated accordingly. Docker allows only one macvlan network per parent interface, so these networks are a shared resource keyed by interface: created on demand from the host interface configuration via NetworkManager, recreated when that configuration changed and garbage collected once no installed app references them anymore. If the endpoint cannot be set up at app start (interface gone, subnet changed), the app falls back to host networking and a resolution issue is raised; a scheduled check detects such drift as well. Apps with an isolated endpoint no longer run in the host network namespace, so the host network security rating penalty and the unprotected Docker gateway boot gate do not apply to them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Docker 28 assigns a random MAC address to macvlan endpoints on every endpoint creation, while older engines derived it from the endpoint's IP address. As app containers are recreated on every start, the isolated endpoint would get a different MAC address on each restart, making it impossible to reference the app in router or firewall rules. Pin the MAC address in the endpoint configuration instead, using the same derivation older engines used (02:42 followed by the static IPv4 octets). With the static IP enforced per app, the MAC address stays stable across restarts and updates and only changes when the user assigns a different IP address. Since the MAC address is now fully determined by the configuration, report it in the app info API from the configured IP rather than from the running container's network metadata. This makes it available as soon as isolation is configured, so users can set up router or firewall rules before the first start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The isolated endpoint is a real L2 presence on the physical network, so the container kernel can autoconfigure IPv6 from the local network's router advertisements without any address management on our side. This serves the IPv6 use cases from home-assistant/architecture#1034 (Thread border routers, Matter) that the NAT-ed internal network cannot. Enable IPv6 on external networks without specifying a subnet: Docker allocates a unique local address prefix to satisfy the macvlan driver, while real addressing comes from SLAAC. Existing IPv4-only external networks are recreated through the regular drift handling. Two kernel defaults inside the container network namespace would break the flagship use cases, so the endpoint sets per-interface sysctls via the endpoint configuration (Docker API 1.47, covered by the existing Docker 28 requirement): accept_ra=2 keeps accepting router advertisements when the app enables IP forwarding (Thread border routers), and accept_ra_rt_info_max_plen=64 enables RFC 4191 route information option processing (off by default), which routes announced by border routers need to reach the container at all. Together with the pinned MAC address the SLAAC and link-local addresses are stable across restarts (EUI-64 derivation from a stable MAC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2fdb801 to
cfefef9
Compare
There was a problem hiding this comment.
Pull request overview
Adds the first phase of “isolated physical network access” for Supervisor-managed apps by allowing host-network apps to instead attach an isolated endpoint on a physical interface (Docker macvlan), while keeping the internal hassio network attachment for Supervisor ingress/DNS/inter-container connectivity.
Changes:
- Introduces external physical network management (
DockerExternalNetworks) and support for attaching extra network endpoints to containers at start. - Adds app-level network isolation configuration (schema + persistence), API support (options + info fields), and host-interface capability reporting.
- Adds a new resolution issue/check (
network_isolation_failed) plus test coverage across API, docker, apps, and resolution layers.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/resolution/check/test_check_app_network_isolation.py | Adds tests for the new resolution check behavior. |
| tests/docker/test_external_network.py | Adds unit tests for external network create/ensure/connect/gc logic. |
| tests/docker/test_app.py | Adds integration-style tests for isolated run path and fallback behavior. |
| tests/conftest.py | Wires docker.external_networks.coresys in the test CoreSys fixture. |
| tests/apps/test_network_isolation.py | Tests schema validation, persistence round-trip, and security rating interaction. |
| tests/apps/test_app.py | Adjusts mocks to avoid false-truthy persisted isolation config. |
| tests/api/test_apps_network_isolation.py | Adds API tests for setting/clearing/validating isolation options. |
| supervisor/validate.py | Adds ipv4_address() validator helper for schema normalization. |
| supervisor/resolution/const.py | Adds IssueType.NETWORK_ISOLATION_FAILED. |
| supervisor/resolution/checks/app_network_isolation.py | New check to detect drift/broken assigned isolation endpoints. |
| supervisor/exceptions.py | Adds API error types for isolation validation failures. |
| supervisor/docker/manager.py | Adds support for connecting extra (external) network endpoints during container start. |
| supervisor/docker/external_network.py | New external network manager: ensure/recreate/connect/gc for per-interface macvlan networks. |
| supervisor/docker/const.py | Adds ExternalNetworkDriver, NetworkIsolationConfig, and ExtraNetworkEndpoint types. |
| supervisor/docker/app.py | Implements isolated endpoint setup + fallback logic and ensures internal IP reporting stays correct. |
| supervisor/const.py | Adds new constants used by network isolation config/persistence. |
| supervisor/apps/validate.py | Adds persisted user schema for network_isolation. |
| supervisor/apps/utils.py | Adjusts security rating penalty so host-network apps with isolation don’t get penalized. |
| supervisor/apps/model.py | Adds network_isolation property to the model interface (default None). |
| supervisor/apps/manager.py | Updates boot gating so isolated apps are not blocked by Docker gateway firewall state. |
| supervisor/apps/app.py | Adds persistence for isolation config, derived external MAC exposure, and gc on uninstall. |
| supervisor/api/network.py | Exposes per-interface network_isolation_capable flag. |
| supervisor/api/const.py | Adds API attribute constants for isolation info/availability/mac. |
| supervisor/api/apps.py | Adds options validation + info fields for network isolation and triggers external network gc. |
| if config := self.app.network_isolation: | ||
| try: | ||
| network_name = await self.sys_docker.external_networks.ensure(config) | ||
| except (HostNetworkNotFound, DockerError) as err: |
| # Connect additional (external) network endpoints. Unlike the | ||
| # internal network these are essential for the container's | ||
| # purpose, so failures abort the start. | ||
| for endpoint in extra_networks or []: | ||
| await self.external_networks.connect_container( | ||
| container.id, name, endpoint | ||
| ) |
| return current_ipam is not None and current_ipam.get( | ||
| "Gateway" | ||
| ) == wanted_ipam.get("Gateway") |
|
There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. |
Proposed change
Implements the first phase of isolated physical network access: users can assign apps that use host networking an isolated endpoint on a physical network interface instead. The app then runs in bridge mode with its own IP and MAC address on the selected interface (Docker macvlan), providing full multicast/mDNS support and interface selection on multi-homed hosts while restoring the isolation that host networking gives up.
Key design points:
hassionetwork, so ingress, API proxy, DNS plug-in and inter-app communication work unchanged. The macvlan endpoint provides the physical network presence and wins the default route viaGwPriority.hassio-macvlan-<iface>) are a shared resource derived from the host interface configuration (subnet/gateway via NetworkManager), created on demand, recreated on drift and garbage collected when no app references them.accept_ra=2andaccept_ra_rt_info_max_plen=64so RFC 4191 routes announced by Thread border routers reach the container.network_isolation_failedissue is raised; a scheduled resolution check detects drift. The fallback is blocked when the Docker gateway is unprotected, since that protection does not apply to isolated apps (their traffic bypasses the host network namespace, which also lifts the security rating penalty for host networking).The feature requires Docker >= 28 (for
GwPriorityand per-endpoint sysctls) and is gated accordingly; existing installations and apps are unaffected unless a user opts in per app.Type of change
Additional information
Checklist
ruff format supervisor tests)If API endpoints or add-on configuration are added/changed: