DNSSEC: say where the DS record really goes
The DNSSEC tab said "give this to your registrar" for every zone whose parent wasn't in Jarl in the same view. The DNSSEC response now has ds_at/ds_place/ds_note: the parent zone in Jarl (any view, its own first, then a public one), the registrar for a registered domain, the parent zone wherever it is hosted for a subdomain, the ISP or RIR for a reverse zone, and nowhere for a private name. The page and the CLI use them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
82c282cd32
commit
a36c54ce60
8 files changed
+161
-26
No files matched your search
@@ -33,6 +33,15 @@ All notable changes to Jarl are documented here. The format follows
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- The DNSSEC tab told you to give the DS record to your registrar for
|
||||||
|
every zone whose parent isn't in Jarl in the same view. Now it (and
|
||||||
|
`jarl zone dnssec status`, and the API's new `ds_at`, `ds_place` and
|
||||||
|
`ds_note`) says where it really goes: the parent zone in Jarl (in any
|
||||||
|
view now, the zone's own first), the registrar for a registered
|
||||||
|
domain, the parent zone wherever it is hosted for a subdomain, your
|
||||||
|
ISP or RIR for a reverse zone, and nowhere for a private name such as
|
||||||
|
`home.lan` (signing it has no effect).
|
||||||
|
|
||||||
- The Registrar tab (and `jarl zone registrar`, and the DNSSEC page's
|
- The Registrar tab (and `jarl zone registrar`, and the DNSSEC page's
|
||||||
registrar button) was offered for every zone when `porkbun.domains` is
|
registrar button) was offered for every zone when `porkbun.domains` is
|
||||||
empty, and always failed for subdomain zones (`lab.adbraun.lol`),
|
empty, and always failed for subdomain zones (`lab.adbraun.lol`),
|
||||||
|
|||||||
+20
-9
@@ -23,8 +23,8 @@ keeps writing the unsigned zone, and named keeps a signed copy next to it
|
|||||||
and its keys in Jarl's key directory. See docs/dnssec.md.
|
and its keys in Jarl's key directory. See docs/dnssec.md.
|
||||||
|
|
||||||
Turning signing on is safe at any time; validation only starts once you add
|
Turning signing on is safe at any time; validation only starts once you add
|
||||||
the zone's DS record at the parent zone (your registrar), and 'status' says
|
the zone's DS record at the parent zone (your registrar for a domain, the
|
||||||
when that is safe. Turning it off goes through BIND's insecure policy, which
|
parent zone for a subdomain), and 'status' says when that is safe and where. Turning it off goes through BIND's insecure policy, which
|
||||||
keeps the zone signed until the DS is gone from the parent.`,
|
keeps the zone signed until the DS is gone from the parent.`,
|
||||||
}
|
}
|
||||||
var view string
|
var view string
|
||||||
@@ -71,7 +71,7 @@ DS after that.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !flagQuiet {
|
if !flagQuiet {
|
||||||
fmt.Printf("\nnamed is signing %s now. Add the DS record at your registrar only when\n'jarl zone dnssec status %s' says the zone is ready.\n", strings.TrimSuffix(args[0], "."), args[0]+viewFlag(view))
|
fmt.Printf("\nnamed is signing %s now. Add its DS record to the parent zone only when\n'jarl zone dnssec status %s' says the zone is ready; it also says where it goes.\n", strings.TrimSuffix(args[0], "."), args[0]+viewFlag(view))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -87,8 +87,8 @@ DS after that.`,
|
|||||||
parent zone still has its DS record:
|
parent zone still has its DS record:
|
||||||
|
|
||||||
1. The first run switches the zone to BIND's insecure policy. named keeps it
|
1. The first run switches the zone to BIND's insecure policy. named keeps it
|
||||||
signed and publishes a CDS "delete" record. Remove the DS record at your
|
signed and publishes a CDS "delete" record. Remove the DS record at the
|
||||||
registrar, then run 'jarl zone dnssec check-ds' so named knows. named
|
parent ('status' says where), then run 'jarl zone dnssec check-ds' so named knows. named
|
||||||
waits until resolvers have forgotten the DS (the parent's DS TTL, often a
|
waits until resolvers have forgotten the DS (the parent's DS TTL, often a
|
||||||
day) and then removes the keys and signatures.
|
day) and then removes the keys and signatures.
|
||||||
2. Run it again when 'status' says turning off has finished: DNSSEC is then
|
2. Run it again when 'status' says turning off has finished: DNSSEC is then
|
||||||
@@ -137,7 +137,7 @@ A zone whose DS never reached the parent can be wiped at once instead
|
|||||||
if next == "insecure" && st.ParentZone != "" {
|
if next == "insecure" && st.ParentZone != "" {
|
||||||
fmt.Printf("\nNext: remove the DS record for %s from %s:\n %s\nthen run\n jarl zone dnssec check-ds %s\nRun 'jarl zone dnssec disable %s' again when 'status' says it has finished.\n", displayName(st.Zone), displayName(st.ParentZone), parentDSCommand(st, "delete", ""), args[0]+viewFlag(view), args[0]+viewFlag(view))
|
fmt.Printf("\nNext: remove the DS record for %s from %s:\n %s\nthen run\n jarl zone dnssec check-ds %s\nRun 'jarl zone dnssec disable %s' again when 'status' says it has finished.\n", displayName(st.Zone), displayName(st.ParentZone), parentDSCommand(st, "delete", ""), args[0]+viewFlag(view), args[0]+viewFlag(view))
|
||||||
} else if next == "insecure" {
|
} else if next == "insecure" {
|
||||||
fmt.Printf("\nNext: remove the DS record for %s at your registrar, then run\n jarl zone dnssec check-ds %s\nRun 'jarl zone dnssec disable %s' again when 'status' says it has finished.\n", displayName(st.Zone), args[0]+viewFlag(view), args[0]+viewFlag(view))
|
fmt.Printf("\nNext: remove the DS record for %s %s, then run\n jarl zone dnssec check-ds %s\nRun 'jarl zone dnssec disable %s' again when 'status' says it has finished.\n", displayName(st.Zone), dsPlace(st), args[0]+viewFlag(view), args[0]+viewFlag(view))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("\nDNSSEC is off. The old keys stay in the key directory until you wipe them ('jarl zone dnssec wipe').")
|
fmt.Println("\nDNSSEC is off. The old keys stay in the key directory until you wipe them ('jarl zone dnssec wipe').")
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ A zone whose DS never reached the parent can be wiped at once instead
|
|||||||
Short: "Check the parent zone's DS records and tell named",
|
Short: "Check the parent zone's DS records and tell named",
|
||||||
Long: `Asks every server of the parent zone for the zone's DS records, and tells
|
Long: `Asks every server of the parent zone for the zone's DS records, and tells
|
||||||
named (rndc dnssec -checkds) when its key's DS has appeared there (after you
|
named (rndc dnssec -checkds) when its key's DS has appeared there (after you
|
||||||
added it at the registrar) or is gone (while turning DNSSEC off). named
|
added it there) or is gone (while turning DNSSEC off). named
|
||||||
moves to the next step only once it knows.
|
moves to the next step only once it knows.
|
||||||
|
|
||||||
The parent is found through jarld's resolver, or --resolver. Exits with 2
|
The parent is found through jarld's resolver, or --resolver. Exits with 2
|
||||||
@@ -345,9 +345,12 @@ func printDNSSEC(w io.Writer, r *api.DNSSECResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(ds) > 0 && r.Policy != "insecure" {
|
if len(ds) > 0 && r.Policy != "insecure" {
|
||||||
where := "at your registrar"
|
if r.DSAt == "none" {
|
||||||
|
fmt.Fprintf(w, "\n%s\n", r.DSNote)
|
||||||
|
}
|
||||||
|
where := dsPlace(r)
|
||||||
if r.ParentZone != "" {
|
if r.ParentZone != "" {
|
||||||
where = "in the parent zone " + displayName(r.ParentZone) + ", which is in Jarl: add it as a DS record there"
|
where += ", which is in Jarl: add it as a DS record there"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\nDS record for the parent zone (%s):\n", where)
|
fmt.Fprintf(w, "\nDS record for the parent zone (%s):\n", where)
|
||||||
if !r.Phase.CanPublishDS && r.Phase.DSReadyAt != nil {
|
if !r.Phase.CanPublishDS && r.Phase.DSReadyAt != nil {
|
||||||
@@ -368,6 +371,14 @@ func printDNSSEC(w io.Writer, r *api.DNSSECResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dsPlace says where the zone's DS record goes ("at your registrar").
|
||||||
|
func dsPlace(r *api.DNSSECResponse) string {
|
||||||
|
if r.DSPlace != "" {
|
||||||
|
return r.DSPlace
|
||||||
|
}
|
||||||
|
return "at the parent zone"
|
||||||
|
}
|
||||||
|
|
||||||
// parentDSCommand is the jarl command that adds (or deletes) the zone's DS
|
// parentDSCommand is the jarl command that adds (or deletes) the zone's DS
|
||||||
// record in its parent zone in Jarl.
|
// record in its parent zone in Jarl.
|
||||||
func parentDSCommand(r *api.DNSSECResponse, verb, data string) string {
|
func parentDSCommand(r *api.DNSSECResponse, verb, data string) string {
|
||||||
|
|||||||
+10
-1
@@ -294,11 +294,20 @@ view ([dnssec.md](dnssec.md#what-jarl-refuses-and-why)).
|
|||||||
},
|
},
|
||||||
"named": {"secure": true, "inline_signing": true, "serial": 2026100802, "signed_serial": 2026100804, "next_key_event": "..."},
|
"named": {"secure": true, "inline_signing": true, "serial": 2026100802, "signed_serial": 2026100804, "next_key_event": "..."},
|
||||||
"policies": ["default"],
|
"policies": ["default"],
|
||||||
"parent_zone": "", // set when the parent zone is a primary zone in Jarl
|
"ds_at": "registrar", // parent_zone, registrar, parent, reverse or none
|
||||||
|
"ds_place": "at your registrar", // completes "add the DS record ..."
|
||||||
|
"ds_note": "", // for ds_at none: why it can't have one
|
||||||
|
"parent_zone": "", // set when the parent zone is a primary zone in Jarl (any view, its own first)
|
||||||
"registrar": "porkbun" // when the DS can be published through Jarl
|
"registrar": "porkbun" // when the DS can be published through Jarl
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`ds_at` says where the DS record goes ([dnssec.md](dnssec.md#should-you-sign)):
|
||||||
|
`parent_zone` (a zone in Jarl: `parent_zone`, `parent_zone_id`,
|
||||||
|
`parent_view`), `registrar` (the zone is a registered domain), `parent` (a
|
||||||
|
subdomain whose parent zone is hosted elsewhere), `reverse` (whoever
|
||||||
|
delegates the addresses) or `none` (nothing on the internet delegates it).
|
||||||
|
|
||||||
`phase.code` is one of `off`, `leftover_keys`, `starting`, `signing`,
|
`phase.code` is one of `off`, `leftover_keys`, `starting`, `signing`,
|
||||||
`ready`, `ds_pending`, `secure`, `removing_ds`, `ds_expiring`,
|
`ready`, `ds_pending`, `secure`, `removing_ds`, `ds_expiring`,
|
||||||
`removing_keys`, `unsigned` ([dnssec.md](dnssec.md#phases)).
|
`removing_keys`, `unsigned` ([dnssec.md](dnssec.md#phases)).
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ Signing an internal-only zone (one no registrar delegates, or a split-DNS
|
|||||||
view only your own network reaches) adds nothing. There is no parent to
|
view only your own network reaches) adds nothing. There is no parent to
|
||||||
publish a DS, and BIND doesn't validate the zones it serves itself.
|
publish a DS, and BIND doesn't validate the zones it serves itself.
|
||||||
|
|
||||||
|
**Where the DS record goes** depends on the zone. The DNSSEC tab and
|
||||||
|
`jarl zone dnssec status` say which:
|
||||||
|
|
||||||
|
| Zone | The DS record goes |
|
||||||
|
|---|---|
|
||||||
|
| a registered domain (`adbraun.lol`) | at your registrar ([Porkbun](#porkbun) can do it for you) |
|
||||||
|
| a subdomain whose parent zone is in Jarl (`lab.adbraun.lol`) | in the parent zone, as a DS record named `lab`: one button, or the `jarl record add` command shown. The parent in the zone's own view counts first, else one in another view (a public one first). |
|
||||||
|
| a subdomain whose parent zone is hosted elsewhere | in that parent zone, wherever it is hosted (at Porkbun: the domain's DNS records) |
|
||||||
|
| a reverse zone | with whoever delegates it to you: your ISP, or your RIR for an address block of your own. Many ISPs don't take DS records. |
|
||||||
|
| a private name (`home.lan`) | nowhere: nothing on the internet delegates it, so signing it has no effect |
|
||||||
|
|
||||||
**Split DNS.** In Jarl a zone belongs to a view, so you sign the external
|
**Split DNS.** In Jarl a zone belongs to a view, so you sign the external
|
||||||
view's `adbraun.lol` and leave the internal view's copy unsigned. That is
|
view's `adbraun.lol` and leave the internal view's copy unsigned. That is
|
||||||
the usual setup, with one catch. Once the DS is at the parent, the world
|
the usual setup, with one catch. Once the DS is at the parent, the world
|
||||||
|
|||||||
+13
-1
@@ -465,8 +465,20 @@ type DNSSECResponse struct {
|
|||||||
View string `json:"view,omitempty"`
|
View string `json:"view,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
*dnssec.Status
|
*dnssec.Status
|
||||||
|
// DSAt says where the zone's DS record goes: "parent_zone" (a primary
|
||||||
|
// zone in Jarl, below), "registrar" (the zone is a registered domain),
|
||||||
|
// "parent" (a subdomain whose parent zone is hosted elsewhere),
|
||||||
|
// "reverse" (whoever delegates the addresses) or "none" (nothing on
|
||||||
|
// the internet delegates the zone, so it can't have one).
|
||||||
|
DSAt string `json:"ds_at"`
|
||||||
|
// DSPlace completes "add the DS record …" ("at your registrar"); ""
|
||||||
|
// for "none".
|
||||||
|
DSPlace string `json:"ds_place,omitempty"`
|
||||||
|
// DSNote explains DSAt "none".
|
||||||
|
DSNote string `json:"ds_note,omitempty"`
|
||||||
// ParentZone is set when the parent zone is a primary zone in Jarl
|
// ParentZone is set when the parent zone is a primary zone in Jarl
|
||||||
// (in the same view): the DS record then goes there, as a record.
|
// (in the zone's own view, else preferably a public one): the DS
|
||||||
|
// record then goes there, as a record.
|
||||||
ParentZone string `json:"parent_zone,omitempty"`
|
ParentZone string `json:"parent_zone,omitempty"`
|
||||||
ParentZoneID int64 `json:"parent_zone_id,omitempty"`
|
ParentZoneID int64 `json:"parent_zone_id,omitempty"`
|
||||||
ParentView string `json:"parent_view,omitempty"`
|
ParentView string `json:"parent_view,omitempty"`
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The DNSSEC page says where each zone's DS record goes.
|
||||||
|
func TestDNSSECWhereDSGoes(t *testing.T) {
|
||||||
|
e := newAPIEnv(t)
|
||||||
|
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
|
||||||
|
e.do("POST", "/views", e.token, map[string]any{"name": "external"}, 201)
|
||||||
|
zone := func(name, view string) {
|
||||||
|
e.do("POST", "/zones", e.token, map[string]any{"name": name, "view": view, "nameservers": []string{"ns1.example.net."}}, 201)
|
||||||
|
}
|
||||||
|
zone("adbraun.lol", "external")
|
||||||
|
zone("lab.adbraun.lol", "external")
|
||||||
|
zone("dev.adbraun.lol", "internal") // its parent only in external
|
||||||
|
zone("sub.example.com", "external")
|
||||||
|
zone("10.0.10.0/24", "internal")
|
||||||
|
zone("home.lan", "internal")
|
||||||
|
for _, c := range []struct{ zone, at, place, parent string }{
|
||||||
|
{"adbraun.lol?view=external", "registrar", "at your registrar", ""},
|
||||||
|
{"lab.adbraun.lol?view=external", "parent_zone", "in the parent zone adbraun.lol", "adbraun.lol."},
|
||||||
|
{"dev.adbraun.lol?view=internal", "parent_zone", "in the parent zone adbraun.lol (view external)", "adbraun.lol."},
|
||||||
|
{"sub.example.com?view=external", "parent", "in its parent zone (most likely example.com)", ""},
|
||||||
|
{"10.0.10.in-addr.arpa?view=internal", "reverse", "your ISP", ""},
|
||||||
|
{"home.lan?view=internal", "none", "", ""},
|
||||||
|
} {
|
||||||
|
r := e.do("GET", "/zones/"+strings.Replace(c.zone, "?", "/dnssec?", 1), e.token, nil, 200)
|
||||||
|
place, _ := r["ds_place"].(string)
|
||||||
|
parent, _ := r["parent_zone"].(string)
|
||||||
|
if r["ds_at"] != c.at || !strings.Contains(place, c.place) || (c.place == "" && place != "") || parent != c.parent {
|
||||||
|
t.Errorf("%s: ds_at %v, ds_place %q, parent_zone %q", c.zone, r["ds_at"], place, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-10
@@ -36,31 +36,70 @@ func (s *Server) zoneDNSSEC(w http.ResponseWriter, r *http.Request, p *auth.Prin
|
|||||||
return badRequest("only primary zones are signed by Jarl (this is a %s zone)", z.Type)
|
return badRequest("only primary zones are signed by Jarl (this is a %s zone)", z.Type)
|
||||||
}
|
}
|
||||||
resp := api.DNSSECResponse{Zone: z.Name, View: st.ViewName(z), Type: z.Type, Status: s.eng.DNSSEC(r.Context(), st, z), Registrar: s.registrarFor(z)}
|
resp := api.DNSSECResponse{Zone: z.Name, View: st.ViewName(z), Type: z.Type, Status: s.eng.DNSSEC(r.Context(), st, z), Registrar: s.registrarFor(z)}
|
||||||
if pz := parentInJarl(st, z); pz != nil {
|
public := map[int64]bool{}
|
||||||
|
for _, e := range s.eng.Exposures(st) {
|
||||||
|
public[e.ViewID] = e.Public
|
||||||
|
}
|
||||||
|
name := dnsname.Display(z.Name)
|
||||||
|
reg, regOK := dnsname.RegisteredDomain(z.Name)
|
||||||
|
switch pz := parentInJarl(st, z, public); {
|
||||||
|
case pz != nil:
|
||||||
|
resp.DSAt = "parent_zone"
|
||||||
resp.ParentZone, resp.ParentZoneID, resp.ParentView = pz.Name, pz.ID, st.ViewName(pz)
|
resp.ParentZone, resp.ParentZoneID, resp.ParentView = pz.Name, pz.ID, st.ViewName(pz)
|
||||||
for _, rec := range st.ServedRecords(pz.ID) {
|
for _, rec := range st.ServedRecords(pz.ID) {
|
||||||
if rec.Name == z.Name && rec.Type == "DS" {
|
if rec.Name == z.Name && rec.Type == "DS" {
|
||||||
resp.ParentDS = append(resp.ParentDS, rec.Data)
|
resp.ParentDS = append(resp.ParentDS, rec.Data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resp.DSPlace = "in the parent zone " + dnsname.Display(pz.Name)
|
||||||
|
if pz.ViewID != z.ViewID {
|
||||||
|
resp.DSPlace += " (view " + st.ViewName(pz) + ")"
|
||||||
|
}
|
||||||
|
case dnsname.IsReverse(z.Name):
|
||||||
|
resp.DSAt = "reverse"
|
||||||
|
resp.DSPlace = "with whoever delegates " + name + " to you (your ISP, or your RIR for an address block of your own; many ISPs don't take DS records)"
|
||||||
|
case !regOK:
|
||||||
|
resp.DSAt = "none"
|
||||||
|
resp.DSNote = "No zone on the internet delegates " + name + ", so it can't get a DS record and resolvers never validate it. Signing only matters for a zone on the internet."
|
||||||
|
case reg == name:
|
||||||
|
resp.DSAt, resp.DSPlace = "registrar", "at your registrar"
|
||||||
|
default:
|
||||||
|
resp.DSAt = "parent"
|
||||||
|
resp.DSPlace = "in its parent zone (most likely " + reg + "), wherever that zone's DNS is hosted (at Porkbun: " + reg + "'s DNS records, as a DS record named " + strings.TrimSuffix(name, "."+reg) + ")"
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, resp)
|
writeJSON(w, http.StatusOK, resp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parentInJarl returns the primary zone in z's view that z is delegated
|
// parentInJarl returns the primary zone in Jarl that z is delegated from:
|
||||||
// from, if Jarl has it.
|
// in z's own view, else in another view, a public one first (the one the
|
||||||
func parentInJarl(st *model.State, z *model.Zone) *model.Zone {
|
// internet asks).
|
||||||
var best *model.Zone
|
func parentInJarl(st *model.State, z *model.Zone, public map[int64]bool) *model.Zone {
|
||||||
for _, o := range st.ZonesInView(z.ViewID) {
|
in := func(viewID int64) *model.Zone {
|
||||||
if o.ID == z.ID || o.Type != model.ZonePrimary || o.Name == z.Name || !dnsname.IsSubdomain(z.Name, o.Name) {
|
var best *model.Zone
|
||||||
|
for _, o := range st.ZonesInView(viewID) {
|
||||||
|
if o.Type != model.ZonePrimary || o.Name == z.Name || !dnsname.IsSubdomain(z.Name, o.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if best == nil || len(o.Name) > len(best.Name) {
|
||||||
|
best = o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
if pz := in(z.ViewID); pz != nil {
|
||||||
|
return pz
|
||||||
|
}
|
||||||
|
var other *model.Zone
|
||||||
|
for _, v := range st.OrderedViews() {
|
||||||
|
if v.ID == z.ViewID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if best == nil || len(o.Name) > len(best.Name) {
|
if pz := in(v.ID); pz != nil && (other == nil || public[v.ID] && !public[other.ViewID]) {
|
||||||
best = o
|
other = pz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return best
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
// dsChecker returns the checker for parent DS lookups, with ?resolver=IP.
|
// dsChecker returns the checker for parent DS lookups, with ?resolver=IP.
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export async function dnssecTab(el, zone) {
|
|||||||
s.named_error ? h("div", { class: "msg warn small" }, "named: " + s.named_error) : null,
|
s.named_error ? h("div", { class: "msg warn small" }, "named: " + s.named_error) : null,
|
||||||
s.key_error ? h("div", { class: "msg err small" }, "The keys can't be read: " + s.key_error) : null,
|
s.key_error ? h("div", { class: "msg err small" }, "The keys can't be read: " + s.key_error) : null,
|
||||||
!s.policy && !s.keys.length ? h("p", { class: "muted small" },
|
!s.policy && !s.keys.length ? h("p", { class: "muted small" },
|
||||||
"Signing lets resolvers that validate DNSSEC detect forged answers for the zone. It only matters for a zone on the internet: validation needs a DS record in the parent zone (at your registrar), and resolvers on your own network don't validate zones this server answers itself.") : null,
|
"Signing lets resolvers that validate DNSSEC detect forged answers for the zone. It only matters for a zone on the internet: validation needs a DS record " + (s.ds_place || "in the parent zone") + ", and resolvers on your own network don't validate zones this server answers itself.") : null,
|
||||||
|
s.ds_at === "none" ? h("div", { class: "msg warn small" }, s.ds_note) : null,
|
||||||
out)];
|
out)];
|
||||||
|
|
||||||
const live = s.keys.filter((k) => k.ds && k.goal === "omnipresent");
|
const live = s.keys.filter((k) => k.ds && k.goal === "omnipresent");
|
||||||
@@ -98,8 +99,12 @@ async function addToParent(zone, s, k) {
|
|||||||
function dsPanel(zone, s, keys) {
|
function dsPanel(zone, s, keys) {
|
||||||
const ph = s.phase;
|
const ph = s.phase;
|
||||||
const where = s.parent_zone
|
const where = s.parent_zone
|
||||||
? ["The parent zone ", h("a", { href: "#/zones/" + s.parent_zone_id + "/records" }, display(s.parent_zone)), " is in Jarl: add this as a DS record there (name ", h("code", null, parentName(zone, s)), "), with the button or the jarl command below."]
|
? ["The parent zone ", h("a", { href: "#/zones/" + s.parent_zone_id + "/records" }, display(s.parent_zone)),
|
||||||
: "Give this to your registrar (at Porkbun: the domain's DNSSEC settings). Registrars ask either for the whole record or for its four fields.";
|
s.parent_view && s.parent_view !== zone.view ? " (view " + s.parent_view + ")" : "",
|
||||||
|
" is in Jarl: add this as a DS record there (name ", h("code", null, parentName(zone, s)), "), with the button or the jarl command below."]
|
||||||
|
: s.ds_at === "registrar"
|
||||||
|
? "Give this to your registrar (at Porkbun: the domain's DNSSEC settings). Registrars ask either for the whole record or for its four fields."
|
||||||
|
: s.ds_at === "none" ? s.ds_note : "Add this " + s.ds_place + ".";
|
||||||
return h("div", { class: "panel" },
|
return h("div", { class: "panel" },
|
||||||
h("h2", null, "DS record for the parent zone"),
|
h("h2", null, "DS record for the parent zone"),
|
||||||
ph.can_publish_ds
|
ph.can_publish_ds
|
||||||
@@ -153,7 +158,7 @@ async function enableDialog(zone, s, others) {
|
|||||||
"The server's clock must be right (NTP): signatures are valid for a fixed time window.",
|
"The server's clock must be right (NTP): signatures are valid for a fixed time window.",
|
||||||
"TTLs in the zone must be at most a day; Jarl refuses longer ones.",
|
"TTLs in the zone must be at most a day; Jarl refuses longer ones.",
|
||||||
"Secondaries get the signed zone by zone transfer; they need nothing else.",
|
"Secondaries get the signed zone by zone transfer; they need nothing else.",
|
||||||
"With the default policy the key is never replaced, so the DS at your registrar never has to change. Keep backups: the daily backup includes the keys.",
|
"With the default policy the key is never replaced, so the DS record at the parent never has to change. Keep backups: the daily backup includes the keys.",
|
||||||
]),
|
]),
|
||||||
split,
|
split,
|
||||||
s.policies.length > 1 ? h("div", { class: "form-grid" }, h("label", null, "Policy"), pol) : null,
|
s.policies.length > 1 ? h("div", { class: "form-grid" }, h("label", null, "Policy"), pol) : null,
|
||||||
@@ -175,7 +180,9 @@ async function disableDialog(zone, s) {
|
|||||||
[h("strong", null, "Now: "), "the zone switches to BIND's insecure policy. named keeps it signed and publishes a CDS \"delete\" record. Nothing breaks."],
|
[h("strong", null, "Now: "), "the zone switches to BIND's insecure policy. named keeps it signed and publishes a CDS \"delete\" record. Nothing breaks."],
|
||||||
s.parent_zone
|
s.parent_zone
|
||||||
? [h("strong", null, "You: "), "remove the DS record from ", display(s.parent_zone), " (", h("code", null, jarlCmd(zone, s, "delete", "")), ", or on its Records page), then click Check DS at parent so named knows it's gone."]
|
? [h("strong", null, "You: "), "remove the DS record from ", display(s.parent_zone), " (", h("code", null, jarlCmd(zone, s, "delete", "")), ", or on its Records page), then click Check DS at parent so named knows it's gone."]
|
||||||
: [h("strong", null, "You: "), "remove the DS record at your registrar (", s.registrar ? "the Registrar tab can do that at Porkbun" : "at Porkbun: the domain's DNSSEC settings", "), then click Check DS at parent so named knows it's gone."],
|
: s.ds_at === "registrar"
|
||||||
|
? [h("strong", null, "You: "), "remove the DS record at your registrar (", s.registrar ? "the Registrar tab can do that at Porkbun" : "at Porkbun: the domain's DNSSEC settings", "), then click Check DS at parent so named knows it's gone."]
|
||||||
|
: [h("strong", null, "You: "), "remove the DS record ", s.ds_place || "from the parent zone", " (if it was ever added), then click Check DS at parent so named knows it's gone."],
|
||||||
[h("strong", null, "named: "), "waits until resolvers have forgotten the DS (the parent's DS TTL, often a day), then removes the keys and signatures."],
|
[h("strong", null, "named: "), "waits until resolvers have forgotten the DS (the parent's DS TTL, often a day), then removes the keys and signatures."],
|
||||||
[h("strong", null, "Then: "), "this page lets you finish, which switches DNSSEC off."],
|
[h("strong", null, "Then: "), "this page lets you finish, which switches DNSSEC off."],
|
||||||
]),
|
]),
|
||||||
|
|||||||
Reference in new issue
Block a user