diff --git a/CHANGES b/CHANGES index 73bd3de0c3..ba83424e8b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,5 @@ +6372. [func] Implement signature jitter for dnssec-policy. [GL #4554] + --- 9.18.26 released --- 6364. [protocol] Add RESOLVER.ARPA to the built in empty zones. diff --git a/bin/named/config.c b/bin/named/config.c index f95e4336ed..6bf0b3ca81 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -309,6 +309,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/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 17309e5ea6..c7dd9efbd8 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -29,6 +29,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 b/bin/tests/system/checkconf/good.conf index f8d04089f0..7e0f87901c 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -30,6 +30,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 3dab21be42..cb2f5126af 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -6509,6 +6509,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 00b8a14656..afe570ad70 100644 --- a/doc/misc/dnssec-policy.default.conf +++ b/doc/misc/dnssec-policy.default.conf @@ -24,6 +24,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 da2847708f..56cbf323b6 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -21,6 +21,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 3c40996271..e59cc4eacb 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 4f560a037f..76ba52ec9c 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -75,6 +75,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; @@ -105,6 +106,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" @@ -233,6 +236,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 d53abfb64c..f90f76065c 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -127,6 +127,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 a94d35276e..b4d2a1258e 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -1492,23 +1492,37 @@ 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; + if (kasp != NULL) { + jitter = dns_kasp_sigjitter(kasp); + sigvalidity = dns_kasp_sigvalidity(kasp); + INSIST(jitter <= sigvalidity); + } else { + jitter = dns_zone_getsigresigninginterval(zone); + if (jitter > sigvalidity) { + jitter = sigvalidity; } else { - expiryinterval = sigvalidityinterval - expiryinterval; + jitter = sigvalidity - jitter; } - uint32_t jitter = isc_random_uniform(expiryinterval); - sigvalidityinterval -= jitter; } - 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 @@ -1561,8 +1575,7 @@ dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, isc_stdtime_get(&state->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 4c5423815b..2bf1a50bb8 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -7184,6 +7184,60 @@ 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); + } else { + jitter = dns_zone_getsigresigninginterval(zone); + if (jitter > sigvalidity) { + jitter = sigvalidity; + } else { + jitter = sigvalidity - jitter; + } + } + + 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) { const char *me = "zone_resigninc"; @@ -7199,7 +7253,6 @@ zone_resigninc(dns_zone_t *zone) { bool check_ksk, keyset_kskonly = false; 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; @@ -7250,38 +7303,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; check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); @@ -8376,7 +8400,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; @@ -8445,31 +8468,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); check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); @@ -9482,7 +9482,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; @@ -9534,32 +9533,8 @@ zone_sign(dns_zone_t *zone) { goto cleanup; } - kasp = dns_zone_getkasp(zone); - 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 @@ -9575,6 +9550,7 @@ zone_sign(dns_zone_t *zone) { check_ksk = false; keyset_kskonly = true; use_kasp = true; + kasp = zone->kasp; } else { check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index ba36b0d8ee..a482062a2b 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -312,7 +312,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; @@ -360,6 +360,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); @@ -376,6 +380,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)) { @@ -388,6 +401,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 d5b28baad2..7bfd8f9bde 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2211,6 +2211,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 },