The mesh computes a private network it cannot impersonate
The first thing the control plane decides rather than relays. Every node's peer list is derived from every node at once, which is what makes this control-plane work by definition: no node has that view. A hub, with direct peering between nodes at the same site. Not a full mesh, and the reason is a property of WireGuard rather than a preference -- there is no failover, so a more specific route to a dead endpoint blackholes instead of falling back. A node gets exactly one path to any peer, because two would mean one of them silently swallowing traffic. A roaming node is hub-only for the same reason. Reachability and the hub are declared, never inferred from an address. The address is evidence and is not the fact: carrier-grade NAT looks public and is not, a routable address behind a closed firewall looks public and is not, and the regular expression that used to decide it got the lab wrong too. Hub election by address prefix failed silently when nobody knew the convention. No private key travels, and that is the whole design. The node generated its own keypair and kept the private half; the configuration points at a file the node wrote, using WireGuard's own PostUp. So the control plane composes a complete configuration for a node it cannot pretend to be -- it knows every public key and holds none of the private ones. Delivered as an ordinary declaration: a package, a file and a service. The host does not know what a private network is and does not learn one. There is a test holding that line, because the moment connectivity needs a new shape in tier 0 is the moment the host stops being small enough to trust. The generated file is written to be read: each peer says why it is there, a peer with no endpoint says why it has none, and the header says not to edit it -- an edit survives until the graph next changes and then vanishes, which is worse than never being applied, because the machine works and then stops and nothing changed that anybody remembers. Fault injection found one weak test. The keepalive rule was asserted only against the hub, whose peer entries happen not to set the field at all, so it was testing an absence rather than the rule. It now checks two direct peers where one is reachable and one is not.
This commit is contained in:
@@ -283,3 +283,80 @@ func (i *Inventory) Owned(ctx context.Context, node string) ([]string, time.Time
|
||||
}
|
||||
return owned, *reported, nil
|
||||
}
|
||||
|
||||
// Overlay is what the mesh knows about one node's place on the private network.
|
||||
type Overlay struct {
|
||||
Node string
|
||||
Name string
|
||||
Key string
|
||||
Endpoint string
|
||||
Site string
|
||||
Hub bool
|
||||
Address string
|
||||
}
|
||||
|
||||
// Reachable reports whether other nodes can dial this one.
|
||||
//
|
||||
// From the endpoint alone, which is declared. Never from the shape of an address: that inference
|
||||
// is wrong for carrier-grade NAT, wrong for IPv6, and wrong for a routable address behind a
|
||||
// closed firewall (novox/hq ADR 0007).
|
||||
func (o Overlay) Reachable() bool { return strings.TrimSpace(o.Endpoint) != "" }
|
||||
|
||||
// RecordOverlayKey keeps the public half a node generated.
|
||||
func (i *Inventory) RecordOverlayKey(ctx context.Context, node, key string) error {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
return errors.New("a node reported an empty overlay key")
|
||||
}
|
||||
_, err := i.store.Pool().Exec(ctx,
|
||||
`update node set overlay_key = $2 where id = $1`, node, key)
|
||||
return err
|
||||
}
|
||||
|
||||
// Overlays is every node's place on the private network, which is what computing the graph needs.
|
||||
//
|
||||
// Every node at once, deliberately: a peer list is derived from all of them, and that is the
|
||||
// whole reason this is the control plane's work rather than a node's.
|
||||
func (i *Inventory) Overlays(ctx context.Context) ([]Overlay, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select id, name, coalesce(overlay_key,''), coalesce(endpoint,''), coalesce(site,''),
|
||||
is_hub, coalesce(host(overlay_address),'')
|
||||
from node order by name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []Overlay
|
||||
for rows.Next() {
|
||||
var o Overlay
|
||||
if err := rows.Scan(&o.Node, &o.Name, &o.Key, &o.Endpoint, &o.Site, &o.Hub, &o.Address); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, o)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ErrNotOneHub is what the mesh says when the graph cannot be computed.
|
||||
//
|
||||
// Its own error because it is not a fault in any node: it means nobody has said which node is the
|
||||
// hub, and a mesh with no hub has no path between sites at all. The old arrangement inferred this
|
||||
// from an address prefix and failed silently when nobody knew the convention.
|
||||
var ErrNotOneHub = errors.New("this mesh has no hub, so there is no path between sites")
|
||||
|
||||
// SetPlace declares where a node is and how it is reached.
|
||||
func (i *Inventory) SetPlace(ctx context.Context, name, endpoint, site string, hub bool, address string) error {
|
||||
node, err := i.NodeByName(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addr any
|
||||
if strings.TrimSpace(address) != "" {
|
||||
addr = address
|
||||
}
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`update node set endpoint = nullif($2,''), site = nullif($3,''), is_hub = $4,
|
||||
overlay_address = $5::inet
|
||||
where id = $1`, node.ID, endpoint, site, hub, addr)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user