[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
@@ -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)
@@ -230,13 +230,13 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) (
var peerInSources, peerInDestinations bool
if rule.SourceResource.Type == string(ResourceTypePeer) && rule.SourceResource.ID != "" {
sourcePeers, peerInSources = c.getPeerFromResource(rule.SourceResource, targetPeerID)
sourcePeers, peerInSources = c.getPeerFromResource(rule.SourceResource, targetPeerID, policy.SourcePostureChecks)
} else {
sourcePeers, peerInSources = c.getAllPeersFromGroups(rule.Sources, targetPeerID, policy.SourcePostureChecks)
}
if rule.DestinationResource.Type == string(ResourceTypePeer) && rule.DestinationResource.ID != "" {
destinationPeers, peerInDestinations = c.getPeerFromResource(rule.DestinationResource, targetPeerID)
destinationPeers, peerInDestinations = c.getPeerFromResource(rule.DestinationResource, targetPeerID, nil)
} else {
destinationPeers, peerInDestinations = c.getAllPeersFromGroups(rule.Destinations, targetPeerID, nil)
}
@@ -373,8 +373,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 {
@@ -427,19 +440,6 @@ func (c *NetworkMapComponents) getUniquePeerIDsFromGroupsIDs(groups []string) []
return ids
}
func (c *NetworkMapComponents) getPeerFromResource(resource nmdata.Resource, peerID string) ([]*nmdata.Peer, bool) {
if resource.ID == peerID {
return []*nmdata.Peer{}, true
}
peerInfo := c.GetPeerInfo(resource.ID)
if peerInfo == nil {
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