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
@@ -0,0 +1,70 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"jarl/internal/model"
|
||||
)
|
||||
|
||||
func TestDNSSEC(t *testing.T) {
|
||||
b := newBuilder()
|
||||
ok := b.primary("signed.example", nil)
|
||||
ok.DNSSECPolicy = model.DNSSECDefault
|
||||
ps := State(b.s, Options{})
|
||||
if len(codes(ps, Error)) != 0 {
|
||||
t.Fatalf("a signed zone: %v", ps)
|
||||
}
|
||||
|
||||
sec := b.zone("copy.example", nil, model.ZoneSecondary)
|
||||
b.opt(model.ScopeZone, sec.ID, "primaries", "{ 192.0.2.1; }")
|
||||
sec.DNSSECPolicy = model.DNSSECDefault
|
||||
unknown := b.primary("custom.example", nil)
|
||||
unknown.DNSSECPolicy = "fast"
|
||||
long := b.primary("long.example", nil)
|
||||
long.DNSSECPolicy = model.DNSSECInsecure
|
||||
r := b.rec(long, "www", "A", "192.0.2.80")
|
||||
r.TTL = model.Ptr(uint32(172800))
|
||||
b.opt(model.ScopeZone, ok.ID, "inline-signing", "no")
|
||||
ps = State(b.s, Options{})
|
||||
for _, c := range []string{"dnssec_zone_type", "dnssec_policy_unknown", "dnssec_ttl_too_long", "dnssec_inline_signing"} {
|
||||
if !hasCode(ps, c) {
|
||||
t.Errorf("missing %s in %v", c, ps)
|
||||
}
|
||||
}
|
||||
|
||||
// A custom policy: its max-zone-ttl applies; an empty keys list would
|
||||
// crash named.
|
||||
b2 := newBuilder()
|
||||
z := b2.primary("custom.example", nil)
|
||||
z.DNSSECPolicy = "fast"
|
||||
z.DefaultTTL = 600
|
||||
b2.opt(model.ScopeTopLevel, 0, "dnssec-policy", `fast { keys { csk lifetime unlimited algorithm 13; }; max-zone-ttl PT5M; }`)
|
||||
b2.opt(model.ScopeTopLevel, 0, "dnssec-policy", `"off" { keys { }; }`)
|
||||
ps = State(b2.s, Options{})
|
||||
if !hasCode(ps, "dnssec_ttl_too_long") || !hasCode(ps, "dnssec_policy_no_keys") || hasCode(ps, "dnssec_policy_unknown") {
|
||||
t.Errorf("custom policy: %v", ps)
|
||||
}
|
||||
|
||||
// dnssec-policy is set per zone in Jarl, not as an option.
|
||||
b3 := newBuilder()
|
||||
z = b3.primary("example.com", nil)
|
||||
b3.opt(model.ScopeZone, z.ID, "dnssec-policy", "default")
|
||||
b3.opt(model.ScopeOptions, 0, "dnssec-policy", "default")
|
||||
b3.opt(model.ScopeZone, z.ID, "key-directory", `"/tmp"`)
|
||||
if n := len(codes(State(b3.s, Options{}), Error)); n != 3 {
|
||||
t.Errorf("managed options: %v", State(b3.s, Options{}))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDuration(t *testing.T) {
|
||||
for in, want := range map[string]uint32{"86400": 86400, "P1D": 86400, "PT5M": 300, "P1W": 604800, "1d": 86400, "12h30m": 45000, "pt1h": 3600} {
|
||||
if got, ok := parseDuration(in); !ok || got != want {
|
||||
t.Errorf("parseDuration(%q) = %d %v, want %d", in, got, ok, want)
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"", "P", "unlimited", "1x"} {
|
||||
if _, ok := parseDuration(bad); ok {
|
||||
t.Errorf("parseDuration(%q) accepted", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user