README.md, netbird-zero-trust.md: bigger zero-trust section, expanded doc, drop over-specific personal-device detail

README: real dedicated 'Zero Trust, Actually Implemented' section (general
concept -> NetBird as the concrete implementation -> link out), host/doc
count bumped to 12/16, NetBird tool bullet.

netbird-zero-trust.md: dropped the LXC-provisioning tangent (not relevant
to a doc about NetBird itself), added self-hosted-vs-SaaS rationale, a
real Addressing table, and expanded split-DNS mechanism detail. Also
genericized personal-device specifics (device types/exact split of
access) that were too specific for the public repo.
This commit is contained in:
thesabear committed 2026-09-01 00:51:07 +03:00
1 parent 685c82a6f9
commit 1d5b8ed65e
2 files changed
+35 -11

No files matched your search

+17 -8
View File
@@ -13,10 +13,6 @@ This is the actual shape of it — the control plane coordinates, but it's delib
That's the actual difference from a VPN concentrator, drawn rather than just asserted: a concentrator puts itself in every data path by design; here the control plane's spokes only ever carry coordination traffic, and every thick line is two peers talking directly to each other.
## Built on a capability that didn't exist yet
The mesh's own control plane is the first host in this fleet ever provisioned through a genuinely new capability: Terraform gaining native LXC support (a second provider, `bpg/proxmox`, alongside the existing VM-only one — full story in [`docs/iac-pipeline.md`](iac-pipeline.md)). Every other LXC in this fleet either predates Terraform entirely or was hand-converted after the fact; this one went through the pipeline start to finish with no manual provisioning step anywhere. That's not a coincidence — a lightweight mesh control-plane container was exactly the kind of workload that made closing the LXC gap worth doing.
## Control plane
One `netbirdio/netbird-server` container runs Management, Signal, the embedded relay, and NetBird's own embedded IdP together — combined-server mode, not four separate services. A digest-pinned `netbirdio/dashboard` container serves the admin SPA. This is the real container list, not a claimed one:
@@ -31,6 +27,17 @@ netbird-dashboard f3eb26c93ca9 Up 38 ho
Configuration is Vault-Agent-rendered, never committed — secrets live in Vault under a dedicated AppRole and policy scoped to this one host, the same pattern as every other secret in [`docs/secrets-and-pki.md`](secrets-and-pki.md). The identity provider is NetBird's own embedded one, not Authentik — Authentik is deliberately LAN-only in this fleet and publishing it wasn't in scope, so there's no OIDC migration path here; the first-login owner account is the permanent identity for this system specifically.
Self-hosted rather than the vendor's own managed cloud offering, for the same reason every other security-critical system in this fleet is self-hosted rather than SaaS: every peer's identity, every access policy, every session record stays inside the same trust boundary Vault protects in [`docs/secrets-and-pki.md`](secrets-and-pki.md), instead of a third party holding the map of exactly which devices exist and what they're allowed to reach. For a mesh whose entire job is deciding who gets in, that map is itself sensitive — running the control plane anywhere else would mean trusting someone else's infrastructure with the fleet's own access model.
**Addressing:**
| Item | Value |
|---|---|
| Overlay v4 range | `100.91.0.0/16` |
| Overlay v6 range | a ULA `/64` |
| Peer identity | dashboard name = short hostname; mesh DNS label = `<host>.netbird.selfhosted` |
| Peer WireGuard | port `51820`, kernel interface `wt0` |
## The edge routing problem, and what actually broke
> [!WARNING]
@@ -76,11 +83,13 @@ $ cat Caddyfile
A dedicated Ansible role installs the mesh agent and enrols a host, opt-in per host via the same tag-driven mechanism as every other role in [`docs/iac-pipeline.md`](iac-pipeline.md) — nothing joins the mesh by default just because it exists. Enrolment uses a reusable, class-scoped setup key fetched live from the secrets platform at play time, never stored in the repo, and read only on a host's first join. Because peer approval is required, a brand-new peer pauses for an operator to approve it in the dashboard before it gets any mesh access at all — there's no silent auto-join path.
All fleet hosts are enrolled today, plus two personal devices (a laptop with full mesh access, a home device with none by default — being *on* the mesh and being *allowed* anywhere on it are two separate, independently-granted things). Fleet servers run with local DNS resolution disabled on the mesh interface deliberately — they're not roaming, and the fleet's own internal DNS already handles them; only the one roaming device gets split-DNS behavior, resolving the fleet's internal zones over the mesh specifically because it's the one peer that actually needs to.
All fleet hosts are enrolled today, plus a small number of personal devices — being *on* the mesh and being *allowed* anywhere on it are two separate, independently-granted things, and not every enrolled device gets the same access. Fleet servers run with local DNS resolution disabled on the mesh interface deliberately — they're not roaming, and the fleet's own internal DNS already handles them.
This is the real, live peer roster, not a claimed enrolment count — every fleet host plus both personal devices, each with its own overlay identity:
**Split-DNS for the one device that actually roams.** The mesh distributes a nameserver group scoping the fleet's own internal DNS zones (see [`docs/dns-naming.md`](dns-naming.md)) to a dedicated internal resolver, reachable over the mesh itself — but only to the admin group, and marked non-primary so it never competes with a peer's normal DNS for everything else. On the one peer that runs *with* mesh DNS enabled, those specific internal zones resolve through that mesh path; every other name still goes through whatever resolver the peer would normally use. On-LAN, the directly-connected network resolver still wins for those same zones regardless — this only matters, and only activates, off-LAN, which is exactly the one condition it exists for.
![Real NetBird dashboard: 13 connected peers, a mix of fleet hosts and personal devices, each showing its own group membership, agent version, and OS](../assets/netbird-zero-trust-peers.png)
This is the real, live peer roster, not a claimed enrolment count:
![Real NetBird dashboard: connected peers, a mix of fleet hosts and personal devices, each showing its own group membership, agent version, and OS](../assets/netbird-zero-trust-peers.png)
## Zero-trust policy: deny-by-default, not just present
@@ -106,7 +115,7 @@ This is the mesh actually enforcing that model, not a claimed state — a real e
```
$ netbird status --detail
Peers detail:
[laptop]:
[peer]:
NetBird IP: [overlay address]
Status: Idle
-- detail --