Extract querySiblings from observeCoexistence so both CNAME and DNAME coexistence checks share the same parallel RRset scan. Add observeDNAMECoexistence (called from Collect) that populates AliasData.DNAMECoexistence for each DNAME node in DNAMESubstitutions. Add the dname_coexistence rule (RFC 6672 §2.3) that flags any sibling RRsets at a DNAME owner as CRIT, with matching tests.
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
type dnameCoexistenceRule struct{}
|
|
|
|
func (dnameCoexistenceRule) Name() string { return "dname_coexistence" }
|
|
func (dnameCoexistenceRule) Description() string {
|
|
return "Flags RRsets that sit at the same owner as a DNAME (RFC 6672 §2.3)."
|
|
}
|
|
|
|
func (dnameCoexistenceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
|
data, errState := loadAlias(ctx, obs)
|
|
if errState != nil {
|
|
return errState
|
|
}
|
|
if !apexKnown(data) {
|
|
return skipped("apex lookup failed")
|
|
}
|
|
if len(data.DNAMESubstitutions) == 0 {
|
|
return skipped("no DNAME in chain")
|
|
}
|
|
if len(data.DNAMECoexistence) == 0 {
|
|
return okState(data.Owner, "all DNAME nodes have no sibling records")
|
|
}
|
|
var out []sdk.CheckState
|
|
for owner, coexisting := range data.DNAMECoexistence {
|
|
for _, rr := range coexisting {
|
|
out = append(out, withHint(sdk.CheckState{
|
|
Status: sdk.StatusCrit,
|
|
Subject: owner,
|
|
Message: fmt.Sprintf("%s and DNAME both exist at %s (RFC 6672 §2.3)", rr.Type, owner),
|
|
Code: rr.Type,
|
|
}, "Remove the sibling record or move it under a different label; a DNAME owner must not carry other data."))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
type cnameCoexistenceRule struct{}
|
|
|
|
func (cnameCoexistenceRule) Name() string { return "cname_coexistence" }
|
|
func (cnameCoexistenceRule) Description() string {
|
|
return "Flags other RRsets that sit at the same owner as a CNAME (RFC 1034 §3.6.2 / RFC 2181 §10.1). Honours allowApexCNAME and recognizeApexFlattening."
|
|
}
|
|
|
|
func (cnameCoexistenceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
data, errState := loadAlias(ctx, obs)
|
|
if errState != nil {
|
|
return errState
|
|
}
|
|
if !apexKnown(data) {
|
|
return skipped("apex lookup failed")
|
|
}
|
|
if !data.OwnerHasCNAME {
|
|
return skipped("owner has no CNAME")
|
|
}
|
|
if len(data.Coexisting) == 0 {
|
|
return okState(data.Owner, "CNAME is the only RRset at its owner")
|
|
}
|
|
|
|
isApex := data.OwnerIsApex
|
|
recognize := recognizeApexFlattening(opts)
|
|
allow := allowApexCNAME(opts)
|
|
|
|
var out []sdk.CheckState
|
|
for _, rr := range data.Coexisting {
|
|
// Provider-side flattening serves apex A/AAAA on a synthetic owner,
|
|
// so the wire-level coexistence is intentional, not a zone bug.
|
|
if isApex && recognize && data.ApexFlattening && (rr.Type == "A" || rr.Type == "AAAA") {
|
|
continue
|
|
}
|
|
status := sdk.StatusCrit
|
|
if isApex && allow {
|
|
status = sdk.StatusWarn
|
|
}
|
|
out = append(out, withHint(sdk.CheckState{
|
|
Status: status,
|
|
Subject: data.Owner,
|
|
Message: fmt.Sprintf("%s and CNAME both exist at %s (RFC 1034 §3.6.2 / RFC 2181 §10.1)", rr.Type, data.Owner),
|
|
Code: rr.Type,
|
|
}, "Remove the sibling record or move it under a different label; a name cannot simultaneously carry a CNAME and other data."))
|
|
}
|
|
if len(out) == 0 {
|
|
return okState(data.Owner, "CNAME coexistence exempted by ALIAS/ANAME flattening")
|
|
}
|
|
return out
|
|
}
|