mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-08 14:39:09 +02:00
[management] Scope the change to the private-capability check
The PR grew past its purpose. What it needs to do is refuse to bootstrap an agent network endpoint onto a cluster that cannot serve it, which is the private capability check on the picked cluster. Everything that accreted around it — canonicalising proxy addresses at connect, refusing another account's cluster or a host another account pinned, withdrawing a claim lost to a concurrent one, folding casing on migrated settings rows — is security work in its own right and moves to follow-up PRs, where each can be reviewed against its own threat rather than as a rider on this one. This restores main's version of every file outside that purpose and reduces the validation to: a cluster the account can see must have a live embedded proxy, and a cluster management holds no row for stays pinnable (address-first). The e2e test and the fixture seeds are unchanged. 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
4ed71f8987
commit
5502ea08ac
@@ -2,7 +2,6 @@ package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -15,7 +14,6 @@ import (
|
||||
type store interface {
|
||||
SaveProxy(ctx context.Context, p *proxy.Proxy) error
|
||||
DisconnectProxy(ctx context.Context, proxyID, sessionID string) error
|
||||
DeleteProxy(ctx context.Context, proxyID, sessionID string) error
|
||||
UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error
|
||||
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
||||
GetActiveProxyClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error)
|
||||
@@ -28,7 +26,6 @@ type store interface {
|
||||
GetProxyByAccountID(ctx context.Context, accountID string) (*proxy.Proxy, error)
|
||||
CountProxiesByAccountID(ctx context.Context, accountID string) (int64, error)
|
||||
IsClusterAddressConflicting(ctx context.Context, clusterAddress, accountID string) (bool, error)
|
||||
HasGatewayPinnedByOtherAccount(ctx context.Context, host, accountID string) (bool, error)
|
||||
DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error
|
||||
}
|
||||
|
||||
@@ -76,12 +73,6 @@ func (m *Manager) Connect(ctx context.Context, proxyID, sessionID, clusterAddres
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if accountID != nil {
|
||||
if err := m.confirmClusterAddressClaim(ctx, p, *accountID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.WithContext(ctx).WithFields(log.Fields{
|
||||
"proxyID": proxyID,
|
||||
"sessionID": sessionID,
|
||||
@@ -92,35 +83,6 @@ func (m *Manager) Connect(ctx context.Context, proxyID, sessionID, clusterAddres
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// confirmClusterAddressClaim re-reads availability once the proxy's row is
|
||||
// committed and withdraws the row if the claim is lost; see
|
||||
// proxy.ErrClusterAddressUnavailable for why the re-read is what closes the
|
||||
// race with a concurrent claim. An inconclusive re-read refuses the connect
|
||||
// but only marks the row disconnected: SaveProxy upserts on the proxy ID, so
|
||||
// on a reconnect the row is a claim the account already held, and a transient
|
||||
// store error must not surrender it.
|
||||
func (m *Manager) confirmClusterAddressClaim(ctx context.Context, p *proxy.Proxy, accountID string) error {
|
||||
available, err := m.IsClusterAddressAvailable(ctx, p.ClusterAddress, accountID)
|
||||
if err != nil {
|
||||
if discErr := m.store.DisconnectProxy(ctx, p.ID, p.SessionID); discErr != nil {
|
||||
log.WithContext(ctx).Errorf("failed to mark proxy %s session %s disconnected after an inconclusive claim check on %s: %v",
|
||||
p.ID, p.SessionID, p.ClusterAddress, discErr)
|
||||
}
|
||||
return fmt.Errorf("confirm claim on cluster address %s: %w", p.ClusterAddress, err)
|
||||
}
|
||||
if available {
|
||||
return nil
|
||||
}
|
||||
|
||||
if delErr := m.store.DeleteProxy(ctx, p.ID, p.SessionID); delErr != nil {
|
||||
log.WithContext(ctx).Errorf("failed to withdraw proxy %s session %s after losing the claim on %s: %v",
|
||||
p.ID, p.SessionID, p.ClusterAddress, delErr)
|
||||
}
|
||||
log.WithContext(ctx).Warnf("cluster address %s was claimed while proxy %s registered for account %s, withdrawing its row",
|
||||
p.ClusterAddress, p.ID, accountID)
|
||||
return fmt.Errorf("cluster address %s: %w", p.ClusterAddress, proxy.ErrClusterAddressUnavailable)
|
||||
}
|
||||
|
||||
// Disconnect marks a proxy as disconnected in the database.
|
||||
func (m *Manager) Disconnect(ctx context.Context, proxyID, sessionID string) error {
|
||||
if err := m.store.DisconnectProxy(ctx, proxyID, sessionID); err != nil {
|
||||
@@ -207,36 +169,12 @@ func (m *Manager) CountAccountProxies(ctx context.Context, accountID string) (in
|
||||
return m.store.CountProxiesByAccountID(ctx, accountID)
|
||||
}
|
||||
|
||||
// IsClusterAddressAvailable reports whether the account may claim this cluster
|
||||
// address.
|
||||
//
|
||||
// Two kinds of claim make an address unavailable, and both are checked here so
|
||||
// that no caller can consult one and forget the other. A proxy row is the
|
||||
// obvious one. An agent network gateway pinned to the address by another
|
||||
// account is the second: that pin is immutable and is served by whichever
|
||||
// proxy declares the address, so letting a proxy from a different account take
|
||||
// it strands the pin — an account-scoped proxy never receives another
|
||||
// account's mappings. An account claiming an address its own gateway is pinned
|
||||
// to is the intended order, not a conflict: pin first, deploy the proxy after.
|
||||
func (m *Manager) IsClusterAddressAvailable(ctx context.Context, clusterAddress, accountID string) (bool, error) {
|
||||
conflicting, err := m.store.IsClusterAddressConflicting(ctx, clusterAddress, accountID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if conflicting {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pinned, err := m.store.HasGatewayPinnedByOtherAccount(ctx, clusterAddress, accountID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pinned {
|
||||
log.WithContext(ctx).Infof("cluster address %s is pinned as another account's agent network gateway, refusing claim by account %s", clusterAddress, accountID)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return !conflicting, nil
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error {
|
||||
|
||||
Reference in New Issue
Block a user