Merge branch '4554-dnssec-policy-jitter' into 'main'

Add signatures-jitter option

Closes #4554

See merge request isc-projects/bind9!8686
This commit is contained in:
Matthijs Mekking 2024-04-18 08:11:18 +00:00
commit f8a09fd91a
17 changed files with 196 additions and 104 deletions

View file

@ -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]

View file

@ -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\

View file

@ -87,6 +87,7 @@ dnssec-policy "jitter" {
signatures-validity P10D;
signatures-validity-dnskey P10D;
signatures-refresh P2D;
signatures-jitter P8D;
};
# Jitter, NSEC3

View file

@ -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

View file

@ -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;
};

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -23,6 +23,7 @@ dnssec-policy <string> {
publish-safety <duration>;
purge-keys <duration>;
retire-safety <duration>;
signatures-jitter <duration>;
signatures-refresh <duration>;
signatures-validity <duration>;
signatures-validity-dnskey <duration>;

View file

@ -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
~~~~~~~~~~~~~~~~

View file

@ -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);
/*%<

View file

@ -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));

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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 },