mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-02 04:51:29 +02:00
Merge branch 'main' into embedded-vnc
This commit is contained in:
@@ -324,19 +324,13 @@ func (nmd *NetworkMapData) getPeersGroupsPoliciesRoutes(
|
||||
var peerInSources, peerInDestinations bool
|
||||
|
||||
if rule.SourceResource.Type == string(types.ResourceTypePeer) && rule.SourceResource.ID != "" {
|
||||
sourcePeers = []string{rule.SourceResource.ID}
|
||||
if rule.SourceResource.ID == peerID {
|
||||
peerInSources = true
|
||||
}
|
||||
sourcePeers, peerInSources = nmd.getPeerFromResource(rule.SourceResource, peerID, policy.SourcePostureChecks, postureFailedPeers)
|
||||
} else {
|
||||
sourcePeers, peerInSources = nmd.getPeersFromGroups(rule.Sources, peerID, policy.SourcePostureChecks, postureFailedPeers)
|
||||
}
|
||||
|
||||
if rule.DestinationResource.Type == string(types.ResourceTypePeer) && rule.DestinationResource.ID != "" {
|
||||
destinationPeers = []string{rule.DestinationResource.ID}
|
||||
if rule.DestinationResource.ID == peerID {
|
||||
peerInDestinations = true
|
||||
}
|
||||
destinationPeers, peerInDestinations = nmd.getPeerFromResource(rule.DestinationResource, peerID, nil, postureFailedPeers)
|
||||
} else {
|
||||
destinationPeers, peerInDestinations = nmd.getPeersFromGroups(rule.Destinations, peerID, nil, postureFailedPeers)
|
||||
}
|
||||
@@ -403,30 +397,16 @@ func (nmd *NetworkMapData) getPeersFromGroups(groups []string, peerID string, so
|
||||
filteredPeerIDs = make([]string, 0, len(group.Peers))
|
||||
peerInGroups = false
|
||||
for _, pid := range group.Peers {
|
||||
peer, ok := nmd.Peers[pid]
|
||||
if !ok || peer == nil {
|
||||
if !nmd.admitPolicyPeer(pid, sourcePostureChecksIDs, postureFailedPeers) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := nmd.ValidatedPeers[peer.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(sourcePostureChecksIDs, peer.ID)
|
||||
if !isValid && len(pname) > 0 {
|
||||
if _, ok := (*postureFailedPeers)[pname]; !ok {
|
||||
(*postureFailedPeers)[pname] = make(map[string]struct{})
|
||||
}
|
||||
(*postureFailedPeers)[pname][peer.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.ID == peerID {
|
||||
if pid == peerID {
|
||||
peerInGroups = true
|
||||
continue
|
||||
}
|
||||
|
||||
filteredPeerIDs = append(filteredPeerIDs, peer.ID)
|
||||
filteredPeerIDs = append(filteredPeerIDs, pid)
|
||||
}
|
||||
return filteredPeerIDs, peerInGroups
|
||||
}
|
||||
@@ -436,36 +416,59 @@ func (nmd *NetworkMapData) getPeersFromGroups(groups []string, peerID string, so
|
||||
continue
|
||||
}
|
||||
seenPeerIds[pid] = struct{}{}
|
||||
peer, ok := nmd.Peers[pid]
|
||||
if !ok || peer == nil {
|
||||
if !nmd.admitPolicyPeer(pid, sourcePostureChecksIDs, postureFailedPeers) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := nmd.ValidatedPeers[peer.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(sourcePostureChecksIDs, peer.ID)
|
||||
if !isValid && len(pname) > 0 {
|
||||
if _, ok := (*postureFailedPeers)[pname]; !ok {
|
||||
(*postureFailedPeers)[pname] = make(map[string]struct{})
|
||||
}
|
||||
(*postureFailedPeers)[pname][peer.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.ID == peerID {
|
||||
if pid == peerID {
|
||||
peerInGroups = true
|
||||
continue
|
||||
}
|
||||
|
||||
filteredPeerIDs = append(filteredPeerIDs, peer.ID)
|
||||
filteredPeerIDs = append(filteredPeerIDs, pid)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredPeerIDs, peerInGroups
|
||||
}
|
||||
|
||||
// getPeerFromResource resolves a rule side that names a peer directly, admitting it
|
||||
// like a member of a group holding only that peer.
|
||||
func (nmd *NetworkMapData) getPeerFromResource(resource nmdata.Resource, peerID string, sourcePostureChecksIDs []string,
|
||||
postureFailedPeers *map[string]map[string]struct{}) ([]string, bool) {
|
||||
if !nmd.admitPolicyPeer(resource.ID, sourcePostureChecksIDs, postureFailedPeers) {
|
||||
return nil, false
|
||||
}
|
||||
if resource.ID == peerID {
|
||||
return nil, true
|
||||
}
|
||||
return []string{resource.ID}, false
|
||||
}
|
||||
|
||||
// admitPolicyPeer applies the per-peer admission of a rule side: the peer must exist,
|
||||
// be validated and pass the rule's posture checks. A failed check is recorded in
|
||||
// postureFailedPeers.
|
||||
func (nmd *NetworkMapData) admitPolicyPeer(pid string, sourcePostureChecksIDs []string, postureFailedPeers *map[string]map[string]struct{}) bool {
|
||||
peer, ok := nmd.Peers[pid]
|
||||
if !ok || peer == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := nmd.ValidatedPeers[pid]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
isValid, pname := nmd.validatePostureChecksOnPeerGetFailed(sourcePostureChecksIDs, pid)
|
||||
if !isValid && len(pname) > 0 {
|
||||
if _, ok := (*postureFailedPeers)[pname]; !ok {
|
||||
(*postureFailedPeers)[pname] = make(map[string]struct{})
|
||||
}
|
||||
(*postureFailedPeers)[pname][pid] = struct{}{}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (nmd *NetworkMapData) validatePostureChecksOnPeerGetFailed(sourcePostureChecksID []string, peerID string) (bool, string) {
|
||||
peer, ok := nmd.Peers[peerID]
|
||||
if !ok || peer == nil {
|
||||
|
||||
@@ -448,10 +448,9 @@ func TestGetPeerNetworkMapComponents_PeerResourceRules(t *testing.T) {
|
||||
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) {
|
||||
// A directly referenced peer is admitted like a member of a group holding only
|
||||
// that peer: the ValidatedPeers gate and the posture checks apply equally.
|
||||
t.Run("unvalidated source resource peer is excluded", func(t *testing.T) {
|
||||
target := newPeer(targetID, 1)
|
||||
unval := newPeer("peer-unval", 2)
|
||||
nmd := newNMD(target, unval)
|
||||
@@ -463,10 +462,10 @@ func TestGetPeerNetworkMapComponents_PeerResourceRules(t *testing.T) {
|
||||
|
||||
c := compute(nmd, targetID)
|
||||
|
||||
assert.ElementsMatch(t, []string{targetID, unval.ID}, peerIDSet(c.Peers))
|
||||
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
|
||||
})
|
||||
|
||||
t.Run("source resource peer bypasses posture checks", func(t *testing.T) {
|
||||
t.Run("source resource peer failing posture checks is excluded", func(t *testing.T) {
|
||||
target := newPeer(targetID, 1)
|
||||
failing := newPeer("peer-failing", 2)
|
||||
failing.Meta.WtVersion = failingVersion
|
||||
@@ -481,10 +480,65 @@ func TestGetPeerNetworkMapComponents_PeerResourceRules(t *testing.T) {
|
||||
|
||||
c := compute(nmd, targetID)
|
||||
|
||||
assert.ElementsMatch(t, []string{targetID, failing.ID}, peerIDSet(c.Peers))
|
||||
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
|
||||
assert.Empty(t, c.PostureFailedPeers)
|
||||
})
|
||||
|
||||
t.Run("direct source peer failure recorded when connected via another policy", func(t *testing.T) {
|
||||
target := newPeer(targetID, 1)
|
||||
failing := newPeer("peer-failing", 2)
|
||||
failing.Meta.WtVersion = failingVersion
|
||||
nmd := newNMD(target, failing)
|
||||
addVersionCheck(nmd, "pc-1", postureMinVersion)
|
||||
addGroup(nmd, "g-dst", targetID)
|
||||
checkedRule := newRule(nil, []string{"g-dst"})
|
||||
checkedRule.SourceResource = peerResource(failing.ID)
|
||||
checked := newPolicy("p-checked", checkedRule)
|
||||
checked.SourcePostureChecks = []string{"pc-1"}
|
||||
openRule := newRule(nil, []string{"g-dst"})
|
||||
openRule.SourceResource = peerResource(failing.ID)
|
||||
nmd.Policies = []*nmdata.Policy{checked, newPolicy("p-open", openRule)}
|
||||
|
||||
c := compute(nmd, targetID)
|
||||
|
||||
assert.ElementsMatch(t, []string{targetID, failing.ID}, peerIDSet(c.Peers))
|
||||
assert.Equal(t, map[string]map[string]struct{}{"pc-1": {failing.ID: {}}}, c.PostureFailedPeers)
|
||||
})
|
||||
|
||||
t.Run("target as source resource failing posture checks gets no 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-dst", dst.ID)
|
||||
rule := newRule(nil, []string{"g-dst"})
|
||||
rule.SourceResource = peerResource(targetID)
|
||||
p := newPolicy("p-1", rule)
|
||||
p.SourcePostureChecks = []string{"pc-1"}
|
||||
nmd.Policies = []*nmdata.Policy{p}
|
||||
|
||||
c := compute(nmd, targetID)
|
||||
|
||||
assert.Empty(t, policyIDs(c.Policies))
|
||||
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
|
||||
})
|
||||
|
||||
t.Run("unvalidated destination resource peer is excluded", 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-src", targetID)
|
||||
rule := newRule([]string{"g-src"}, nil)
|
||||
rule.DestinationResource = peerResource(unval.ID)
|
||||
nmd.Policies = []*nmdata.Policy{newPolicy("p-1", rule)}
|
||||
|
||||
c := compute(nmd, targetID)
|
||||
|
||||
assert.ElementsMatch(t, []string{targetID}, peerIDSet(c.Peers))
|
||||
})
|
||||
|
||||
t.Run("unrelated peer resource rule ignored", func(t *testing.T) {
|
||||
target := newPeer(targetID, 1)
|
||||
a := newPeer("peer-a", 2)
|
||||
|
||||
@@ -45,6 +45,22 @@ func PassesChecks(checks []Check, peer *Peer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PostureVerdictChanged reports whether any check in the bundles gives a different
|
||||
// verdict for newPeer than for oldPeer. Checks are replayed one by one, so a change
|
||||
// that moves a field but stays on the same side of a threshold does not count. An
|
||||
// evaluation error is a deny, like in PassesChecks.
|
||||
func PostureVerdictChanged(checks []*PostureChecks, oldPeer, newPeer *Peer) bool {
|
||||
for _, pc := range checks {
|
||||
for _, c := range pc.GetChecks() {
|
||||
single := []Check{c}
|
||||
if PassesChecks(single, oldPeer) != PassesChecks(single, newPeer) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetChecks returns the initialized checks in the same order as posture.Checks.GetChecks.
|
||||
func (pc *PostureChecks) GetChecks() []Check {
|
||||
var checks []Check
|
||||
|
||||
54
shared/management/networkmap/nmdata/posture_test.go
Normal file
54
shared/management/networkmap/nmdata/posture_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package nmdata
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func bundle(def ChecksDefinition) []*PostureChecks {
|
||||
return []*PostureChecks{{Checks: def}}
|
||||
}
|
||||
|
||||
func TestPostureVerdictChanged_ErrorCountsAsDeny(t *testing.T) {
|
||||
c := bundle(ChecksDefinition{NBVersionCheck: &NBVersionCheck{MinVersion: "1.2.0"}})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
oldVer, newVer string
|
||||
want bool
|
||||
}{
|
||||
{"both above min, no flip", "1.3.0", "1.4.0", false},
|
||||
{"crosses up below->above", "1.1.0", "1.3.0", true},
|
||||
{"unparsable old only -> flip", "garbage", "1.3.0", true},
|
||||
{"unparsable both -> no flip", "garbage", "junk", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
oldPeer := &Peer{Meta: PeerSystemMeta{WtVersion: tt.oldVer}}
|
||||
newPeer := &Peer{Meta: PeerSystemMeta{WtVersion: tt.newVer}}
|
||||
assert.Equal(t, tt.want, PostureVerdictChanged(c, oldPeer, newPeer))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostureVerdictChanged_ReplaysEachCheck(t *testing.T) {
|
||||
// Old fails the version check, new fails the kernel check: the bundle denies on
|
||||
// both sides, yet every single check flipped, so the posture must be re-evaluated.
|
||||
c := bundle(ChecksDefinition{
|
||||
NBVersionCheck: &NBVersionCheck{MinVersion: "1.0.0"},
|
||||
OSVersionCheck: &OSVersionCheck{Linux: &MinKernelVersionCheck{MinKernelVersion: "5.0.0"}},
|
||||
})
|
||||
oldPeer := &Peer{Meta: PeerSystemMeta{WtVersion: "0.9.0", GoOS: "linux", KernelVersion: "6.0.0"}}
|
||||
newPeer := &Peer{Meta: PeerSystemMeta{WtVersion: "1.1.0", GoOS: "linux", KernelVersion: "4.0.0"}}
|
||||
|
||||
assert.False(t, c[0].Passes(oldPeer))
|
||||
assert.False(t, c[0].Passes(newPeer))
|
||||
assert.True(t, PostureVerdictChanged(c, oldPeer, newPeer))
|
||||
}
|
||||
|
||||
func TestPostureVerdictChanged_NoChecks(t *testing.T) {
|
||||
oldPeer := &Peer{Meta: PeerSystemMeta{WtVersion: "1.0.0"}}
|
||||
newPeer := &Peer{Meta: PeerSystemMeta{WtVersion: "2.0.0"}}
|
||||
assert.False(t, PostureVerdictChanged(nil, oldPeer, newPeer))
|
||||
}
|
||||
@@ -370,8 +370,21 @@ func (c *NetworkMapComponents) connResourcesGenerator(targetPeer *nmdata.Peer) (
|
||||
}
|
||||
|
||||
func (c *NetworkMapComponents) getAllPeersFromGroups(groups []string, peerID string, sourcePostureChecksIDs []string) ([]*nmdata.Peer, bool) {
|
||||
return c.filterPolicyPeers(c.getUniquePeerIDsFromGroupsIDs(groups), peerID, sourcePostureChecksIDs)
|
||||
}
|
||||
|
||||
// getPeerFromResource resolves a rule side that names a peer directly. The peer is
|
||||
// subject to the same admission as a group member, so a direct peer behaves exactly
|
||||
// like a group holding only that peer.
|
||||
func (c *NetworkMapComponents) getPeerFromResource(resource nmdata.Resource, peerID string, sourcePostureChecksIDs []string) ([]*nmdata.Peer, bool) {
|
||||
return c.filterPolicyPeers([]string{resource.ID}, peerID, sourcePostureChecksIDs)
|
||||
}
|
||||
|
||||
// filterPolicyPeers admits the peers of one rule side: known to the components and
|
||||
// passing the rule's posture checks. It reports the admitted peers other than peerID
|
||||
// and whether peerID itself is admitted on that side.
|
||||
func (c *NetworkMapComponents) filterPolicyPeers(uniquePeerIDs []string, peerID string, sourcePostureChecksIDs []string) ([]*nmdata.Peer, bool) {
|
||||
peerInGroups := false
|
||||
uniquePeerIDs := c.getUniquePeerIDsFromGroupsIDs(groups)
|
||||
filteredPeers := make([]*nmdata.Peer, 0, len(uniquePeerIDs))
|
||||
|
||||
for _, p := range uniquePeerIDs {
|
||||
@@ -424,25 +437,6 @@ func (c *NetworkMapComponents) getUniquePeerIDsFromGroupsIDs(groups []string) []
|
||||
return ids
|
||||
}
|
||||
|
||||
func (c *NetworkMapComponents) getPeerFromResource(resource nmdata.Resource, peerID string, postureChecks []string) ([]*nmdata.Peer, bool) {
|
||||
if resource.ID == peerID {
|
||||
if len(postureChecks) > 0 && !c.ValidatePostureChecksOnPeer(peerID, postureChecks) {
|
||||
return []*nmdata.Peer{}, false
|
||||
}
|
||||
return []*nmdata.Peer{}, true
|
||||
}
|
||||
|
||||
peerInfo := c.GetPeerInfo(resource.ID)
|
||||
if peerInfo == nil {
|
||||
return []*nmdata.Peer{}, false
|
||||
}
|
||||
if len(postureChecks) > 0 && !c.ValidatePostureChecksOnPeer(resource.ID, postureChecks) {
|
||||
return []*nmdata.Peer{}, false
|
||||
}
|
||||
|
||||
return []*nmdata.Peer{peerInfo}, false
|
||||
}
|
||||
|
||||
func (c *NetworkMapComponents) filterPeersByLoginExpiration(aclPeers []*nmdata.Peer) ([]*nmdata.Peer, []*nmdata.Peer) {
|
||||
peersToConnect := make([]*nmdata.Peer, 0, len(aclPeers))
|
||||
var expiredPeers []*nmdata.Peer
|
||||
|
||||
Reference in New Issue
Block a user