Routes and policy: giving the mesh the keys, one team at a time
An authenticated peer that can't reach anything is useless. This part turns the gateway into a routing peer, advertises the internal network, pushes internal DNS, and replaces 'connected means full access' with default-deny, group-based policy.
Real client work, anonymized. Generic subnets and domains throughout.
After Part 3 a remote user can install the client, authenticate through the SSO with a second factor, and join the mesh. And then reach nothing internal, because their tunnel only connects them to other peers, not to the office network. This part fixes that, and takes the chance to fix something the old VPN got wrong, which was treating “connected” as “allowed everywhere.”
The mental model: a routing peer is a deliberate on-ramp¶
In a mesh, peers talk to peers. To reach a plain server that isn’t running the client, like a database or an internal web app, some peer has to volunteer to forward traffic onto the LAN for everyone else. That volunteer is the routing peer.
Why one peer advertises the whole subnet
Most internal hosts will never run a mesh client. You don’t install an agent on every
appliance and database. So you pick a peer that lives on the internal network, the gateway VM,
and have it advertise the internal subnets. Now any authenticated peer can reach
10.0.0.0/24 through the gateway, the same way OpenVPN pushed a route to the whole network.
The difference is that this is one explicit, revocable on-ramp you can reason about, rather
than something you get for merely holding a certificate.
With masquerade left on, which is the default, return traffic is NAT’d back through the routing peer, so the internal hosts don’t need a route back toward mesh clients. That’s one less thing the edge firewall has to know about.
Advertise the routes, then push internal DNS¶
In the dashboard, add the gateway as the routing peer and advertise the internal subnets it
fronts (10.0.0.0/24, plus any others). Then replicate the other thing OpenVPN used to do for
free, which is internal name resolution.
Why peers need a nameserver group
OpenVPN pushed an internal DNS server so clients could resolve *.int.example.com. The mesh
needs the same thing: a nameserver group that says “for the int.example.com domain, use the
internal resolver at 10.0.0.20.” Without it, a peer can reach 10.0.0.x by IP but can’t
resolve app.int.example.com, and every internal link and bookmark breaks. Routing and
resolution are two separate jobs, and you have to wire up both.
# from a remote test client, after routes + DNS are pushed:
ssh [email protected] # reaches an internal host through the routing peer
dig +short app.int.example.com # resolves via the internal nameserver
Both worked from a remote client. Internal hosts were reachable through the routing peer, and
*.int.example.com names resolved through the pushed nameserver. That’s the same reach OpenVPN
gave, now arriving over WireGuard.
Default-deny, group-based policy, the real upgrade¶
The old VPN’s implicit rule was that if you connected, you could reach everything. That’s the part I specifically didn’t want to rebuild. The directory groups we imported into Keycloak in Part 2 flow as claims into NetBird groups, and the policy is written against those groups.
Why default-deny with per-team policies
“Connected means full access” means one compromised laptop can see the whole network. So you start from default-deny and open only what each team needs. Engineering reaches the engineering subnet and the dev hosts. Ops reaches the management plane. Staff reach the handful of apps they actually use. Because the groups come from the directory through SSO, access stays correct on its own as people join, change teams, or leave. The policy follows identity rather than a file. This is the unglamorous, practical version of zero trust: stop trusting the network and start trusting the authenticated, group-scoped identity.
The bar for this part was that a remote peer gets the same internal reach OpenVPN gave them, but scoped to their team, while a peer in another group is provably denied the things that aren’t theirs.
Both halves checked out. A peer in the engineering group reached its team’s hosts, and a peer outside that group was cleanly denied the resources that weren’t theirs. Default-deny doing its job.
Honest limits¶
- The routing peer is a chokepoint. All peer-to-LAN traffic for non-mesh hosts flows through the gateway. That’s fine at this client’s scale, but it’s a real capacity and availability consideration, not something to wave away. Direct peer-to-peer traffic still goes direct.
- Policy is only as good as the group data. This leans entirely on the directory groups being accurate. Part of the payoff of the identity spine is that there’s now one place to get that right.
What’s next¶
Routes work, DNS works, and access is scoped by team. Everything is in place, but not a single real user has moved yet, and the old VPN is still carrying everyone. Part 5 is the part that takes some nerve: cutting over in waves, with OpenVPN live as the safety net the whole time, and the backup-and-rollback discipline that makes “build it alongside” actually true.