From 1d5b8ed65e2460f2bcc9ee1f553096079eb9f5cd Mon Sep 17 00:00:00 2001 From: thesabear Date: Tue, 1 Sep 2026 00:51:07 +0300 Subject: [PATCH] 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. --- README.md | 21 ++++++++++++++++++--- docs/netbird-zero-trust.md | 25 +++++++++++++++++-------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index dd05135..9465084 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ - + - +
πŸ–₯️
11
fleet hosts
πŸ–₯️
12
fleet hosts
🐳
44
compose stacks
πŸ“š
15
documented systems
πŸ“š
16
documented systems
🧱
4
trust boundaries
πŸ”
13
automated maintenance flows
-I'm a curious, self-taught technologist who runs a real, multi-host homelab as a production-grade playground. Most homelabs are a media server with some extra apps bolted on. This one is a platform: The same categories of systems a company's platform/security team owns, run at homelab scale, for real, 24/7, by one person. +I'm a curious, self-taught technologist who runs a real, multi-host homelab as a production-grade playground. Most homelabs are a media server with some extra apps bolted on. This one is a platform: the same categories of systems a company's platform/security team owns, run at homelab scale, for real, 24/7, by one person. My long-term goal is to build a career in IT β€” specifically in environments where learning never stops and innovation is part of the culture. What started as a hobby project has grown into a full-on journey into DevOps, automation, infrastructure, and security; @@ -30,6 +30,7 @@ What started as a hobby project has grown into a full-on journey into DevOps, au - HashiCorp Vault β€” issuing PKI certs and managing every application secret - Caddy + Authentik β€” a two-zone reverse-proxy design fronted by SSO, behind pfSense and firewalld for real network segmentation - Kestra β€” fleet orchestration with scheduled flows backed by Ansible roles and playbooks +- NetBird β€” a self-hosted zero-trust WireGuard mesh, every host and device with its own identity, deny-by-default policy instead of trusting LAN position - Gitea β€” a self-hosted registry building and shipping container images for every stack that's opted in - Wazuh + Dashy β€” a self-built SOC watching all of it @@ -47,6 +48,16 @@ The three routing authorities are also split across two separate physical nodes, Full breakdown β€” the boundary-by-boundary reasoning, the routing-authority failure table, all 8 traffic laws: [docs/architecture-overview.md](docs/architecture-overview.md). +## Zero Trust, Actually Implemented + +Zero trust is where enterprise security has been heading for years now β€” NIST formalized it as a reference architecture, and "trusted because it's on the LAN" has been steadily losing ground everywhere as the default posture. The core idea is simple to state and hard to actually build: nothing gets access because of *where* it's connecting from, only because of *what it's proven itself to be*. Network position stops being a substitute for identity, and every single request gets checked against explicit policy instead of inheriting trust from whatever segment it happened to arrive on. + +This fleet doesn't just talk about that model β€” the four trust boundaries above are one expression of it, and remote/roaming access is a second, separate one, since "how do you reach the fleet from outside it" is a genuinely different problem from "how do services inside it talk to each other." That second problem is solved with a self-hosted zero-trust mesh (**NetBird**, WireGuard-based): every host and every device gets its own cryptographic identity, access is granted per-identity through explicit policy, and the mesh's own built-in allow-everything default is disabled β€” nothing crosses it unless a rule says so. + +![The NetBird control plane sends thin coordination-only signals to every peer, while real data flows over direct peer-to-peer WireGuard tunnels that bypass the control plane entirely, with relay fallback only when a direct path isn't possible](assets/netbird-zero-trust-mesh-topology.svg) + +Full breakdown β€” the mesh-vs-VPN reasoning, the complete policy model (groups, scoped keys, peer approval, posture checks), the real bring-up story, and live evidence straight from the running system: [docs/netbird-zero-trust.md](docs/netbird-zero-trust.md). + ## Architecture at a Glance ![Architecture Overview](assets/architecture-overview.svg) @@ -110,6 +121,10 @@ Docker images get built and hosted entirely in-fleet on the Git server's own bui Authentik, self-hosted, fronts single sign-on fleet-wide β€” native OIDC where an app supports it, proxy-based forward-auth covering everything else, so apps that were never built with SSO in mind still get it. Every OIDC login crosses reverse-proxy zones mid-handshake, and Git automation runs on a scoped bot account rather than an admin identity. β†’ [docs/sso-and-identity.md](docs/sso-and-identity.md) +### πŸ•ΈοΈ Zero-Trust Mesh Overlay +A self-hosted WireGuard mesh gives every fleet host and personal device its own stable identity, independent of where it physically sits β€” access is granted per-identity through mesh policy, deny-by-default, not inferred from LAN position. Rejected a classic VPN concentrator on purpose: the mesh's control plane only ever coordinates, real traffic flows peer-to-peer and never routes through it. +β†’ [docs/netbird-zero-trust.md](docs/netbird-zero-trust.md) + ## What's Running 44 Docker Compose stacks, grouped by purpose rather than by which host happens to run them β€” see [`stacks/`](stacks/): identity & security, platform/infra, media, productivity, and dev tools. diff --git a/docs/netbird-zero-trust.md b/docs/netbird-zero-trust.md index f53d45e..1c80632 100644 --- a/docs/netbird-zero-trust.md +++ b/docs/netbird-zero-trust.md @@ -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 = `.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 --