From 653ce04bc115368b5ddcdefcee4fcbb47ee8826d Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 12 Nov 2025 19:17:45 +0000 Subject: [PATCH] Improve sensor proxy cluster validation (Related to #703) --- cmd/pulse-sensor-proxy/validation.go | 51 +++++++++++++++++++++-- cmd/pulse-sensor-proxy/validation_test.go | 16 +++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/cmd/pulse-sensor-proxy/validation.go b/cmd/pulse-sensor-proxy/validation.go index 4709e5bdc..6d91046bc 100644 --- a/cmd/pulse-sensor-proxy/validation.go +++ b/cmd/pulse-sensor-proxy/validation.go @@ -355,7 +355,7 @@ func (v *nodeValidator) Validate(ctx context.Context, node string) error { } if v.clusterEnabled { - allowed, err := v.matchesCluster(node) + allowed, err := v.matchesCluster(ctx, node) if err != nil { v.recordFailure(validationReasonClusterFailed) return fmt.Errorf("failed to evaluate cluster membership: %w", err) @@ -408,12 +408,16 @@ func (v *nodeValidator) matchesAllowlist(ctx context.Context, node string) (bool return false, nil } -func (v *nodeValidator) matchesCluster(node string) (bool, error) { +func (v *nodeValidator) matchesCluster(ctx context.Context, node string) (bool, error) { if v.clusterFetcher == nil { return false, errors.New("cluster membership disabled") } - members, err := v.getClusterMembers() + if ctx == nil { + ctx = context.Background() + } + + members, err := v.getClusterMembers(ctx) if err != nil { return false, err } @@ -427,7 +431,11 @@ func (v *nodeValidator) matchesCluster(node string) (bool, error) { return ok, nil } -func (v *nodeValidator) getClusterMembers() (map[string]struct{}, error) { +func (v *nodeValidator) getClusterMembers(ctx context.Context) (map[string]struct{}, error) { + if ctx == nil { + ctx = context.Background() + } + now := time.Now() if v.clock != nil { now = v.clock() @@ -446,10 +454,45 @@ func (v *nodeValidator) getClusterMembers() (map[string]struct{}, error) { } result := make(map[string]struct{}, len(nodes)) + resolvedHosts := make(map[string]struct{}) for _, node := range nodes { if normalized := normalizeAllowlistEntry(node); normalized != "" { result[normalized] = struct{}{} } + + host := stripNodeDelimiters(strings.TrimSpace(node)) + if host == "" { + continue + } + + if net.ParseIP(host) != nil { + continue + } + + if _, seen := resolvedHosts[host]; seen { + continue + } + resolvedHosts[host] = struct{}{} + + if v.resolver == nil { + continue + } + + ips, err := v.resolver.LookupIP(ctx, host) + if err != nil { + log.Debug(). + Str("host", host). + Err(err). + Msg("Failed to resolve cluster node hostname to IP") + continue + } + + for _, ip := range ips { + if ip == nil { + continue + } + result[ip.String()] = struct{}{} + } } ttl := v.cacheTTL diff --git a/cmd/pulse-sensor-proxy/validation_test.go b/cmd/pulse-sensor-proxy/validation_test.go index 692d1ac3f..3fbac30dc 100644 --- a/cmd/pulse-sensor-proxy/validation_test.go +++ b/cmd/pulse-sensor-proxy/validation_test.go @@ -209,6 +209,22 @@ func TestNodeValidatorClusterCaching(t *testing.T) { } } +func TestNodeValidatorClusterResolvesHostIPs(t *testing.T) { + v := &nodeValidator{ + clusterEnabled: true, + clusterFetcher: func() ([]string, error) { + return []string{"worker.local"}, nil + }, + resolver: stubResolver{ + ips: []net.IP{net.ParseIP("10.0.0.5")}, + }, + } + + if err := v.Validate(context.Background(), "10.0.0.5"); err != nil { + t.Fatalf("expected cluster hostname resolution to permit node: %v", err) + } +} + func TestNodeValidatorStrictNoSources(t *testing.T) { v := &nodeValidator{ strict: true,