Commit Graph
2 Commits
Author SHA1 Message Date
Claude e3bb1d00fe [management] decide the agent network cluster check on record, not heartbeats
The bootstrap check keyed on GetClusterSupportsPrivate, which only looks at
proxies heartbeating inside the 2-minute active window. That made the
answer depend on timing rather than on the cluster: a centralised cluster
was refused while its proxies were live, and the same cluster became
pinnable once they had been quiet for two minutes, because an unreported
capability was read as "nothing to judge". Waiting for a proxy to go quiet
was a way to pin the account's immutable endpoint to a cluster that can
never serve its gateway.

Decide on the proxy rows instead. A cluster's rows outlive its proxies'
liveness — only the hourly stale reaper removes them — so a cluster
management has ever seen stays judged as one, and it must then prove it
can serve the gateway with a live embedded proxy: both an explicit false
and an unproven capability are refused, since the pin cannot be revisited
later. Ownership comes from the same time-independent source, so a foreign
BYOP cluster stays refused while it is offline too.

Only a cluster no proxy has ever declared is still pinnable, which is the
address-first order the dedicated path documents.

The e2e test now walks one cluster address through all three states —
live centralised, stopped, then embedded — so the middle one is covered
against the real thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 09:51:48 +00:00
Claude 083906b84d [management] validate the proxy cluster an agent network bootstraps onto
The agent network gateway service is synthesised as private: agents reach
it over the WireGuard tunnel and are authorised by ValidateTunnelPeer
against the enabled policies' source groups, and its only target is the
cluster itself with DirectUpstream. Only a proxy running embedded in a
netbird client can serve that, which management already reports per
cluster as the `private` capability.

CreateSettings accepted any hostname as proxy_address, so a labeled
bootstrap could pin the account to a cluster that cannot serve its
gateway — another account's BYOP cluster, or one whose proxies are all
centralised. The endpoint assigned at bootstrap is immutable, so the
account is then stuck with a dead gateway until someone deletes and
re-bootstraps the settings row.

Validate the cluster before allocating an endpoint beneath it: a cluster
whose live proxies have reported their capabilities must be one the
account may route through and must be private-capable. A cluster nothing
is connected to is left alone, so claiming an address ahead of the
proxy's first connection keeps working — the same address-first order the
dedicated (self-addressed) path documents, and the one the e2e suite and
self-hosted setups follow.

The e2e coverage drives the real thing: one combined server and two
proxies in the same cluster — a centralised one that makes the cluster
live but unusable, then an embedded one that makes it usable — so both
the refusal and the acceptance are exercised against the same account and
cluster address, with the domains endpoint (the list the dashboard picks
from) as the barrier between starting a proxy and asserting on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 22:56:07 +00:00