mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 20:00:00 -04:00
fix: usr: rndc sign during ZSK rollover will now replace signatures
When performing a ZSK rollover, if the new DNSKEY is omnipresent, the :option:`rndc sign` command now signs the zone completely with the successor key, replacing all zone signatures from the predecessor key with new ones. Closes #5483 Merge branch '5483-smooth-operator-bug' into 'main' See merge request isc-projects/bind9!10867
This commit is contained in:
commit
6246f9d7cb
9 changed files with 125 additions and 63 deletions
|
|
@ -72,11 +72,12 @@ def IpubC(config, rollover=True):
|
|||
return config["zone-propagation-delay"] + max(ttl1, ttl2)
|
||||
|
||||
|
||||
def Iret(config, zsk=True, ksk=False, rollover=True):
|
||||
def Iret(config, zsk=True, ksk=False, rollover=True, smooth=True):
|
||||
sign_delay = timedelta(0)
|
||||
safety_interval = timedelta(0)
|
||||
if rollover:
|
||||
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
|
||||
if smooth:
|
||||
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
|
||||
safety_interval = config["retire-safety"]
|
||||
|
||||
iretKSK = timedelta(0)
|
||||
|
|
@ -246,7 +247,9 @@ class KeyProperties:
|
|||
if "Lifetime" not in self.metadata or self.metadata["Lifetime"] == 0:
|
||||
return
|
||||
|
||||
iret = Iret(config, zsk=self.key.is_zsk(), ksk=self.key.is_ksk())
|
||||
sigdel = self.key.get_timing("SigRemoved", must_exist=False)
|
||||
smooth = sigdel is None
|
||||
iret = Iret(config, zsk=self.key.is_zsk(), ksk=self.key.is_ksk(), smooth=smooth)
|
||||
self.timing["Removed"] = self.timing["Retired"] + iret
|
||||
|
||||
def set_expected_keytimes(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ POLICY = "zsk-prepub"
|
|||
ZSK_LIFETIME = TIMEDELTA["P30D"]
|
||||
LIFETIME_POLICY = int(ZSK_LIFETIME.total_seconds())
|
||||
IPUB = Ipub(CONFIG)
|
||||
IRET = Iret(CONFIG, rollover=True)
|
||||
IRET = Iret(CONFIG)
|
||||
KEYTTLPROP = CONFIG["dnskey-ttl"] + CONFIG["zone-propagation-delay"]
|
||||
OFFSETS = {}
|
||||
OFFSETS["step1-p"] = -int(TIMEDELTA["P7D"].total_seconds())
|
||||
|
|
@ -222,6 +222,15 @@ def test_zsk_prepub_step3(tld, alg, size, ns3):
|
|||
}
|
||||
isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step)
|
||||
|
||||
# Force full resign and check all signatures have been replaced.
|
||||
with ns3.watch_log_from_here() as watcher:
|
||||
ns3.rndc(f"sign {zone}", log=False)
|
||||
watcher.wait_for_line(f"zone {zone}/IN (signed): sending notifies")
|
||||
|
||||
step["smooth"] = False
|
||||
step["nextev"] = Iret(CONFIG, smooth=False)
|
||||
isctest.kasp.check_rollover_step(ns3, CONFIG, POLICY, step)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tld",
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ static const char *timingtags[TIMING_NTAGS] = {
|
|||
|
||||
"DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:",
|
||||
|
||||
"DSRemoved:"
|
||||
"DSRemoved:", "ZRRSIGPublish", "ZRRSIGRemoved"
|
||||
};
|
||||
|
||||
#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1)
|
||||
|
|
@ -1991,6 +1991,8 @@ write_key_state(const dst_key_t *key, int type, const char *directory) {
|
|||
printtime(key, DST_TIME_DELETE, "Removed", fp);
|
||||
printtime(key, DST_TIME_DSPUBLISH, "DSPublish", fp);
|
||||
printtime(key, DST_TIME_DSDELETE, "DSRemoved", fp);
|
||||
printtime(key, DST_TIME_SIGPUBLISH, "SigPublish", fp);
|
||||
printtime(key, DST_TIME_SIGDELETE, "SigRemoved", fp);
|
||||
printtime(key, DST_TIME_SYNCPUBLISH, "PublishCDS", fp);
|
||||
printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,10 +54,9 @@
|
|||
|
||||
#define TIMING_NTAGS (DST_MAX_TIMES + 1)
|
||||
static const char *timetags[TIMING_NTAGS] = {
|
||||
"Created:", "Publish:", "Activate:", "Revoke:",
|
||||
"Inactive:", "Delete:", "DSPublish:", "SyncPublish:",
|
||||
"SyncDelete:", NULL, NULL, NULL,
|
||||
NULL
|
||||
"Created:", "Publish:", "Activate:", "Revoke:", "Inactive:",
|
||||
"Delete:", "DSPublish:", "SyncPublish:", "SyncDelete:", NULL,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
#define DNS_KEYMGRATTR_NONE 0x00 /*%< No ordering. */
|
||||
#define DNS_KEYMGRATTR_S2I 0x01 /*%< Secure to insecure. */
|
||||
#define DNS_KEYMGRATTR_NOROLL 0x02 /*%< No rollover allowed. */
|
||||
#define DNS_KEYMGRATTR_FORCESTEP 0x04 /*%< Force next step in manual-mode */
|
||||
#define DNS_KEYMGRATTR_FORCESTEP 0x04 /*%< Force next step in manual-mode. */
|
||||
#define DNS_KEYMGRATTR_FULLSIGN 0x08 /*%< Full sign was issued. */
|
||||
|
||||
void
|
||||
dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first);
|
||||
|
|
|
|||
|
|
@ -2164,14 +2164,6 @@ dns_zone_getsignatures(dns_zone_t *zone);
|
|||
* Get the number of signatures that will be generated per quantum.
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
dns_zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm,
|
||||
uint16_t keyid, bool deleteit);
|
||||
/*%<
|
||||
* Initiate/resume signing of the entire zone with the zone DNSKEY(s)
|
||||
* that match the given algorithm and keyid.
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param);
|
||||
/*%<
|
||||
|
|
|
|||
|
|
@ -152,7 +152,9 @@ typedef enum dst_algorithm {
|
|||
#define DST_TIME_KRRSIG 11
|
||||
#define DST_TIME_DS 12
|
||||
#define DST_TIME_DSDELETE 13
|
||||
#define DST_MAX_TIMES 13
|
||||
#define DST_TIME_SIGPUBLISH 14
|
||||
#define DST_TIME_SIGDELETE 15
|
||||
#define DST_MAX_TIMES 15
|
||||
|
||||
/* Numeric metadata definitions */
|
||||
#define DST_NUM_PREDECESSOR 0
|
||||
|
|
|
|||
|
|
@ -1296,9 +1296,9 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
|||
dst_key_state_t next_state, dns_kasp_t *kasp,
|
||||
isc_stdtime_t now, isc_stdtime_t *when) {
|
||||
isc_result_t ret;
|
||||
isc_stdtime_t lastchange, dstime, nexttime = now;
|
||||
isc_stdtime_t lastchange, dstime, sigtime, nexttime = now;
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
||||
uint32_t dsstate;
|
||||
uint32_t dsstate, sigstate, signdelay = 0;
|
||||
|
||||
/*
|
||||
* No need to wait if we move things into an uncertain state.
|
||||
|
|
@ -1352,6 +1352,17 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
|||
switch (next_state) {
|
||||
case OMNIPRESENT:
|
||||
case HIDDEN:
|
||||
/* Was there a full sign? */
|
||||
sigstate = (next_state == HIDDEN) ? DST_TIME_SIGDELETE
|
||||
: DST_TIME_SIGPUBLISH;
|
||||
ret = dst_key_gettime(key->key, sigstate, &sigtime);
|
||||
if (ret == ISC_R_SUCCESS && sigtime <= now) {
|
||||
signdelay = 0;
|
||||
} else {
|
||||
sigtime = lastchange;
|
||||
signdelay = dns_kasp_signdelay(kasp);
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 7583: The retire interval (Iret) is the amount
|
||||
* of time that must elapse after a DNSKEY or
|
||||
|
|
@ -1369,7 +1380,7 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
|||
*
|
||||
* Dsgn + zone-propagation-delay + max-zone-ttl.
|
||||
*/
|
||||
nexttime = lastchange + ttlsig +
|
||||
nexttime = sigtime + ttlsig +
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
/*
|
||||
* Only add the sign delay Dsgn and retire-safety if
|
||||
|
|
@ -1383,7 +1394,7 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
|||
DST_NUM_SUCCESSOR, &tag);
|
||||
}
|
||||
if (ret == ISC_R_SUCCESS) {
|
||||
nexttime += dns_kasp_signdelay(kasp) +
|
||||
nexttime += signdelay +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
break;
|
||||
|
|
@ -2106,6 +2117,32 @@ dst_key_doublematch(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
|
|||
return matches > 1;
|
||||
}
|
||||
|
||||
static void
|
||||
keymgr_zrrsig(dns_dnsseckeylist_t *keyring, isc_stdtime_t now) {
|
||||
ISC_LIST_FOREACH(*keyring, dkey, link) {
|
||||
isc_result_t ret;
|
||||
bool zsk = false;
|
||||
|
||||
ret = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk);
|
||||
if (ret == ISC_R_SUCCESS && zsk) {
|
||||
dst_key_state_t state;
|
||||
isc_result_t result = dst_key_getstate(
|
||||
dkey->key, DST_KEY_ZRRSIG, &state);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
if (state == RUMOURED) {
|
||||
dst_key_settime(dkey->key,
|
||||
DST_TIME_SIGPUBLISH,
|
||||
now);
|
||||
} else if (state == UNRETENTIVE) {
|
||||
dst_key_settime(dkey->key,
|
||||
DST_TIME_SIGDELETE,
|
||||
now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine 'keys' and match 'kasp' policy.
|
||||
*
|
||||
|
|
@ -2308,6 +2345,11 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
|
|||
opts |= DNS_KEYMGRATTR_S2I;
|
||||
}
|
||||
|
||||
/* In case of a full sign, store ZRRSIGPublish/ZRRSIGDelete. */
|
||||
if ((opts & DNS_KEYMGRATTR_FULLSIGN) != 0) {
|
||||
keymgr_zrrsig(keyring, now);
|
||||
}
|
||||
|
||||
/* Read to update key states. */
|
||||
isc_result_t retval = keymgr_update(keyring, kasp, now, nexttime, opts);
|
||||
|
||||
|
|
|
|||
|
|
@ -751,6 +751,7 @@ struct dns_signing {
|
|||
dst_algorithm_t algorithm;
|
||||
uint16_t keyid;
|
||||
bool deleteit;
|
||||
bool fullsign;
|
||||
bool done;
|
||||
ISC_LINK(dns_signing_t) link;
|
||||
};
|
||||
|
|
@ -993,7 +994,7 @@ static void
|
|||
dump_done(void *arg, isc_result_t result);
|
||||
static isc_result_t
|
||||
zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid,
|
||||
bool deleteit);
|
||||
bool deleteit, bool fullsign);
|
||||
static isc_result_t
|
||||
delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
|
||||
dns_name_t *name, dns_diff_t *diff);
|
||||
|
|
@ -4101,7 +4102,7 @@ resume_signingwithkey(dns_zone_t *zone) {
|
|||
: ((rdata.data[5] << 8) | rdata.data[6]);
|
||||
result = zone_signwithkey(zone, alg,
|
||||
(rdata.data[1] << 8) | rdata.data[2],
|
||||
rdata.data[3]);
|
||||
rdata.data[3], false);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey failed: %s",
|
||||
|
|
@ -7907,7 +7908,7 @@ failure:
|
|||
static bool
|
||||
signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node,
|
||||
dns_dbversion_t *version, dns_rdatatype_t type,
|
||||
dst_key_t *key) {
|
||||
dst_key_t *key, bool fullsign) {
|
||||
isc_result_t result;
|
||||
dns_rdataset_t rdataset;
|
||||
dns_rdata_rrsig_t rrsig;
|
||||
|
|
@ -7940,7 +7941,7 @@ signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node,
|
|||
}
|
||||
}
|
||||
|
||||
if (zone->kasp != NULL) {
|
||||
if (zone->kasp != NULL && !fullsign) {
|
||||
int zsk_count = 0;
|
||||
bool approved;
|
||||
|
||||
|
|
@ -8041,8 +8042,9 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3,
|
||||
bool build_nsec, dst_key_t *key, isc_stdtime_t now,
|
||||
isc_stdtime_t inception, isc_stdtime_t expire, dns_ttl_t nsecttl,
|
||||
bool both, bool is_ksk, bool is_zsk, bool is_bottom_of_zone,
|
||||
dns_diff_t *diff, int32_t *signatures, isc_mem_t *mctx) {
|
||||
bool both, bool is_ksk, bool is_zsk, bool fullsign,
|
||||
bool is_bottom_of_zone, dns_diff_t *diff, int32_t *signatures,
|
||||
isc_mem_t *mctx) {
|
||||
isc_result_t result;
|
||||
dns_rdatasetiter_t *iterator = NULL;
|
||||
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
||||
|
|
@ -8152,7 +8154,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
continue;
|
||||
}
|
||||
if (signed_with_good_key(zone, db, node, version, rdataset.type,
|
||||
key))
|
||||
key, fullsign))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -10168,8 +10170,8 @@ zone_sign(dns_zone_t *zone) {
|
|||
db, zone, name, node, version, build_nsec3,
|
||||
build_nsec, zone_keys[i], now, inception,
|
||||
expire, zone_nsecttl(zone), both, is_ksk,
|
||||
is_zsk, is_bottom_of_zone, zonediff.diff,
|
||||
&signatures, zone->mctx));
|
||||
is_zsk, signing->fullsign, is_bottom_of_zone,
|
||||
zonediff.diff, &signatures, zone->mctx));
|
||||
/*
|
||||
* If we are adding we are done. Look for other keys
|
||||
* of the same algorithm if deleting.
|
||||
|
|
@ -20407,22 +20409,6 @@ dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay) {
|
|||
UNLOCK_ZONE(zone);
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
dns_zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm,
|
||||
uint16_t keyid, bool deleteit) {
|
||||
isc_result_t result;
|
||||
REQUIRE(DNS_ZONE_VALID(zone));
|
||||
|
||||
dnssec_log(zone, ISC_LOG_NOTICE,
|
||||
"dns_zone_signwithkey(algorithm=%u, keyid=%u)", algorithm,
|
||||
keyid);
|
||||
LOCK_ZONE(zone);
|
||||
result = zone_signwithkey(zone, algorithm, keyid, deleteit);
|
||||
UNLOCK_ZONE(zone);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a dynamic update for an NSEC3PARAM record is received.
|
||||
*
|
||||
|
|
@ -20494,7 +20480,7 @@ dns_zone_getprivatetype(dns_zone_t *zone) {
|
|||
|
||||
static isc_result_t
|
||||
zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid,
|
||||
bool deleteit) {
|
||||
bool deleteit, bool fullsign) {
|
||||
dns_signing_t *signing = NULL;
|
||||
isc_result_t result = ISC_R_SUCCESS;
|
||||
isc_time_t now;
|
||||
|
|
@ -20508,6 +20494,7 @@ zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid,
|
|||
signing->algorithm = algorithm;
|
||||
signing->keyid = keyid;
|
||||
signing->deleteit = deleteit;
|
||||
signing->fullsign = fullsign;
|
||||
signing->done = false;
|
||||
|
||||
now = isc_time_now();
|
||||
|
|
@ -22473,6 +22460,9 @@ zone_rekey(dns_zone_t *zone) {
|
|||
* fully signed now.
|
||||
*/
|
||||
fullsign = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_FULLSIGN);
|
||||
if (fullsign) {
|
||||
options |= DNS_KEYMGRATTR_FULLSIGN;
|
||||
}
|
||||
|
||||
/*
|
||||
* True when called from "rndc dnssec -step". Indicates the zone
|
||||
|
|
@ -22845,7 +22835,8 @@ zone_rekey(dns_zone_t *zone) {
|
|||
/* Remove any signatures from removed keys. */
|
||||
ISC_LIST_FOREACH(rmkeys, key, link) {
|
||||
result = zone_signwithkey(zone, dst_key_alg(key->key),
|
||||
dst_key_id(key->key), true);
|
||||
dst_key_id(key->key), true,
|
||||
false);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey failed: "
|
||||
|
|
@ -22874,20 +22865,41 @@ zone_rekey(dns_zone_t *zone) {
|
|||
* with all active keys, whether they're new or not.
|
||||
*/
|
||||
ISC_LIST_FOREACH(dnskeys, key, link) {
|
||||
if (!key->force_sign && !key->hint_sign) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result = zone_signwithkey(
|
||||
zone, dst_key_alg(key->key),
|
||||
dst_key_id(key->key), false);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey failed: "
|
||||
"%s",
|
||||
isc_result_totext(result));
|
||||
if (key->force_sign || key->hint_sign) {
|
||||
result = zone_signwithkey(
|
||||
zone, dst_key_alg(key->key),
|
||||
dst_key_id(key->key), false,
|
||||
true);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey "
|
||||
"failed: "
|
||||
"%s",
|
||||
isc_result_totext(
|
||||
result));
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* ...and remove signatures for all inactive keys.
|
||||
*/
|
||||
ISC_LIST_FOREACH(dnskeys, key, link) {
|
||||
if (!key->force_sign && !key->hint_sign) {
|
||||
result = zone_signwithkey(
|
||||
zone, dst_key_alg(key->key),
|
||||
dst_key_id(key->key), true,
|
||||
false);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey "
|
||||
"failed: "
|
||||
"%s",
|
||||
isc_result_totext(
|
||||
result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (newalg) {
|
||||
/*
|
||||
* We haven't been told to sign fully, but a new
|
||||
|
|
@ -22902,7 +22914,7 @@ zone_rekey(dns_zone_t *zone) {
|
|||
|
||||
result = zone_signwithkey(
|
||||
zone, dst_key_alg(key->key),
|
||||
dst_key_id(key->key), false);
|
||||
dst_key_id(key->key), false, false);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_signwithkey failed: "
|
||||
|
|
|
|||
Loading…
Reference in a new issue