2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-09-05 09:05:40 +00:00

Implement 'rndc dnssec -checkds'

Add a new 'rndc' command 'dnssec -checkds' that allows the user to
signal named that a new DS record has been seen published in the
parent, or that an existing DS record has been withdrawn from the
parent.

Upon the 'checkds' request, 'named' will write out the new state for
the key, updating the 'DSPublish' or 'DSRemoved' timing metadata.

This replaces the "parent-registration-delay" configuration option,
this was unreliable because it was purely time based (if the user
did not actually submit the new DS to the parent for example, this
could result in an invalid DNSSEC state).

Because we cannot rely on the parent registration delay for state
transition, we need to replace it with a different guard. Instead,
if a key wants its DS state to be moved to RUMOURED, the "DSPublish"
time must be set and must not be in the future. If a key wants its
DS state to be moved to UNRETENTIVE, the "DSRemoved" time must be set
and must not be in the future.

By default, with '-checkds' you set the time that the DS has been
published or withdrawn to now, but you can set a different time with
'-when'. If there is only one KSK for the zone, that key has its
DS state moved to RUMOURED. If there are multiple keys for the zone,
specify the right key with '-key'.
This commit is contained in:
Matthijs Mekking
2020-07-31 08:37:51 +02:00
parent f0fa6f0245
commit 04d8fc0143
10 changed files with 344 additions and 62 deletions

View File

@@ -128,9 +128,6 @@ keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
dns_kasp_parentpropagationdelay(kasp) +
dns_kasp_retiresafety(kasp);
}
if (zsk && ksk) {
ksk_remove += dns_kasp_parentregistrationdelay(kasp);
}
remove = ksk_remove > zsk_remove ? ksk_remove : zsk_remove;
dst_key_settime(key->key, DST_TIME_DELETE, remove);
@@ -263,12 +260,6 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
* so ignore the result code.
*/
(void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
if (!zsk && ksk) {
/*
* Include registration delay in prepublication time.
*/
prepub += dns_kasp_parentregistrationdelay(kasp);
}
ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
if (ret != ISC_R_SUCCESS) {
@@ -965,10 +956,14 @@ keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
* - First introduce the DNSKEY record, as well as the KRRSIG records.
* - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS.
*
* Also check the DS Publish or Delete times, to see if the DS record
* already reached the parent.
*/
static bool
keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
int type, dst_key_state_t next) {
int type, dst_key_state_t next, isc_stdtime_t now) {
isc_result_t ret;
isc_stdtime_t dstime;
dst_key_state_t dnskeystate = HIDDEN;
dst_key_state_t ksk_present[4] = { OMNIPRESENT, NA, OMNIPRESENT,
OMNIPRESENT };
@@ -980,10 +975,10 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
dst_key_state_t ksk_retired[4] = { UNRETENTIVE, NA, NA, OMNIPRESENT };
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
if (next != RUMOURED) {
if (next != RUMOURED && next != UNRETENTIVE) {
/*
* Local policy only adds an extra barrier on transitions to
* the RUMOURED state.
* the RUMOURED and UNRETENTIVE states.
*/
return (true);
}
@@ -993,6 +988,9 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
/* No restrictions. */
return (true);
case DST_KEY_ZRRSIG:
if (next != RUMOURED) {
return (true);
}
/* Make sure the DNSKEY record is OMNIPRESENT. */
(void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
if (dnskeystate == OMNIPRESENT) {
@@ -1013,13 +1011,35 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
keyring, key, type, next, ksk_retired,
ksk_rumoured, true, true)));
case DST_KEY_KRRSIG:
if (next != RUMOURED) {
return (true);
}
/* Only introduce if the DNSKEY is also introduced. */
(void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
return (dnskeystate != HIDDEN);
case DST_KEY_DS:
/* Make sure the DNSKEY record is OMNIPRESENT. */
(void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
return (dnskeystate == OMNIPRESENT);
if (next == RUMOURED) {
/* Make sure the DNSKEY record is OMNIPRESENT. */
(void)dst_key_getstate(key->key, DST_KEY_DNSKEY,
&dnskeystate);
if (dnskeystate != OMNIPRESENT) {
return (false);
}
/* Make sure DS has been seen in the parent. */
ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
&dstime);
if (ret != ISC_R_SUCCESS || dstime > now) {
return (false);
}
} else if (next == UNRETENTIVE) {
/* Make sure DS has been withdrawn from the parent. */
ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
&dstime);
if (ret != ISC_R_SUCCESS || dstime > now) {
return (false);
}
}
return (true);
default:
return (false);
}
@@ -1203,16 +1223,13 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
*
* Iret = DprpP + TTLds
*
* So we need to wait Dreg + Iret before the DS becomes
* OMNIPRESENT. This translates to:
* This translates to:
*
* parent-registration-delay +
* parent-propagation-delay + parent-ds-ttl.
*
* We will also add the retire-safety interval.
*/
nexttime = lastchange + dns_kasp_dsttl(kasp) +
dns_kasp_parentregistrationdelay(kasp) +
dns_kasp_parentpropagationdelay(kasp) +
dns_kasp_retiresafety(kasp);
break;
@@ -1302,7 +1319,7 @@ transition:
/* Is the transition allowed according to policy? */
if (!keymgr_policy_approval(keyring, dkey, i,
next_state)) {
next_state, now)) {
/* No, please respect rollover methods. */
isc_log_write(
dns_lctx, DNS_LOGCATEGORY_DNSSEC,
@@ -1433,7 +1450,6 @@ keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) {
ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
if (syncpub <= now && ret == ISC_R_SUCCESS) {
dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp);
ds_ttl += dns_kasp_parentregistrationdelay(kasp);
ds_ttl += dns_kasp_parentpropagationdelay(kasp);
if ((syncpub + ds_ttl) <= now) {
ds_state = OMNIPRESENT;
@@ -1854,6 +1870,83 @@ failure:
return (result);
}
static isc_result_t
keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
const char *directory, isc_stdtime_t now, bool dspublish,
dns_keytag_t id, bool check_id) {
int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
isc_dir_t dir;
isc_result_t result;
dns_dnsseckey_t *ksk_key = NULL;
REQUIRE(DNS_KASP_VALID(kasp));
REQUIRE(keyring != NULL);
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
dkey = ISC_LIST_NEXT(dkey, link))
{
isc_result_t ret;
bool ksk = false;
ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
if (ret == ISC_R_SUCCESS && ksk) {
if (check_id && dst_key_id(dkey->key) != id) {
continue;
}
if (ksk_key != NULL) {
/*
* Only checkds for one key at a time.
*/
return (ISC_R_FAILURE);
}
ksk_key = dkey;
}
}
if (ksk_key == NULL) {
return (ISC_R_NOTFOUND);
}
if (dspublish) {
dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, now);
} else {
dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, now);
}
/* Store key state and update hints. */
isc_dir_init(&dir);
if (directory == NULL) {
directory = ".";
}
result = isc_dir_open(&dir, directory);
if (result != ISC_R_SUCCESS) {
return result;
}
dns_dnssec_get_hints(ksk_key, now);
result = dst_key_tofile(ksk_key->key, options, directory);
isc_dir_close(&dir);
return (result);
}
isc_result_t
dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
const char *directory, isc_stdtime_t now, bool dspublish) {
return (keymgr_checkds(kasp, keyring, directory, now, dspublish, 0,
false));
}
isc_result_t
dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
const char *directory, isc_stdtime_t now, bool dspublish,
dns_keytag_t id) {
return (keymgr_checkds(kasp, keyring, directory, now, dspublish, id,
true));
}
static void
keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf,
const char *pre, int ks, int kt) {