From 905e1c14babacdd4bc438e95ab72513c5d616298 Mon Sep 17 00:00:00 2001 From: pascal Date: Mon, 10 Aug 2026 14:01:57 +0200 Subject: [PATCH] add validated peers cache for nmdata --- .../network_map/controller/controller.go | 2 + .../networkmap/networkmapcompute.go | 77 +++++++++++ .../networkmap/networkmapcompute_test.go | 120 ++++++++++++++++++ .../management/networkmap/networkmapdata.go | 6 + .../management/networkmap/nmdata/posture.go | 16 ++- 5 files changed, 217 insertions(+), 4 deletions(-) diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index bee550f86..fd93a759f 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -372,6 +372,8 @@ func (c *Controller) sendUpdatesFromData(ctx context.Context, accountID string, return fmt.Errorf("failed to get flow enabled status: %v", err) } + nmData.PrecomputePostureValidation() + dnsCache := &cache.DNSConfigCache{} dnsDomain := c.getDNSDomainFromData(nmData.AccountSettings) peersCustomZone := networkmap.PeersCustomZone(ctx, accountID, dnsDomain, nmData.Peers, ipv6AllowedPeersFromData(nmData)) diff --git a/shared/management/networkmap/networkmapcompute.go b/shared/management/networkmap/networkmapcompute.go index 0ec64c2bb..0701da710 100644 --- a/shared/management/networkmap/networkmapcompute.go +++ b/shared/management/networkmap/networkmapcompute.go @@ -465,6 +465,13 @@ func (nmd *NetworkMapData) validatePostureChecksOnPeerGetFailed(sourcePostureChe } for _, postureChecksID := range sourcePostureChecksID { + if valid, cached := nmd.cachedPostureCheckResult(postureChecksID, peerID); cached { + if !valid { + return false, postureChecksID + } + continue + } + postureChecks := nmd.PostureChecks[postureChecksID] if postureChecks == nil { continue @@ -476,6 +483,76 @@ func (nmd *NetworkMapData) validatePostureChecksOnPeerGetFailed(sourcePostureChe return true, "" } +func (nmd *NetworkMapData) PrecomputePostureValidation() { + if len(nmd.PostureChecks) == 0 { + nmd.PostureValidation = nil + return + } + + checkPeerIDs := make(map[string]map[string]struct{}) + for _, policy := range nmd.Policies { + if policy == nil || !policy.Enabled || len(policy.SourcePostureChecks) == 0 { + continue + } + + groupPeerIDs := nmd.getUniquePeerIDsFromGroupsIDs(policy.SourceGroups()) + for _, postureChecksID := range policy.SourcePostureChecks { + set := checkPeerIDs[postureChecksID] + if set == nil { + set = make(map[string]struct{}, len(groupPeerIDs)) + checkPeerIDs[postureChecksID] = set + } + for _, pid := range groupPeerIDs { + set[pid] = struct{}{} + } + for _, rule := range policy.Rules { + if rule == nil { + continue + } + if rule.SourceResource.Type == string(types.ResourceTypePeer) && rule.SourceResource.ID != "" { + set[rule.SourceResource.ID] = struct{}{} + } + } + } + } + + results := make(map[string]map[string]bool, len(checkPeerIDs)) + for postureChecksID, peerIDs := range checkPeerIDs { + results[postureChecksID] = nmd.evaluatePostureChecksForPeers(postureChecksID, peerIDs) + } + nmd.PostureValidation = results +} + +func (nmd *NetworkMapData) evaluatePostureChecksForPeers(postureChecksID string, peerIDs map[string]struct{}) map[string]bool { + postureChecks := nmd.PostureChecks[postureChecksID] + if postureChecks == nil { + return nil + } + + checks := postureChecks.GetChecks() + results := make(map[string]bool, len(peerIDs)) + for peerID := range peerIDs { + peer := nmd.Peers[peerID] + if peer == nil { + continue + } + results[peerID] = nmdata.PassesChecks(checks, peer) + } + return results +} + +func (nmd *NetworkMapData) cachedPostureCheckResult(postureChecksID, peerID string) (bool, bool) { + results, ok := nmd.PostureValidation[postureChecksID] + if !ok { + return false, false + } + if results == nil { + return true, true + } + valid, found := results[peerID] + return valid, found +} + func (nmd *NetworkMapData) getPostureValidPeersSaveFailed(inputPeers []string, postureChecksIDs []string, postureFailedPeers *map[string]map[string]struct{}) []string { var dest []string for _, peerID := range inputPeers { diff --git a/shared/management/networkmap/networkmapcompute_test.go b/shared/management/networkmap/networkmapcompute_test.go index d00851f67..0daae81dd 100644 --- a/shared/management/networkmap/networkmapcompute_test.go +++ b/shared/management/networkmap/networkmapcompute_test.go @@ -1475,6 +1475,126 @@ func TestGetPeerNetworkMapComponents_StoreImmutableAndDeterministic(t *testing.T assert.Equal(t, first.GroupIDToUserIDs, second.GroupIDToUserIDs) } +func TestPrecomputePostureValidation(t *testing.T) { + newFixture := func() *networkmap.NetworkMapData { + target := newPeer(targetID, 1) + srcPass := newPeer("peer-src-pass", 2) + srcFail := newPeer("peer-src-fail", 3) + srcFail.Meta.WtVersion = failingVersion + other := newPeer("peer-other", 4) + other.Meta.WtVersion = failingVersion + + nmd := newNMD(target, srcPass, srcFail, other) + addVersionCheck(nmd, "pc-1", postureMinVersion) + addGroup(nmd, "g-src", srcPass.ID, srcFail.ID) + addGroup(nmd, "g-dst", targetID) + addGroup(nmd, "g-open", srcPass.ID, srcFail.ID, other.ID) + + checked := newPolicy("p-checked", newRule([]string{"g-src"}, []string{"g-dst"})) + checked.SourcePostureChecks = []string{"pc-1"} + open := newPolicy("p-open", newRule([]string{"g-open"}, []string{"g-dst"})) + disabled := newPolicy("p-disabled", newRule([]string{"g-open"}, []string{"g-dst"})) + disabled.Enabled = false + disabled.SourcePostureChecks = []string{"pc-1"} + nmd.Policies = []*nmdata.Policy{checked, open, disabled} + + return nmd + } + + type snapshot struct { + peers []string + postureFailedPeers map[string]map[string]struct{} + } + snapshotAll := func(nmd *networkmap.NetworkMapData) map[string]snapshot { + out := make(map[string]snapshot, len(nmd.Peers)) + for peerID := range nmd.Peers { + c := compute(nmd, peerID) + out[peerID] = snapshot{peers: peerIDSet(c.Peers), postureFailedPeers: c.PostureFailedPeers} + } + return out + } + + t.Run("memoized results match direct evaluation", func(t *testing.T) { + nmd := newFixture() + direct := snapshotAll(nmd) + + nmd.PrecomputePostureValidation() + memoized := snapshotAll(nmd) + + require.Len(t, memoized, len(direct)) + for peerID, want := range direct { + assert.ElementsMatch(t, want.peers, memoized[peerID].peers, "visible peers changed for %s", peerID) + assert.Equal(t, want.postureFailedPeers, memoized[peerID].postureFailedPeers, "posture failures changed for %s", peerID) + } + }) + + t.Run("only source peers of enabled checked policies are evaluated", func(t *testing.T) { + nmd := newFixture() + nmd.PrecomputePostureValidation() + + assert.Equal(t, map[string]map[string]bool{ + "pc-1": {"peer-src-pass": true, "peer-src-fail": false}, + }, nmd.PostureValidation) + }) + + t.Run("peer source resources are evaluated", func(t *testing.T) { + nmd := newFixture() + resourcePolicy := newPolicy("p-resource", newRule(nil, []string{"g-dst"})) + resourcePolicy.Rules[0].SourceResource = nmdata.Resource{ID: "peer-other", Type: string(nbtypes.ResourceTypePeer)} + resourcePolicy.SourcePostureChecks = []string{"pc-1"} + nmd.Policies = append(nmd.Policies, resourcePolicy) + + nmd.PrecomputePostureValidation() + + assert.Equal(t, map[string]bool{"peer-src-pass": true, "peer-src-fail": false, "peer-other": false}, + nmd.PostureValidation["pc-1"]) + }) + + t.Run("memoized result wins over direct evaluation", func(t *testing.T) { + nmd := newFixture() + nmd.PostureValidation = map[string]map[string]bool{ + "pc-1": {"peer-src-pass": false, "peer-src-fail": true}, + } + + c := compute(nmd, targetID) + + assert.Equal(t, map[string]map[string]struct{}{"pc-1": {"peer-src-pass": {}}}, c.PostureFailedPeers) + }) + + t.Run("no posture checks clears the memo", func(t *testing.T) { + nmd := newFixture() + nmd.PrecomputePostureValidation() + require.NotEmpty(t, nmd.PostureValidation) + + nmd.PostureChecks = nil + nmd.PrecomputePostureValidation() + + assert.Nil(t, nmd.PostureValidation) + }) + + t.Run("unresolvable check id memoized as passing", func(t *testing.T) { + nmd := newFixture() + nmd.Policies[0].SourcePostureChecks = []string{"pc-ghost"} + nmd.PrecomputePostureValidation() + + require.Contains(t, nmd.PostureValidation, "pc-ghost") + assert.Nil(t, nmd.PostureValidation["pc-ghost"]) + + c := compute(nmd, targetID) + assert.ElementsMatch(t, []string{targetID, "peer-src-pass", "peer-src-fail", "peer-other"}, peerIDSet(c.Peers)) + assert.Empty(t, c.PostureFailedPeers) + }) + + t.Run("peers missing from the memo fall back to direct evaluation", func(t *testing.T) { + nmd := newFixture() + nmd.PostureValidation = map[string]map[string]bool{"pc-1": {"peer-src-pass": true}} + + c := compute(nmd, targetID) + + assert.Equal(t, map[string]map[string]struct{}{"pc-1": {"peer-src-fail": {}}}, c.PostureFailedPeers) + }) +} + func TestNetworkMapData_GetPeerGroups(t *testing.T) { target := newPeer(targetID, 1) other := newPeer("peer-other", 2) diff --git a/shared/management/networkmap/networkmapdata.go b/shared/management/networkmap/networkmapdata.go index e874752d2..3e8ee1cca 100644 --- a/shared/management/networkmap/networkmapdata.go +++ b/shared/management/networkmap/networkmapdata.go @@ -25,6 +25,12 @@ type NetworkMapData struct { //nolint:revive // established name across the code PostureChecks map[string]*nmdata.PostureChecks + // PostureValidation holds the precomputed posture-check results, keyed by + // posture check ID then peer ID. Filled by PrecomputePostureValidation; a + // present but nil inner map marks a check ID that resolves to no posture + // check, which the calc treats as passing. + PostureValidation map[string]map[string]bool + AllowedUserIDs map[string]struct{} NetworkXIDToPublicID map[string]string PostureCheckXIDToPublicID map[string]string diff --git a/shared/management/networkmap/nmdata/posture.go b/shared/management/networkmap/nmdata/posture.go index c8fb08fd9..dc1753791 100644 --- a/shared/management/networkmap/nmdata/posture.go +++ b/shared/management/networkmap/nmdata/posture.go @@ -20,7 +20,9 @@ type ChecksDefinition struct { ProcessCheck *ProcessCheck } -type postureCheck interface { +// Check is the slim twin of posture.Check. It is sealed: only the check types +// in this package implement it. +type Check interface { check(peer *Peer) (bool, error) } @@ -28,7 +30,13 @@ type postureCheck interface { // mirrors the server posture path: a check returning (false, _) — including on // an evaluation error — fails the bundle. func (pc *PostureChecks) Passes(peer *Peer) bool { - for _, c := range pc.GetChecks() { + return PassesChecks(pc.GetChecks(), peer) +} + +// PassesChecks is Passes over an already built check set, for callers that +// evaluate many peers against the same bundle. +func PassesChecks(checks []Check, peer *Peer) bool { + for _, c := range checks { valid, _ := c.check(peer) if !valid { return false @@ -38,8 +46,8 @@ func (pc *PostureChecks) Passes(peer *Peer) bool { } // GetChecks returns the initialized checks in the same order as posture.Checks.GetChecks. -func (pc *PostureChecks) GetChecks() []postureCheck { - var checks []postureCheck +func (pc *PostureChecks) GetChecks() []Check { + var checks []Check if pc.Checks.NBVersionCheck != nil { checks = append(checks, pc.Checks.NBVersionCheck) }