checker: expose standalone /check route via CheckerInteractive

Implements RenderForm/ParseForm on the provider: users can hit /check
with just a domain name; ParseForm resolves CAA records via direct DNS
queries (walking up the label tree per RFC 8659) and hands the SDK a
synthetic service payload so the standard Collect → Evaluate pipeline
runs without a happyDomain host. TLS probes are not gathered here, so
the rule reports StatusUnknown for the TLS cross-check in this mode.
This commit is contained in:
nemunaire 2026-04-23 11:29:39 +07:00
commit 1d562e7aa4

View file

@ -36,6 +36,7 @@ func (p *caaProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) {
return nil, errors.New("domain is required") return nil, errors.New("domain is required")
} }
domain = dns.Fqdn(domain) domain = dns.Fqdn(domain)
bare := strings.TrimSuffix(domain, ".")
records, err := lookupCAA(domain) records, err := lookupCAA(domain)
if err != nil { if err != nil {
@ -55,12 +56,12 @@ func (p *caaProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) {
svc := serviceMessage{ svc := serviceMessage{
Type: serviceType, Type: serviceType,
Domain: strings.TrimSuffix(domain, "."), Domain: bare,
Service: svcBody, Service: svcBody,
} }
return sdk.CheckerOptions{ return sdk.CheckerOptions{
"domain": strings.TrimSuffix(domain, "."), "domain": bare,
"service": svc, "service": svc,
}, nil }, nil
} }
@ -74,12 +75,12 @@ func lookupCAA(fqdn string) ([]CAARecord, error) {
return nil, err return nil, err
} }
c := new(dns.Client)
for name := fqdn; name != "" && name != "."; { for name := fqdn; name != "" && name != "."; {
msg := new(dns.Msg) msg := new(dns.Msg)
msg.SetQuestion(name, dns.TypeCAA) msg.SetQuestion(name, dns.TypeCAA)
msg.RecursionDesired = true msg.RecursionDesired = true
c := new(dns.Client)
in, _, err := c.Exchange(msg, resolver) in, _, err := c.Exchange(msg, resolver)
if err != nil { if err != nil {
return nil, err return nil, err