package checker import ( "encoding/json" "github.com/miekg/dns" ) // ObservationKeyDelegation is the observation key for delegation data. const ObservationKeyDelegation = "delegation" // DelegationData is the raw, judgment-free observation produced by Collect. // Every field records what was observed (or the error string returned by // the probe) but never classifies severity. Rules consume this data and // emit CheckStates. type DelegationData struct { // DelegatedFQDN is the FQDN of the delegated zone (subdomain + parent). DelegatedFQDN string `json:"delegated_fqdn"` // ParentZone is the FQDN of the parent zone that delegates DelegatedFQDN. ParentZone string `json:"parent_zone"` // DeclaredNS lists the NS hostnames declared by the service definition, // lowercased and FQDN-normalized. DeclaredNS []string `json:"declared_ns,omitempty"` // DeclaredDS lists the DS records declared by the service definition. DeclaredDS []DSRecord `json:"declared_ds,omitempty"` // ParentDiscoveryError captures why the parent zone's authoritative // servers could not be resolved, when that step failed outright. ParentDiscoveryError string `json:"parent_discovery_error,omitempty"` // ParentNS lists the parent zone's authoritative server addresses that // were queried (host:port entries). ParentNS []string `json:"parent_ns,omitempty"` // ParentViews holds one entry per queried parent server describing // what it returned for the delegation of DelegatedFQDN. ParentViews []ParentView `json:"parent_views,omitempty"` // Children holds one entry per NS name learned from the first // successful parent view, with the per-address probes performed // against that NS. Children []ChildNSView `json:"children,omitempty"` } // ParentView captures everything one specific parent server returned // while being probed for the delegation. type ParentView struct { Server string `json:"server"` UDPNSError string `json:"udp_ns_error,omitempty"` TCPNSError string `json:"tcp_ns_error,omitempty"` NS []string `json:"ns,omitempty"` Glue map[string][]string `json:"glue,omitempty"` DSQueryError string `json:"ds_query_error,omitempty"` DS []DSRecord `json:"ds,omitempty"` DSRRSIGs []DSRRSIGObservation `json:"ds_rrsigs,omitempty"` } // ChildNSView holds the observations for a single delegated NS hostname, // possibly probed across several addresses. type ChildNSView struct { NSName string `json:"ns_name"` ResolveError string `json:"resolve_error,omitempty"` Addresses []ChildAddressView `json:"addresses,omitempty"` } // ChildAddressView captures the probes performed against a single // (NS, IP address) pair. type ChildAddressView struct { Address string `json:"address"` Server string `json:"server"` UDPError string `json:"udp_error,omitempty"` Authoritative bool `json:"authoritative"` SOASerial uint32 `json:"soa_serial,omitempty"` SOASerialKnown bool `json:"soa_serial_known,omitempty"` TCPError string `json:"tcp_error,omitempty"` ChildNS []string `json:"child_ns,omitempty"` ChildNSError string `json:"child_ns_error,omitempty"` ChildGlueAddrs []string `json:"child_glue_addrs,omitempty"` DNSKEYError string `json:"dnskey_error,omitempty"` DNSKEYs []DNSKEYRecord `json:"dnskeys,omitempty"` } // DSRecord mirrors a DS RR in a form that is both human-readable (Text) // and directly comparable (the structured fields). type DSRecord struct { Text string `json:"text"` KeyTag uint16 `json:"keytag"` Algorithm uint8 `json:"algorithm"` DigestType uint8 `json:"digest_type"` Digest string `json:"digest"` } // ToMiekg rebuilds a *dns.DS from the stored fields. func (d DSRecord) ToMiekg() *dns.DS { return &dns.DS{ KeyTag: d.KeyTag, Algorithm: d.Algorithm, DigestType: d.DigestType, Digest: d.Digest, } } // NewDSRecord converts a miekg *dns.DS into the wire-friendly DSRecord. func NewDSRecord(d *dns.DS) DSRecord { return DSRecord{ Text: d.String(), KeyTag: d.KeyTag, Algorithm: d.Algorithm, DigestType: d.DigestType, Digest: d.Digest, } } // DNSKEYRecord preserves the fields needed to recompute DS digests. type DNSKEYRecord struct { Name string `json:"name"` Flags uint16 `json:"flags"` Protocol uint8 `json:"protocol"` Algorithm uint8 `json:"algorithm"` PublicKey string `json:"public_key"` } // ToMiekg rebuilds a *dns.DNSKEY from the stored fields so the rule can // call k.ToDS(digestType). func (k DNSKEYRecord) ToMiekg() *dns.DNSKEY { name := k.Name if name == "" { name = "." } return &dns.DNSKEY{ Hdr: dns.RR_Header{Name: dns.Fqdn(name), Rrtype: dns.TypeDNSKEY, Class: dns.ClassINET}, Flags: k.Flags, Protocol: k.Protocol, Algorithm: k.Algorithm, PublicKey: k.PublicKey, } } // NewDNSKEYRecord converts a miekg *dns.DNSKEY into the wire-friendly form. func NewDNSKEYRecord(k *dns.DNSKEY) DNSKEYRecord { return DNSKEYRecord{ Name: k.Hdr.Name, Flags: k.Flags, Protocol: k.Protocol, Algorithm: k.Algorithm, PublicKey: k.PublicKey, } } // DSRRSIGObservation records an RRSIG covering a DS RRset; rules decide // whether the current clock falls inside the validity window. type DSRRSIGObservation struct { Inception uint32 `json:"inception"` Expiration uint32 `json:"expiration"` } // delegationService is the minimal local mirror of happyDomain's // `services/abstract.Delegation` type. It is duplicated on purpose so that // this checker does not have to import the (heavy) happyDomain server module // just to decode the service payload. type delegationService struct { NameServers []*dns.NS `json:"ns"` DS []*dns.DS `json:"ds"` } // serviceMessage is the minimal local mirror of happyDomain's ServiceMessage // envelope. We only need the embedded service JSON; the rest of the meta // fields are ignored. type serviceMessage struct { Type string `json:"_svctype"` Domain string `json:"_domain"` Service json.RawMessage `json:"Service"` }