From 8aa9dc01b77581f408263c3608487d69b8382755 Mon Sep 17 00:00:00 2001 From: reoring Date: Wed, 21 Oct 2026 15:26:42 +0900 Subject: [PATCH] hosted: evaluate parent delegation with the shared zone evaluator observeDelegation flagged DNSProbeFailed whenever len(views) != len(servers), but the probe appends the authority views reached through glue after the recursive views, so a correct delegation (observed NS equal to the configured nameServers, authority answering AA) was reported matches=false on the on-prem PoC. Use dnsprobe.EvaluateZone (required views, consistent NS/SOA, authoritative answers) and publish its reason/message as the bounded delegation error; transport errors stay opaque. --- .../controllers/zoneunit/controller_test.go | 11 ++++++- .../hosted/controllers/zoneunit/model.go | 29 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/internal/go/providers/hosted/controllers/zoneunit/controller_test.go b/internal/go/providers/hosted/controllers/zoneunit/controller_test.go index 8e85413..334878a 100644 --- a/internal/go/providers/hosted/controllers/zoneunit/controller_test.go +++ b/internal/go/providers/hosted/controllers/zoneunit/controller_test.go @@ -398,7 +398,16 @@ func TestDefaultDelegationAlwaysReported(t *testing.T) { if in.Policy.Mode != "Cluster" || len(in.Policy.Servers) != 1 { t.Fatalf("default delegation policy=%+v", in.Policy) } - return dnsprobe.ProbeResult{Zone: &dnsprobe.ZoneEvidence{Views: []dnsprobe.AuthorityObservation{{Nameservers: []string{"NS.EXAMPLE.TEST."}}}}}, nil + // Shape of a real probe: the recursive view, then the authority view + // reached through glue; both must agree and the authority answers AA. + return dnsprobe.ProbeResult{Zone: &dnsprobe.ZoneEvidence{ + Zone: in.Zone, + Authorities: []string{"192.0.2.10:53"}, + Views: []dnsprobe.AuthorityObservation{ + {ID: "recursive/" + in.Policy.Servers[0], Nameservers: []string{"NS.EXAMPLE.TEST."}, SOAOwner: in.Zone}, + {ID: "authority/192.0.2.10:53", Nameservers: []string{"ns.example.test"}, SOAOwner: in.Zone, Authoritative: true}, + }, + }}, nil } _, unit := reconcileOnce(t, r, req) var data zoneData diff --git a/internal/go/providers/hosted/controllers/zoneunit/model.go b/internal/go/providers/hosted/controllers/zoneunit/model.go index ed793bc..3cacb0c 100644 --- a/internal/go/providers/hosted/controllers/zoneunit/model.go +++ b/internal/go/providers/hosted/controllers/zoneunit/model.go @@ -15,6 +15,7 @@ import ( "github.com/appthrust/dns-api/internal/go/providers/hosted/withdrawal" dnsv1alpha1 "github.com/appthrust/dns-api/pkg/go/api/dns/v1alpha1" hostedv1alpha1 "github.com/appthrust/dns-api/pkg/go/api/hosted/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -110,16 +111,22 @@ func (r *ZoneReconciler) observeDelegation(ctx context.Context, unit *dnsv1alpha observed, err = r.Options.Resolver.Probe(ctx, request) } data := delegationData{ObservedNameServers: []string{}, LastProbeTime: r.Options.Clock()} - if err != nil || observed.Zone == nil || len(observed.Zone.Views) != len(policy.Servers) { + if err != nil { + // Transport errors may carry private network details; publish only the class. data.Error = "DNSProbeFailed" - } - if observed.Zone != nil { - for _, view := range observed.Zone.Views { - data.ObservedNameServers = append(data.ObservedNameServers, view.Nameservers...) - if view.Failure != "" { - data.Error = "DNSProbeFailed" + } else { + // The probe appends authority views after the recursive views, so the + // view count is not a failure signal; the shared evaluator decides + // (every required view answered, consistent NS/SOA, authorities AA). + decision := dnsprobe.EvaluateZone(request, observed.Zone) + if observed.Zone != nil { + for _, view := range observed.Zone.Views { + data.ObservedNameServers = append(data.ObservedNameServers, view.Nameservers...) } } + if decision.Status != metav1.ConditionTrue { + data.Error = boundedError(decision.Reason + ": " + decision.Message) + } } data.ObservedNameServers = dnsprobe.Names(data.ObservedNameServers) data.Matches = data.Error == "" && slices.Equal(data.ObservedNameServers, expected) @@ -236,3 +243,11 @@ func retainPublication(state *schedulerState, spec hostedv1alpha1.HostedZoneSpec } } } + +// boundedError keeps delegation diagnostics within the 512-byte public schema. +func boundedError(message string) string { + if len(message) > 512 { + return message[:512] + } + return message +}