mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-11 07:59:08 +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
@@ -6292,25 +6292,6 @@ func (s *SqlStore) DisconnectProxy(ctx context.Context, proxyID, sessionID strin
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProxy removes the proxy's row, but only while it still carries the
|
||||
// given session: a registration withdrawing its own claim must not take out a
|
||||
// newer session's row for the same proxy. A row already superseded or gone is
|
||||
// not an error — the claim it would have withdrawn is no longer this session's
|
||||
// to withdraw.
|
||||
func (s *SqlStore) DeleteProxy(ctx context.Context, proxyID, sessionID string) error {
|
||||
result := s.db.
|
||||
Where("id = ? AND session_id = ?", proxyID, sessionID).
|
||||
Delete(&proxy.Proxy{})
|
||||
if result.Error != nil {
|
||||
log.WithContext(ctx).Errorf("failed to delete proxy %s session %s: %v", proxyID, sessionID, result.Error)
|
||||
return status.Errorf(status.Internal, "failed to delete proxy")
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
log.WithContext(ctx).Debugf("proxy %s session %s: no row deleted (already gone or superseded by a newer session)", proxyID, sessionID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllProxies returns all reverse proxy instance rows.
|
||||
func (s *SqlStore) GetAllProxies(ctx context.Context) ([]*proxy.Proxy, error) {
|
||||
var proxies []*proxy.Proxy
|
||||
@@ -6435,13 +6416,11 @@ func (s *SqlStore) CountProxiesByAccountID(ctx context.Context, accountID string
|
||||
// queries. Backs the agent-network settings delete guard: settings cannot be
|
||||
// deleted while a proxy declares the endpoint hostname as its address.
|
||||
//
|
||||
// The comparison folds case on both sides. Addresses are canonicalized where
|
||||
// they are written now (canonicalProxyAddress on the proxy-connect path), so
|
||||
// this mostly matters for a row written before that: hostnames are
|
||||
// case-insensitive per RFC 4343, and on a case-sensitive collation a proxy
|
||||
// stored as "GW.Example.com" would otherwise slip past the guard. This runs
|
||||
// only on the settings delete path, so folding costs nothing worth indexing
|
||||
// around.
|
||||
// The comparison folds case on both sides: the caller passes a normalized
|
||||
// (lowercase) hostname, but proxies declare their cluster address verbatim
|
||||
// and Connect stores it unchanged, so on case-sensitive collations a proxy
|
||||
// declaring "GW.Example.com" would otherwise slip past the guard. Hostnames
|
||||
// are case-insensitive per RFC 4343; the guard must be too.
|
||||
func (s *SqlStore) HasActiveProxyAtClusterAddress(ctx context.Context, clusterAddress string) (bool, error) {
|
||||
var count int64
|
||||
result := s.db.
|
||||
@@ -6455,39 +6434,6 @@ func (s *SqlStore) HasActiveProxyAtClusterAddress(ctx context.Context, clusterAd
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// IsClusterAddressConflicting reports whether the address is already declared
|
||||
// by a proxy outside the account — a shared proxy or another account's. The
|
||||
// match is exact, and stays exact so it uses the cluster_address index:
|
||||
// addresses are canonicalised where they are written (canonicalProxyAddress on
|
||||
// the proxy-connect path), so one host has one spelling in this column.
|
||||
// HasForeignAccountProxyAtHost reports whether a proxy owned by another
|
||||
// account declares this host, folding case on both sides.
|
||||
//
|
||||
// Shared proxies (account_id IS NULL) are deliberately not foreign: they are
|
||||
// what most accounts pin their gateway to. What this catches is two accounts
|
||||
// claiming one hostname, which IsClusterAddressConflicting prevents going
|
||||
// forward but cannot see for a row written before addresses were
|
||||
// canonicalized.
|
||||
//
|
||||
// It folds case where IsClusterAddressConflicting stays exact because the
|
||||
// callers differ in cost and in what they can assume. That one runs on every
|
||||
// account-scoped proxy connect, where both sides are canonical and the match
|
||||
// must stay exact to use the cluster_address index. This one runs once per
|
||||
// account, when an agent network bootstraps, and is the only thing standing
|
||||
// between that account and pinning its immutable endpoint to a cluster
|
||||
// somebody else runs — worth a scan on a path taken once.
|
||||
func (s *SqlStore) HasForeignAccountProxyAtHost(ctx context.Context, host, accountID string) (bool, error) {
|
||||
var count int64
|
||||
result := s.db.
|
||||
Model(&proxy.Proxy{}).
|
||||
Where("LOWER(cluster_address) = LOWER(?) AND account_id IS NOT NULL AND account_id != ?", host, accountID).
|
||||
Count(&count)
|
||||
if result.Error != nil {
|
||||
return false, status.Errorf(status.Internal, "check proxy host ownership: %v", result.Error)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) IsClusterAddressConflicting(ctx context.Context, clusterAddress, accountID string) (bool, error) {
|
||||
var count int64
|
||||
result := s.db.
|
||||
|
||||
Reference in New Issue
Block a user