checker-legacy-records/checker/deprecated.go

238 lines
8.2 KiB
Go

package checker
import (
"github.com/miekg/dns"
)
// DeprecatedSeverity grades a deprecated record family.
//
// "Critical" reflects record types whose continued use breaks DNSSEC
// validation or modern resolvers (KEY/SIG/NXT, replaced by the DNSSEC-bis
// triplet, RFC 3755). "Warning" covers types that still parse but have a
// long-standing replacement (SPF→TXT, A6→AAAA, …). "Info" is reserved for
// experimental types nobody implements anymore (NULL, NSAP, …): present in
// a zone is harmless but pointless.
type DeprecatedSeverity int
const (
SeverityInfo DeprecatedSeverity = iota
SeverityWarn
SeverityCrit
)
func (s DeprecatedSeverity) String() string {
switch s {
case SeverityCrit:
return "crit"
case SeverityWarn:
return "warn"
default:
return "info"
}
}
// DeprecationInfo describes one deprecated RR type.
type DeprecationInfo struct {
Reason string
// Replacement is the modern record type to use instead, or "" when the
// type has no replacement (just remove the record).
Replacement string
// HowToFix is the actionable instruction shown in the HTML report.
// Phrased as a direct imperative so the user can act without context
// switching to the relevant RFC.
HowToFix string
Severity DeprecatedSeverity
}
// deprecatedTypes is the source of truth for what counts as legacy.
//
// Numeric keys (instead of dns.TypeXxx) are used for types miekg/dns does
// not export as named constants; they remain valid wire types and may
// well show up in zones imported from BIND or older tooling.
var deprecatedTypes = map[uint16]DeprecationInfo{
// --- DNSSEC predecessors (RFC 3755, RFC 4033 family) -----------------
dns.TypeKEY: {
Reason: "RFC 3755 obsoleted KEY in favour of DNSKEY",
Replacement: "DNSKEY",
HowToFix: "Re-sign the zone with a DNSSEC implementation that emits DNSKEY/RRSIG/NSEC records, then remove the KEY entries.",
Severity: SeverityCrit,
},
dns.TypeSIG: {
Reason: "RFC 3755 obsoleted SIG in favour of RRSIG",
Replacement: "RRSIG",
HowToFix: "SIG records are not validated by modern resolvers. Drop them; RRSIG records are produced automatically when the zone is DNSSEC-signed.",
Severity: SeverityCrit,
},
dns.TypeNXT: {
Reason: "RFC 3755 obsoleted NXT in favour of NSEC",
Replacement: "NSEC",
HowToFix: "NXT predates DNSSEC-bis and is not understood by current validators. Re-sign the zone to produce NSEC (or NSEC3) records and remove NXT.",
Severity: SeverityCrit,
},
// --- Replaced by a clear modern equivalent ---------------------------
dns.TypeSPF: {
Reason: "RFC 7208 §3.1 deprecated the SPF record type; publish SPF policy in TXT only",
Replacement: "TXT",
HowToFix: "Publish the SPF policy as a TXT record (`v=spf1 …`) at the same owner name, then delete the SPF-typed record. Some receivers ignore SPF-typed records entirely.",
Severity: SeverityWarn,
},
38: { // A6
Reason: "RFC 6563 moved A6 to historic status",
Replacement: "AAAA",
HowToFix: "Replace each A6 record with an equivalent AAAA record carrying the full IPv6 address.",
Severity: SeverityWarn,
},
dns.TypeMD: {
Reason: "RFC 973 obsoleted MD in 1986; use MX",
Replacement: "MX",
HowToFix: "Translate the mail-destination into an MX record (preference + exchange host) and delete the MD record.",
Severity: SeverityWarn,
},
dns.TypeMF: {
Reason: "RFC 973 obsoleted MF in 1986; use MX",
Replacement: "MX",
HowToFix: "Translate the mail-forwarder into an MX record (preference + exchange host) and delete the MF record.",
Severity: SeverityWarn,
},
dns.TypeGPOS: {
Reason: "RFC 1712 superseded GPOS with LOC",
Replacement: "LOC",
HowToFix: "If geolocation is genuinely needed, publish a LOC record instead. Otherwise delete the GPOS record.",
Severity: SeverityInfo,
},
// --- Privacy/info-leak deprecations ----------------------------------
dns.TypeMB: {
Reason: "RFC 2505/RFC 1035 §3.3: experimental, unused; replaced by MX",
Replacement: "MX",
HowToFix: "Delete the MB record; route mailbox traffic via MX.",
Severity: SeverityInfo,
},
dns.TypeMG: {
Reason: "RFC 1035 §3.3: experimental mail-group record, never widely deployed",
Replacement: "",
HowToFix: "Delete the MG record; mail-group semantics now belong on the SMTP layer.",
Severity: SeverityInfo,
},
dns.TypeMR: {
Reason: "RFC 1035 §3.3: experimental mail-rename record, never widely deployed",
Replacement: "",
HowToFix: "Delete the MR record.",
Severity: SeverityInfo,
},
dns.TypeMINFO: {
Reason: "RFC 1035 §3.3: experimental mailbox-info record, never widely deployed",
Replacement: "",
HowToFix: "Delete the MINFO record.",
Severity: SeverityInfo,
},
dns.TypeNULL: {
Reason: "RFC 1035 §3.3.10: experimental, must not appear in master files",
Replacement: "",
HowToFix: "Delete the NULL record. If it is used as a private channel, switch to TXT or a dedicated underscore label.",
Severity: SeverityInfo,
},
11: { // WKS
Reason: "RFC 1123 §6.1.3.6 discouraged WKS; modern stacks ignore it",
Replacement: "",
HowToFix: "Delete the WKS record. Service availability belongs in SRV, ALPN, or HTTPS/SVCB records, not WKS.",
Severity: SeverityInfo,
},
// --- Historical address families (no live deployment) ----------------
22: { // NSAP
Reason: "RFC 1706 historical: OSI/CLNP addressing, no current deployment",
Replacement: "",
HowToFix: "Delete the NSAP record.",
Severity: SeverityInfo,
},
dns.TypeNSAPPTR: {
Reason: "RFC 1706 historical: OSI reverse mapping, no current deployment",
Replacement: "",
HowToFix: "Delete the NSAP-PTR record.",
Severity: SeverityInfo,
},
dns.TypeX25: {
Reason: "RFC 1183 historical: X.25 addressing, no current deployment",
Replacement: "",
HowToFix: "Delete the X25 record.",
Severity: SeverityInfo,
},
dns.TypeISDN: {
Reason: "RFC 1183 historical: ISDN addressing, no current deployment",
Replacement: "",
HowToFix: "Delete the ISDN record.",
Severity: SeverityInfo,
},
dns.TypeRT: {
Reason: "RFC 1183 historical: route-through, superseded by direct routing",
Replacement: "",
HowToFix: "Delete the RT record.",
Severity: SeverityInfo,
},
dns.TypeATMA: {
Reason: "ATM Forum AF-SAA-0069 historical: ATM addressing, no current deployment",
Replacement: "",
HowToFix: "Delete the ATMA record.",
Severity: SeverityInfo,
},
31: { // EID
Reason: "Nimrod EID: never deployed beyond the experiment",
Replacement: "",
HowToFix: "Delete the EID record.",
Severity: SeverityInfo,
},
32: { // NIMLOC
Reason: "Nimrod NIMLOC: never deployed beyond the experiment",
Replacement: "",
HowToFix: "Delete the NIMLOC record.",
Severity: SeverityInfo,
},
40: { // SINK
Reason: "draft-eastlake-kitchen-sink: never standardised",
Replacement: "",
HowToFix: "Delete the SINK record.",
Severity: SeverityInfo,
},
56: { // NINFO
Reason: "draft-reid-dnsext-zs: never standardised",
Replacement: "TXT",
HowToFix: "If you need free-form zone metadata, use a TXT record at the apex with a clearly scoped prefix.",
Severity: SeverityInfo,
},
57: { // RKEY
Reason: "draft-reid-dnsext-rkey: never standardised",
Replacement: "",
HowToFix: "Delete the RKEY record.",
Severity: SeverityInfo,
},
}
// extraTypeNames covers the deprecated record types that miekg/dns does
// not list in TypeToString (WKS, NSAP, A6, SINK). Without this fallback,
// typeLabel would return "TYPEnnn" for them and the report would lose the
// human-friendly name.
var extraTypeNames = map[uint16]string{
11: "WKS",
22: "NSAP",
38: "A6",
40: "SINK",
}
// typeLabel returns the textual record type name. dns.TypeToString covers
// the well-known set; for unknown rrtypes we fall back to RFC 3597 form
// ("TYPEnnn") so the report stays readable.
func typeLabel(rrtype uint16) string {
if name, ok := dns.TypeToString[rrtype]; ok {
return name
}
if name, ok := extraTypeNames[rrtype]; ok {
return name
}
// dns.Type stringer produces "TYPEnnn" for unknown types (RFC 3597).
return dns.Type(rrtype).String()
}