diff --git a/CHANGES b/CHANGES index ffd0749d42..dcf18af4a0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,5 @@ +6372. [func] Implement signature jitter for dnssec-policy. [GL #4554] + 6371. [bug] Access to the trust bytes in the ncache data needed to be made thread safe. [GL #4475] diff --git a/bin/named/config.c b/bin/named/config.c index b1d6c04b3f..56e58d285a 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -298,6 +298,7 @@ dnssec-policy \"default\" {\n\ publish-safety " DNS_KASP_PUBLISH_SAFETY "; \n\ retire-safety " DNS_KASP_RETIRE_SAFETY "; \n\ purge-keys " DNS_KASP_PURGE_KEYS "; \n\ + signatures-jitter " DNS_KASP_SIG_JITTER "; \n\ signatures-refresh " DNS_KASP_SIG_REFRESH "; \n\ signatures-validity " DNS_KASP_SIG_VALIDITY "; \n\ signatures-validity-dnskey " DNS_KASP_SIG_VALIDITY_DNSKEY "; \n\ diff --git a/bin/tests/system/autosign/ns3/named.conf.in b/bin/tests/system/autosign/ns3/named.conf.in index 9bc5d655e1..2316de2932 100644 --- a/bin/tests/system/autosign/ns3/named.conf.in +++ b/bin/tests/system/autosign/ns3/named.conf.in @@ -87,6 +87,7 @@ dnssec-policy "jitter" { signatures-validity P10D; signatures-validity-dnskey P10D; signatures-refresh P2D; + signatures-jitter P8D; }; # Jitter, NSEC3 diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index 1839738966..3b7613226d 100755 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -91,10 +91,10 @@ checkjitter() { _count=0 # Check if we have at least 4 days - # This number has been tuned for `signatures-validity 10d; signatures-refresh 2d`, as - # 1 signature expiration dates should be spread out across at most 8 (10-2) days - # 2. we remove first and last day to remove frequency outlier, we are left with 6 (8-2) days - # 3. we subtract two more days to allow test pass on day boundaries, etc. leaving us with 4 (6-2) + # This number has been tuned for `signatures-validity 10d; signatures-jitter 8d`, as + # 1. signature expiration dates should be spread out across at most 8 days + # 2. we remove first and last day to remove frequency outlier, we are left with 6 days + # 3. we subtract two more days to allow test pass on day boundaries, etc. leaving us with 4 days for _num in $_expiretimes; do _count=$((_count + 1)) done diff --git a/bin/tests/system/checkconf/bad-kasp-jitter.conf b/bin/tests/system/checkconf/bad-kasp-jitter.conf new file mode 100644 index 0000000000..e358957437 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-jitter.conf @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * The dnssec-policy jitter is more than signatures-validity, + * which is not allowed. + */ +dnssec-policy high-jitter { + signatures-jitter P8DT1S; + signatures-validity P8D; +}; + +zone "example.net" { + type primary; + file "example.db"; + dnssec-policy high-jitter; +}; diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index 95ba817b3b..42e2478f96 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -34,6 +34,7 @@ dnssec-policy "test" { parent-propagation-delay PT1H; publish-safety PT3600S; retire-safety PT3600S; + signatures-jitter PT12H; signatures-refresh P3D; signatures-validity P2W; signatures-validity-dnskey P14D; diff --git a/bin/tests/system/checkconf/good.conf.in b/bin/tests/system/checkconf/good.conf.in index 2fde415a40..076ecc432d 100644 --- a/bin/tests/system/checkconf/good.conf.in +++ b/bin/tests/system/checkconf/good.conf.in @@ -34,6 +34,7 @@ dnssec-policy "test" { publish-safety PT3600S; purge-keys P90D; retire-safety PT3600S; + signatures-jitter PT12H; signatures-refresh P3D; signatures-validity P2W; signatures-validity-dnskey P14D; diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index df24019828..aab79e9064 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -6479,6 +6479,18 @@ The following options can be specified in a :any:`dnssec-policy` statement: unforeseen events. This increases the time a key remains published after it is no longer active. The default is ``PT1H`` (1 hour). +.. namedconf:statement:: signatures-jitter + :tags: dnssec + :short: Specifies a range for signatures expirations. + + To prevent all signatures from expiring at the same moment, BIND 9 may + vary the validity interval of individual signatures. The validity of a + newly generated signatures is in range between :any:`signatures-validity` + (maximum) and :any:`signatures-validity` minus :any:`signatures-jitter` + (minimum). The default jitter is 12 hours and the configured value must + be lower than :any:`signatures-validity` and + :any:`signatures-validity-dnskey`. + .. namedconf:statement:: signatures-refresh :tags: dnssec :short: Specifies how frequently an RRSIG record is refreshed. diff --git a/doc/misc/dnssec-policy.default.conf b/doc/misc/dnssec-policy.default.conf index cd033c1760..a6f526c743 100644 --- a/doc/misc/dnssec-policy.default.conf +++ b/doc/misc/dnssec-policy.default.conf @@ -26,6 +26,7 @@ dnssec-policy "default" { purge-keys P90D; // Signature timings + signatures-jitter 12h; signatures-refresh 5d; signatures-validity 14d; signatures-validity-dnskey 14d; diff --git a/doc/misc/options b/doc/misc/options index 1142bb6f18..7c94dcd180 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -23,6 +23,7 @@ dnssec-policy { publish-safety ; purge-keys ; retire-safety ; + signatures-jitter ; signatures-refresh ; signatures-validity ; signatures-validity-dnskey ; diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 5fbe33da5c..9812b77e46 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -22,6 +22,9 @@ New Features - None. +- A new option :any:`signatures-jitter` is added to :any:`dnssec-policy` to + spread out signature expiration times over a period of time. :gl:`#4554` + Removed Features ~~~~~~~~~~~~~~~~ diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 42fe126396..cd8a5bd13f 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -83,6 +83,7 @@ struct dns_kasp { ISC_LINK(struct dns_kasp) link; /* Configuration: signatures */ + uint32_t signatures_jitter; uint32_t signatures_refresh; uint32_t signatures_validity; uint32_t signatures_validity_dnskey; @@ -116,6 +117,8 @@ struct dns_kasp { #define DNS_KASP_VALID(kasp) ISC_MAGIC_VALID(kasp, DNS_KASP_MAGIC) /* Defaults */ +#define DEFAULT_JITTER (12 * 3600) +#define DNS_KASP_SIG_JITTER "PT12H" #define DNS_KASP_SIG_REFRESH "P5D" #define DNS_KASP_SIG_VALIDITY "P14D" #define DNS_KASP_SIG_VALIDITY_DNSKEY "P14D" @@ -244,6 +247,30 @@ dns_kasp_signdelay(dns_kasp_t *kasp); *\li signature refresh interval. */ +uint32_t +dns_kasp_sigjitter(dns_kasp_t *kasp); +/*%< + * Get signature jitter value. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature jitter value. + */ + +void +dns_kasp_setsigjitter(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature jitter value. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_sigrefresh(dns_kasp_t *kasp); /*%< diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 8658fd629c..b6a54a1074 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -138,6 +138,22 @@ dns_kasp_signdelay(dns_kasp_t *kasp) { return (kasp->signatures_validity - kasp->signatures_refresh); } +uint32_t +dns_kasp_sigjitter(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->signatures_jitter); +} + +void +dns_kasp_setsigjitter(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->signatures_jitter = value; +} + uint32_t dns_kasp_sigrefresh(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); diff --git a/lib/dns/update.c b/lib/dns/update.c index cbcbe1c139..a6b8f2adf4 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -1477,23 +1477,30 @@ struct dns_update_state { }; static uint32_t -dns__jitter_expire(dns_zone_t *zone, uint32_t sigvalidityinterval) { +dns__jitter_expire(dns_zone_t *zone) { /* Spread out signatures over time */ - if (sigvalidityinterval >= 3600U) { - uint32_t expiryinterval = - dns_zone_getsigresigninginterval(zone); + isc_stdtime_t jitter = DEFAULT_JITTER; + isc_stdtime_t sigvalidity = dns_zone_getsigvalidityinterval(zone); + dns_kasp_t *kasp = dns_zone_getkasp(zone); - if (sigvalidityinterval < 7200U) { - expiryinterval = 1200; - } else if (expiryinterval > sigvalidityinterval) { - expiryinterval = sigvalidityinterval; - } else { - expiryinterval = sigvalidityinterval - expiryinterval; - } - uint32_t jitter = isc_random_uniform(expiryinterval); - sigvalidityinterval -= jitter; + if (kasp != NULL) { + jitter = dns_kasp_sigjitter(kasp); + sigvalidity = dns_kasp_sigvalidity(kasp); + INSIST(jitter <= sigvalidity); } - return (sigvalidityinterval); + + if (jitter > sigvalidity) { + jitter = sigvalidity; + } + + if (sigvalidity >= 3600U) { + if (sigvalidity > 7200U) { + sigvalidity -= isc_random_uniform(jitter); + } else { + sigvalidity -= isc_random_uniform(1200); + } + } + return (sigvalidity); } isc_result_t @@ -1549,8 +1556,7 @@ dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, state->now = isc_stdtime_now(); state->inception = state->now - 3600; /* Allow for some clock skew. */ - state->expire = state->now + - dns__jitter_expire(zone, sigvalidityinterval); + state->expire = state->now + dns__jitter_expire(zone); state->soaexpire = state->now + sigvalidityinterval; state->keyexpire = dns_zone_getkeyvalidityinterval(zone); if (state->keyexpire == 0) { diff --git a/lib/dns/zone.c b/lib/dns/zone.c index cf09283221..c70579e0af 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -6911,6 +6911,53 @@ failure: return (result); } +static void +calculate_rrsig_validity(dns_zone_t *zone, isc_stdtime_t now, + isc_stdtime_t *inception, isc_stdtime_t *soaexpire, + isc_stdtime_t *expire, isc_stdtime_t *fullexpire) { + REQUIRE(inception != NULL); + REQUIRE(soaexpire != NULL); + /* expire and fullexpire are optional */ + + isc_stdtime_t jitter = DEFAULT_JITTER; + isc_stdtime_t sigvalidity = dns_zone_getsigvalidityinterval(zone); + isc_stdtime_t shortjitter = 0, fulljitter = 0; + + if (zone->kasp != NULL) { + jitter = dns_kasp_sigjitter(zone->kasp); + sigvalidity = dns_kasp_sigvalidity(zone->kasp); + INSIST(jitter <= sigvalidity); + } + + if (jitter > sigvalidity) { + jitter = sigvalidity; + } + + *inception = now - 3600; /* Allow for clock skew. */ + *soaexpire = now + sigvalidity; + + /* + * Spread out signatures over time if they happen to be + * clumped. We don't do this for each add_sigs() call as + * we still want some clustering to occur. In normal operations + * the records should be re-signed as they fall due and they should + * already be spread out. However if the server is off for a + * period we need to ensure that the clusters don't become + * synchronised by using the full jitter range. + */ + if (sigvalidity >= 3600U) { + if (sigvalidity > 7200U) { + shortjitter = isc_random_uniform(3600); + fulljitter = isc_random_uniform(jitter); + } else { + shortjitter = fulljitter = isc_random_uniform(1200); + } + } + + SET_IF_NOT_NULL(expire, *soaexpire - shortjitter - 1); + SET_IF_NOT_NULL(fullexpire, *soaexpire - fulljitter - 1); +} + static void zone_resigninc(dns_zone_t *zone) { dns_db_t *db = NULL; @@ -6924,7 +6971,6 @@ zone_resigninc(dns_zone_t *zone) { dst_key_t *zone_keys[DNS_MAXZONEKEYS]; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire, fullexpire, stop; - uint32_t sigvalidityinterval, expiryinterval; unsigned int i; unsigned int nkeys = 0; unsigned int resign; @@ -6972,38 +7018,9 @@ zone_resigninc(dns_zone_t *zone) { goto failure; } - sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); - inception = now - 3600; /* Allow for clock skew. */ - soaexpire = now + sigvalidityinterval; - expiryinterval = dns_zone_getsigresigninginterval(zone); - if (expiryinterval > sigvalidityinterval) { - expiryinterval = sigvalidityinterval; - } else { - expiryinterval = sigvalidityinterval - expiryinterval; - } + calculate_rrsig_validity(zone, now, &inception, &soaexpire, &expire, + &fullexpire); - /* - * Spread out signatures over time if they happen to be - * clumped. We don't do this for each add_sigs() call as - * we still want some clustering to occur. In normal operations - * the records should be re-signed as they fall due and they should - * already be spread out. However if the server is off for a - * period we need to ensure that the clusters don't become - * synchronised by using the full jitter range. - */ - if (sigvalidityinterval >= 3600U) { - uint32_t normaljitter, fulljitter; - if (sigvalidityinterval > 7200U) { - normaljitter = isc_random_uniform(3600); - fulljitter = isc_random_uniform(expiryinterval); - } else { - normaljitter = fulljitter = isc_random_uniform(1200); - } - expire = soaexpire - normaljitter - 1; - fullexpire = soaexpire - fulljitter - 1; - } else { - expire = fullexpire = soaexpire - 1; - } stop = now + 5; name = dns_fixedname_initname(&fixed); @@ -8138,7 +8155,6 @@ zone_nsec3chain(dns_zone_t *zone) { bool first; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire; - uint32_t jitter, sigvalidityinterval, expiryinterval; unsigned int i; unsigned int nkeys = 0; uint32_t nodes; @@ -8207,31 +8223,8 @@ zone_nsec3chain(dns_zone_t *zone) { goto failure; } - sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); - inception = now - 3600; /* Allow for clock skew. */ - soaexpire = now + sigvalidityinterval; - expiryinterval = dns_zone_getsigresigninginterval(zone); - if (expiryinterval > sigvalidityinterval) { - expiryinterval = sigvalidityinterval; - } else { - expiryinterval = sigvalidityinterval - expiryinterval; - } - - /* - * Spread out signatures over time if they happen to be - * clumped. We don't do this for each add_sigs() call as - * we still want some clustering to occur. - */ - if (sigvalidityinterval >= 3600U) { - if (sigvalidityinterval > 7200U) { - jitter = isc_random_uniform(expiryinterval); - } else { - jitter = isc_random_uniform(1200); - } - expire = soaexpire - jitter - 1; - } else { - expire = soaexpire - 1; - } + calculate_rrsig_validity(zone, now, &inception, &soaexpire, NULL, + &expire); /* * We keep pulling nodes off each iterator in turn until @@ -9240,7 +9233,6 @@ zone_sign(dns_zone_t *zone) { bool first; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire; - uint32_t jitter, sigvalidityinterval, expiryinterval; unsigned int i, j; unsigned int nkeys = 0; uint32_t nodes; @@ -9293,31 +9285,9 @@ zone_sign(dns_zone_t *zone) { } kasp = zone->kasp; - sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); - inception = now - 3600; /* Allow for clock skew. */ - soaexpire = now + sigvalidityinterval; - expiryinterval = dns_zone_getsigresigninginterval(zone); - if (expiryinterval > sigvalidityinterval) { - expiryinterval = sigvalidityinterval; - } else { - expiryinterval = sigvalidityinterval - expiryinterval; - } - /* - * Spread out signatures over time if they happen to be - * clumped. We don't do this for each add_sigs() call as - * we still want some clustering to occur. - */ - if (sigvalidityinterval >= 3600U) { - if (sigvalidityinterval > 7200U) { - jitter = isc_random_uniform(expiryinterval); - } else { - jitter = isc_random_uniform(1200); - } - expire = soaexpire - jitter - 1; - } else { - expire = soaexpire - 1; - } + calculate_rrsig_validity(zone, now, &inception, &soaexpire, NULL, + &expire); /* * We keep pulling nodes off each iterator in turn until diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index f756ed97da..419818f257 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -412,7 +412,7 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, const char *kaspname = NULL; dns_kasp_t *kasp = NULL; size_t i = 0; - uint32_t sigrefresh = 0, sigvalidity = 0; + uint32_t sigjitter = 0, sigrefresh = 0, sigvalidity = 0; uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0; uint32_t publishsafety = 0, retiresafety = 0; uint32_t zonepropdelay = 0, parentpropdelay = 0; @@ -460,6 +460,10 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, maps[i] = NULL; /* Configuration: Signatures */ + sigjitter = get_duration(maps, "signatures-jitter", + DNS_KASP_SIG_JITTER); + dns_kasp_setsigjitter(kasp, sigjitter); + sigrefresh = get_duration(maps, "signatures-refresh", DNS_KASP_SIG_REFRESH); dns_kasp_setsigrefresh(kasp, sigrefresh); @@ -476,6 +480,15 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, } dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity); + if (sigjitter > sigvalidity) { + cfg_obj_log( + config, logctx, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' signatures-jitter cannot " + "be larger than signatures-validity-dnskey", + kaspname); + result = ISC_R_FAILURE; + } + sigvalidity = get_duration(maps, "signatures-validity", DNS_KASP_SIG_VALIDITY); if (sigrefresh >= (sigvalidity * 0.9)) { @@ -488,6 +501,15 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, } dns_kasp_setsigvalidity(kasp, sigvalidity); + if (sigjitter > sigvalidity) { + cfg_obj_log( + config, logctx, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' signatures-jitter cannot " + "be larger than signatures-validity", + kaspname); + result = ISC_R_FAILURE; + } + if (result != ISC_R_SUCCESS) { goto cleanup; } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 5c50c586f1..70bf565f19 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2281,6 +2281,7 @@ static cfg_clausedef_t dnssecpolicy_clauses[] = { { "publish-safety", &cfg_type_duration, 0 }, { "purge-keys", &cfg_type_duration, 0 }, { "retire-safety", &cfg_type_duration, 0 }, + { "signatures-jitter", &cfg_type_duration, 0 }, { "signatures-refresh", &cfg_type_duration, 0 }, { "signatures-validity", &cfg_type_duration, 0 }, { "signatures-validity-dnskey", &cfg_type_duration, 0 },