From be06016ad215726192e1f3e1f63f405364d5b6ed Mon Sep 17 00:00:00 2001 From: pascal Date: Tue, 23 Jun 2026 22:58:32 +0200 Subject: [PATCH 1/4] validate posture checks on meta change before account update --- management/server/peer.go | 4 +- management/server/peer/peer.go | 145 +++++-------- .../server/posture/affects_posture_test.go | 202 ++++++++++++++++++ management/server/posture/checks.go | 51 +++-- 4 files changed, 291 insertions(+), 111 deletions(-) create mode 100644 management/server/posture/affects_posture_test.go diff --git a/management/server/peer.go b/management/server/peer.go index a5da29243..17067a530 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -1051,8 +1051,8 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy return nil, nil, nil, 0, err } - metaDiffAffectsPosture := posture.AffectsPosture(&metaDiff, resPostureChecks) - if isStatusChanged || sync.UpdateAccountPeers || ipv6CapabilityChanged || metaDiffAffectsPosture || metaDiff.VersionChanged || metaDiff.Hostname { + metaDiffAffectsPosture := posture.AffectsPosture(ctx, &metaDiff, resPostureChecks) + if isStatusChanged || sync.UpdateAccountPeers || ipv6CapabilityChanged || metaDiffAffectsPosture || metaDiff.VersionChanged() || metaDiff.HostnameChanged() { changedPeerIDs := []string{peer.ID} affectedPeerIDs := am.syncPeerAffectedPeers(ctx, accountID, peer.ID, nmap, peerNotValid, metaDiffAffectsPosture) if err = am.networkMapController.OnPeersUpdated(ctx, accountID, changedPeerIDs, affectedPeerIDs); err != nil { diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index 4a846ebdd..3110cd9c1 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -107,6 +107,15 @@ type Location struct { GeoNameID uint // city level geoname id } +// equal reports whether two locations match. ConnectionIP is a net.IP slice, so it uses +// IP.Equal, not ==. +func (l Location) equal(other Location) bool { + return l.CountryCode == other.CountryCode && + l.CityName == other.CityName && + l.GeoNameID == other.GeoNameID && + l.ConnectionIP.Equal(other.ConnectionIP) +} + // NetworkAddress is the IP address with network and MAC address of a network interface type NetworkAddress struct { NetIP netip.Prefix `gorm:"serializer:json"` @@ -267,185 +276,141 @@ func (p *Peer) UpdateMetaIfNew(ctx context.Context, meta PeerSystemMeta, newLoca return MetaDiff{} } - versionChanged := p.Meta.WtVersion != meta.WtVersion - // Avoid overwriting UIVersion if the update was triggered sole by the CLI client if meta.UIVersion == "" { meta.UIVersion = p.Meta.UIVersion } - oldVersion := p.Meta.WtVersion + effectiveLocation := p.Location + if newLocation != nil { + effectiveLocation = *newLocation + } - diff := diffMeta(p.Meta, meta) - if diff.Any() { + diff := diffMeta(p.Meta, meta, p.Location, effectiveLocation) + if diff.Updated() { p.Meta = meta } - diff.VersionChanged = versionChanged + p.Location = effectiveLocation - locationInfo := "" - if newLocation != nil { - p.Location = *newLocation - diff.LocationChanged = true - locationInfo = fmt.Sprintf("location changed to %s, ", newLocation.ConnectionIP) - } - - versionInfo := "" - if diff.VersionChanged { - versionInfo = fmt.Sprintf("version changed: %s -> %s, ", oldVersion, meta.WtVersion) - } - - if diff.Any() || diff.VersionChanged || diff.LocationChanged { - log.WithContext(ctx). - Debugf("peer meta updated, %s%s%d field(s) changed: %s", versionInfo, locationInfo, len(diff.Changed), strings.Join(diff.Changed, ", ")) + if diff.Updated() { + log.WithContext(ctx).Debug(diff.LogSummary()) } return diff } -// MetaDiff records which PeerSystemMeta fields differ between two metas. Each bool -// maps to a single struct field, except Environment, which is split into Cloud and -// Platform. Changed holds the human-readable `field: -> ` entries so the -// existing log line and isEqual can be derived from the same comparison. -// -// VersionChanged and LocationChanged sit outside the per-meta-field set: -// VersionChanged tracks the WireGuard client version specifically (compared before -// the UIVersion fixup, to signal client upgrades) and LocationChanged tracks the -// peer's connection geo location, which lives on Peer rather than PeerSystemMeta. -// Neither contributes an entry to Changed, so the field-coverage accounting stays -// driven purely by the PeerSystemMeta comparison. +// MetaDiff holds a peer's full before/after state across a sync: both metas and both +// connection locations (the location lives on Peer, not PeerSystemMeta, but posture +// checks read it). Changed lists what moved, for logging and the persistence decision; +// the snapshots let a posture check be replayed against old and new. Everything is derived +// from these fields, so there are no parallel per-field flags to keep in sync. type MetaDiff struct { - Hostname bool - GoOS bool - Kernel bool - KernelVersion bool - Core bool - Platform bool - OS bool - OSVersion bool - WtVersion bool - UIVersion bool - SystemSerialNumber bool - SystemProductName bool - SystemManufacturer bool - EnvironmentCloud bool - EnvironmentPlatform bool - Flags bool - Capabilities bool - NetworkAddresses bool - Files bool - - VersionChanged bool - LocationChanged bool + OldMeta PeerSystemMeta + NewMeta PeerSystemMeta + OldLocation Location + NewLocation Location Changed []string } -// Any reports whether any PeerSystemMeta field changed. -func (d MetaDiff) Any() bool { +// Updated reports whether anything changed and the peer must be persisted. diffMeta fills +// Changed in the pass that builds the diff, so this is a length check, not a re-comparison. +// Pointer receiver: MetaDiff embeds two metas, so copying it per call is wasteful. +func (d *MetaDiff) Updated() bool { return len(d.Changed) != 0 } -// Updated reports whether the peer needs to be persisted: any meta field changed -// or the geo location changed. The version flag alone does not imply a write, -// since a version change is also reflected in the WtVersion meta field. -func (d MetaDiff) Updated() bool { - return d.Any() || d.LocationChanged || d.VersionChanged +// VersionChanged reports whether the WireGuard client version changed (a client upgrade). +func (d *MetaDiff) VersionChanged() bool { + return d.OldMeta.WtVersion != d.NewMeta.WtVersion +} + +// HostnameChanged reports whether the peer's hostname changed. +func (d *MetaDiff) HostnameChanged() bool { + return d.OldMeta.Hostname != d.NewMeta.Hostname +} + +// LogSummary renders the changed fields as a single human-readable line. +func (d *MetaDiff) LogSummary() string { + return fmt.Sprintf("peer meta updated, %d field(s) changed: %s", + len(d.Changed), strings.Join(d.Changed, ", ")) } func metaDiff(oldMeta, newMeta PeerSystemMeta) []string { - return diffMeta(oldMeta, newMeta).Changed + return diffMeta(oldMeta, newMeta, Location{}, Location{}).Changed } -// diffMeta compares two metas field by field, returning both a per-field flag set -// (for callers that need to know exactly what changed, e.g. matching against -// posture checks) and the human-readable Changed list. It is the single source of -// truth for meta comparison: isEqual reports equality as an empty diff, so the log -// line, the change decision, and the flags can never disagree. -func diffMeta(oldMeta, newMeta PeerSystemMeta) MetaDiff { - var d MetaDiff +// diffMeta snapshots a peer's old and new state and records a Changed entry per field that +// moved. It is the single source of truth for the comparison: isEqual is an empty Changed +// list, so the log line and the persistence decision can never disagree. +func diffMeta(oldMeta, newMeta PeerSystemMeta, oldLocation, newLocation Location) MetaDiff { + d := MetaDiff{OldMeta: oldMeta, NewMeta: newMeta, OldLocation: oldLocation, NewLocation: newLocation} add := func(field string, oldVal, newVal any) { d.Changed = append(d.Changed, fmt.Sprintf("%s: %v -> %v", field, oldVal, newVal)) } if oldMeta.Hostname != newMeta.Hostname { - d.Hostname = true add("hostname", oldMeta.Hostname, newMeta.Hostname) } if oldMeta.GoOS != newMeta.GoOS { - d.GoOS = true add("goos", oldMeta.GoOS, newMeta.GoOS) } if oldMeta.Kernel != newMeta.Kernel { - d.Kernel = true add("kernel", oldMeta.Kernel, newMeta.Kernel) } if oldMeta.KernelVersion != newMeta.KernelVersion { - d.KernelVersion = true add("kernel_version", oldMeta.KernelVersion, newMeta.KernelVersion) } if oldMeta.Core != newMeta.Core { - d.Core = true add("core", oldMeta.Core, newMeta.Core) } if oldMeta.Platform != newMeta.Platform { - d.Platform = true add("platform", oldMeta.Platform, newMeta.Platform) } if oldMeta.OS != newMeta.OS { - d.OS = true add("os", oldMeta.OS, newMeta.OS) } if oldMeta.OSVersion != newMeta.OSVersion { - d.OSVersion = true add("os_version", oldMeta.OSVersion, newMeta.OSVersion) } if oldMeta.WtVersion != newMeta.WtVersion { - d.WtVersion = true add("wt_version", oldMeta.WtVersion, newMeta.WtVersion) } if oldMeta.UIVersion != newMeta.UIVersion { - d.UIVersion = true add("ui_version", oldMeta.UIVersion, newMeta.UIVersion) } if oldMeta.SystemSerialNumber != newMeta.SystemSerialNumber { - d.SystemSerialNumber = true add("system_serial_number", oldMeta.SystemSerialNumber, newMeta.SystemSerialNumber) } if oldMeta.SystemProductName != newMeta.SystemProductName { - d.SystemProductName = true add("system_product_name", oldMeta.SystemProductName, newMeta.SystemProductName) } if oldMeta.SystemManufacturer != newMeta.SystemManufacturer { - d.SystemManufacturer = true add("system_manufacturer", oldMeta.SystemManufacturer, newMeta.SystemManufacturer) } if oldMeta.Environment.Cloud != newMeta.Environment.Cloud { - d.EnvironmentCloud = true add("environment_cloud", oldMeta.Environment.Cloud, newMeta.Environment.Cloud) } if oldMeta.Environment.Platform != newMeta.Environment.Platform { - d.EnvironmentPlatform = true add("environment_platform", oldMeta.Environment.Platform, newMeta.Environment.Platform) } if !oldMeta.Flags.isEqual(newMeta.Flags) { - d.Flags = true add("flags", fmt.Sprintf("%+v", oldMeta.Flags), fmt.Sprintf("%+v", newMeta.Flags)) } if !capabilitiesEqual(oldMeta.Capabilities, newMeta.Capabilities) { - d.Capabilities = true add("capabilities", oldMeta.Capabilities, newMeta.Capabilities) } - if !sameMultiset(oldMeta.NetworkAddresses, newMeta.NetworkAddresses) { - d.NetworkAddresses = true add("network_addresses", fmt.Sprintf("%v", oldMeta.NetworkAddresses), fmt.Sprintf("%v", newMeta.NetworkAddresses)) } - if !sameMultiset(oldMeta.Files, newMeta.Files) { - d.Files = true add("files", fmt.Sprintf("%v", oldMeta.Files), fmt.Sprintf("%v", newMeta.Files)) } + if !oldLocation.equal(newLocation) { + add("connection_ip", oldLocation.ConnectionIP, newLocation.ConnectionIP) + } + return d } diff --git a/management/server/posture/affects_posture_test.go b/management/server/posture/affects_posture_test.go new file mode 100644 index 000000000..3fdcb03a9 --- /dev/null +++ b/management/server/posture/affects_posture_test.go @@ -0,0 +1,202 @@ +package posture + +import ( + "context" + "net" + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" +) + +// diffFrom builds a MetaDiff from the old/new snapshots AffectsPosture replays against. +func diffFrom(oldMeta, newMeta nbpeer.PeerSystemMeta, oldLoc, newLoc nbpeer.Location) *nbpeer.MetaDiff { + return &nbpeer.MetaDiff{ + OldMeta: oldMeta, + NewMeta: newMeta, + OldLocation: oldLoc, + NewLocation: newLoc, + } +} + +func checks(def ChecksDefinition) []*Checks { + return []*Checks{{Checks: def}} +} + +func TestAffectsPosture_NilDiff(t *testing.T) { + assert.False(t, AffectsPosture(context.Background(), nil, checks(ChecksDefinition{ + NBVersionCheck: &NBVersionCheck{MinVersion: "1.0.0"}, + }))) +} + +func TestAffectsPosture_NBVersion(t *testing.T) { + c := checks(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}, + {"both below min, no flip", "1.0.0", "1.1.0", false}, + {"crosses up below->above", "1.1.0", "1.3.0", true}, + {"crosses down above->below", "1.3.0", "1.1.0", true}, + {"unparseable old only -> flip", "garbage", "1.3.0", true}, + {"unparseable both -> no flip", "garbage", "junk", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + diff := diffFrom( + nbpeer.PeerSystemMeta{WtVersion: tt.oldVer}, + nbpeer.PeerSystemMeta{WtVersion: tt.newVer}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.Equal(t, tt.want, AffectsPosture(context.Background(), diff, c)) + }) + } +} + +func TestAffectsPosture_OSVersion_KernelBumpWithinMin(t *testing.T) { + c := checks(ChecksDefinition{OSVersionCheck: &OSVersionCheck{ + Linux: &MinKernelVersionCheck{MinKernelVersion: "5.0.0"}, + }}) + + // Kernel moves but stays above the minimum: verdict stays pass -> not affected. + withinMin := diffFrom( + nbpeer.PeerSystemMeta{GoOS: "linux", KernelVersion: "5.10.0-arch1"}, + nbpeer.PeerSystemMeta{GoOS: "linux", KernelVersion: "5.15.0-arch2"}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.False(t, AffectsPosture(context.Background(), withinMin, c)) + + // Kernel drops below the minimum: verdict flips pass -> fail -> affected. + crossesDown := diffFrom( + nbpeer.PeerSystemMeta{GoOS: "linux", KernelVersion: "5.10.0-arch1"}, + nbpeer.PeerSystemMeta{GoOS: "linux", KernelVersion: "4.19.0-arch1"}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.True(t, AffectsPosture(context.Background(), crossesDown, c)) +} + +func TestAffectsPosture_OSVersion_GoOSSwitchFlipsVerdict(t *testing.T) { + // Only Linux is constrained. An OS outside the switch (freebsd) passes; switching to a + // failing linux kernel flips the verdict pass -> fail. + c := checks(ChecksDefinition{OSVersionCheck: &OSVersionCheck{ + Linux: &MinKernelVersionCheck{MinKernelVersion: "6.0.0"}, + }}) + + diff := diffFrom( + nbpeer.PeerSystemMeta{GoOS: "freebsd"}, + nbpeer.PeerSystemMeta{GoOS: "linux", KernelVersion: "4.19.0"}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.True(t, AffectsPosture(context.Background(), diff, c)) +} + +func TestAffectsPosture_Process_GoOSSwitchFlipsVerdict(t *testing.T) { + // Process runs at a linux path. Switching GoOS to windows (no WindowsPath configured) + // flips the verdict. + c := checks(ChecksDefinition{ProcessCheck: &ProcessCheck{ + Processes: []Process{{LinuxPath: "/usr/bin/foo"}}, + }}) + + files := []nbpeer.File{{Path: "/usr/bin/foo", ProcessIsRunning: true}} + diff := diffFrom( + nbpeer.PeerSystemMeta{GoOS: "linux", Files: files}, + nbpeer.PeerSystemMeta{GoOS: "windows", Files: files}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.True(t, AffectsPosture(context.Background(), diff, c)) +} + +func TestAffectsPosture_Process_UnrelatedFileChange(t *testing.T) { + // A tracked process stays running while an unrelated file is added: the verdict does + // not move, so posture is not affected. + c := checks(ChecksDefinition{ProcessCheck: &ProcessCheck{ + Processes: []Process{{LinuxPath: "/usr/bin/foo"}}, + }}) + + diff := diffFrom( + nbpeer.PeerSystemMeta{GoOS: "linux", Files: []nbpeer.File{ + {Path: "/usr/bin/foo", ProcessIsRunning: true}, + }}, + nbpeer.PeerSystemMeta{GoOS: "linux", Files: []nbpeer.File{ + {Path: "/usr/bin/foo", ProcessIsRunning: true}, + {Path: "/usr/bin/bar", ProcessIsRunning: true}, + }}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.False(t, AffectsPosture(context.Background(), diff, c)) +} + +func TestAffectsPosture_GeoLocation(t *testing.T) { + c := checks(ChecksDefinition{GeoLocationCheck: &GeoLocationCheck{ + Action: CheckActionAllow, + Locations: []Location{{CountryCode: "DE"}}, + }}) + + // Moving within allowed countries keeps the verdict; moving out flips it. + stayAllowed := diffFrom( + nbpeer.PeerSystemMeta{}, nbpeer.PeerSystemMeta{}, + nbpeer.Location{CountryCode: "DE", CityName: "Berlin"}, + nbpeer.Location{CountryCode: "DE", CityName: "Munich"}, + ) + assert.False(t, AffectsPosture(context.Background(), stayAllowed, c)) + + moveOut := diffFrom( + nbpeer.PeerSystemMeta{}, nbpeer.PeerSystemMeta{}, + nbpeer.Location{CountryCode: "DE"}, + nbpeer.Location{CountryCode: "FR"}, + ) + assert.True(t, AffectsPosture(context.Background(), moveOut, c)) +} + +func TestAffectsPosture_PeerNetworkRange_ConnectionIP(t *testing.T) { + // The check reads the connection IP. Moving out of the allowed range flips the verdict; + // moving within it does not. + _, allowed, _ := net.ParseCIDR("10.0.0.0/8") + c := checks(ChecksDefinition{PeerNetworkRangeCheck: &PeerNetworkRangeCheck{ + Action: CheckActionAllow, + Ranges: []netip.Prefix{netip.MustParsePrefix(allowed.String())}, + }}) + + movesOutOfRange := diffFrom( + nbpeer.PeerSystemMeta{}, nbpeer.PeerSystemMeta{}, + nbpeer.Location{ConnectionIP: net.ParseIP("10.1.2.3")}, + nbpeer.Location{ConnectionIP: net.ParseIP("8.8.8.8")}, + ) + assert.True(t, AffectsPosture(context.Background(), movesOutOfRange, c)) + + staysInRange := diffFrom( + nbpeer.PeerSystemMeta{}, nbpeer.PeerSystemMeta{}, + nbpeer.Location{ConnectionIP: net.ParseIP("10.1.2.3")}, + nbpeer.Location{ConnectionIP: net.ParseIP("10.9.9.9")}, + ) + assert.False(t, AffectsPosture(context.Background(), staysInRange, c)) +} + +func TestAffectsPosture_IrrelevantFieldChange(t *testing.T) { + // Hostname changes but no check reads it: not affected even with checks present. + c := checks(ChecksDefinition{ + NBVersionCheck: &NBVersionCheck{MinVersion: "1.0.0"}, + GeoLocationCheck: &GeoLocationCheck{Action: CheckActionAllow, Locations: []Location{{CountryCode: "DE"}}}, + }) + + diff := diffFrom( + nbpeer.PeerSystemMeta{Hostname: "old", WtVersion: "1.5.0"}, + nbpeer.PeerSystemMeta{Hostname: "new", WtVersion: "1.5.0"}, + nbpeer.Location{CountryCode: "DE"}, nbpeer.Location{CountryCode: "DE"}, + ) + assert.False(t, AffectsPosture(context.Background(), diff, c)) +} + +func TestAffectsPosture_NoChecks(t *testing.T) { + diff := diffFrom( + nbpeer.PeerSystemMeta{WtVersion: "1.0.0"}, + nbpeer.PeerSystemMeta{WtVersion: "2.0.0"}, + nbpeer.Location{}, nbpeer.Location{}, + ) + assert.False(t, AffectsPosture(context.Background(), diff, nil)) +} diff --git a/management/server/posture/checks.go b/management/server/posture/checks.go index 6a98edb99..6b3d512b4 100644 --- a/management/server/posture/checks.go +++ b/management/server/posture/checks.go @@ -7,6 +7,7 @@ import ( "regexp" "github.com/hashicorp/go-version" + log "github.com/sirupsen/logrus" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/shared/management/http/api" @@ -52,34 +53,46 @@ type Checks struct { Checks ChecksDefinition `gorm:"serializer:json"` } -// AffectsPosture reports whether the peer metadata changes described by diff can -// alter the outcome of any of the given posture checks. It maps each check kind to -// the metadata fields it inspects, so an unrelated change (e.g. a hostname update) -// does not force a posture re-evaluation. -func AffectsPosture(diff *nbpeer.MetaDiff, checks []*Checks) bool { +// AffectsPosture reports whether the change in diff flips the verdict of any check. It +// replays each check against the peer's old and new state and compares verdicts, so a +// change that moves a field but stays the right side of a threshold (e.g. a kernel bump +// still above the minimum) does not force a re-evaluation. See verdictChanged for how an +// evaluation error counts. +func AffectsPosture(ctx context.Context, diff *nbpeer.MetaDiff, checks []*Checks) bool { if diff == nil { return false } + + oldPeer := nbpeer.Peer{Meta: diff.OldMeta, Location: diff.OldLocation} + newPeer := nbpeer.Peer{Meta: diff.NewMeta, Location: diff.NewLocation} + for _, c := range checks { - if c.Checks.ProcessCheck != nil && diff.Files { - return true - } - if c.Checks.OSVersionCheck != nil && (diff.OSVersion || diff.OS || diff.KernelVersion) { - return true - } - if c.Checks.NBVersionCheck != nil && diff.WtVersion { - return true - } - if c.Checks.GeoLocationCheck != nil && diff.LocationChanged { - return true - } - if c.Checks.PeerNetworkRangeCheck != nil && diff.NetworkAddresses { - return true + for _, check := range c.GetChecks() { + if verdictChanged(ctx, check, oldPeer, newPeer) { + return true + } } } return false } +// verdictChanged replays check against old and new state and reports whether the verdict +// differs. Like callers, it treats an evaluation error as deny: two errors are the same +// verdict (no change), an error on one side only is a flip. +func verdictChanged(ctx context.Context, check Check, oldPeer, newPeer nbpeer.Peer) bool { + oldPass, oldErr := check.Check(ctx, oldPeer) + newPass, newErr := check.Check(ctx, newPeer) + + oldVerdict := oldPass && oldErr == nil + newVerdict := newPass && newErr == nil + changed := oldVerdict != newVerdict + + log.WithContext(ctx).Tracef("posture check %s replay: verdict %t -> %t (changed=%t), errs: %v -> %v", + check.Name(), oldVerdict, newVerdict, changed, oldErr, newErr) + + return changed +} + // ChecksDefinition contains definition of actual check type ChecksDefinition struct { NBVersionCheck *NBVersionCheck `json:",omitempty"` From 9eadf50f4c610e6aec808d6c16c6eea53997579c Mon Sep 17 00:00:00 2001 From: pascal Date: Tue, 23 Jun 2026 23:02:48 +0200 Subject: [PATCH 2/4] fix typo --- management/server/posture/affects_posture_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management/server/posture/affects_posture_test.go b/management/server/posture/affects_posture_test.go index 3fdcb03a9..6aa54d892 100644 --- a/management/server/posture/affects_posture_test.go +++ b/management/server/posture/affects_posture_test.go @@ -43,8 +43,8 @@ func TestAffectsPosture_NBVersion(t *testing.T) { {"both below min, no flip", "1.0.0", "1.1.0", false}, {"crosses up below->above", "1.1.0", "1.3.0", true}, {"crosses down above->below", "1.3.0", "1.1.0", true}, - {"unparseable old only -> flip", "garbage", "1.3.0", true}, - {"unparseable both -> no flip", "garbage", "junk", false}, + {"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) { From 5ae36cd260036c69cc0a3825e2a5f1dc0544870c Mon Sep 17 00:00:00 2001 From: pascal Date: Wed, 24 Jun 2026 14:13:55 +0200 Subject: [PATCH 3/4] remove posture check took timing logs --- .../internals/controllers/network_map/controller/controller.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index d271c499d..e16796471 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -610,12 +610,10 @@ func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresAppr return nil, nil, 0, err } - startPosture := time.Now() postureChecks, err := c.getPeerPostureChecks(account, peerID) if err != nil { return nil, nil, 0, err } - log.WithContext(ctx).Debugf("getPeerPostureChecks took %s", time.Since(startPosture)) accountZones, err := c.repo.GetAccountZones(ctx, account.Id) if err != nil { From 845dd0c9bbf0bfe5f724c600999d930eb4c80ed2 Mon Sep 17 00:00:00 2001 From: pascal Date: Thu, 25 Jun 2026 13:08:36 +0200 Subject: [PATCH 4/4] add brackets --- management/server/posture/checks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management/server/posture/checks.go b/management/server/posture/checks.go index 6b3d512b4..23ae4efa9 100644 --- a/management/server/posture/checks.go +++ b/management/server/posture/checks.go @@ -83,8 +83,8 @@ func verdictChanged(ctx context.Context, check Check, oldPeer, newPeer nbpeer.Pe oldPass, oldErr := check.Check(ctx, oldPeer) newPass, newErr := check.Check(ctx, newPeer) - oldVerdict := oldPass && oldErr == nil - newVerdict := newPass && newErr == nil + oldVerdict := oldPass && (oldErr == nil) + newVerdict := newPass && (newErr == nil) changed := oldVerdict != newVerdict log.WithContext(ctx).Tracef("posture check %s replay: verdict %t -> %t (changed=%t), errs: %v -> %v",