checker-dangling/contract/entry.go

86 lines
3.3 KiB
Go

// Package contract defines the DiscoveryEntry types published by
// checker-dangling and consumed by companion checkers (notably
// domain-expiry, which subscribes to ExternalTargetType to perform RDAP
// on the target's registrable domain).
//
// This package is deliberately tiny and dependency-light so that any
// consumer can import it without dragging the whole checker in.
package contract
import (
"encoding/json"
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// ExternalTargetType is the DiscoveryEntry.Type for an out-of-zone
// pointer target (CNAME/MX/SRV/NS host) whose registrable domain is
// distinct from the zone apex. Consumers subscribed to this type are
// expected to look up the registrable domain (RDAP/WHOIS) and publish a
// "whois" observation per entry Ref.
const ExternalTargetType = "dangling.external-target.v1"
// InZoneTargetType is the DiscoveryEntry.Type for a pointer target that
// resolves within the same zone or the same registrable domain. It is
// declared so future probing checkers (ping/http/alias) can subscribe to
// it for in-zone reachability checks. v1 of checker-dangling does not
// itself rely on observations attached to in-zone entries.
const InZoneTargetType = "dangling.in-zone-target.v1"
// ExternalTarget is the payload of an ExternalTargetType entry.
//
// Owner is the FQDN whose pointer is at risk (e.g. "old-promo.example.com.").
// Pointer captures the type+target verbatim so a consumer can refer to
// the precise record when reporting findings. Registrable is the eTLD+1
// (or PSL-derived equivalent) that an RDAP probe should query.
type ExternalTarget struct {
Owner string `json:"owner"`
Rrtype string `json:"rrtype"` // "CNAME", "MX", "SRV", "NS"
Target string `json:"target"` // FQDN, no trailing dot
Registrable string `json:"registrable"`
}
// InZoneTarget mirrors ExternalTarget for in-zone or same-registrable
// pointers. Registrable is set when known so a subscriber can decide to
// skip records that point at the user's own domain.
type InZoneTarget struct {
Owner string `json:"owner"`
Rrtype string `json:"rrtype"`
Target string `json:"target"`
Registrable string `json:"registrable,omitempty"`
}
// Ref builds the canonical, stable Ref for a (owner, rrtype, target)
// triple. Callers must use this on both the producer and consumer side
// so RelatedObservation.Ref correlates with the right entry.
func Ref(owner, rrtype, target string) string {
return fmt.Sprintf("%s|%s|%s", owner, rrtype, target)
}
// NewExternalEntry builds a DiscoveryEntry of type ExternalTargetType
// with the canonical Ref.
func NewExternalEntry(t ExternalTarget) (sdk.DiscoveryEntry, error) {
payload, err := json.Marshal(t)
if err != nil {
return sdk.DiscoveryEntry{}, fmt.Errorf("marshal external target: %w", err)
}
return sdk.DiscoveryEntry{
Type: ExternalTargetType,
Ref: Ref(t.Owner, t.Rrtype, t.Target),
Payload: payload,
}, nil
}
// NewInZoneEntry builds a DiscoveryEntry of type InZoneTargetType.
func NewInZoneEntry(t InZoneTarget) (sdk.DiscoveryEntry, error) {
payload, err := json.Marshal(t)
if err != nil {
return sdk.DiscoveryEntry{}, fmt.Errorf("marshal in-zone target: %w", err)
}
return sdk.DiscoveryEntry{
Type: InZoneTargetType,
Ref: Ref(t.Owner, t.Rrtype, t.Target),
Payload: payload,
}, nil
}