[management] fix posture check evaluation for direct peers in policy definition (#7348)

This commit is contained in:
Pascal Fischer
2026-08-28 16:46:42 +02:00
committed by GitHub
parent 611a9291cd
commit 353251d886
19 changed files with 695 additions and 116 deletions
@@ -577,7 +577,7 @@ func peerPostureChecksFromData(nmData *networkmap.NetworkMapData, peerID string)
if policy == nil || !policy.Enabled || len(policy.SourcePostureChecks) == 0 {
continue
}
if !isPeerInPolicySourceGroupsFromData(nmData, peerID, policy) {
if !isPeerInPolicySourcesFromData(nmData, peerID, policy) {
continue
}
for _, checkID := range policy.SourcePostureChecks {
@@ -590,11 +590,14 @@ func peerPostureChecksFromData(nmData *networkmap.NetworkMapData, peerID string)
return maps.Values(peerPostureChecks)
}
func isPeerInPolicySourceGroupsFromData(nmData *networkmap.NetworkMapData, peerID string, policy *nmdata.Policy) bool {
func isPeerInPolicySourcesFromData(nmData *networkmap.NetworkMapData, peerID string, policy *nmdata.Policy) bool {
for _, rule := range policy.Rules {
if rule == nil || !rule.Enabled {
continue
}
if rule.SourceResource.Type == string(types.ResourceTypePeer) && rule.SourceResource.ID == peerID {
return true
}
for _, groupID := range rule.Sources {
if group := nmData.Groups[groupID]; group != nil && slices.Contains(group.Peers, peerID) {
return true
@@ -1314,7 +1317,7 @@ func computeForwarderPortFromVersions(wtVersions []string, requiredVersion strin
// addPolicyPostureChecks adds posture checks from a policy to the peer posture checks map if the peer is in the policy's source groups.
func addPolicyPostureChecks(account *types.Account, peerID string, policy *types.Policy, peerPostureChecks map[string]*posture.Checks) error {
isInGroup, err := isPeerInPolicySourceGroups(account, peerID, policy)
isInGroup, err := isPeerInPolicySources(account, peerID, policy)
if err != nil {
return err
}
@@ -1334,13 +1337,17 @@ func addPolicyPostureChecks(account *types.Account, peerID string, policy *types
return nil
}
// isPeerInPolicySourceGroups checks if a peer is present in any of the policy rule source groups.
func isPeerInPolicySourceGroups(account *types.Account, peerID string, policy *types.Policy) (bool, error) {
// isPeerInPolicySources checks if a peer is a source of the policy, directly or through a source group.
func isPeerInPolicySources(account *types.Account, peerID string, policy *types.Policy) (bool, error) {
for _, rule := range policy.Rules {
if !rule.Enabled {
continue
}
if rule.SourceResource.Type == types.ResourceTypePeer && rule.SourceResource.ID == peerID {
return true, nil
}
for _, sourceGroup := range rule.Sources {
group := account.GetGroup(sourceGroup)
if group == nil {
@@ -4,35 +4,65 @@ import (
"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"
"github.com/netbirdio/netbird/shared/management/types"
)
func TestPeerPostureChecksFromData_ReturnsTwinsUnchanged(t *testing.T) {
check := &nmdata.PostureChecks{
ID: "pc1",
Checks: nmdata.ChecksDefinition{
NBVersionCheck: &nmdata.NBVersionCheck{MinVersion: "0.30.0"},
OSVersionCheck: &nmdata.OSVersionCheck{Linux: &nmdata.MinKernelVersionCheck{MinKernelVersion: "6.1"}},
func postureSelectionData(policies ...*nmdata.Policy) *networkmap.NetworkMapData {
return &networkmap.NetworkMapData{
Groups: map[string]*nmdata.Group{"g-src": {ID: "g-src", Peers: []string{"peer-group"}}},
Policies: policies,
PostureChecks: map[string]*nmdata.PostureChecks{
"pc1": {ID: "pc1", Checks: nmdata.ChecksDefinition{NBVersionCheck: &nmdata.NBVersionCheck{MinVersion: "0.30.0"}}},
},
}
nmData := &networkmap.NetworkMapData{
Groups: map[string]*nmdata.Group{"g1": {ID: "g1", Peers: []string{"peer1"}}},
Policies: []*nmdata.Policy{{
ID: "policy1",
Enabled: true,
SourcePostureChecks: []string{"pc1"},
Rules: []*nmdata.PolicyRule{{ID: "rule1", Enabled: true, Sources: []string{"g1"}}},
}},
PostureChecks: map[string]*nmdata.PostureChecks{"pc1": check},
}
func gatedPolicy(id string, rule *nmdata.PolicyRule, checkIDs ...string) *nmdata.Policy {
return &nmdata.Policy{ID: id, Enabled: true, SourcePostureChecks: checkIDs, Rules: []*nmdata.PolicyRule{rule}}
}
func checkIDs(checks []*nmdata.PostureChecks) []string {
ids := make([]string, 0, len(checks))
for _, c := range checks {
ids = append(ids, c.ID)
}
return ids
}
func TestPeerPostureChecksFromData_SelectsPolicySourcePeers(t *testing.T) {
groupRule := &nmdata.PolicyRule{ID: "r-group", Enabled: true, Sources: []string{"g-src"}, Destinations: []string{"g-dst"}}
directRule := &nmdata.PolicyRule{ID: "r-direct", Enabled: true, SourceResource: nmdata.Resource{ID: "peer-direct", Type: string(types.ResourceTypePeer)}, Destinations: []string{"g-dst"}}
t.Run("source group member and direct source peer both get the checks", func(t *testing.T) {
nmData := postureSelectionData(gatedPolicy("p1", groupRule, "pc1"), gatedPolicy("p2", directRule, "pc1"))
assert.Equal(t, []string{"pc1"}, checkIDs(peerPostureChecksFromData(nmData, "peer-group")))
assert.Equal(t, []string{"pc1"}, checkIDs(peerPostureChecksFromData(nmData, "peer-direct")))
assert.Empty(t, peerPostureChecksFromData(nmData, "peer-elsewhere"))
})
t.Run("source resource of a non-peer type never matches a peer", func(t *testing.T) {
hostRule := &nmdata.PolicyRule{ID: "r-host", Enabled: true, SourceResource: nmdata.Resource{ID: "peer-direct", Type: string(types.ResourceTypeHost)}, Destinations: []string{"g-dst"}}
nmData := postureSelectionData(gatedPolicy("p1", hostRule, "pc1"))
assert.Empty(t, peerPostureChecksFromData(nmData, "peer-direct"))
})
t.Run("same check through two policies is returned once", func(t *testing.T) {
nmData := postureSelectionData(gatedPolicy("p1", groupRule, "pc1"), gatedPolicy("p2", groupRule, "pc1"))
assert.Equal(t, []string{"pc1"}, checkIDs(peerPostureChecksFromData(nmData, "peer-group")))
})
got := peerPostureChecksFromData(nmData, "peer1")
require.Len(t, got, 1)
assert.Same(t, check, got[0])
assert.Len(t, got[0].GetChecks(), 2)
t.Run("disabled policy, disabled rule and dangling check are ignored", func(t *testing.T) {
disabledPolicy := gatedPolicy("p-off", groupRule, "pc1")
disabledPolicy.Enabled = false
disabledRule := &nmdata.PolicyRule{ID: "r-off", Enabled: false, Sources: []string{"g-src"}}
nmData := postureSelectionData(disabledPolicy, gatedPolicy("p-rule-off", disabledRule, "pc1"), gatedPolicy("p-dangling", groupRule, "pc-missing"))
assert.Empty(t, peerPostureChecksFromData(nmData, "peer-outside-source-group"))
assert.Empty(t, peerPostureChecksFromData(nmData, "peer-group"))
})
}