Files
netbird/shared/management/networkmap/networkmapcompute_test.go
2026-08-06 15:57:43 +02:00

1490 lines
56 KiB
Go

package networkmap_test
import (
"context"
"fmt"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/shared/management/networkmap"
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
nbtypes "github.com/netbirdio/netbird/shared/management/types"
)
const (
targetID = "peer-target"
postureMinVersion = "0.30.0"
passingVersion = "1.0.0"
failingVersion = "0.1.0"
)
func newPeer(id string, hostNum byte) *nmdata.Peer {
return &nmdata.Peer{
ID: id,
Key: "key-" + id,
IP: netip.AddrFrom4([4]byte{100, 64, 0, hostNum}),
DNSLabel: id,
Meta: nmdata.PeerSystemMeta{WtVersion: passingVersion},
}
}
func newNMD(peers ...*nmdata.Peer) *networkmap.NetworkMapData {
nmd := &networkmap.NetworkMapData{
Peers: make(map[string]*nmdata.Peer),
Groups: make(map[string]*nmdata.Group),
ValidatedPeers: make(map[string]struct{}),
Network: &nmdata.Network{Identifier: "network-1", Serial: 7},
AccountSettings: &nmdata.AccountSettingsInfo{},
DNSSettings: &nmdata.DNSSettings{},
}
for _, p := range peers {
nmd.Peers[p.ID] = p
nmd.ValidatedPeers[p.ID] = struct{}{}
}
return nmd
}
func addGroup(nmd *networkmap.NetworkMapData, id string, peerIDs ...string) *nmdata.Group {
g := &nmdata.Group{ID: id, Name: id, Peers: peerIDs}
nmd.Groups[id] = g
return g
}
func newRule(sources, destinations []string) *nmdata.PolicyRule {
return &nmdata.PolicyRule{
Enabled: true,
Action: string(nbtypes.PolicyTrafficActionAccept),
Protocol: string(nbtypes.PolicyRuleProtocolTCP),
Bidirectional: true,
Sources: sources,
Destinations: destinations,
}
}
func newPolicy(id string, rules ...*nmdata.PolicyRule) *nmdata.Policy {
for i, r := range rules {
if r.ID == "" {
r.ID = fmt.Sprintf("%s-rule-%d", id, i)
}
r.PolicyID = id
}
return &nmdata.Policy{ID: id, Enabled: true, Rules: rules}
}
func addVersionCheck(nmd *networkmap.NetworkMapData, id, minVersion string) {
if nmd.PostureChecks == nil {
nmd.PostureChecks = make(map[string]*nmdata.PostureChecks)
}
nmd.PostureChecks[id] = &nmdata.PostureChecks{
ID: id,
Checks: nmdata.ChecksDefinition{NBVersionCheck: &nmdata.NBVersionCheck{MinVersion: minVersion}},
}
}
func compute(nmd *networkmap.NetworkMapData, peerID string) *nbtypes.NetworkMapComponents {
return nmd.GetPeerNetworkMapComponents(peerID, nmdata.CustomZone{})
}
func peerIDSet(peers map[string]*nmdata.Peer) []string {
ids := make([]string, 0, len(peers))
for id := range peers {
ids = append(ids, id)
}
return ids
}
func policyIDs(policies []*nmdata.Policy) []string {
ids := make([]string, 0, len(policies))
for _, p := range policies {
ids = append(ids, p.ID)
}
return ids
}
func groupIDSet(groups map[string]*nmdata.Group) []string {
ids := make([]string, 0, len(groups))
for id := range groups {
ids = append(ids, id)
}
return ids
}
func TestGetPeerNetworkMapComponents_UnknownPeer(t *testing.T) {
nmd := newNMD(newPeer("peer-a", 2))
c := compute(nmd, "missing")
require.True(t, c.IsEmpty())
assert.Equal(t, "missing", c.PeerID)
assert.Same(t, nmd.Network, c.Network)
require.Contains(t, c.Peers, "missing")
assert.Nil(t, c.Peers["missing"])
assert.Len(t, c.Peers, 1)
assert.Nil(t, c.AccountSettings)
assert.Nil(t, c.Policies)
assert.False(t, c.ForceRoutingPeerDNSResolution)
}
func TestGetPeerNetworkMapComponents_UnvalidatedPeer(t *testing.T) {
target := newPeer(targetID, 1)
nmd := newNMD(target)
delete(nmd.ValidatedPeers, targetID)
c := compute(nmd, targetID)
require.True(t, c.IsEmpty())
assert.Equal(t, targetID, c.PeerID)
assert.Same(t, target, c.Peers[targetID])
assert.Len(t, c.Peers, 1)
assert.Nil(t, c.AccountSettings)
assert.Nil(t, c.Groups)
}
// The forced-DNS flag must be computed even on the empty-components early
// exits, so an unknown or unvalidated proxy routing peer still starts its DNS
// forwarder.
func TestGetPeerNetworkMapComponents_EmptyComponentsKeepForcedDNSResolution(t *testing.T) {
build := func() *networkmap.NetworkMapData {
nmd := newNMD(newPeer("unval-router", 1))
delete(nmd.ValidatedPeers, "unval-router")
nmd.NetworkResources = []*nmdata.NetworkResource{
{ID: "res-1", NetworkID: "net-1", Type: string(nbtypes.ResourceTypeDomain), Enabled: true},
}
nmd.ProxyTargetedDomainResourceIDs = map[string]struct{}{"res-1": {}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {"ghost-router": {}, "unval-router": {}}}
return nmd
}
t.Run("unknown peer", func(t *testing.T) {
c := compute(build(), "ghost-router")
require.True(t, c.IsEmpty())
assert.True(t, c.ForceRoutingPeerDNSResolution)
})
t.Run("unvalidated peer", func(t *testing.T) {
c := compute(build(), "unval-router")
require.True(t, c.IsEmpty())
assert.True(t, c.ForceRoutingPeerDNSResolution)
})
}
func TestGetPeerNetworkMapComponents_ForceRoutingPeerDNSResolution(t *testing.T) {
forced := func(mutate func(*networkmap.NetworkMapData)) bool {
nmd := newNMD(newPeer(targetID, 1))
nmd.NetworkResources = []*nmdata.NetworkResource{
{ID: "res-1", NetworkID: "net-1", Type: string(nbtypes.ResourceTypeDomain), Enabled: true},
}
nmd.ProxyTargetedDomainResourceIDs = map[string]struct{}{"res-1": {}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {targetID: {}}}
if mutate != nil {
mutate(nmd)
}
return compute(nmd, targetID).ForceRoutingPeerDNSResolution
}
t.Run("router of targeted domain resource is forced", func(t *testing.T) {
assert.True(t, forced(nil))
})
t.Run("no proxy-targeted resources", func(t *testing.T) {
assert.False(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.ProxyTargetedDomainResourceIDs = nil
}))
})
t.Run("resource disabled", func(t *testing.T) {
assert.False(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.NetworkResources[0].Enabled = false
}))
})
t.Run("resource not a domain", func(t *testing.T) {
assert.False(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.NetworkResources[0].Type = string(nbtypes.ResourceTypeHost)
}))
})
t.Run("resource not targeted", func(t *testing.T) {
assert.False(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.ProxyTargetedDomainResourceIDs = map[string]struct{}{"res-other": {}}
}))
})
t.Run("peer not a router of the resource network", func(t *testing.T) {
assert.False(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {"someone-else": {}}}
}))
})
t.Run("nil resource entry tolerated", func(t *testing.T) {
assert.True(t, forced(func(nmd *networkmap.NetworkMapData) {
nmd.NetworkResources = append([]*nmdata.NetworkResource{nil}, nmd.NetworkResources...)
}))
})
}
func TestGetPeerNetworkMapComponents_CoreFieldsPassThrough(t *testing.T) {
target := newPeer(targetID, 1)
nmd := newNMD(target)
nmd.NetworkXIDToPublicID = map[string]string{"net-xid": "net-pub"}
nmd.PostureCheckXIDToPublicID = map[string]string{"pc-xid": "pc-pub"}
c := nmd.GetPeerNetworkMapComponents(targetID, nmdata.CustomZone{Domain: "acme.netbird.cloud."})
require.False(t, c.IsEmpty())
assert.Equal(t, targetID, c.PeerID)
assert.Same(t, nmd.Network, c.Network)
assert.Same(t, nmd.AccountSettings, c.AccountSettings)
assert.Same(t, nmd.DNSSettings, c.DNSSettings)
assert.Equal(t, "acme.netbird.cloud.", c.CustomZoneDomain)
assert.Equal(t, nmd.NetworkXIDToPublicID, c.NetworkXIDToPublicID)
assert.Equal(t, nmd.PostureCheckXIDToPublicID, c.PostureCheckXIDToPublicID)
assert.Equal(t, map[string]*nmdata.Peer{targetID: target}, c.Peers)
assert.Empty(t, c.Groups)
assert.Empty(t, c.Policies)
assert.Empty(t, c.Routes)
assert.Empty(t, c.NameServerGroups)
assert.Empty(t, c.NetworkResources)
assert.Empty(t, c.ResourcePoliciesMap)
assert.Empty(t, c.RoutersMap)
assert.Empty(t, c.RouterPeers)
assert.Empty(t, c.PostureFailedPeers)
assert.Nil(t, c.AllDNSRecords)
assert.Empty(t, c.AccountZones)
assert.Nil(t, c.GroupIDToUserIDs)
assert.Nil(t, c.AllowedUserIDs)
assert.False(t, c.ForceRoutingPeerDNSResolution)
}
func TestGetPeerNetworkMapComponents_OwnGroupsTrimmedWithoutMutatingStore(t *testing.T) {
target := newPeer(targetID, 1)
bystander := newPeer("peer-bystander", 2)
nmd := newNMD(target, bystander)
stored := addGroup(nmd, "g-mixed", targetID, bystander.ID)
c := compute(nmd, targetID)
require.Contains(t, c.Groups, "g-mixed")
assert.Equal(t, []string{targetID}, c.Groups["g-mixed"].Peers)
assert.NotSame(t, stored, c.Groups["g-mixed"])
assert.Equal(t, []string{targetID, bystander.ID}, stored.Peers)
}
func TestGetPeerNetworkMapComponents_PolicyRelevance(t *testing.T) {
t.Run("peer in sources pulls destination peers and groups", func(t *testing.T) {
target := newPeer(targetID, 1)
srcSibling := newPeer("peer-src-sibling", 2)
dst := newPeer("peer-dst", 3)
nmd := newNMD(target, srcSibling, dst)
addGroup(nmd, "g-src", targetID, srcSibling.ID)
addGroup(nmd, "g-dst", dst.ID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, dst.ID}, peerIDSet(c.Peers),
"source-side siblings must not be connected")
assert.ElementsMatch(t, []string{"g-src", "g-dst"}, groupIDSet(c.Groups))
assert.Equal(t, []string{targetID}, c.Groups["g-src"].Peers)
assert.Equal(t, []string{dst.ID}, c.Groups["g-dst"].Peers)
})
t.Run("peer in destinations pulls source peers and groups", func(t *testing.T) {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
nmd := newNMD(target, src)
addGroup(nmd, "g-src", src.ID)
addGroup(nmd, "g-dst", targetID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, src.ID}, peerIDSet(c.Peers))
assert.ElementsMatch(t, []string{"g-src", "g-dst"}, groupIDSet(c.Groups))
})
t.Run("unrelated policy contributes nothing", func(t *testing.T) {
target := newPeer(targetID, 1)
a := newPeer("peer-a", 2)
b := newPeer("peer-b", 3)
nmd := newNMD(target, a, b)
addGroup(nmd, "g-own", targetID)
addGroup(nmd, "g-a", a.ID)
addGroup(nmd, "g-b", b.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-a"}, []string{"g-b"}))}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
assert.ElementsMatch(t, []string{"g-own"}, groupIDSet(c.Groups))
})
t.Run("disabled policy ignored", func(t *testing.T) {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
nmd := newNMD(target, src)
addGroup(nmd, "g-src", src.ID)
addGroup(nmd, "g-dst", targetID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.Enabled = false
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("disabled rule ignored", func(t *testing.T) {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
nmd := newNMD(target, src)
addGroup(nmd, "g-src", src.ID)
addGroup(nmd, "g-dst", targetID)
rule := newRule([]string{"g-src"}, []string{"g-dst"})
rule.Enabled = false
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("peer on both sides pulls peers from both directions", func(t *testing.T) {
target := newPeer(targetID, 1)
x := newPeer("peer-x", 2)
y := newPeer("peer-y", 3)
nmd := newNMD(target, x, y)
addGroup(nmd, "g-src", targetID, x.ID)
addGroup(nmd, "g-dst", targetID, y.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, x.ID, y.ID}, peerIDSet(c.Peers),
"both the source-side and destination-side counterparts must connect")
})
t.Run("rule referencing missing group tolerated", func(t *testing.T) {
target := newPeer(targetID, 1)
nmd := newNMD(target)
addGroup(nmd, "g-dst", targetID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-ghost"}, []string{"g-dst"}))}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("nil policy and rule entries tolerated", func(t *testing.T) {
target := newPeer(targetID, 1)
dst := newPeer("peer-dst", 2)
nmd := newNMD(target, dst)
addGroup(nmd, "g-src", targetID)
addGroup(nmd, "g-dst", dst.ID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.Rules = append([]*nmdata.PolicyRule{nil}, p.Rules...)
nmd.Policies = []*nmdata.Policy{nil, p}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, dst.ID}, peerIDSet(c.Peers))
})
}
func TestGetPeerNetworkMapComponents_PeerResourceRules(t *testing.T) {
peerResource := func(id string) nmdata.Resource {
return nmdata.Resource{ID: id, Type: string(nbtypes.ResourceTypePeer)}
}
t.Run("target as source resource", func(t *testing.T) {
target := newPeer(targetID, 1)
dst := newPeer("peer-dst", 2)
nmd := newNMD(target, dst)
addGroup(nmd, "g-dst", dst.ID)
rule := newRule(nil, []string{"g-dst"})
rule.SourceResource = peerResource(targetID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, dst.ID}, peerIDSet(c.Peers))
})
t.Run("target as destination resource", func(t *testing.T) {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
nmd := newNMD(target, src)
addGroup(nmd, "g-src", src.ID)
rule := newRule([]string{"g-src"}, nil)
rule.DestinationResource = peerResource(targetID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, src.ID}, peerIDSet(c.Peers))
})
t.Run("remote peer as destination resource", func(t *testing.T) {
target := newPeer(targetID, 1)
remote := newPeer("peer-remote", 2)
nmd := newNMD(target, remote)
addGroup(nmd, "g-src", targetID)
rule := newRule([]string{"g-src"}, nil)
rule.DestinationResource = peerResource(remote.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, remote.ID}, peerIDSet(c.Peers))
})
// Legacy parity: directly referenced peers bypass the ValidatedPeers gate
// and posture checks that group-derived peers go through; the client-side
// Calculate shares this behavior via getPeerFromResource.
t.Run("unvalidated source resource peer still connects", func(t *testing.T) {
target := newPeer(targetID, 1)
unval := newPeer("peer-unval", 2)
nmd := newNMD(target, unval)
delete(nmd.ValidatedPeers, unval.ID)
addGroup(nmd, "g-dst", targetID)
rule := newRule(nil, []string{"g-dst"})
rule.SourceResource = peerResource(unval.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, unval.ID}, peerIDSet(c.Peers))
})
t.Run("source resource peer bypasses posture checks", func(t *testing.T) {
target := newPeer(targetID, 1)
failer := newPeer("peer-failer", 2)
failer.Meta.WtVersion = failingVersion
nmd := newNMD(target, failer)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-dst", targetID)
rule := newRule(nil, []string{"g-dst"})
rule.SourceResource = peerResource(failer.ID)
p := newPolicy("p-1", rule)
p.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, failer.ID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers)
})
t.Run("unrelated peer resource rule ignored", func(t *testing.T) {
target := newPeer(targetID, 1)
a := newPeer("peer-a", 2)
b := newPeer("peer-b", 3)
nmd := newNMD(target, a, b)
rule := newRule(nil, nil)
rule.SourceResource = peerResource(a.ID)
rule.DestinationResource = peerResource(b.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
}
// A destination list containing a group named "All" short-circuits peer
// expansion to that group alone, dropping peers accumulated from earlier
// groups. Groups themselves are still all shipped. Mirrors legacy behavior
// that the wire encoding depends on (see
// TestEnvelopeRoundTrip_AllGroupShortCircuitParity).
func TestGetPeerNetworkMapComponents_AllGroupShortCircuit(t *testing.T) {
target := newPeer(targetID, 1)
first := newPeer("peer-first", 2)
allMember := newPeer("peer-all-member", 3)
nmd := newNMD(target, first, allMember)
addGroup(nmd, "g-src", targetID)
addGroup(nmd, "g-first", first.ID)
nmd.Groups["g-all"] = &nmdata.Group{ID: "g-all", Name: nmdata.GroupAllName, Peers: []string{targetID, allMember.ID}}
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-first", "g-all"}))}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, allMember.ID}, peerIDSet(c.Peers),
"peers from groups before the All group must be dropped by the short-circuit")
assert.ElementsMatch(t, []string{"g-src", "g-first", "g-all"}, groupIDSet(c.Groups))
assert.Empty(t, c.Groups["g-first"].Peers)
}
func TestGetPeerNetworkMapComponents_UnvalidatedPolicyPeersExcluded(t *testing.T) {
target := newPeer(targetID, 1)
srcOK := newPeer("peer-src-ok", 2)
srcUnval := newPeer("peer-src-unval", 3)
nmd := newNMD(target, srcOK, srcUnval)
delete(nmd.ValidatedPeers, srcUnval.ID)
addGroup(nmd, "g-src", srcOK.ID, srcUnval.ID, "peer-deleted")
addGroup(nmd, "g-dst", targetID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, srcOK.ID}, peerIDSet(c.Peers),
"unvalidated and dangling group members must not connect")
assert.Equal(t, []string{srcOK.ID}, c.Groups["g-src"].Peers)
}
// Multi-group rules take the union path of getPeersFromGroups (no All-group
// short-circuit); validation and source posture checks apply per member.
func TestGetPeerNetworkMapComponents_MultiGroupSources(t *testing.T) {
target := newPeer(targetID, 1)
dup := newPeer("peer-dup", 2)
unval := newPeer("peer-unval", 3)
failer := newPeer("peer-failer", 4)
failer.Meta.WtVersion = failingVersion
solo := newPeer("peer-solo", 5)
nmd := newNMD(target, dup, unval, failer, solo)
delete(nmd.ValidatedPeers, unval.ID)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-1", targetID, dup.ID, unval.ID, "peer-deleted")
addGroup(nmd, "g-2", dup.ID, failer.ID, solo.ID)
addGroup(nmd, "g-tgt", targetID)
p := newPolicy("p-1", newRule([]string{"g-1", "g-2"}, []string{"g-tgt"}))
p.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, dup.ID, solo.ID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers,
"failer is not otherwise connected, so its failure record is pruned")
}
func TestGetPeerNetworkMapComponents_PostureChecks(t *testing.T) {
t.Run("failing source peer excluded without orphan failure record", func(t *testing.T) {
target := newPeer(targetID, 1)
failer := newPeer("peer-failer", 2)
failer.Meta.WtVersion = failingVersion
nmd := newNMD(target, failer)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-src", failer.ID)
addGroup(nmd, "g-dst", targetID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers,
"failure records for peers absent from the map must be pruned")
})
t.Run("failure recorded when peer is connected via another policy", func(t *testing.T) {
target := newPeer(targetID, 1)
failer := newPeer("peer-failer", 2)
failer.Meta.WtVersion = failingVersion
nmd := newNMD(target, failer)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-src", failer.ID)
addGroup(nmd, "g-dst", targetID)
checked := newPolicy("p-checked", newRule([]string{"g-src"}, []string{"g-dst"}))
checked.SourcePostureChecks = []string{"pc-1"}
open := newPolicy("p-open", newRule([]string{"g-src"}, []string{"g-dst"}))
nmd.Policies = []*nmdata.Policy{checked, open}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, failer.ID}, peerIDSet(c.Peers))
assert.Equal(t, map[string]map[string]struct{}{"pc-1": {failer.ID: {}}}, c.PostureFailedPeers)
})
t.Run("destination peers bypass source posture checks", func(t *testing.T) {
target := newPeer(targetID, 1)
failer := newPeer("peer-failer", 2)
failer.Meta.WtVersion = failingVersion
nmd := newNMD(target, failer)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-src", targetID)
addGroup(nmd, "g-dst", failer.ID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, failer.ID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers)
})
t.Run("target failing its own source check drops the policy", func(t *testing.T) {
target := newPeer(targetID, 1)
target.Meta.WtVersion = failingVersion
dst := newPeer("peer-dst", 2)
nmd := newNMD(target, dst)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-src", targetID)
addGroup(nmd, "g-dst", dst.ID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers)
})
t.Run("failure keyed by the first failing check", func(t *testing.T) {
target := newPeer(targetID, 1)
failer := newPeer("peer-failer", 2)
nmd := newNMD(target, failer)
addVersionCheck(nmd, "pc-pass", postureMinVersion)
addVersionCheck(nmd, "pc-fail", "2.0.0")
addGroup(nmd, "g-src", failer.ID)
addGroup(nmd, "g-dst", targetID)
checked := newPolicy("p-checked", newRule([]string{"g-src"}, []string{"g-dst"}))
checked.SourcePostureChecks = []string{"pc-pass", "pc-fail"}
open := newPolicy("p-open", newRule([]string{"g-src"}, []string{"g-dst"}))
nmd.Policies = []*nmdata.Policy{checked, open}
c := compute(nmd, targetID)
assert.Equal(t, map[string]map[string]struct{}{"pc-fail": {failer.ID: {}}}, c.PostureFailedPeers,
"the record must be keyed by the failing check, not the first listed")
})
t.Run("unknown posture check id passes everyone", func(t *testing.T) {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
nmd := newNMD(target, src)
addGroup(nmd, "g-src", src.ID)
addGroup(nmd, "g-dst", targetID)
p := newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))
p.SourcePostureChecks = []string{"pc-ghost"}
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, src.ID}, peerIDSet(c.Peers))
assert.Empty(t, c.PostureFailedPeers)
})
}
func TestGetPeerNetworkMapComponents_Routes(t *testing.T) {
t.Run("owned route relevant even when disabled", func(t *testing.T) {
target := newPeer(targetID, 1)
dist := newPeer("peer-dist", 2)
nmd := newNMD(target, dist)
addGroup(nmd, "g-dist", dist.ID)
addGroup(nmd, "g-acl")
r := &nmdata.Route{ID: "r-1", Peer: targetID, Enabled: false, Groups: []string{"g-dist"}, AccessControlGroups: []string{"g-acl"}}
nmd.Routes = []*nmdata.Route{r}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.Same(t, r, c.Routes[0])
assert.Contains(t, c.Groups, "g-dist")
assert.NotContains(t, c.Groups, "g-acl",
"access control groups of a disabled route must not be collected")
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers),
"distribution group members are not connected by the route itself")
})
t.Run("peer-group route disabled still ships and connects HA members", func(t *testing.T) {
target := newPeer(targetID, 1)
ha := newPeer("peer-ha", 2)
haUnval := newPeer("peer-ha-unval", 3)
nmd := newNMD(target, ha, haUnval)
delete(nmd.ValidatedPeers, haUnval.ID)
addGroup(nmd, "g-ha", targetID, ha.ID, haUnval.ID)
r := &nmdata.Route{ID: "r-1", PeerGroups: []string{"g-ha", "g-ghost"}, Enabled: false}
nmd.Routes = []*nmdata.Route{r}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.ElementsMatch(t, []string{targetID, ha.ID}, peerIDSet(c.Peers))
assert.Equal(t, []string{targetID, ha.ID}, c.Groups["g-ha"].Peers)
})
t.Run("route consumer connects HA routing peers from peer groups", func(t *testing.T) {
target := newPeer(targetID, 1)
router1 := newPeer("peer-router-1", 2)
router2 := newPeer("peer-router-2", 3)
routerUnval := newPeer("peer-router-unval", 4)
nmd := newNMD(target, router1, router2, routerUnval)
delete(nmd.ValidatedPeers, routerUnval.ID)
addGroup(nmd, "g-ha", router1.ID, router2.ID, routerUnval.ID)
addGroup(nmd, "g-dist", targetID)
nmd.Routes = []*nmdata.Route{{ID: "r-1", PeerGroups: []string{"g-ha"}, Groups: []string{"g-dist"}, Enabled: true}}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.ElementsMatch(t, []string{targetID, router1.ID, router2.ID}, peerIDSet(c.Peers),
"the consumer must connect to every validated HA router")
assert.Equal(t, []string{router1.ID, router2.ID}, c.Groups["g-ha"].Peers)
})
t.Run("distribution route connects routing peer", func(t *testing.T) {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
nmd := newNMD(target, router)
addGroup(nmd, "g-dist", targetID)
r := &nmdata.Route{ID: "r-1", Peer: router.ID, Enabled: true, Groups: []string{"g-dist"}}
nmd.Routes = []*nmdata.Route{r}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.ElementsMatch(t, []string{targetID, router.ID}, peerIDSet(c.Peers))
})
t.Run("disabled distribution route not relevant", func(t *testing.T) {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
nmd := newNMD(target, router)
addGroup(nmd, "g-dist", targetID)
nmd.Routes = []*nmdata.Route{{ID: "r-1", Peer: router.ID, Enabled: false, Groups: []string{"g-dist"}}}
c := compute(nmd, targetID)
assert.Empty(t, c.Routes)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("unvalidated routing peer excluded but route ships", func(t *testing.T) {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
nmd := newNMD(target, router)
delete(nmd.ValidatedPeers, router.ID)
addGroup(nmd, "g-dist", targetID)
nmd.Routes = []*nmdata.Route{{ID: "r-1", Peer: router.ID, Enabled: true, Groups: []string{"g-dist"}}}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("nil and unrelated routes skipped", func(t *testing.T) {
target := newPeer(targetID, 1)
other := newPeer("peer-other", 2)
nmd := newNMD(target, other)
addGroup(nmd, "g-dist", targetID)
addGroup(nmd, "g-foreign", other.ID)
owned := &nmdata.Route{ID: "r-owned", Peer: targetID, Enabled: true}
nmd.Routes = []*nmdata.Route{nil, {ID: "r-foreign", Peer: other.ID, Enabled: true, Groups: []string{"g-foreign"}}, owned}
c := compute(nmd, targetID)
require.Len(t, c.Routes, 1)
assert.Same(t, owned, c.Routes[0])
})
}
// A policy whose destinations hit an enabled route's access control groups is
// shipped so the routing peer can build route firewall rules, but its peers
// are not connected through this bridge.
func TestGetPeerNetworkMapComponents_RouteAccessControlBridging(t *testing.T) {
t.Run("policy targeting route ACG becomes relevant", func(t *testing.T) {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
remote := newPeer("peer-remote", 3)
nmd := newNMD(target, router, remote)
addGroup(nmd, "g-dist", targetID)
addGroup(nmd, "g-acl")
addGroup(nmd, "g-remote", remote.ID)
nmd.Routes = []*nmdata.Route{{ID: "r-1", Peer: router.ID, Enabled: true, Groups: []string{"g-dist"}, AccessControlGroups: []string{"g-acl"}}}
nmd.Policies = []*nmdata.Policy{newPolicy("p-acl", newRule([]string{"g-remote"}, []string{"g-acl"}))}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-acl"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{"g-dist", "g-acl", "g-remote"}, groupIDSet(c.Groups))
assert.ElementsMatch(t, []string{targetID, router.ID}, peerIDSet(c.Peers),
"the bridged policy's source peers must not be connected")
})
t.Run("disabled route does not bridge its ACG policies", func(t *testing.T) {
target := newPeer(targetID, 1)
remote := newPeer("peer-remote", 2)
nmd := newNMD(target, remote)
addGroup(nmd, "g-acl")
addGroup(nmd, "g-remote", remote.ID)
nmd.Routes = []*nmdata.Route{{ID: "r-1", Peer: targetID, Enabled: false, AccessControlGroups: []string{"g-acl"}}}
nmd.Policies = []*nmdata.Policy{newPolicy("p-acl", newRule([]string{"g-remote"}, []string{"g-acl"}))}
c := compute(nmd, targetID)
assert.Empty(t, c.Policies)
})
}
func TestGetPeerNetworkMapComponents_SSHRequirements(t *testing.T) {
allowedUsers := map[string]struct{}{"user-1": {}, "user-2": {}}
groupUsers := map[string][]string{"g-auth": {"user-a"}, "g-other": {"user-b"}}
cases := []struct {
name string
mutateRule func(*nmdata.PolicyRule)
sshEnabled bool
targetInSrc bool
wantAllowed bool
wantGroupsMap map[string][]string
}{
{
name: "netbird-ssh with authorized groups",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
r.AuthorizedGroups = map[string][]string{"g-auth": nil}
},
wantGroupsMap: map[string][]string{"g-auth": {"user-a"}},
},
{
name: "netbird-ssh with authorized user",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
r.AuthorizedUser = "root"
},
},
{
name: "netbird-ssh default needs allowed users",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
},
wantAllowed: true,
},
{
name: "legacy all-protocol with SSH enabled",
mutateRule: func(r *nmdata.PolicyRule) { r.Protocol = string(nbtypes.PolicyRuleProtocolALL) },
sshEnabled: true,
wantAllowed: true,
},
{
name: "legacy all-protocol with SSH disabled",
mutateRule: func(r *nmdata.PolicyRule) { r.Protocol = string(nbtypes.PolicyRuleProtocolALL) },
},
{
name: "tcp port 22 with SSH enabled",
mutateRule: func(r *nmdata.PolicyRule) { r.Ports = []string{"22"} },
sshEnabled: true,
wantAllowed: true,
},
{
name: "tcp port range covering 22",
mutateRule: func(r *nmdata.PolicyRule) { r.PortRanges = []nmdata.RulePortRange{{Start: 20, End: 30}} },
sshEnabled: true,
wantAllowed: true,
},
{
name: "tcp native ssh port 22022",
mutateRule: func(r *nmdata.PolicyRule) { r.Ports = []string{"22022"} },
sshEnabled: true,
wantAllowed: true,
},
{
name: "tcp port range covering only native ssh port",
mutateRule: func(r *nmdata.PolicyRule) { r.PortRanges = []nmdata.RulePortRange{{Start: 22000, End: 23000}} },
sshEnabled: true,
wantAllowed: true,
},
{
name: "tcp unrelated port",
mutateRule: func(r *nmdata.PolicyRule) { r.Ports = []string{"443"} },
sshEnabled: true,
},
{
name: "netbird-ssh only counts on the destination side",
mutateRule: func(r *nmdata.PolicyRule) {
r.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
},
targetInSrc: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
target := newPeer(targetID, 1)
target.SSHEnabled = tc.sshEnabled
admin := newPeer("peer-admin", 2)
nmd := newNMD(target, admin)
nmd.AllowedUserIDs = allowedUsers
nmd.GroupIDToUserIDs = groupUsers
addGroup(nmd, "g-adm", admin.ID)
addGroup(nmd, "g-tgt", targetID)
rule := newRule([]string{"g-adm"}, []string{"g-tgt"})
if tc.targetInSrc {
rule = newRule([]string{"g-tgt"}, []string{"g-adm"})
}
tc.mutateRule(rule)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
c := compute(nmd, targetID)
if tc.wantAllowed {
assert.Equal(t, allowedUsers, c.AllowedUserIDs)
} else {
assert.Nil(t, c.AllowedUserIDs)
}
assert.Equal(t, tc.wantGroupsMap, c.GroupIDToUserIDs)
})
}
}
func TestGetPeerNetworkMapComponents_DNSRecordFiltering(t *testing.T) {
record := func(name, rdata string) nmdata.SimpleRecord {
return nmdata.SimpleRecord{Name: name, Type: 1, Class: "IN", TTL: 300, RData: rdata}
}
build := func(ipv6Target bool) (*networkmap.NetworkMapData, nmdata.CustomZone) {
target := newPeer(targetID, 1)
if ipv6Target {
target.IPv6 = netip.MustParseAddr("fd00::1")
target.Meta.Capabilities = []int32{nmdata.PeerCapabilityIPv6Overlay}
}
buddy := newPeer("peer-buddy", 2)
buddy.IPv6 = netip.MustParseAddr("fd00::2")
stranger := newPeer("peer-stranger", 3)
nmd := newNMD(target, buddy, stranger)
addGroup(nmd, "g-src", targetID)
addGroup(nmd, "g-dst", buddy.ID)
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-src"}, []string{"g-dst"}))}
zone := nmdata.CustomZone{
Domain: "acme.netbird.cloud.",
Records: []nmdata.SimpleRecord{
record(targetID, "100.64.0.1"),
record("peer-buddy", "100.64.0.2"),
record("peer-stranger", "100.64.0.3"),
record("outsider", "9.9.9.9"),
record("peer-buddy-v6", "fd00::2"),
},
}
return nmd, zone
}
t.Run("records limited to relevant peers, IPv6 dropped without capability", func(t *testing.T) {
nmd, zone := build(false)
c := nmd.GetPeerNetworkMapComponents(targetID, zone)
assert.Equal(t, "acme.netbird.cloud.", c.CustomZoneDomain)
assert.Equal(t, []nmdata.SimpleRecord{
record(targetID, "100.64.0.1"),
record("peer-buddy", "100.64.0.2"),
}, c.AllDNSRecords)
})
t.Run("IPv6 records of relevant peers kept for capable target", func(t *testing.T) {
nmd, zone := build(true)
c := nmd.GetPeerNetworkMapComponents(targetID, zone)
assert.Equal(t, []nmdata.SimpleRecord{
record(targetID, "100.64.0.1"),
record("peer-buddy", "100.64.0.2"),
record("peer-buddy-v6", "fd00::2"),
}, c.AllDNSRecords)
})
t.Run("no records yields nil", func(t *testing.T) {
nmd, _ := build(false)
c := nmd.GetPeerNetworkMapComponents(targetID, nmdata.CustomZone{Domain: "acme.netbird.cloud."})
assert.Nil(t, c.AllDNSRecords)
})
}
func TestGetPeerNetworkMapComponents_AccountZones(t *testing.T) {
rec := func(name string) nmdata.SimpleRecord {
return nmdata.SimpleRecord{Name: name, Type: 1, Class: "IN", RData: "100.64.0.9"}
}
t.Run("applied and private service zones for peer groups", func(t *testing.T) {
target := newPeer(targetID, 1)
nmd := newNMD(target)
addGroup(nmd, "g-a", targetID)
appliedZone := nmdata.CustomZone{Domain: "zone-one.example.com.", Records: []nmdata.SimpleRecord{rec("z1")}}
nmd.AppliedZoneCandidates = []networkmap.AppliedZoneCandidate{
{DistributionGroups: []string{"g-a"}, Zone: appliedZone},
{DistributionGroups: []string{"g-x"}, Zone: nmdata.CustomZone{Domain: "zone-two.example.com."}},
}
nmd.PrivateServiceCandidates = []networkmap.PrivateServiceCandidate{
{AccessGroups: []string{"g-a"}, Zone: nmdata.CustomZone{Domain: "svc.example.com", SearchDomainDisabled: true, NonAuthoritative: true, Records: []nmdata.SimpleRecord{rec("svc-1")}}},
{AccessGroups: []string{"g-a"}, Zone: nmdata.CustomZone{Domain: "svc.example.com", Records: []nmdata.SimpleRecord{rec("svc-2")}}},
{AccessGroups: []string{"g-x"}, Zone: nmdata.CustomZone{Domain: "other.example.com", Records: []nmdata.SimpleRecord{rec("other")}}},
{AccessGroups: []string{"g-a"}, Zone: nmdata.CustomZone{Domain: "empty.example.com"}},
}
c := compute(nmd, targetID)
require.Len(t, c.AccountZones, 2)
assert.Equal(t, appliedZone, c.AccountZones[0])
assert.Equal(t, nmdata.CustomZone{
Domain: "svc.example.com",
SearchDomainDisabled: true,
NonAuthoritative: true,
Records: []nmdata.SimpleRecord{rec("svc-1"), rec("svc-2")},
}, c.AccountZones[1], "same-apex private service candidates must merge, flags from the first")
})
t.Run("groupless peer receives no zones", func(t *testing.T) {
target := newPeer(targetID, 1)
nmd := newNMD(target)
nmd.AppliedZoneCandidates = []networkmap.AppliedZoneCandidate{
{DistributionGroups: []string{"g-a"}, Zone: nmdata.CustomZone{Domain: "zone-one.example.com."}},
}
nmd.PrivateServiceCandidates = []networkmap.PrivateServiceCandidate{
{AccessGroups: []string{"g-a"}, Zone: nmdata.CustomZone{Domain: "svc.example.com", Records: []nmdata.SimpleRecord{rec("svc")}}},
}
c := compute(nmd, targetID)
assert.Empty(t, c.AccountZones)
})
}
func TestGetPeerNetworkMapComponents_NameServerGroups(t *testing.T) {
target := newPeer(targetID, 1)
other := newPeer("peer-other", 2)
nmd := newNMD(target, other)
addGroup(nmd, "g-own", targetID)
addGroup(nmd, "g-dst", other.ID)
addGroup(nmd, "g-foreign")
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", newRule([]string{"g-own"}, []string{"g-dst"}))}
nsOwn := &nmdata.NameServerGroup{ID: "ns-own", Enabled: true, Groups: []string{"g-own"}}
nsDst := &nmdata.NameServerGroup{ID: "ns-dst", Enabled: true, Groups: []string{"g-dst"}}
nsDisabled := &nmdata.NameServerGroup{ID: "ns-disabled", Enabled: false, Groups: []string{"g-own"}}
nsForeign := &nmdata.NameServerGroup{ID: "ns-foreign", Enabled: true, Groups: []string{"g-foreign"}}
nsBoth := &nmdata.NameServerGroup{ID: "ns-both", Enabled: true, Groups: []string{"g-own", "g-dst"}}
nmd.NameServerGroups = []*nmdata.NameServerGroup{nsOwn, nil, nsDst, nsDisabled, nsForeign, nsBoth}
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NameServerGroup{nsOwn, nsDst, nsBoth}, c.NameServerGroups,
"nameserver groups attach to any relevant group and ship once even when several groups match")
}
func TestGetPeerNetworkMapComponents_NetworkResources_SourceSide(t *testing.T) {
target := newPeer(targetID, 1)
routerOK := newPeer("peer-router-ok", 2)
routerUnval := newPeer("peer-router-unval", 3)
nmd := newNMD(target, routerOK, routerUnval)
delete(nmd.ValidatedPeers, routerUnval.ID)
addGroup(nmd, "g-clients", targetID)
addGroup(nmd, "g-resource")
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rp := newPolicy("rp-1", newRule([]string{"g-clients"}, []string{"g-resource"}))
nmd.Policies = []*nmdata.Policy{rp}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
routers := map[string]*nmdata.NetworkRouter{
routerOK.ID: {Metric: 100},
routerUnval.ID: {Metric: 200},
}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": routers}
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
assert.Equal(t, map[string][]*nmdata.Policy{"res-1": {rp}}, c.ResourcePoliciesMap)
assert.Equal(t, map[string]map[string]*nmdata.NetworkRouter{"net-1": routers}, c.RoutersMap)
assert.ElementsMatch(t, []string{routerOK.ID, routerUnval.ID}, peerIDSet(c.RouterPeers),
"RouterPeers carries all routing peers regardless of validation")
assert.ElementsMatch(t, []string{targetID, routerOK.ID}, peerIDSet(c.Peers),
"only validated routing peers are connected")
assert.ElementsMatch(t, []string{"g-clients", "g-resource"}, groupIDSet(c.Groups))
}
func TestGetPeerNetworkMapComponents_NetworkResources_RouterSide(t *testing.T) {
t.Run("posture-valid validated source peers connected, failures recorded", func(t *testing.T) {
target := newPeer(targetID, 1)
clientOK := newPeer("peer-client-ok", 2)
clientUnval := newPeer("peer-client-unval", 3)
clientFail := newPeer("peer-client-fail", 4)
clientFail.Meta.WtVersion = failingVersion
nmd := newNMD(target, clientOK, clientUnval, clientFail)
delete(nmd.ValidatedPeers, clientUnval.ID)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-clients", clientOK.ID, clientUnval.ID, clientFail.ID)
addGroup(nmd, "g-resource")
addGroup(nmd, "g-tgt", targetID)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rp := newPolicy("rp-1", newRule([]string{"g-clients"}, []string{"g-resource"}))
rp.SourcePostureChecks = []string{"pc-1"}
acl := newPolicy("p-acl", newRule([]string{"g-clients"}, []string{"g-tgt"}))
nmd.Policies = []*nmdata.Policy{acl}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {targetID: {Metric: 100}}}
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.RouterPeers))
assert.ElementsMatch(t, []string{targetID, clientOK.ID, clientFail.ID}, peerIDSet(c.Peers),
"clientFail connects via the open ACL policy, clientUnval never connects")
assert.Equal(t, map[string]map[string]struct{}{"pc-1": {clientFail.ID: {}}}, c.PostureFailedPeers)
})
t.Run("peer source resource collects exactly that peer", func(t *testing.T) {
target := newPeer(targetID, 1)
client := newPeer("peer-client", 2)
other := newPeer("peer-other", 3)
nmd := newNMD(target, client, other)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rule := newRule(nil, nil)
rule.SourceResource = nmdata.Resource{ID: client.ID, Type: string(nbtypes.ResourceTypePeer)}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {newPolicy("rp-1", rule)}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {targetID: {}}}
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
assert.ElementsMatch(t, []string{targetID, client.ID}, peerIDSet(c.Peers))
})
t.Run("multiple source groups unioned, missing group tolerated", func(t *testing.T) {
target := newPeer(targetID, 1)
c1 := newPeer("peer-c1", 2)
c2 := newPeer("peer-c2", 3)
nmd := newNMD(target, c1, c2)
addGroup(nmd, "g-c1", c1.ID)
addGroup(nmd, "g-c2", c2.ID)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{
"res-1": {newPolicy("rp-1", newRule([]string{"g-c1", "g-c2", "g-ghost"}, nil))},
}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {targetID: {}}}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, c1.ID, c2.ID}, peerIDSet(c.Peers))
})
}
func TestGetPeerNetworkMapComponents_NetworkResources_PeerResourceSource(t *testing.T) {
build := func(sourcePeerID string) *networkmap.NetworkMapData {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
other := newPeer("peer-other", 3)
nmd := newNMD(target, router, other)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rule := newRule(nil, nil)
rule.SourceResource = nmdata.Resource{ID: sourcePeerID, Type: string(nbtypes.ResourceTypePeer)}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {newPolicy("rp-1", rule)}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {"peer-router": {}}}
return nmd
}
t.Run("target named as source resource gains access", func(t *testing.T) {
c := compute(build(targetID), targetID)
assert.Len(t, c.NetworkResources, 1)
assert.ElementsMatch(t, []string{targetID, "peer-router"}, peerIDSet(c.Peers))
})
t.Run("other peer named as source resource denies target", func(t *testing.T) {
c := compute(build("peer-other"), targetID)
assert.Empty(t, c.NetworkResources)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
}
func TestGetPeerNetworkMapComponents_NetworkResources_Gating(t *testing.T) {
build := func() (*networkmap.NetworkMapData, *nmdata.NetworkResource, *nmdata.Policy) {
target := newPeer(targetID, 1)
router := newPeer("peer-router", 2)
nmd := newNMD(target, router)
addGroup(nmd, "g-clients", targetID)
addGroup(nmd, "g-resource")
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rp := newPolicy("rp-1", newRule([]string{"g-clients"}, []string{"g-resource"}))
nmd.Policies = []*nmdata.Policy{rp}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {router.ID: {}}}
return nmd, res, rp
}
assertResourceSkipped := func(t *testing.T, c *nbtypes.NetworkMapComponents) {
t.Helper()
assert.Empty(t, c.NetworkResources)
assert.Empty(t, c.RoutersMap)
assert.Empty(t, c.RouterPeers)
assert.Empty(t, c.ResourcePoliciesMap)
}
t.Run("baseline grants access", func(t *testing.T) {
nmd, res, _ := build()
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
})
t.Run("disabled resource skipped", func(t *testing.T) {
nmd, res, _ := build()
res.Enabled = false
assertResourceSkipped(t, compute(nmd, targetID))
})
t.Run("resource without policies skipped", func(t *testing.T) {
nmd, _, _ := build()
nmd.ResourcePolicies = nil
assertResourceSkipped(t, compute(nmd, targetID))
})
t.Run("peer neither router nor in sources skipped", func(t *testing.T) {
nmd, _, _ := build()
nmd.Groups["g-clients"].Peers = []string{"peer-router"}
c := compute(nmd, targetID)
assertResourceSkipped(t, c)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
t.Run("nil and rule-less resource policy entries tolerated", func(t *testing.T) {
nmd, res, rp := build()
nmd.NetworkResources = append([]*nmdata.NetworkResource{nil}, nmd.NetworkResources...)
rp.Rules = append(rp.Rules, nil)
nmd.ResourcePolicies["res-1"] = append([]*nmdata.Policy{nil, {ID: "rp-empty", Enabled: true}}, nmd.ResourcePolicies["res-1"]...)
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources,
"poisoned sibling entries must not prevent the valid policy from granting access")
assert.NotPanics(t, func() { c.Calculate(context.Background()) },
"the downstream network map calculation must survive the poisoned components")
})
t.Run("granting policy without routers still ships the resource", func(t *testing.T) {
nmd, res, rp := build()
nmd.Routers = nil
c := compute(nmd, targetID)
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
assert.Equal(t, map[string][]*nmdata.Policy{"res-1": {rp}}, c.ResourcePoliciesMap)
assert.Contains(t, c.RoutersMap, "net-1")
assert.Empty(t, c.RoutersMap["net-1"])
assert.Empty(t, c.RouterPeers)
})
t.Run("target failing resource policy posture check skipped", func(t *testing.T) {
nmd, _, rp := build()
addVersionCheck(nmd, "pc-1", postureMinVersion)
rp.SourcePostureChecks = []string{"pc-1"}
nmd.Policies = nil
nmd.Peers[targetID].Meta.WtVersion = failingVersion
c := compute(nmd, targetID)
assertResourceSkipped(t, c)
assert.Empty(t, c.PostureFailedPeers)
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
})
}
// Legacy parity: resource-policy access consults only Rules[0] for peer-type
// sources, while group sources union across all rules via SourceGroups.
func TestGetPeerNetworkMapComponents_MultiRulePolicies(t *testing.T) {
t.Run("policy matching via multiple rules ships once", func(t *testing.T) {
target := newPeer(targetID, 1)
a := newPeer("peer-a", 2)
b := newPeer("peer-b", 3)
nmd := newNMD(target, a, b)
addGroup(nmd, "g-tgt", targetID)
addGroup(nmd, "g-a", a.ID)
addGroup(nmd, "g-b", b.ID)
p := newPolicy("p-1",
newRule([]string{"g-tgt"}, []string{"g-a"}),
newRule([]string{"g-tgt"}, []string{"g-b"}))
nmd.Policies = []*nmdata.Policy{p}
c := compute(nmd, targetID)
assert.Equal(t, []string{"p-1"}, policyIDs(c.Policies))
assert.ElementsMatch(t, []string{targetID, a.ID, b.ID}, peerIDSet(c.Peers))
})
t.Run("resource access consults only the first rule's peer source", func(t *testing.T) {
target := newPeer(targetID, 1)
other := newPeer("peer-other", 2)
router := newPeer("peer-router", 3)
nmd := newNMD(target, other, router)
addGroup(nmd, "g-other", other.ID)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
second := newRule(nil, nil)
second.SourceResource = nmdata.Resource{ID: targetID, Type: string(nbtypes.ResourceTypePeer)}
rp := newPolicy("rp-1", newRule([]string{"g-other"}, nil), second)
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {router.ID: {}}}
c := compute(nmd, targetID)
assert.Empty(t, c.NetworkResources,
"a second rule naming the target as peer source must not grant resource access")
})
t.Run("router-side source collection consults only the first rule's peer source", func(t *testing.T) {
target := newPeer(targetID, 1)
x := newPeer("peer-x", 2)
y := newPeer("peer-y", 3)
nmd := newNMD(target, x, y)
addGroup(nmd, "g-y", y.ID)
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
first := newRule(nil, nil)
first.SourceResource = nmdata.Resource{ID: x.ID, Type: string(nbtypes.ResourceTypePeer)}
rp := newPolicy("rp-1", first, newRule([]string{"g-y"}, nil))
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {targetID: {}}}
c := compute(nmd, targetID)
assert.ElementsMatch(t, []string{targetID, x.ID}, peerIDSet(c.Peers),
"second-rule group sources are not collected when the first rule names a peer")
})
}
// Characterization of legacy parity: once one resource policy grants the peer
// access, the source peers of the resource's subsequent policies are collected
// as if the peer were a router.
func TestGetPeerNetworkMapComponents_NetworkResources_LaterPoliciesContributeSourcePeers(t *testing.T) {
target := newPeer(targetID, 1)
otherSrc := newPeer("peer-other-src", 2)
router := newPeer("peer-router", 3)
nmd := newNMD(target, otherSrc, router)
addGroup(nmd, "g-a", targetID)
addGroup(nmd, "g-b", otherSrc.ID)
addGroup(nmd, "g-resource")
res := &nmdata.NetworkResource{ID: "res-1", NetworkID: "net-1", Enabled: true}
nmd.NetworkResources = []*nmdata.NetworkResource{res}
rpA := newPolicy("rp-a", newRule([]string{"g-a"}, []string{"g-resource"}))
rpB := newPolicy("rp-b", newRule([]string{"g-b"}, []string{"g-resource"}))
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rpA, rpB}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {router.ID: {}}}
c := compute(nmd, targetID)
assert.Contains(t, c.Peers, otherSrc.ID)
}
func TestGetPeerNetworkMapComponents_StoreImmutableAndDeterministic(t *testing.T) {
zone := nmdata.CustomZone{
Domain: "acme.netbird.cloud.",
Records: []nmdata.SimpleRecord{{Name: "peer-src", Type: 1, Class: "IN", TTL: 300, RData: "100.64.0.2"}},
}
build := func() *networkmap.NetworkMapData {
target := newPeer(targetID, 1)
src := newPeer("peer-src", 2)
failer := newPeer("peer-failer", 3)
failer.Meta.WtVersion = failingVersion
router := newPeer("peer-router", 4)
resRouter := newPeer("peer-res-router", 5)
nmd := newNMD(target, src, failer, router, resRouter)
addVersionCheck(nmd, "pc-1", postureMinVersion)
addGroup(nmd, "g-src", src.ID, failer.ID)
addGroup(nmd, "g-dst", targetID, src.ID)
addGroup(nmd, "g-dist", targetID, src.ID)
addGroup(nmd, "g-auth", src.ID)
addGroup(nmd, "g-clients", targetID)
addGroup(nmd, "g-resource")
nmd.AllowedUserIDs = map[string]struct{}{"user-1": {}}
nmd.GroupIDToUserIDs = map[string][]string{"g-auth": {"user-a"}}
checked := newPolicy("p-checked", newRule([]string{"g-src"}, []string{"g-dst"}))
checked.SourcePostureChecks = []string{"pc-1"}
open := newPolicy("p-open", newRule([]string{"g-src"}, []string{"g-dst"}))
sshAuth := newRule([]string{"g-src"}, []string{"g-dst"})
sshAuth.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
sshAuth.AuthorizedGroups = map[string][]string{"g-auth": nil}
sshPlain := newRule([]string{"g-src"}, []string{"g-dst"})
sshPlain.Protocol = string(nbtypes.PolicyRuleProtocolNetbirdSSH)
rp := newPolicy("rp-1", newRule([]string{"g-clients"}, []string{"g-resource"}))
nmd.Policies = []*nmdata.Policy{checked, open, newPolicy("p-ssh", sshAuth, sshPlain), rp}
nmd.Routes = []*nmdata.Route{{ID: "r-1", Peer: router.ID, Enabled: true, Groups: []string{"g-dist"}}}
nmd.NetworkResources = []*nmdata.NetworkResource{{ID: "res-1", NetworkID: "net-1", Enabled: true}}
nmd.ResourcePolicies = map[string][]*nmdata.Policy{"res-1": {rp}}
nmd.Routers = map[string]map[string]*nmdata.NetworkRouter{"net-1": {resRouter.ID: {Metric: 100}}}
nmd.NameServerGroups = []*nmdata.NameServerGroup{{ID: "ns-1", Enabled: true, Groups: []string{"g-dst"}}}
nmd.AppliedZoneCandidates = []networkmap.AppliedZoneCandidate{
{DistributionGroups: []string{"g-dst"}, Zone: nmdata.CustomZone{Domain: "zone.example.com.", Records: []nmdata.SimpleRecord{{Name: "z", Type: 1, RData: "100.64.0.9"}}}},
}
nmd.PrivateServiceCandidates = []networkmap.PrivateServiceCandidate{
{AccessGroups: []string{"g-dst"}, Zone: nmdata.CustomZone{Domain: "svc.example.com", Records: []nmdata.SimpleRecord{{Name: "s", Type: 1, RData: "100.64.0.8"}}}},
}
return nmd
}
nmd := build()
groupSnapshots := make(map[string][]string, len(nmd.Groups))
for id, g := range nmd.Groups {
groupSnapshots[id] = append([]string(nil), g.Peers...)
}
first := nmd.GetPeerNetworkMapComponents(targetID, zone)
_ = nmd.GetPeerNetworkMapComponents("peer-src", zone)
second := nmd.GetPeerNetworkMapComponents(targetID, zone)
for id, g := range nmd.Groups {
assert.Equal(t, groupSnapshots[id], g.Peers, "group %s mutated in the store", id)
}
for name, field := range map[string]any{
"Peers": first.Peers,
"PostureFailedPeers": first.PostureFailedPeers,
"RoutersMap": first.RoutersMap,
"RouterPeers": first.RouterPeers,
"NetworkResources": first.NetworkResources,
"NameServerGroups": first.NameServerGroups,
"AccountZones": first.AccountZones,
"AllDNSRecords": first.AllDNSRecords,
"AllowedUserIDs": first.AllowedUserIDs,
"GroupIDToUserIDs": first.GroupIDToUserIDs,
"ResourcePoliciesMap": first.ResourcePoliciesMap,
} {
require.NotEmpty(t, field, "fixture must populate %s or the determinism check is vacuous", name)
}
assert.Equal(t, first.Peers, second.Peers)
assert.Equal(t, first.Groups, second.Groups)
assert.Equal(t, first.Policies, second.Policies)
assert.Equal(t, first.Routes, second.Routes)
assert.Equal(t, first.PostureFailedPeers, second.PostureFailedPeers)
assert.Equal(t, first.ResourcePoliciesMap, second.ResourcePoliciesMap)
assert.Equal(t, first.RoutersMap, second.RoutersMap)
assert.Equal(t, first.RouterPeers, second.RouterPeers)
assert.Equal(t, first.NetworkResources, second.NetworkResources)
assert.Equal(t, first.NameServerGroups, second.NameServerGroups)
assert.Equal(t, first.AccountZones, second.AccountZones)
assert.Equal(t, first.AllDNSRecords, second.AllDNSRecords)
assert.Equal(t, first.AllowedUserIDs, second.AllowedUserIDs)
assert.Equal(t, first.GroupIDToUserIDs, second.GroupIDToUserIDs)
}
func TestNetworkMapData_GetPeerGroups(t *testing.T) {
target := newPeer(targetID, 1)
other := newPeer("peer-other", 2)
nmd := newNMD(target, other)
addGroup(nmd, "g-1", targetID, other.ID)
addGroup(nmd, "g-2", targetID)
addGroup(nmd, "g-3", other.ID)
nmd.Groups["g-nil"] = nil
assert.Equal(t, map[string]struct{}{"g-1": {}, "g-2": {}}, nmd.GetPeerGroups(targetID))
assert.Empty(t, nmd.GetPeerGroups("missing"))
}