mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-28 18:41:30 +02:00
feat(dns): derive the mesh DNS apex for zone-based endpoints
The zone a private service's synthesized A record hangs under was derived from the serving proxy's address or from a validated custom domain. A placement-free endpoint matches neither, so the apex came out empty, the service was skipped, and the tenant's hostname resolved to nothing -- with no error logged. Synthesized services now carry their zone explicitly and it is preferred when deriving the apex. The field is in-memory only: these services are built per read and never persisted, and the zone cannot be supplied as a parameter instead because it is captured per account at allocation time, so a single current-config value would misclassify any tenant allocated under a previous one. A blanket "use the parent of the hostname" fallback was rejected: the same empty apex also occurs for a service whose domain has no validated entry for its cluster, and those resolve to nothing deliberately, so a blanket fallback would turn domain validation into a no-op.
This commit is contained in:
@@ -945,6 +945,7 @@ func buildAccountService(
|
||||
Name: "agent-network-" + accountID,
|
||||
Domain: domain,
|
||||
ProxyCluster: cluster,
|
||||
DNSZone: settings.Zone, // empty for legacy rows → unchanged behavior
|
||||
Mode: rpservice.ModeHTTP,
|
||||
Enabled: true,
|
||||
Private: true,
|
||||
|
||||
@@ -255,6 +255,13 @@ type Service struct {
|
||||
Private bool
|
||||
// AccessGroups is the group ID allowlist for inbound peers on private services. Mutually exclusive with bearer SSO.
|
||||
AccessGroups []string `json:"access_groups,omitempty" gorm:"serializer:json"`
|
||||
// DNSZone is the parent zone a private service's synthesized mesh A record
|
||||
// hangs under, for the case where that zone cannot be derived from
|
||||
// ProxyCluster or a validated custom domain — i.e. placement-free
|
||||
// agent-network endpoints, which are <subdomain>.<zone>. In-memory only:
|
||||
// set by the agent-network synthesizer on services it builds per read,
|
||||
// never stored and never exposed on the API or the proxy wire.
|
||||
DNSZone string `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// InitNewRecord generates a new unique ID and resets metadata for a newly created
|
||||
@@ -1412,6 +1419,7 @@ func (s *Service) Copy() *Service {
|
||||
PortAutoAssigned: s.PortAutoAssigned,
|
||||
Private: s.Private,
|
||||
AccessGroups: accessGroups,
|
||||
DNSZone: s.DNSZone,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1215,6 +1215,17 @@ func TestService_Copy_RoundtripsPrivate(t *testing.T) {
|
||||
assert.Equal(t, []string{"grp-admins", "grp-ops"}, svc.AccessGroups)
|
||||
}
|
||||
|
||||
// TestServiceCopy_PreservesDNSZone — DNSZone is in-memory only, so it is easy
|
||||
// to omit from Copy()'s explicit field list; if it is dropped, a copied
|
||||
// account silently loses its zone apex and the tenant's endpoint resolves to
|
||||
// nothing.
|
||||
func TestServiceCopy_PreservesDNSZone(t *testing.T) {
|
||||
svc := &Service{Domain: "brave-otter.gateway.netbird.ai", DNSZone: "gateway.netbird.ai"}
|
||||
cp := svc.Copy()
|
||||
require.NotNil(t, cp)
|
||||
assert.Equal(t, "gateway.netbird.ai", cp.DNSZone)
|
||||
}
|
||||
|
||||
func TestService_APIRoundtrip_Private(t *testing.T) {
|
||||
enabled := true
|
||||
private := true
|
||||
|
||||
Reference in New Issue
Block a user