package dnssec import ( "fmt" "strings" "time" ) // Phases of a zone, from unsigned to signed and back. Turning signing on: // off -> starting -> signing -> ready (add the DS at the parent) -> // ds_pending -> secure. Turning it off: secure -> removing_ds (remove the // DS at the parent) -> ds_expiring -> removing_keys -> unsigned -> off. const ( PhaseOff = "off" // not signed, no keys PhaseLeftover = "leftover_keys" // not signed, but old keys are still in the key directory PhaseStarting = "starting" // signing is on in Jarl; named hasn't made keys yet PhaseSigning = "signing" // keys made, zone signed; not yet safe to add the DS PhaseReady = "ready" // safe to add the DS at the parent PhaseDSPending = "ds_pending" // the DS is at the parent; named waits for its TTL PhaseSecure = "secure" // signed, and the parent's DS is in place PhaseRemovingDS = "removing_ds" // turning off: the DS must go from the parent first PhaseDSExpiring = "ds_expiring" // the DS is gone; named waits until caches forget it PhaseRemovingKeys = "removing_keys" // named removes the keys and signatures PhaseUnsigned = "unsigned" // turning off has finished; switch DNSSEC off ) // Phase says where a zone is and what comes next. type Phase struct { Code string `json:"code"` Summary string `json:"summary"` Next string `json:"next,omitempty"` // DSReadyAt is when the DS can safely be added at the parent (phase // signing). DSReadyAt *time.Time `json:"ds_ready_at,omitempty"` // CanPublishDS: the zone is ready for (or already has) a DS at the // parent. CanPublishDS bool `json:"can_publish_ds"` // CanFinish: the zone is being turned off and the parent has no DS // any more (as far as named knows), so DNSSEC can be switched off. CanFinish bool `json:"can_finish"` // NeedsDS lists the signing keys whose DS should be at the parent, // and StaleDS those whose DS should be removed from it. NeedsDS []uint16 `json:"needs_ds,omitempty"` StaleDS []uint16 `json:"stale_ds,omitempty"` } // Assess works out the phase of a zone signed with policy ("" when Jarl // doesn't sign it, "insecure" while signing is being removed) that has // keys. func Assess(policy string, keys []*Key) Phase { var live []*Key // signing keys named keeps (or is introducing) for _, k := range keys { if k.SigningKey() && k.Goal == Omnipresent { live = append(live, k) } } switch { case policy == "": if len(keys) == 0 { return Phase{Code: PhaseOff, Summary: "DNSSEC is off."} } return Phase{Code: PhaseLeftover, Summary: "DNSSEC is off, but keys from when the zone was signed are still in its key directory.", Next: "Wipe them (jarl zone dnssec wipe) once you're sure the parent zone has no DS record for them, or turn signing on again to reuse them."} case policy == "insecure": return assessOff(keys) } if len(live) == 0 { return Phase{Code: PhaseStarting, Summary: "Signing is on; named hasn't created the zone's keys yet.", Next: "Wait a moment: named makes the keys when it loads the configuration."} } p := Phase{} var notReady []*Key for _, k := range live { if k.DNSKEYState != Omnipresent || k.KeyRRSIG != Omnipresent { notReady = append(notReady, k) } } for _, k := range keys { if k.SigningKey() && k.Goal == Hidden && k.DSState != Hidden && k.DSRemoved == nil { p.StaleDS = append(p.StaleDS, k.Tag) } } if len(notReady) > 0 { p.Code = PhaseSigning p.Summary = "named is signing the zone. Resolvers still treat it as unsigned, because the parent zone has no DS record for it." for _, k := range notReady { if k.PublishCDS != nil && (p.DSReadyAt == nil || k.PublishCDS.After(*p.DSReadyAt)) { t := *k.PublishCDS p.DSReadyAt = &t } } if p.DSReadyAt != nil { p.Next = fmt.Sprintf("Wait until %s before adding the DS at the parent: until then some resolvers may still have unsigned answers cached, and would reject them once the DS appears.", p.DSReadyAt.UTC().Format("Mon 2 Jan 15:04 MST")) } else { p.Next = "Wait until named has published the keys long enough for caches to pick them up before adding the DS." } return p } p.CanPublishDS = true var pending, secure int for _, k := range live { switch { case k.DSState == Omnipresent: secure++ case k.DSPublished != nil: pending++ default: p.NeedsDS = append(p.NeedsDS, k.Tag) } } switch { case len(p.NeedsDS) > 0: p.Code = PhaseReady p.Summary = "The zone is signed and ready: add the DS record at the parent zone (your registrar) to turn validation on." p.Next = "Add the DS record (" + tags(p.NeedsDS) + ") at your registrar, then run the DS check so named knows it's there." case pending > 0: p.Code = PhaseDSPending p.Summary = "The parent zone has the DS record. named waits for the parent's TTL before it treats it as in place." default: p.Code = PhaseSecure p.Summary = "The zone is signed and the parent zone's DS record points at its key: validating resolvers check its answers." } if len(p.StaleDS) > 0 && p.Next == "" { p.Next = "Remove the DS record of the retired key " + tags(p.StaleDS) + " at your registrar, then run the DS check." } return p } func assessOff(keys []*Key) Phase { var dsUp, dsGoing, keysLeft bool for _, k := range keys { if !k.SigningKey() { if !k.Gone() { keysLeft = true } continue } switch { case k.DSState == Hidden || k.DSState == "": case k.DSRemoved != nil: dsGoing = true default: dsUp = true } if !k.Gone() { keysLeft = true } } switch { case dsUp: return Phase{Code: PhaseRemovingDS, Summary: "Turning DNSSEC off: named keeps the zone signed until the parent zone's DS record is gone.", Next: "Remove the DS record at your registrar, then run the DS check so named knows. Don't switch DNSSEC off before that: the zone would fail to validate."} case dsGoing: return Phase{Code: PhaseDSExpiring, Summary: "Turning DNSSEC off: the parent's DS record is gone; named keeps signing until resolvers' cached copies of it expire (the parent's DS TTL, often a day).", Next: "Wait: switching DNSSEC off now would make the zone fail to validate for resolvers that still have the DS cached."} case keysLeft: return Phase{Code: PhaseRemovingKeys, Summary: "Turning DNSSEC off: the parent has no DS any more, and named is removing the keys and signatures.", Next: "Wait until named has finished, then switch DNSSEC off.", CanFinish: true} } return Phase{Code: PhaseUnsigned, Summary: "Turning DNSSEC off has finished: the zone is no longer signed.", Next: "Switch DNSSEC off for the zone to finish (jarl zone dnssec disable), and wipe the old keys if you like.", CanFinish: true} } func tags(ts []uint16) string { var s []string for _, t := range ts { s = append(s, fmt.Sprintf("key %d", t)) } return strings.Join(s, ", ") }