mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-26 22:39:07 +01:00
fix applied zones
This commit is contained in:
@@ -500,15 +500,18 @@ func (c *Controller) getDNSDomainFromData(settings *nmdata.AccountSettingsInfo)
|
||||
|
||||
func IPv6AllowedPeersFromData(nmData *networkmap.NetworkMapData) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
if nmData.AccountSettings != nil {
|
||||
for _, groupID := range nmData.AccountSettings.IPv6EnabledGroups {
|
||||
group := nmData.Groups[groupID]
|
||||
if group == nil {
|
||||
continue
|
||||
}
|
||||
for _, peerID := range group.Peers {
|
||||
result[peerID] = struct{}{}
|
||||
}
|
||||
// An account with no IPv6-enabled group runs no overlay at all, so the
|
||||
// embedded-proxy carve-out below has nothing to reach and stays shut.
|
||||
if nmData.AccountSettings == nil || len(nmData.AccountSettings.IPv6EnabledGroups) == 0 {
|
||||
return result
|
||||
}
|
||||
for _, groupID := range nmData.AccountSettings.IPv6EnabledGroups {
|
||||
group := nmData.Groups[groupID]
|
||||
if group == nil {
|
||||
continue
|
||||
}
|
||||
for _, peerID := range group.Peers {
|
||||
result[peerID] = struct{}{}
|
||||
}
|
||||
}
|
||||
for id, p := range nmData.Peers {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
// The account-side builder (types.Account.peerIPv6AllowedSet) is the reference:
|
||||
// an account with no IPv6-enabled group runs no IPv6 overlay at all, embedded
|
||||
// proxy peers included — see TestPeerIPv6AllowedEmbeddedProxy. Both builders
|
||||
// gate the same AAAA records, so the store-backed one has to agree.
|
||||
func TestIPv6AllowedPeersFromData(t *testing.T) {
|
||||
data := func(enabledGroups []string) *networkmap.NetworkMapData {
|
||||
return &networkmap.NetworkMapData{
|
||||
AccountSettings: &nmdata.AccountSettingsInfo{IPv6EnabledGroups: enabledGroups},
|
||||
Peers: map[string]*nmdata.Peer{
|
||||
"peer1": {ID: "peer1"},
|
||||
"lonely": {ID: "lonely"},
|
||||
"proxy": {ID: "proxy", ProxyMeta: nmdata.ProxyMeta{Embedded: true, Cluster: "netbird.test"}},
|
||||
},
|
||||
Groups: map[string]*nmdata.Group{
|
||||
"group-devs": {ID: "group-devs", Peers: []string{"peer1"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("embedded proxy allowed when any v6 group exists, without group membership", func(t *testing.T) {
|
||||
allowed := IPv6AllowedPeersFromData(data([]string{"group-devs"}))
|
||||
assert.Contains(t, allowed, "proxy", "embedded proxy participates in v6 overlay")
|
||||
assert.Contains(t, allowed, "peer1", "regular peer in enabled group still allowed")
|
||||
})
|
||||
|
||||
t.Run("embedded proxy denied when no v6 group enabled", func(t *testing.T) {
|
||||
allowed := IPv6AllowedPeersFromData(data(nil))
|
||||
assert.NotContains(t, allowed, "proxy", "v6 disabled account-wide denies embedded proxies too")
|
||||
assert.Empty(t, allowed, "no peer participates in the v6 overlay")
|
||||
})
|
||||
|
||||
t.Run("non-embedded peer outside any enabled group is not pulled in", func(t *testing.T) {
|
||||
allowed := IPv6AllowedPeersFromData(data([]string{"group-devs"}))
|
||||
assert.NotContains(t, allowed, "lonely", "embedded-proxy bypass must not leak to regular peers")
|
||||
})
|
||||
}
|
||||
@@ -13,8 +13,8 @@ const (
|
||||
select zones.id as id, domain, not enable_search_domain as search_domain_disabled, distribution_groups,
|
||||
r.name as record_name, r.type as record_type, 'IN' record_class, r.ttl as record_ttl, r.content as record_rdata
|
||||
from zones
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=$1
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=$1 and zones.enabled
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -248,6 +248,12 @@ func ZonesToAppliedZoneCandidates(zones []Zone) ([]networkmap.AppliedZoneCandida
|
||||
}
|
||||
|
||||
if z.Id != currentZoneId {
|
||||
// The account-side builder (types.buildAppliedZoneCandidates) states
|
||||
// the shape of an applied zone: names fully qualified, served
|
||||
// non-authoritatively. Both builders feed the same client-facing map,
|
||||
// so this one has to produce the same value.
|
||||
zone.Domain = dns.Fqdn(zone.Domain)
|
||||
zone.NonAuthoritative = true
|
||||
zone.Records = []nmdata.SimpleRecord{}
|
||||
toret = append(toret, AppliedZoneCandidateFromZone(zone, distributionGroups))
|
||||
currentZoneId = z.Id
|
||||
@@ -263,7 +269,7 @@ func ZonesToAppliedZoneCandidates(zones []Zone) ([]networkmap.AppliedZoneCandida
|
||||
|
||||
lastZone := &toret[len(toret)-1]
|
||||
lastZone.Zone.Records = append(lastZone.Zone.Records, nmdata.SimpleRecord{
|
||||
Name: z.RecordName.String,
|
||||
Name: dns.Fqdn(z.RecordName.String),
|
||||
Class: z.RecordClass.String,
|
||||
TTL: int(z.RecordTTL.Int64),
|
||||
RData: rdata,
|
||||
|
||||
@@ -12,8 +12,8 @@ const (
|
||||
select zones.id as id, domain, not enable_search_domain as search_domain_disabled, distribution_groups,
|
||||
r.name as record_name, r.type as record_type, 'IN' record_class, r.ttl as record_ttl, r.content as record_rdata
|
||||
from zones
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=?
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=? and zones.enabled
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -42,15 +42,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
goproto "google.golang.org/protobuf/proto"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
gormlogger "gorm.io/gorm/logger"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
networkmap_pgsql "github.com/netbirdio/netbird/management/internals/network_map_db/pgsql"
|
||||
@@ -60,7 +60,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
"github.com/netbirdio/netbird/management/server/types/legacynmap"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
"github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestNetworkMapProtoEquivalence(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
checkAccount(ctx, t, nmStore, account, maxPeers, stats)
|
||||
checkAccount(ctx, t, testStore, nmStore, account, maxPeers, stats)
|
||||
|
||||
account = nil
|
||||
debug.FreeOSMemory()
|
||||
@@ -126,7 +126,7 @@ func TestNetworkMapProtoEquivalence(t *testing.T) {
|
||||
|
||||
// checkAccount compares both paths for every peer of one account. Nothing is
|
||||
// retained across peers, so memory stays flat within an account.
|
||||
func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.NetworkMapDBStoreImpl, account *types.Account, maxPeers int, stats *equivStats) {
|
||||
func checkAccount(ctx context.Context, t *testing.T, accountStore store.Store, nmStore *networkmapdb.NetworkMapDBStoreImpl, account *types.Account, maxPeers int, stats *equivStats) {
|
||||
t.Helper()
|
||||
|
||||
if len(account.Peers) == 0 {
|
||||
@@ -150,11 +150,14 @@ func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.Netwo
|
||||
// Production fills ValidatedPeers via the integrated-validator wrapper; here
|
||||
// every peer counts as validated, matching the legacy side's map.
|
||||
nmData.ValidatedPeers = validated
|
||||
// The legacy side receives no account zones (main sourced them from the
|
||||
// external zones manager), so the DB-sourced applied-zone candidates must be
|
||||
// dropped to keep the comparison surface identical. PrivateServiceCandidates
|
||||
// stay: all paths derive them from account/DB data.
|
||||
nmData.AppliedZoneCandidates = nil
|
||||
|
||||
// Custom DNS zones are built twice from the same rows — the account side
|
||||
// from the zones manager, the store side in SQL — so both are fed in and
|
||||
// compared rather than dropped. The same goes for the peers zone below:
|
||||
// each side computes it with its own helper, which is where an AAAA gate
|
||||
// that disagrees between the two would show up.
|
||||
accountZones, err := accountStore.GetAccountZones(ctx, store.LockingStrengthNone, account.Id)
|
||||
require.NoError(t, err, "account %s: load account zones", account.Id)
|
||||
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
@@ -176,6 +179,9 @@ func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.Netwo
|
||||
settings = &types.Settings{}
|
||||
}
|
||||
|
||||
accountPeersZone := account.GetPeersCustomZone(ctx, equivDNSName)
|
||||
storePeersZone := networkmap.PeersCustomZone(ctx, account.Id, equivDNSName, nmData.Peers, controller.IPv6AllowedPeersFromData(nmData))
|
||||
|
||||
for _, peerID := range peerIDs {
|
||||
peer := account.Peers[peerID]
|
||||
if peer == nil {
|
||||
@@ -188,7 +194,7 @@ func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.Netwo
|
||||
|
||||
// STORE PATH — nmdata store through the production computation, mirroring
|
||||
// the controller's networkMapFromData.
|
||||
components := nmData.GetPeerNetworkMapComponents(peerID, nmdata.CustomZone{})
|
||||
components := nmData.GetPeerNetworkMapComponents(peerID, storePeersZone)
|
||||
storeNM := &types.NetworkMap{Network: components.Network}
|
||||
if !components.IsEmpty() {
|
||||
storeNM = types.CalculateNetworkMapFromComponents(ctx, components)
|
||||
@@ -202,7 +208,7 @@ func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.Netwo
|
||||
|
||||
// ACCOUNT PATH — Account → toNetworkMapData twins → components.
|
||||
acctNM := account.GetPeerNetworkMapFromComponents(
|
||||
ctx, peerID, nbdns.CustomZone{}, nil, validated, resourcePolicies, routers, nil, groupUsers,
|
||||
ctx, peerID, accountPeersZone, accountZones, validated, resourcePolicies, routers, nil, groupUsers,
|
||||
)
|
||||
acctProto := mgmtgrpc.ToSyncResponse(
|
||||
ctx, nil, nil, nil, types.TwinPeer(peer), nil, nil, acctNM, equivDNSName, nil,
|
||||
@@ -211,7 +217,7 @@ func checkAccount(ctx context.Context, t *testing.T, nmStore *networkmapdb.Netwo
|
||||
|
||||
// LEGACY PATH — main's frozen copy.
|
||||
legacyNM := legacynmap.GetPeerNetworkMapFromComponents(
|
||||
&legacyAccount, ctx, peerID, nbdns.CustomZone{}, nil, validated, legacyResourcePolicies, routers, nil, groupUsers,
|
||||
&legacyAccount, ctx, peerID, accountPeersZone, accountZones, validated, legacyResourcePolicies, routers, nil, groupUsers,
|
||||
)
|
||||
if legacyNM == nil {
|
||||
t.Fatalf("after %d peers: account=%s peer=%s legacy NetworkMap nil, new non-nil", stats.peersChecked, account.Id, peerID)
|
||||
|
||||
Reference in New Issue
Block a user