diff --git a/pkg/discovery/discovery.go b/pkg/discovery/discovery.go index 5a19083..ac85eb6 100644 --- a/pkg/discovery/discovery.go +++ b/pkg/discovery/discovery.go @@ -51,13 +51,13 @@ type DiscoveryResult struct { // friendlyPhaseName converts technical phase names to user-friendly descriptions func friendlyPhaseName(phase string) string { friendlyNames := map[string]string{ - "lxc_container_network": "Container network", - "docker_bridge_network": "Docker bridge network", - "docker_container_network": "Docker container network", - "host_local_network": "Local network", - "inferred_gateway_network": "Gateway network", - "extra_targets": "Additional targets", - "proxmox_cluster_network": "Proxmox cluster network", + "lxc_container_network": "Container network", + "docker_bridge_network": "Docker bridge network", + "docker_container_network": "Docker container network", + "host_local_network": "Local network", + "inferred_gateway_network": "Gateway network", + "extra_targets": "Additional targets", + "proxmox_cluster_network": "Proxmox cluster network", } if friendly, ok := friendlyNames[phase]; ok { @@ -1193,9 +1193,12 @@ func (s *Scanner) applyPBSHeuristics(ctx context.Context, address string, result } case http.StatusUnauthorized, http.StatusForbidden: if versionFinding.ProductGuess == productPBS { - result.addConfidence(productPBS, "auth headers indicated PBS", 0.45, true) + result.addConfidence(productPBS, "auth headers indicated PBS", 0.55, true) } else { - result.addConfidence(productPBS, "version endpoint on PBS port requires auth", 0.35, true) + // High confidence: Port 8007 + api2/json/version exists (even if auth required) is a very strong signal. + // We bump this to ensure we cross the threshold and avoid probing other endpoints (status/datastore) + // which would generate "authentication failure" logs on the server. + result.addConfidence(productPBS, "version endpoint on PBS port requires auth", 0.55, true) } default: if result.Reachable && result.ProductScores[productPBS] < 0.25 { diff --git a/pkg/discovery/discovery_test.go b/pkg/discovery/discovery_test.go index 8a47c15..c70668c 100644 --- a/pkg/discovery/discovery_test.go +++ b/pkg/discovery/discovery_test.go @@ -427,3 +427,41 @@ func TestDiscoverServersCancelledContext(t *testing.T) { t.Fatalf("expected no servers on cancelled context") } } + +func TestPBSDiscoveryWithUnauthorizedVersion(t *testing.T) { + // Handler that simulates PBS requiring auth for version endpoint + // and NOT providing any helpful headers initially to test the port+auth heuristic + pbsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/api2/json/version" { + w.WriteHeader(http.StatusUnauthorized) + return + } + http.NotFound(w, r) + }) + + server := startTLSServerOn(t, "127.0.0.1:8007", pbsHandler) + _ = server + + scanner := newTestScanner(&http.Client{ + Timeout: 500 * time.Millisecond, + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, + }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + // Probe port 8007 + probe := scanner.ProbeProxmoxService(ctx, "127.0.0.1", 8007) + + if probe == nil { + t.Fatal("ProbeProxmoxService returned nil") + } + + if !probe.Positive { + t.Errorf("Expected positive identification for PBS on port 8007 with 401 version response, got negative. Score: %f", probe.PrimaryScore) + } + + if probe.PrimaryProduct != ProductPBS { + t.Errorf("Expected product %q, got %q", ProductPBS, probe.PrimaryProduct) + } +}