fix(monitoring): preserve custom bridge link evidence

Limit automatic Docker bridge filtering to the generated br-<12 hex> convention so custom management bridges such as br-mgmt remain eligible for host association. Keep docker-prefixed and non-global-unicast exclusions intact.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-06 09:28:51 +01:00
parent 20bd7092c2
commit 2ed9965968
4 changed files with 38 additions and 17 deletions
@@ -21,10 +21,12 @@ Host ingestion must not create a host-to-PVE or reciprocal PVE-to-agent link
solely from a shared host-local network address. Uniqueness among monitored
PVE nodes does not establish uniqueness across unrelated agent hosts.
Automatic network evidence excludes loopback, unspecified, multicast and
link-local IPs, and interfaces named lo or prefixed docker/br-. This changes
association evidence only: enrollment, token binding, removal, re-enrollment
and command authority remain unchanged. It does not migrate or repair
persisted incorrect links.
link-local IPs, interfaces named lo or prefixed docker, and Docker-generated
bridge names matching `br-` plus a 12-character hexadecimal network ID. Other
bridge names remain eligible because they may carry management traffic. This
changes association evidence only: enrollment, token binding, removal,
re-enrollment and command authority remain unchanged. It does not migrate or
repair persisted incorrect links.
Verification: `TestApplyHostReportDoesNotLinkUnrelatedDockerBridge` in
`internal/monitoring/monitor_host_agents_test.go` ingests repeated synthetic
@@ -19,14 +19,15 @@
### Host-local addresses are not PVE identity
Automatic host/PVE network matching excludes non-global-unicast addresses
and lo/docker*/br-* interfaces from both reported-host and provider-node
address inventories. A Docker bridge address seen on only one PVE node can
still exist on an unrelated NAS; provider-only owner counts cannot make it
machine identity. Management bridges such as vmbr0, unnamed legacy interfaces,
private IPv4 and IPv6/ULA remain eligible. Explicit unicast report-IP hints
remain eligible independently of inferred interface evidence. No blanket
private-subnet exclusion is permitted.
Automatic host/PVE network matching excludes non-global-unicast addresses,
lo/docker* interfaces and Docker-generated `br-` plus 12-hex-character bridge
names from both reported-host and provider-node address inventories. A Docker
bridge address seen on only one PVE node can still exist on an unrelated NAS;
provider-only owner counts cannot make it machine identity. Management bridges
such as vmbr0 and br-mgmt, unnamed legacy interfaces, private IPv4 and IPv6/ULA
remain eligible. Explicit unicast report-IP hints remain eligible independently
of inferred interface evidence. No blanket private-subnet exclusion is
permitted.
Verification in `internal/monitoring/monitor_host_agents_test.go`:
`TestFindLinkedProxmoxEntityWithHints_RejectsHostLocalNetworkIdentity` covers
+19 -3
View File
@@ -4400,11 +4400,27 @@ func normalizeAgentLinkIP(address string) string {
return ""
}
// Do not discard all bridges: Proxmox management addresses commonly use vmbr0.
// Explicit report-IP and endpoint hints remain usable for routable addresses.
// Do not discard all bridges: Proxmox management addresses commonly use vmbr0,
// and operators may use names such as br-mgmt. Docker's automatically named
// user-defined bridges use br- followed by the first 12 hex characters of the
// network ID. Explicit report-IP and endpoint hints remain usable for routable
// addresses.
func isHostLocalAgentLinkInterface(name string) bool {
name = strings.ToLower(strings.TrimSpace(name))
return name == "lo" || strings.HasPrefix(name, "docker") || strings.HasPrefix(name, "br-")
if name == "lo" || strings.HasPrefix(name, "docker") {
return true
}
bridgeID, found := strings.CutPrefix(name, "br-")
if !found || len(bridgeID) != 12 {
return false
}
for _, character := range bridgeID {
if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
return false
}
}
return true
}
func collectReportedHostIPs(
@@ -5691,7 +5691,7 @@ func TestApplyHostReportPreservesPoolOnlyUnraidCount(t *testing.T) {
func TestFindLinkedProxmoxEntityWithHints_RejectsHostLocalNetworkIdentity(t *testing.T) {
for _, tc := range []struct{ name, nic, address string }{
{"docker", "docker0", "172.17.0.1/16"},
{"custom docker bridge", "br-123abc", "192.0.2.1/24"},
{"generated docker bridge", "br-0123456789ab", "192.0.2.1/24"},
{"loopback", "lo", "127.0.0.1/8"},
{"IPv6 loopback", "lo", "::1/128"},
{"link local", "eth0", "169.254.1.2/16"},
@@ -5726,8 +5726,10 @@ func TestAgentLinkNetworkIdentityFiltering(t *testing.T) {
{"IPv6 management", "vmbr0", "2001:db8::10/64", true},
{"ULA management", "eth0", "fd00::10/64", true},
{"unnamed legacy interface", "", "192.0.2.10", true},
{"custom management bridge", "br-mgmt", "192.0.2.10", true},
{"docker", "docker0", "172.17.0.1/16", false},
{"custom docker", "br-abc", "192.0.2.1/24", false},
{"generated docker", "br-0123456789ab", "192.0.2.1/24", false},
{"non-hex bridge", "br-0123456789ag", "192.0.2.1/24", true},
{"loopback", "eth0", "127.1.2.3/8", false},
{"IPv6 unspecified", "eth0", "::", false},
{"multicast", "eth0", "224.0.0.1", false},