Add DNSSEC signing, key backups and Porkbun support
Zones get a DNSSEC setting: named signs them with BIND's default (or a custom) dnssec-policy and inline signing, with keys in a per-view key directory. Jarl shows the keys, the DS record and when it is safe to publish it, checks the parent's DS records (check-ds, which also tells named), and refuses steps that would break a signed domain: switching straight to unsigned, finishing a turn-off too early, deleting or moving a signed zone, TTLs above max-zone-ttl. Turning off goes through the insecure policy; wipe starts over once the parent has no DS. Backups store the DNSSEC keys and restore puts them back for named; the backup unit gets CAP_DAC_READ_SEARCH to read them. Jarl raises a zone's serial above named's whenever it writes an older one (after a restore). The serials check compares the signed serial. With a Porkbun API key in jarl.yaml, the registrar's name servers, glue records and DS records can be shown and changed, each change checked first (dry run, force to override). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
293e6ddd33
commit
5630e7a848
72 files changed
+5760
-153
No files matched your search
+256
-46
@@ -9,6 +9,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"jarl/internal/dnssec"
|
||||
)
|
||||
|
||||
// Finding is one conclusion of a delegation check. Level is "ok",
|
||||
@@ -28,8 +30,73 @@ type ParentAnswer struct {
|
||||
NS []string `json:"ns,omitempty"`
|
||||
// Glue is the addresses the parent hands out for in-zone name
|
||||
// servers.
|
||||
Glue map[string][]string `json:"glue,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Glue map[string][]string `json:"glue,omitempty"`
|
||||
// DS is the zone's DS records at this server ("" in DSError when it
|
||||
// couldn't be asked).
|
||||
DS []dnssec.DS `json:"ds,omitempty"`
|
||||
DSError string `json:"ds_error,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Signing is how Jarl signs a zone, for comparing with the parent's DS
|
||||
// records.
|
||||
type Signing struct {
|
||||
// Policy is the zone's dnssec-policy ("" when unsigned, "insecure"
|
||||
// while signing is being removed).
|
||||
Policy string
|
||||
// DS are the DS records of the zone's key-signing keys that named
|
||||
// still publishes.
|
||||
DS []dnssec.DS
|
||||
}
|
||||
|
||||
// ParentDSReport is what the parent zone's servers say about the zone's
|
||||
// DS records.
|
||||
type ParentDSReport struct {
|
||||
Zone string `json:"zone"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Resolver string `json:"resolver,omitempty"`
|
||||
// Delegated: at least one parent server delegates the zone. Reached
|
||||
// is how many answered.
|
||||
Delegated bool `json:"delegated"`
|
||||
Reached int `json:"reached"`
|
||||
Servers []ParentAnswer `json:"parent_servers"`
|
||||
// DS is every DS record any parent server has.
|
||||
DS []dnssec.DS `json:"ds"`
|
||||
// Consistent: every server that answered has the same DS records.
|
||||
Consistent bool `json:"consistent"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// AllHaveDS reports whether every parent server that answered has a DS
|
||||
// record for key tag (with digest, unless "").
|
||||
func (r *ParentDSReport) AllHaveDS(tag uint16, digest string) bool {
|
||||
n := 0
|
||||
for _, a := range r.Servers {
|
||||
if a.Status != "delegated" || a.DSError != "" {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
found := false
|
||||
for _, d := range a.DS {
|
||||
if d.KeyTag == tag && (digest == "" || strings.EqualFold(d.Digest, digest)) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return n > 0
|
||||
}
|
||||
|
||||
// HasDS reports whether a parent server has a DS record for key tag.
|
||||
func (r *ParentDSReport) HasDS(tag uint16, digest string) bool {
|
||||
for _, d := range r.DS {
|
||||
if d.KeyTag == tag && (digest == "" || strings.EqualFold(d.Digest, digest)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GlueCheck compares the parent's glue for an in-zone name server with
|
||||
@@ -52,6 +119,8 @@ type DelegationReport struct {
|
||||
JarlNS []string `json:"jarl_ns"`
|
||||
Parents []ParentAnswer `json:"parent_servers"`
|
||||
Glue []GlueCheck `json:"glue"`
|
||||
// ParentDS are the zone's DS records at the parent (DNSSEC).
|
||||
ParentDS []dnssec.DS `json:"parent_ds"`
|
||||
// Servers are the delegated name servers' SOA answers, as resolvers
|
||||
// on the internet would reach them.
|
||||
Servers []Result `json:"servers"`
|
||||
@@ -60,12 +129,14 @@ type DelegationReport struct {
|
||||
|
||||
// Delegation checks how the parent zone delegates zone: it finds the
|
||||
// parent through the resolver, asks each of the parent's servers for the
|
||||
// zone's NS records and glue, compares those with jarlNS and jarlAddrs
|
||||
// (Jarl's NS records and its addresses for in-zone name servers), and
|
||||
// asks every delegated server for the zone's serial (compared with ref).
|
||||
func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string, jarlAddrs map[string][]string, ref *uint32) *DelegationReport {
|
||||
// zone's NS records, glue and DS records, compares those with jarlNS and
|
||||
// jarlAddrs (Jarl's NS records and its addresses for in-zone name
|
||||
// servers) and with sig (how Jarl signs the zone; nil skips the DNSSEC
|
||||
// part), and asks every delegated server for the zone's serial (compared
|
||||
// with ref).
|
||||
func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string, jarlAddrs map[string][]string, ref *uint32, sig *Signing) *DelegationReport {
|
||||
zone = dns.CanonicalName(zone)
|
||||
rep := &DelegationReport{Zone: zone, JarlNS: canonAll(jarlNS), ParentNS: []string{}, Parents: []ParentAnswer{}, Glue: []GlueCheck{}, Servers: []Result{}, Findings: []Finding{}}
|
||||
rep := &DelegationReport{Zone: zone, JarlNS: canonAll(jarlNS), ParentNS: []string{}, Parents: []ParentAnswer{}, Glue: []GlueCheck{}, ParentDS: []dnssec.DS{}, Servers: []Result{}, Findings: []Finding{}}
|
||||
sort.Strings(rep.JarlNS)
|
||||
add := func(level, format string, args ...any) {
|
||||
for i, a := range args {
|
||||
@@ -78,42 +149,17 @@ func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string,
|
||||
}
|
||||
rep.Findings = append(rep.Findings, Finding{level, fmt.Sprintf(format, args...)})
|
||||
}
|
||||
res, err := c.resolver()
|
||||
if err != nil {
|
||||
add("error", "No resolver to find the parent zone with: %v", err)
|
||||
pr := c.ParentDS(ctx, zone)
|
||||
rep.Resolver, rep.Parent, rep.Parents = pr.Resolver, pr.Parent, pr.Servers
|
||||
if pr.Error != "" {
|
||||
level := "error"
|
||||
if zone == "." {
|
||||
level = "info"
|
||||
}
|
||||
rep.Findings = append(rep.Findings, Finding{level, pr.Error})
|
||||
return rep
|
||||
}
|
||||
rep.Resolver = res
|
||||
if zone == "." {
|
||||
add("info", "The root zone has no parent to delegate it.")
|
||||
return rep
|
||||
}
|
||||
|
||||
// The parent zone: the zone that holds the name just above ours.
|
||||
parent, err := c.enclosingZone(ctx, res, parentName(zone))
|
||||
if err != nil {
|
||||
add("error", "Couldn't find the parent zone of %s through %s: %v", zone, res, err)
|
||||
return rep
|
||||
}
|
||||
rep.Parent = parent
|
||||
pns, err := c.nsNames(ctx, res, parent)
|
||||
if err != nil || len(pns) == 0 {
|
||||
add("error", "Couldn't find the name servers of the parent zone %s through %s: %v", parent, res, errOr(err, "none listed"))
|
||||
return rep
|
||||
}
|
||||
|
||||
// Ask every parent server, in parallel.
|
||||
var wg sync.WaitGroup
|
||||
answers := make([]ParentAnswer, len(pns))
|
||||
for i, name := range pns {
|
||||
wg.Add(1)
|
||||
go func(i int, name string) {
|
||||
defer wg.Done()
|
||||
answers[i] = c.askParent(ctx, zone, name)
|
||||
}(i, name)
|
||||
}
|
||||
wg.Wait()
|
||||
rep.Parents = answers
|
||||
parent, answers := pr.Parent, pr.Servers
|
||||
|
||||
parentNS := map[string]bool{}
|
||||
glue := map[string]map[string]bool{}
|
||||
@@ -141,10 +187,6 @@ func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string,
|
||||
notDelegated++
|
||||
}
|
||||
}
|
||||
if reached == 0 {
|
||||
add("error", "None of the parent zone's servers (%s) answered; this server may not be able to reach them.", pns)
|
||||
return rep
|
||||
}
|
||||
if delegated == 0 {
|
||||
rep.Delegated = false
|
||||
where := "the parent zone " + show(parent)
|
||||
@@ -154,6 +196,7 @@ func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string,
|
||||
add("info", "%s isn't delegated from %s, so the internet can't see it. That's expected for a zone used only on your own network. To publish it, register the domain and give your registrar these name servers: %s.", zone, where, rep.JarlNS)
|
||||
return rep
|
||||
}
|
||||
rep.ParentDS = pr.DS
|
||||
rep.Delegated = true
|
||||
if notDelegated > 0 {
|
||||
add("warning", "%d of the parent's %d servers don't know the delegation yet (a recent change at the registrar can take a while to reach them all).", notDelegated, reached)
|
||||
@@ -207,6 +250,12 @@ func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string,
|
||||
}
|
||||
}
|
||||
|
||||
if sig != nil {
|
||||
for _, f := range dsFindings(zone, pr, sig) {
|
||||
rep.Findings = append(rep.Findings, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Every delegated server, reached the way resolvers reach it: by its
|
||||
// glue, or by looking its name up.
|
||||
var targets []Target
|
||||
@@ -262,7 +311,151 @@ func (c *Checker) Delegation(ctx context.Context, zone string, jarlNS []string,
|
||||
return rep
|
||||
}
|
||||
|
||||
// askParent asks one of the parent's servers (by name) for zone's NS.
|
||||
// ParentDS finds the parent of zone through the resolver and asks each of
|
||||
// its servers for the zone's delegation and DS records.
|
||||
func (c *Checker) ParentDS(ctx context.Context, zone string) *ParentDSReport {
|
||||
zone = dns.CanonicalName(zone)
|
||||
rep := &ParentDSReport{Zone: zone, Servers: []ParentAnswer{}, DS: []dnssec.DS{}}
|
||||
res, err := c.resolver()
|
||||
if err != nil {
|
||||
rep.Error = "No resolver to find the parent zone with: " + err.Error()
|
||||
return rep
|
||||
}
|
||||
rep.Resolver = res
|
||||
if zone == "." {
|
||||
rep.Error = "The root zone has no parent to delegate it."
|
||||
return rep
|
||||
}
|
||||
// The parent zone: the zone that holds the name just above ours.
|
||||
parent, err := c.enclosingZone(ctx, res, parentName(zone))
|
||||
if err != nil {
|
||||
rep.Error = fmt.Sprintf("Couldn't find the parent zone of %s through %s: %v", show(zone), res, err)
|
||||
return rep
|
||||
}
|
||||
rep.Parent = parent
|
||||
pns, err := c.nsNames(ctx, res, parent)
|
||||
if err != nil || len(pns) == 0 {
|
||||
rep.Error = fmt.Sprintf("Couldn't find the name servers of the parent zone %s through %s: %v", show(parent), res, errOr(err, "none listed"))
|
||||
return rep
|
||||
}
|
||||
// Ask every parent server, in parallel.
|
||||
var wg sync.WaitGroup
|
||||
answers := make([]ParentAnswer, len(pns))
|
||||
for i, name := range pns {
|
||||
wg.Add(1)
|
||||
go func(i int, name string) {
|
||||
defer wg.Done()
|
||||
answers[i] = c.askParent(ctx, zone, name)
|
||||
}(i, name)
|
||||
}
|
||||
wg.Wait()
|
||||
rep.Servers = answers
|
||||
all := map[string]dnssec.DS{}
|
||||
sets := map[string]bool{}
|
||||
for _, a := range answers {
|
||||
if a.Status != "delegated" && a.Status != "not_delegated" {
|
||||
continue
|
||||
}
|
||||
rep.Reached++
|
||||
if a.Status == "delegated" {
|
||||
rep.Delegated = true
|
||||
}
|
||||
if a.DSError != "" {
|
||||
continue
|
||||
}
|
||||
var set []string
|
||||
for _, d := range a.DS {
|
||||
k := dsKey(d)
|
||||
all[k] = d
|
||||
set = append(set, k)
|
||||
}
|
||||
sort.Strings(set)
|
||||
sets[strings.Join(set, " ")] = true
|
||||
}
|
||||
if rep.Reached == 0 {
|
||||
names := make([]string, len(pns))
|
||||
for i, n := range pns {
|
||||
names[i] = show(n)
|
||||
}
|
||||
rep.Error = fmt.Sprintf("None of the parent zone's servers (%s) answered; this server may not be able to reach them.", strings.Join(names, ", "))
|
||||
return rep
|
||||
}
|
||||
for _, k := range keys(boolSet(all)) {
|
||||
rep.DS = append(rep.DS, all[k])
|
||||
}
|
||||
rep.Consistent = len(sets) <= 1
|
||||
return rep
|
||||
}
|
||||
|
||||
func dsKey(d dnssec.DS) string {
|
||||
return fmt.Sprintf("%05d %d %d %s", d.KeyTag, d.Algorithm, d.DigestType, strings.ToUpper(d.Digest))
|
||||
}
|
||||
|
||||
func boolSet(m map[string]dnssec.DS) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for k := range m {
|
||||
out[k] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// dsFindings compares the parent's DS records with how Jarl signs the
|
||||
// zone.
|
||||
func dsFindings(zone string, pr *ParentDSReport, sig *Signing) []Finding {
|
||||
var out []Finding
|
||||
add := func(level, format string, args ...any) {
|
||||
out = append(out, Finding{level, fmt.Sprintf(format, args...)})
|
||||
}
|
||||
z := show(zone)
|
||||
var tags []string
|
||||
for _, d := range pr.DS {
|
||||
tags = append(tags, fmt.Sprint(d.KeyTag))
|
||||
}
|
||||
if !pr.Consistent {
|
||||
add("warning", "The parent's servers have different DS records for %s (a change at the registrar may still be on its way to them all).", z)
|
||||
}
|
||||
matches := func(d dnssec.DS) bool {
|
||||
for _, k := range sig.DS {
|
||||
if k.KeyTag == d.KeyTag && k.Algorithm == d.Algorithm && k.DigestType == d.DigestType && strings.EqualFold(k.Digest, d.Digest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
var good, stale []string
|
||||
for _, d := range pr.DS {
|
||||
if matches(d) {
|
||||
good = append(good, fmt.Sprint(d.KeyTag))
|
||||
} else {
|
||||
stale = append(stale, fmt.Sprint(d.KeyTag))
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(pr.DS) == 0 && sig.Policy == "":
|
||||
// Unsigned and no DS: nothing to say about DNSSEC.
|
||||
case len(pr.DS) == 0 && sig.Policy == "insecure":
|
||||
add("ok", "The parent has no DS record for %s any more, so turning DNSSEC off can go ahead.", z)
|
||||
case len(pr.DS) == 0:
|
||||
add("info", "%s is signed, but the parent has no DS record for it, so resolvers treat it as unsigned. Add the DS record at your registrar when the zone's DNSSEC page says it's ready.", z)
|
||||
case sig.Policy == "":
|
||||
add("error", "The parent has a DS record for %s (key %s), but the zone isn't signed: validating resolvers reject all of its answers. Remove the DS record at your registrar now.", z, strings.Join(tags, ", "))
|
||||
case len(good) == 0:
|
||||
add("error", "The parent's DS record for %s (key %s) matches none of the zone's keys: validating resolvers reject all of its answers. Replace it at your registrar with the zone's DS record.", z, strings.Join(tags, ", "))
|
||||
default:
|
||||
if sig.Policy == "insecure" {
|
||||
add("warning", "The parent still has the DS record for %s (key %s). DNSSEC is being turned off: remove it at your registrar, then run the DS check.", z, strings.Join(good, ", "))
|
||||
} else {
|
||||
add("ok", "The parent's DS record matches the zone's key %s: validating resolvers check its answers.", strings.Join(good, ", "))
|
||||
}
|
||||
if len(stale) > 0 {
|
||||
add("warning", "The parent also has a DS record for key %s, which the zone no longer has. Remove it at your registrar.", strings.Join(stale, ", "))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// askParent asks one of the parent's servers (by name) for zone's NS and
|
||||
// DS records.
|
||||
func (c *Checker) askParent(ctx context.Context, zone, server string) ParentAnswer {
|
||||
pa := ParentAnswer{Name: server}
|
||||
addrs, err := c.lookup(ctx, server)
|
||||
@@ -331,6 +524,23 @@ func (c *Checker) askParent(ctx context.Context, zone, server string) ParentAnsw
|
||||
for n := range pa.Glue {
|
||||
pa.Glue[n] = canonAddrs(pa.Glue[n])
|
||||
}
|
||||
// The parent itself answers DS queries for the zone.
|
||||
m = new(dns.Msg)
|
||||
m.SetQuestion(zone, dns.TypeDS)
|
||||
m.RecursionDesired = false
|
||||
resp, _, err = c.exchange(ctx, m, pa.Address, nil)
|
||||
switch {
|
||||
case err != nil:
|
||||
pa.DSError = err.Error()
|
||||
case resp.Rcode != dns.RcodeSuccess:
|
||||
pa.DSError = "answered " + dns.RcodeToString[resp.Rcode]
|
||||
default:
|
||||
for _, rr := range resp.Answer {
|
||||
if d, ok := rr.(*dns.DS); ok && dns.CanonicalName(d.Hdr.Name) == zone {
|
||||
pa.DS = append(pa.DS, dnssec.DS{KeyTag: d.KeyTag, Algorithm: d.Algorithm, DigestType: d.DigestType, Digest: strings.ToUpper(d.Digest), Record: d.String()})
|
||||
}
|
||||
}
|
||||
}
|
||||
return pa
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"jarl/internal/dnssec"
|
||||
)
|
||||
|
||||
// fake is a DNS server on one loopback address that answers with fn.
|
||||
@@ -187,7 +189,7 @@ func TestDelegationMismatch(t *testing.T) {
|
||||
ref := uint32(7)
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.",
|
||||
[]string{"ns1.adbraun.lol.", "ns2.adbraun.lol."},
|
||||
map[string][]string{"ns1.adbraun.lol.": {"127.0.0.99"}, "ns2.adbraun.lol.": {"127.0.0.14"}}, &ref)
|
||||
map[string][]string{"ns1.adbraun.lol.": {"127.0.0.99"}, "ns2.adbraun.lol.": {"127.0.0.14"}}, &ref, nil)
|
||||
if rep.Parent != "lol." || !rep.Delegated {
|
||||
t.Fatalf("report: %+v", rep)
|
||||
}
|
||||
@@ -218,7 +220,7 @@ func TestDelegationGood(t *testing.T) {
|
||||
return m
|
||||
})
|
||||
ref := uint32(7)
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, map[string][]string{"ns1.adbraun.lol.": {"127.0.0.12"}}, &ref)
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, map[string][]string{"ns1.adbraun.lol.": {"127.0.0.12"}}, &ref, nil)
|
||||
l := levels(rep)
|
||||
if len(l["error"])+len(l["warning"])+len(l["info"]) != 0 || len(l["ok"]) != 2 {
|
||||
t.Errorf("findings: %v", l)
|
||||
@@ -234,7 +236,7 @@ func TestNotDelegated(t *testing.T) {
|
||||
m.Ns = []dns.RR{rr("lol. 300 IN SOA ns.nic.lol. hostmaster.lol. 1 3600 900 1209600 300")}
|
||||
return m
|
||||
})
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, nil, nil)
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, nil, nil, nil)
|
||||
l := levels(rep)
|
||||
if rep.Delegated || len(l["info"]) != 1 || len(l["warning"])+len(l["error"]) != 0 {
|
||||
t.Errorf("findings: %v", l)
|
||||
@@ -243,3 +245,65 @@ func TestNotDelegated(t *testing.T) {
|
||||
t.Errorf("note: %s", l["info"][0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelegationDS(t *testing.T) {
|
||||
const dsData = "22642 13 2 0B7A2F5E1C8E2F6A0B7A2F5E1C8E2F6A0B7A2F5E1C8E2F6A0B7A2F5E1C8E2F6A"
|
||||
parent := func(withDS bool) fake {
|
||||
return func(q dns.Question) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
switch {
|
||||
case q.Qtype == dns.TypeNS && q.Name == "adbraun.lol.":
|
||||
m.Ns = []dns.RR{rr("adbraun.lol. 3600 IN NS ns1.adbraun.lol.")}
|
||||
m.Extra = []dns.RR{rr("ns1.adbraun.lol. 3600 IN A 127.0.0.12")}
|
||||
case q.Qtype == dns.TypeDS && q.Name == "adbraun.lol.":
|
||||
m.Authoritative = true
|
||||
if withDS {
|
||||
m.Answer = []dns.RR{rr("adbraun.lol. 3600 IN DS " + dsData)}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
}
|
||||
ours := dnssec.DS{KeyTag: 22642, Algorithm: 13, DigestType: 2, Digest: strings.Fields(dsData)[3]}
|
||||
other := ours
|
||||
other.KeyTag, other.Digest = 1111, strings.Repeat("AB", 32)
|
||||
ref := uint32(7)
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
withDS bool
|
||||
sig Signing
|
||||
level string
|
||||
want string
|
||||
}{
|
||||
{"secure", true, Signing{Policy: "default", DS: []dnssec.DS{ours}}, "ok", "matches the zone's key 22642"},
|
||||
{"wrong key", true, Signing{Policy: "default", DS: []dnssec.DS{other}}, "error", "matches none of the zone's keys"},
|
||||
{"unsigned", true, Signing{}, "error", "but the zone isn't signed"},
|
||||
{"turning off", true, Signing{Policy: "insecure", DS: []dnssec.DS{ours}}, "warning", "remove it at your registrar"},
|
||||
{"no DS yet", false, Signing{Policy: "default", DS: []dnssec.DS{ours}}, "info", "the parent has no DS record for it"},
|
||||
{"DS gone", false, Signing{Policy: "insecure"}, "ok", "turning DNSSEC off can go ahead"},
|
||||
} {
|
||||
port, res := internet(t, parent(c.withDS))
|
||||
sig := c.sig
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, map[string][]string{"ns1.adbraun.lol.": {"127.0.0.12"}}, &ref, &sig)
|
||||
found := false
|
||||
for _, f := range rep.Findings {
|
||||
if f.Level == c.level && strings.Contains(f.Message, c.want) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("%s: no %s finding %q in %+v", c.name, c.level, c.want, rep.Findings)
|
||||
}
|
||||
if c.withDS && (len(rep.ParentDS) != 1 || rep.ParentDS[0].KeyTag != 22642) {
|
||||
t.Errorf("%s: parent DS %+v", c.name, rep.ParentDS)
|
||||
}
|
||||
}
|
||||
// Unsigned and no DS: nothing about DNSSEC.
|
||||
port, res := internet(t, parent(false))
|
||||
rep := checker(port, res).Delegation(context.Background(), "adbraun.lol.", []string{"ns1.adbraun.lol."}, map[string][]string{"ns1.adbraun.lol.": {"127.0.0.12"}}, &ref, &Signing{})
|
||||
for _, f := range rep.Findings {
|
||||
if strings.Contains(f.Message, "DS") {
|
||||
t.Errorf("unsigned zone: %+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user