+5486. [func] Add 'rndc dnssec -checkds' command to tell named
+ that the DS record has been published in the parent.
+ [GL #1613]
+
5485. [placeholder]
5484. [func] Expire the 0 TTL RRSet quickly rather using them for
#include <dns/secalg.h>
#include <dns/soa.h>
#include <dns/stats.h>
+#include <dns/time.h>
#include <dns/tkey.h>
#include <dns/tsig.h>
#include <dns/ttl.h>
return (result);
}
+static inline bool
+argcheck(char *cmd, const char *full) {
+ size_t l;
+
+ if (cmd == NULL || cmd[0] != '-') {
+ return (false);
+ }
+
+ cmd++;
+ l = strlen(cmd);
+ if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
isc_result_t
named_server_dnssec(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text) {
dns_kasp_t *kasp = NULL;
dns_dnsseckeylist_t keys;
dns_dnsseckey_t *key;
- const char *ptr;
+ char *ptr;
+ const char *msg = NULL;
+ /* variables for -checkds */
+ bool checkds = false, dspublish = false, use_keyid = false;
+ dns_keytag_t keyid = 0;
/* variables for -status */
+ bool status = false;
char output[4096];
- isc_stdtime_t now;
- isc_time_t timenow;
+ isc_stdtime_t now, when;
+ isc_time_t timenow, timewhen;
const char *dir;
/* Skip the command name. */
return (ISC_R_UNEXPECTEDEND);
}
- if (strcasecmp(ptr, "-status") != 0) {
- return (DNS_R_SYNTAX);
- }
+ /* Initialize current time and key list. */
+ TIME_NOW(&timenow);
+ now = isc_time_seconds(&timenow);
+ when = now;
ISC_LIST_INIT(keys);
+ if (strcasecmp(ptr, "-status") == 0) {
+ status = true;
+ } else if (strcasecmp(ptr, "-checkds") == 0) {
+ checkds = true;
+
+ /* Check for options */
+ for (;;) {
+ ptr = next_token(lex, text);
+ if (ptr == NULL) {
+ msg = "Bad format";
+ CHECK(ISC_R_UNEXPECTEDEND);
+ }
+ if (argcheck(ptr, "key")) {
+ uint16_t id;
+ ptr = next_token(lex, text);
+ if (ptr == NULL) {
+ msg = "No key identifier specified";
+ CHECK(ISC_R_UNEXPECTEDEND);
+ }
+ CHECK(isc_parse_uint16(&id, ptr, 10));
+ keyid = (dns_keytag_t)id;
+ use_keyid = true;
+ continue;
+ } else if (argcheck(ptr, "when")) {
+ uint32_t tw;
+ ptr = next_token(lex, text);
+ if (ptr == NULL) {
+ msg = "No time specified";
+ CHECK(ISC_R_UNEXPECTEDEND);
+ }
+ CHECK(dns_time32_fromtext(ptr, &tw));
+ when = (isc_stdtime_t)tw;
+ continue;
+ } else if (ptr[0] == '-') {
+ msg = "Unknown option";
+ CHECK(DNS_R_SYNTAX);
+ } else {
+ /*
+ * No arguments provided, so we must be
+ * parsing "published|withdrawn".
+ */
+ if (strcasecmp(ptr, "publish") == 0) {
+ dspublish = true;
+ } else if (strcasecmp(ptr, "withdraw") != 0) {
+ CHECK(DNS_R_SYNTAX);
+ }
+ }
+ break;
+ }
+ } else {
+ CHECK(DNS_R_SYNTAX);
+ }
+
+ /* Get zone. */
CHECK(zone_from_args(server, lex, NULL, &zone, NULL, text, false));
if (zone == NULL) {
+ msg = "Zone not found";
CHECK(ISC_R_UNEXPECTEDEND);
}
+ /* Trailing garbage? */
+ ptr = next_token(lex, text);
+ if (ptr != NULL) {
+ msg = "Too many arguments";
+ CHECK(DNS_R_SYNTAX);
+ }
+
+ /* Get dnssec-policy. */
kasp = dns_zone_getkasp(zone);
if (kasp == NULL) {
- CHECK(putstr(text, "zone does not have dnssec-policy"));
- CHECK(putnull(text));
+ msg = "Zone does not have dnssec-policy";
goto cleanup;
}
- /* -status */
- TIME_NOW(&timenow);
- now = isc_time_seconds(&timenow);
+ /* Get DNSSEC keys. */
dir = dns_zone_getkeydirectory(zone);
LOCK(&kasp->lock);
result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), dir, now,
dns_zone_getmctx(zone), &keys);
UNLOCK(&kasp->lock);
-
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto cleanup;
}
- LOCK(&kasp->lock);
- dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output));
- UNLOCK(&kasp->lock);
- CHECK(putstr(text, output));
+
+ if (status) {
+ /*
+ * Output the DNSSEC status of the key and signing policy.
+ */
+ LOCK(&kasp->lock);
+ dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output));
+ UNLOCK(&kasp->lock);
+ CHECK(putstr(text, output));
+ } else if (checkds) {
+ /*
+ * Mark DS record has been seen, so it may move to the
+ * rumoured state.
+ */
+ char whenbuf[80];
+ isc_time_set(&timewhen, when, 0);
+ isc_time_formattimestamp(&timewhen, whenbuf, sizeof(whenbuf));
+
+ LOCK(&kasp->lock);
+ if (use_keyid) {
+ result = dns_keymgr_checkds_id(kasp, &keys, dir, when,
+ dspublish, keyid);
+ } else {
+ result = dns_keymgr_checkds(kasp, &keys, dir, when,
+ dspublish);
+ }
+ UNLOCK(&kasp->lock);
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ if (use_keyid) {
+ char tagbuf[6];
+ snprintf(tagbuf, sizeof(tagbuf), "%u", keyid);
+ CHECK(putstr(text, "KSK "));
+ CHECK(putstr(text, tagbuf));
+ CHECK(putstr(text, ": "));
+ }
+ CHECK(putstr(text, "Marked DS as "));
+ if (dspublish) {
+ CHECK(putstr(text, "published "));
+ } else {
+ CHECK(putstr(text, "withdrawn "));
+ }
+ CHECK(putstr(text, "since "));
+ CHECK(putstr(text, whenbuf));
+ break;
+ case ISC_R_NOTFOUND:
+ CHECK(putstr(text, "No matching KSK found"));
+ break;
+ case ISC_R_FAILURE:
+ CHECK(putstr(text,
+ "Error: multiple possible KSKs found, "
+ "retry command with -key id"));
+ break;
+ default:
+ CHECK(putstr(text, "Error executing checkds command"));
+ break;
+ }
+ }
CHECK(putnull(text));
cleanup:
+ if (msg != NULL) {
+ (void)putstr(text, msg);
+ (void)putnull(text);
+ }
+
while (!ISC_LIST_EMPTY(keys)) {
key = ISC_LIST_HEAD(keys);
ISC_LIST_UNLINK(keys, key, link);
return (result);
}
-static inline bool
-argcheck(char *cmd, const char *full) {
- size_t l;
-
- if (cmd == NULL || cmd[0] != '-') {
- return (false);
- }
-
- cmd++;
- l = strlen(cmd);
- if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) {
- return (false);
- }
-
- return (true);
-}
-
isc_result_t
named_server_nta(named_server_t *server, isc_lex_t *lex, bool readonly,
isc_buffer_t **text) {
Add zone to given view. Requires allow-new-zones option.\n\
delzone [-clean] zone [class [view]]\n\
Removes zone from given view.\n\
+ dnssec -checkds [-key id] [-when time] (published|withdrawn) zone [class [view]]\n\
+ Mark the DS record for the KSK of the given zone as seen\n\
+ in the parent. If the zone has multiple KSKs, select a\n\
+ specific key by providing the keytag with -key id.\n\
+ Requires the zone to have a dnssec-policy.\n\
dnssec -status zone [class [view]]\n\
Show the DNSSEC signing state for the specified zone.\n\
Requires the zone to have a dnssec-policy.\n\
See also ``rndc addzone`` and ``rndc modzone``.
-``dnssec`` [**-status** *zone* [*class* [*view*]]
- Show the DNSSEC signing state for the specified zone. Requires the
- zone to have a "dnssec-policy".
+``dnssec`` ( **-status** | **-checkds** [**-key** *id*] [**-when** *time*] ( *published* | *withdrawn* )) *zone* [*class* [*view*]]
+ This command allows you to interact with the "dnssec-policy" of a given
+ zone.
+
+ ``rndc dnssec -status`` show the DNSSEC signing state for the specified
+ zone.
+
+ ``rndc dnssec -checkds`` will let ``named`` know that the DS for the given
+ key has been seen published into or withdrawn from the parent. This is
+ required in order to complete a KSK rollover. If the ``-key id`` argument
+ is specified, look for the key with the given identifier, otherwise if there
+ is only one key acting as a KSK in the zone, assume the DS of that key.
+ The time that the DS has been published or withdrawn is set to now, unless
+ otherwise specified with the argument ``-when time``.
``dnstap`` ( **-reopen** | **-roll** [*number*] )
This command closes and re-opens DNSTAP output files. ``rndc dnstap -reopen`` allows
.sp
See also \fBrndc addzone\fP and \fBrndc modzone\fP\&.
.TP
-\fBdnssec\fP [\fB\-status\fP \fIzone\fP [\fIclass\fP [\fIview\fP]]
-Show the DNSSEC signing state for the specified zone. Requires the
-zone to have a "dnssec\-policy".
+\fBdnssec\fP ( \fB\-status\fP | \fB\-checkds\fP [\fB\-key\fP \fIid\fP] [\fB\-when\fP \fItime\fP] ( \fIpublished\fP | \fIwithdrawn\fP )) \fIzone\fP [\fIclass\fP [\fIview\fP]]
+This command allows you to interact with the "dnssec\-policy" of a given
+zone.
+.sp
+\fBrndc dnssec \-status\fP show the DNSSEC signing state for the specified
+zone.
+.sp
+\fBrndc dnssec \-checkds\fP will let \fBnamed\fP know that the DS for the given
+key has been seen published into or withdrawn from the parent. This is
+required in order to complete a KSK rollover. If the \fB\-key id\fP argument
+is specified, look for the key with the given identifier, otherwise if there
+is only one key acting as a KSK in the zone, assume the DS of that key.
+The time that the DS has been published or withdrawn is set to now, unless
+otherwise specified with the argument \fB\-when time\fP\&.
.TP
\fBdnstap\fP ( \fB\-reopen\fP | \fB\-roll\fP [\fInumber\fP] )
This command closes and re\-opens DNSTAP output files. \fBrndc dnstap \-reopen\fP allows
"DSPublish:", "SyncPublish:", "SyncDelete:",
- "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:"
+ "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:",
+
+ "DSRemoved:"
};
#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1)
printtime(key, DST_TIME_INACTIVE, "Retired", fp);
printtime(key, DST_TIME_REVOKE, "Revoked", fp);
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_SYNCPUBLISH, "PublishCDS", fp);
printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp);
*\li On error, keypool is unchanged
*/
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, bool dspublish);
+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);
+/*%<
+ * Check DS for one key in 'keyring'. The key must have the KSK role.
+ * If 'dspublish' is set to true, set the DS Publish time to 'now'.
+ * If 'dspublish' is set to false, set the DS Removed time to 'now'.
+ * If a specific key 'id' is given it must match the keytag.
+ * The result is stored in the key state file.
+ *
+ * Requires:
+ *\li 'kasp' is not NULL.
+ *\li 'keyring' is not NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS (No error).
+ *\li #ISC_R_FAILURE (More than one matching KSK found).
+ *\li #ISC_R_NOTFOUND (No matching KSK found).
+ *
+ */
+
void
dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
isc_stdtime_t now, char *out, size_t out_len);
#define DST_TIME_ZRRSIG 10
#define DST_TIME_KRRSIG 11
#define DST_TIME_DS 12
-#define DST_MAX_TIMES 12
+#define DST_TIME_DSDELETE 13
+#define DST_MAX_TIMES 13
/* Numeric metadata definitions */
#define DST_NUM_PREDECESSOR 0
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);
* 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) {
* - 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 };
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);
}
/* 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) {
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);
}
*
* 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;
/* 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,
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;
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) {
dns_keydata_fromdnskey
dns_keydata_todnskey
dns_keyflags_fromtext
+dns_keymgr_checkds
+dns_keymgr_checkds_id
dns_keymgr_run
dns_keymgr_status
dns_keynode_dsset