[management] Rework Agent Network endpoint identity and settings bootstrap (#7085)

Store the per-account gateway endpoint as {domain, proxy_address} with a
global unique index on the full hostname; dedicated = (domain ==
proxy_address). Bootstrap becomes an explicit POST carrying exactly one
of proxy_address (server allocates an adjective-noun label beneath it)
or endpoint (claimed verbatim, address-first); provider create loses its
bootstrap side effect. PUT is a full replace with every field required —
the immutable identity fields must be echoed unchanged and a mismatch is
rejected with 422. A guarded DELETE releases the endpoint: refused with
412 while providers exist or a proxy is actively serving the endpoint
hostname (matched case-insensitively); re-creating bootstraps fresh. A
self-addressed pin excludes its address from the account's cluster allow
list, and the live mapping update path now addresses the serving proxy
from the synthesized service. Existing rows are migrated on all three
store engines.
This commit is contained in:
Brad Ison
2026-08-10 19:06:55 +02:00
committed by GitHub
parent e8671a811d
commit ebfdf7d7b8
46 changed files with 2392 additions and 688 deletions
+24
View File
@@ -6340,6 +6340,30 @@ func (s *SqlStore) CountProxiesByAccountID(ctx context.Context, accountID string
return count, nil
}
// HasActiveProxyAtClusterAddress reports whether any proxy — shared or
// account-scoped — is currently active at the given cluster address, using
// the same connected-within-threshold window as the other active-proxy
// 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: 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.
Model(&proxy.Proxy{}).
Where("LOWER(cluster_address) = LOWER(?) AND status = ? AND last_seen > ?", clusterAddress, proxy.StatusConnected, time.Now().Add(-proxyActiveThreshold)).
Count(&count)
if result.Error != nil {
log.WithContext(ctx).Errorf("failed to count active proxies at cluster address: %v", result.Error)
return false, status.Errorf(status.Internal, "failed to count active proxies at cluster address")
}
return count > 0, nil
}
func (s *SqlStore) IsClusterAddressConflicting(ctx context.Context, clusterAddress, accountID string) (bool, error) {
var count int64
result := s.db.