mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 02:09:08 +02:00
[management] Validate the cluster behind a self-addressed endpoint too
The service behind a self-addressed endpoint is the same private one as behind a labeled endpoint, so a proxy that already declares the hostname must be an embedded one for the gateway to be served at all. The check ran only on the labeled path; a self-addressed pin onto a hostname a centralised proxy declares went through and left a dead gateway. Both paths now validate the cluster, with the same address-first exception for a hostname no proxy has declared yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5110871524
commit
2ee84e475a
@@ -1045,6 +1045,9 @@ func (m *managerImpl) bootstrapSelfAddressed(ctx context.Context, settings *type
|
||||
if err := m.requireNotClaimedByOtherAccount(ctx, settings.AccountID, hostname, m.store.HasGatewayClusterPinnedByOtherAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.validateGatewayCluster(ctx, settings.AccountID, hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Domain = hostname
|
||||
settings.ProxyAddress = hostname
|
||||
@@ -1063,8 +1066,10 @@ func (m *managerImpl) bootstrapSelfAddressed(ctx context.Context, settings *type
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateGatewayCluster rejects a labeled bootstrap pinned to a cluster that
|
||||
// cannot serve the account's gateway.
|
||||
// validateGatewayCluster rejects a bootstrap pinned to a cluster that cannot
|
||||
// serve the account's gateway — a labeled endpoint beneath the cluster and a
|
||||
// self-addressed one on the very address a proxy declares alike, since the
|
||||
// service behind either is the same private one.
|
||||
//
|
||||
// The synthesised gateway service is unconditionally private
|
||||
// (buildAccountService): agents reach it over the WireGuard tunnel and are
|
||||
@@ -1162,10 +1167,6 @@ func (m *managerImpl) accountClusterSpellings(ctx context.Context, accountID, cl
|
||||
// checked by read and the domain unique index stays the authority, so a
|
||||
// concurrent allocation of the same tuple surfaces as a unique violation and
|
||||
// another tuple is drawn.
|
||||
//
|
||||
// Unlike the self-addressed path this pins to a cluster that must already
|
||||
// exist, so the cluster is validated before an endpoint is allocated beneath
|
||||
// it.
|
||||
func (m *managerImpl) bootstrapLabeled(ctx context.Context, settings *types.Settings, proxyAddress string) error {
|
||||
parent, err := types.NormalizeHostname(proxyAddress)
|
||||
if err != nil {
|
||||
|
||||
@@ -526,3 +526,38 @@ func TestCreateSettingsRejectsHostAnotherAccountPinned(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCreateSettingsSelfAddressedRequiresPrivateCluster pins that the
|
||||
// capability gate applies to a self-addressed endpoint too: the service behind
|
||||
// it is the same private one, so a proxy that already declares the hostname
|
||||
// must be an embedded one, whether the account's own or a shared cluster's. A
|
||||
// hostname no proxy declares yet stays claimable (TestCreateSettingsSelfAddressed).
|
||||
func TestCreateSettingsSelfAddressedRequiresPrivateCluster(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("centralised proxy at the hostname is refused", func(t *testing.T) {
|
||||
f := newBootstrapFixture(t)
|
||||
f.seedProxy(t, "central", "", "gw.example.com", ptrTo(false))
|
||||
f.expectPermission("account1", "user1", modules.AgentNetworkSettings, operations.Create, true)
|
||||
|
||||
_, err := f.createSettings(ctx, "account1", "user1", "", "gw.example.com")
|
||||
require.Error(t, err, "a self-addressed endpoint on a centralised proxy can never be served")
|
||||
var sErr *status.Error
|
||||
require.ErrorAs(t, err, &sErr)
|
||||
assert.Equal(t, status.InvalidArgument, sErr.Type())
|
||||
assert.Contains(t, err.Error(), "embedded proxy")
|
||||
|
||||
_, err = f.store.GetAgentNetworkSettings(ctx, store.LockingStrengthNone, "account1")
|
||||
assert.Error(t, err, "no row may be left behind by a rejected bootstrap")
|
||||
})
|
||||
|
||||
t.Run("embedded proxy at the hostname is accepted", func(t *testing.T) {
|
||||
f := newBootstrapFixture(t)
|
||||
f.seedProxy(t, "embedded", "", "gw.example.com", ptrTo(true))
|
||||
f.expectPermission("account1", "user1", modules.AgentNetworkSettings, operations.Create, true)
|
||||
|
||||
created, err := f.createSettings(ctx, "account1", "user1", "", "gw.example.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "gw.example.com", created.ProxyAddress)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user