Merge branch 'dnssec'

The DNSSEC notes in CHANGELOG.md belong under Unreleased; git had put
them into the 0.1.0 section, which didn't exist when the branch was made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-09 15:16:33 +00:00
commit 24a2c875fa
72 files changed
+5759 -153

No files matched your search

+58 -1
View File
@@ -61,6 +61,9 @@ type importer struct {
dir string // the directory option, for relative zone file names
seen map[string]bool
ctrl map[string]bool // key names used by controls
// policies are dnssec-policy statements of the options block (key
// "options") and of views ("view:ID"), which Jarl sets per zone.
policies map[string]string
}
func (im *importer) warn(format string, args ...any) {
@@ -90,7 +93,7 @@ func (im *importer) meta() model.Meta {
// Import reads the configuration starting at mainPath.
func Import(ctx context.Context, mainPath string, opts Options) (*model.State, *Report, error) {
im := &importer{ctx: ctx, opts: opts, st: model.NewState(), rep: &Report{}, seen: map[string]bool{}, ctrl: map[string]bool{}}
im := &importer{ctx: ctx, opts: opts, st: model.NewState(), rep: &Report{}, seen: map[string]bool{}, ctrl: map[string]bool{}, policies: map[string]string{}}
stmts, err := im.readFile(mainPath)
if err != nil {
return nil, nil, err
@@ -116,6 +119,7 @@ func Import(ctx context.Context, mainPath string, opts Options) (*model.State, *
return nil, nil, err
}
}
im.dnssecZones()
im.rep.Counts = map[string]int{"views": len(im.st.Views), "zones": len(im.st.Zones), "records": len(im.st.Records), "acls": len(im.st.ACLs), "tsig_keys": len(im.st.Keys), "options": len(im.st.Options)}
sort.Slice(im.rep.Zones, func(i, j int) bool {
a, b := im.rep.Zones[i], im.rep.Zones[j]
@@ -246,6 +250,20 @@ func (im *importer) keepInclude(path string) {
}
func (im *importer) addOption(scope string, scopeID int64, name, value, comment string) {
switch {
case name == "dnssec-policy" && scope == model.ScopeOptions:
im.policies["options"] = value
return
case name == "dnssec-policy" && scope == model.ScopeView:
im.policies[fmt.Sprintf("view:%d", scopeID)] = value
return
case name == "dnssec-policy" && scope == model.ScopeZone:
im.policies[fmt.Sprintf("zone:%d", scopeID)] = value
return
case name == "key-directory" && scope == model.ScopeZone:
// Jarl chooses signed zones' key directories (see dnssecKeys).
return
}
id, err := im.id()
if err != nil {
return
@@ -259,6 +277,45 @@ func (im *importer) addOption(scope string, scopeID int64, name, value, comment
im.st.Options[id] = &model.Option{ID: id, Scope: scope, ScopeID: scopeID, Name: name, Value: value, Position: pos, Comment: comment, Meta: im.meta()}
}
// policyName turns a dnssec-policy value into a zone's DNSSECPolicy.
func policyName(v string) string {
v = strings.Trim(strings.TrimSpace(v), `"`)
switch l := strings.ToLower(v); l {
case "none":
return ""
case model.DNSSECDefault, model.DNSSECInsecure:
return l
}
return v
}
// dnssecZones gives primary zones the dnssec-policy they inherit from
// their view or the options block (Jarl sets it per zone), and warns
// about signed zones' keys: Jarl keeps them in its own key directory, and
// unless the existing keys are moved there, named makes new ones that the
// parent's DS record doesn't match.
func (im *importer) dnssecZones() {
for _, z := range im.st.SortedZones() {
v, ok := im.policies[fmt.Sprintf("zone:%d", z.ID)]
if !ok {
v, ok = im.policies[fmt.Sprintf("view:%d", z.ViewID)]
}
if !ok {
v = im.policies["options"]
}
if z.Type != model.ZonePrimary {
if p := policyName(v); p != "" {
im.warn("zone %s: named signs this %s zone (dnssec-policy %s); Jarl only signs primary zones, so the setting was dropped", dnsname.Display(z.Name), z.Type, p)
}
continue
}
z.DNSSECPolicy = policyName(v)
if z.DNSSECPolicy != "" {
im.warn("zone %s is signed (dnssec-policy %s). Before 'jarld adopt', copy its key files (K%s+*) into the key directory Jarl uses for it ('jarl zone dnssec status %s' shows it), owned by named's user; otherwise named makes new keys, and if the parent zone has a DS record for the old ones, the zone stops validating (docs/dnssec.md#adopting-a-signed-zone)", dnsname.Display(z.Name), z.DNSSECPolicy, z.Name, dnsname.Display(z.Name))
}
}
}
func restText(s *namedconf.Stmt) string {
return namedconf.Compact(s.Items[1:])
}