mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-14 10:49:07 +02:00
[management] Decide gateway cluster ownership before the account's own view
The ownership check only ran when the account's own view of the cluster came back empty, so an account holding any row for the host — its own canonical proxy — skipped it entirely. A row another account left behind under a non-canonical spelling was then never seen, and the pin the check exists to refuse went through. That is the case worth catching, not the one to skip: two accounts claiming one hostname is the ambiguity the connect-time conflict check prevents going forward and cannot see for a row written before addresses were canonicalized, and the endpoint pinned here cannot be moved afterwards. Ask ownership first, and narrow what counts as foreign while doing it. The query treated a shared proxy as outside the account, which was harmless while it ran only for a host the account had no view of — a shared row would have given it one — but refuses the cluster most accounts pin to once it runs first. Only a row owned by a different account is foreign now, which is also what the name says.
This commit is contained in:
@@ -6441,22 +6441,27 @@ func (s *SqlStore) HasActiveProxyAtClusterAddress(ctx context.Context, clusterAd
|
||||
// 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.
|
||||
// HasProxyOutsideAccountAtHost reports the same thing as
|
||||
// IsClusterAddressConflicting, folding case on both sides.
|
||||
// HasForeignAccountProxyAtHost reports whether a proxy owned by another
|
||||
// account declares this host, folding case on both sides.
|
||||
//
|
||||
// The two exist separately because their callers differ in cost and in what
|
||||
// they can assume. IsClusterAddressConflicting 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
|
||||
// — so it also has to see a row written before addresses were canonicalized,
|
||||
// which is worth a scan on a path taken once.
|
||||
func (s *SqlStore) HasProxyOutsideAccountAtHost(ctx context.Context, host, accountID string) (bool, error) {
|
||||
// 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 NULL OR account_id != ?)", host, accountID).
|
||||
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)
|
||||
|
||||
@@ -340,7 +340,7 @@ type Store interface {
|
||||
CountProxiesByAccountID(ctx context.Context, accountID string) (int64, error)
|
||||
IsClusterAddressConflicting(ctx context.Context, clusterAddress, accountID string) (bool, error)
|
||||
HasActiveProxyAtClusterAddress(ctx context.Context, clusterAddress string) (bool, error)
|
||||
HasProxyOutsideAccountAtHost(ctx context.Context, host, accountID string) (bool, error)
|
||||
HasForeignAccountProxyAtHost(ctx context.Context, host, accountID string) (bool, error)
|
||||
DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error
|
||||
|
||||
GetCustomDomainsCounts(ctx context.Context) (total int64, validated int64, err error)
|
||||
|
||||
@@ -3065,19 +3065,19 @@ func (mr *MockStoreMockRecorder) HasActiveProxyAtClusterAddress(ctx, clusterAddr
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasActiveProxyAtClusterAddress", reflect.TypeOf((*MockStore)(nil).HasActiveProxyAtClusterAddress), ctx, clusterAddress)
|
||||
}
|
||||
|
||||
// HasProxyOutsideAccountAtHost mocks base method.
|
||||
func (m *MockStore) HasProxyOutsideAccountAtHost(ctx context.Context, host, accountID string) (bool, error) {
|
||||
// HasForeignAccountProxyAtHost mocks base method.
|
||||
func (m *MockStore) HasForeignAccountProxyAtHost(ctx context.Context, host, accountID string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasProxyOutsideAccountAtHost", ctx, host, accountID)
|
||||
ret := m.ctrl.Call(m, "HasForeignAccountProxyAtHost", ctx, host, accountID)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HasProxyOutsideAccountAtHost indicates an expected call of HasProxyOutsideAccountAtHost.
|
||||
func (mr *MockStoreMockRecorder) HasProxyOutsideAccountAtHost(ctx, host, accountID any) *gomock.Call {
|
||||
// HasForeignAccountProxyAtHost indicates an expected call of HasForeignAccountProxyAtHost.
|
||||
func (mr *MockStoreMockRecorder) HasForeignAccountProxyAtHost(ctx, host, accountID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasProxyOutsideAccountAtHost", reflect.TypeOf((*MockStore)(nil).HasProxyOutsideAccountAtHost), ctx, host, accountID)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasForeignAccountProxyAtHost", reflect.TypeOf((*MockStore)(nil).HasForeignAccountProxyAtHost), ctx, host, accountID)
|
||||
}
|
||||
|
||||
// IncrementAgentNetworkConsumption mocks base method.
|
||||
|
||||
Reference in New Issue
Block a user