SmartestHome/docs/network-integration.md

143 lines
9.9 KiB
Markdown

# Network integration — OPNsense, VLANs, and whether to port-forward anything
**Short answer: don't port-forward any of this to the WAN. Nothing in this repo
needs to be reachable from the public internet, and forwarding it would trade a
huge amount of security for very little.** If you want to reach the household
stack while away from home, the answer is a VPN back into your own network, not
opening ports on the firewall. The rest of this document explains why, and how to
segment things internally with VLANs so a compromised IoT device (cameras
especially — see Phase 5/20's "zero WAN egress" guardrail) can't reach the rest of
your network either.
This is guidance, not automation — **nothing under this repo touches your OPNsense
config**, same convention as this project's HA-integration catalog entries
(`docs/project-plan.md` §2): you apply this by hand in the OPNsense web UI.
## 1. Why no port forward
Every custom service this repo builds (`identity`, `pantry-vision`, `transit`,
`admin-canvas`, `digest-web`/`admin-web`/`pantry-web`/`identity-web`, `chores`,
`trash-calendar`) is a small stdlib-`http.server`/nginx process, bearer-token
gated where it needs to be, but **none of it was built or hardened with "reachable
from the raw internet" as a threat model** — no rate limiting, no WAF, no
DDoS/abuse handling, no security audit. Home Assistant, Grocy, Frigate, Node-RED,
Portainer, Netdata, and every other off-the-shelf piece of the stack are the same
story: capable, actively maintained software, but not written or configured here
with public exposure in mind. Port-forwarding any of them turns "a bug in one of
these" into "a bug reachable by the entire internet, scanning for it constantly."
None of them need to be reachable from the internet in the first place — every
real use case (checking the dashboard from work, registering a guest while
out, planning a trip) is solved just as well by a VPN.
## 2. Remote access: WireGuard, not forwarded HTTP ports
OPNsense ships a WireGuard implementation (Instances/Peers under VPN → WireGuard,
built in since 22.1 — no separate package needed on current OPNsense). Set up one
instance on the firewall and a peer per device you want remote access from (your
phone, a laptop). The only thing that ever needs forwarding is **one UDP port**
for WireGuard itself (commonly 51820, but pick anything free) — and that's a
categorically different exposure than forwarding this stack's HTTP services
directly: WireGuard silently drops any packet that isn't from an already-paired,
cryptographically-authenticated peer, so an internet-wide scanner sees nothing to
attack at all, versus a live HTTP endpoint answering every request that reaches it.
Once connected, your phone/laptop is (virtually) on your LAN/VLAN and reaches
`identity`/`pantry-vision`/HA/etc. at their normal LAN IPs — no per-service
forwarding, no bearer tokens exposed to the WAN, nothing else to configure network-
side per new service this repo adds later.
If you don't need remote access at all, skip this section entirely — every service
in this stack works purely on the LAN with zero WAN configuration.
## 3. VLAN segmentation — not a DMZ, a blast-radius boundary
A traditional DMZ exists to host something the WAN needs to reach. Nothing here
needs that (§1), so "do I need a DMZ" isn't really the right question — the useful
question is **whether an IoT device on this network should be able to reach your
laptop, NAS, or anything else you actually care about if it's ever compromised.**
Zigbee/RuView nodes, ESPHome BLE proxies, and especially the Tapo cameras (Phase
20 — IP cameras have a genuinely bad industry-wide security track record) are the
class of device this matters most for. The fix is a dedicated VLAN, not a DMZ.
A reasonable split for this project's device inventory:
| VLAN | What goes on it | Internet access | Reaches trusted LAN? |
|---|---|---|---|
| **Trusted LAN** (existing) | Your own laptops/desktops, phones (when not on the VPN) | Yes | — |
| **Smart-home VLAN** | The container host, kiosks/thin-clients/touch panels, RuView nodes, ESPHome BLE proxies, Zigbee coordinator | Yes (needed: Ollama model pulls if not fully pre-cached, container image pulls, NTP, and any digest-engine ingestion sources — mail/Telegram/Discord/news feeds/FRED/Stooq all reach out to real external APIs, see `digest-engine/README.md`) | No, by default — see below |
| **Camera VLAN** | Tapo pan/tilt cameras (Phase 20), the existing peephole cam (Phase 5) | **No — block entirely** | No |
Rules to actually write in OPNsense (Firewall → Rules → \<VLAN interface\>):
1. **Inter-VLAN default-deny.** OPNsense's default "allow all outbound, block
inbound from other interfaces" behavior already gets you most of the way —
just don't add a blanket "Smart-home VLAN → Trusted LAN, any/any" rule. Add
narrow allow rules only for what's actually needed (e.g. if you want to browse
to Home Assistant from a trusted-LAN laptop without a VPN, that's a Trusted
LAN → Smart-home VLAN rule scoped to port 8123, not the reverse direction).
2. **Camera VLAN: block outbound to the WAN entirely**, and block Camera VLAN →
every other VLAN except the one narrow path Frigate needs (RTSP/API from the
container host's own interface, since Frigate is what actually pulls camera
streams — the camera itself never needs to *initiate* anything toward the
container host, only *receive* the stream pull, so even that path can often be
a single allow rule scoped to the container host's IP and the camera's RTSP
port). This is the same invariant this project's own testing checklist already
states for the peephole cam (`docs/project-plan.md`'s Phase 5 guardrail, "are
cameras verified to have zero WAN egress?") — it applies identically to every
Tapo camera added in Phase 20.
3. **Smart-home VLAN → Trusted LAN: deny by default**, add narrow exceptions only
if you have a real reason (e.g. Nextcloud/CalDAV living on a trusted-LAN
machine rather than inside this stack — then just that one host:port, not the
whole VLAN).
4. **WireGuard peers land wherever you configure the tunnel's allowed-IPs to
route** — typically you'd route a WireGuard peer into the Smart-home VLAN
(so your phone, once connected, can reach `identity`/`pantry-vision`/HA the
same way it would sitting on that VLAN at home), not into Trusted LAN.
None of this is automated by anything in this repo — the container host's own
network interface/VLAN tagging, and every rule above, is configured by hand in
OPNsense, same "nothing under this repo builds the HA/network side" convention as
the rest of this project's integration catalog entries.
## 4. Full port inventory (LAN-only — none of these get forwarded, see §1)
Every port `setup-container-host.sh` publishes, current as of Phase 20. `ENABLE_*`
column shows which are opt-in vs. always-on with the base stack.
| Port | Service | Always on? | Auth |
|---|---|---|---|
| 8123 | Home Assistant | Yes | HA's own login |
| 1883 | Mosquitto (MQTT) | Yes | MQTT username/password (`mosquitto/config`) |
| 8080 | zigbee2mqtt frontend | Yes | none by default — VERIFY you've set `frontend.auth` if this VLAN isn't fully trusted |
| 1880 | Node-RED | Yes | Node-RED's own login (if enabled) |
| 3000 | Homepage dashboard | `ENABLE_HOMEPAGE` (default on) | none |
| 8090 | ntfy | `ENABLE_NTFY` (default on) | ntfy's own auth (if configured) — off by default, treat the topic name as the only barrier until you set one |
| 9000 | Portainer | `ENABLE_PORTAINER` (default on) | Portainer's own login |
| 445 | Gallery SMB share | `ENABLE_GALLERY_SMB` (default off) | SMB username/password you set (`GALLERY_SMB_USERNAME`/`PASSWORD`) |
| 9925 | Mealie | `ENABLE_MEALIE` (default off) | Mealie's own login |
| 5000, 8554, 8555 | Frigate (web UI, RTSP, WebRTC) | Yes (Phase 5+) | Frigate's own login for the web UI; RTSP/WebRTC unauthenticated on the LAN by Frigate's own design — this is exactly why the camera VLAN's isolation (§3) matters, not the port itself |
| 9283 | Grocy | Yes (Phase 1/7) | Grocy's own login |
| 8091 | digest-web | `ENABLE_DIGEST_ENGINE` | none — read-only rendered output, no credentials in it by design |
| 8094 | admin-web | `ENABLE_ADMIN_CANVAS` | none — same reasoning as digest-web |
| 8095 | pantry-vision | `ENABLE_PANTRY_VISION` | bearer token (`PANTRY_VISION_TOKEN`) |
| 8096 | pantry-web | `ENABLE_PANTRY_VISION` | none |
| 8097 | identity | `ENABLE_IDENTITY` | bearer token (`IDENTITY_TOKEN`) |
| 8098 | identity-web | `ENABLE_IDENTITY` | none |
| 8099 | transit | `ENABLE_TRANSIT` | bearer token (`TRANSIT_TOKEN`) |
| 8100 | OpenTripPlanner (OTP) | `ENABLE_TRIP_PLANNING` | none — OTP has no built-in auth; this is why it's only ever called server-side by `transit`, never exposed to a kiosk browser directly |
| ~8095 (VERIFY) | Music Assistant | `ENABLE_MUSIC_ASSISTANT` | Music Assistant's own auth (if configured) — **also flagged as an assumed/unverified port that collides with `PANTRY_VISION_PORT`, see `docs/project-plan.md` open decision #31** — resolve the actual port before relying on this table for it |
`admin-canvas` (port 8092) is deliberately **not** in this table — it has no
`ports:` mapping in the generated compose file at all, reachable only from other
containers on the compose network (i.e. Home Assistant), by design.
## 5. What's still unverified here
This entire document was written against `setup-container-host.sh`'s current
`ports:` mappings, not tested against a real OPNsense instance — the VLAN/firewall
rule guidance in §3 is standard practice for this class of network, not something
run against your specific hardware/interface names. Confirm your OPNsense version
actually ships WireGuard where you expect (VPN menu) before planning around it,
and treat the port table in §4 as something to re-check against a live
`docker compose ps` after deployment, not a permanent guarantee — a new
`ENABLE_*` service added to the script later needs a new row here too.