mirror of
https://gitlab.nic.cz/knot/knot-dns.git
synced 2026-06-08 16:12:37 -04:00
dnssec/validation: consider end of RRSIG validitiy...
...for dnssec-validate that it is longer than rrsig-refresh ...for keymgr offline-ksk, that it's until the next DNSKEY snapshot
This commit is contained in:
parent
de06303cbc
commit
ce1e335c95
15 changed files with 79 additions and 27 deletions
|
|
@ -1869,6 +1869,10 @@ A period how long at least before a signature expiration the signature will be r
|
|||
in order to prevent expired RRSIGs on secondary servers or resolvers\(aq caches.
|
||||
.sp
|
||||
\fIDefault:\fP 0.1 * \fI\%rrsig\-lifetime\fP + \fI\%propagation\-delay\fP + \fI\%zone\-max\-ttl\fP
|
||||
.sp
|
||||
If \fI\%dnssec\-validation\fP is enabled:
|
||||
.sp
|
||||
\fIDefault:\fP \fB1d\fP (1 day)
|
||||
.SS rrsig\-pre\-refresh
|
||||
.sp
|
||||
A period how long at most before a signature refresh time the signature might be refreshed,
|
||||
|
|
@ -2522,7 +2526,9 @@ Every NSEC(3) RR is linked to the lexicographically next one.
|
|||
.sp
|
||||
The validation is not affected by \fI\%dnssec\-policy\fP configuration,
|
||||
except for \fI\%signing\-threads\fP option, which specifies the number
|
||||
of threads for parallel validation.
|
||||
of threads for parallel validation, and \fI\%rrsig\-refresh\fP, which
|
||||
defines minimal allowed remaining RRSIG validity (otherwise a warning is
|
||||
logged).
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
|
|
|
|||
|
|
@ -2056,6 +2056,10 @@ in order to prevent expired RRSIGs on secondary servers or resolvers' caches.
|
|||
|
||||
*Default:* 0.1 * :ref:`policy_rrsig-lifetime` + :ref:`policy_propagation-delay` + :ref:`policy_zone-max-ttl`
|
||||
|
||||
If :ref:`zone_dnssec-validation` is enabled:
|
||||
|
||||
*Default:* ``1d`` (1 day)
|
||||
|
||||
.. _policy_rrsig-pre-refresh:
|
||||
|
||||
rrsig-pre-refresh
|
||||
|
|
@ -2744,7 +2748,9 @@ List of DNSSEC checks:
|
|||
|
||||
The validation is not affected by :ref:`zone_dnssec-policy` configuration,
|
||||
except for :ref:`policy_signing-threads` option, which specifies the number
|
||||
of threads for parallel validation.
|
||||
of threads for parallel validation, and :ref:`policy_rrsig-refresh`, which
|
||||
defines minimal allowed remaining RRSIG validity (otherwise a warning is
|
||||
logged).
|
||||
|
||||
.. NOTE::
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ inline static int knot_time_cmp(knot_time_t a, knot_time_t b)
|
|||
{
|
||||
return (a == b ? 0 : 1) * ((a && b) == 0 ? -1 : 1) * (a < b ? -1 : 1);
|
||||
}
|
||||
inline static bool knot_time_lt (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) < 0; }
|
||||
inline static bool knot_time_leq(knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) <= 0; }
|
||||
inline static bool knot_time_eq (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) == 0; }
|
||||
inline static bool knot_time_geq(knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) >= 0; }
|
||||
inline static bool knot_time_gt (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) > 0; }
|
||||
|
||||
/*!
|
||||
* \brief Return the smaller (=earlier) from given two timestamps.
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ static const yp_item_t desc_policy[] = {
|
|||
CONF_IO_FRLD_ZONES },
|
||||
{ C_RRSIG_REFRESH, YP_TINT, YP_VINT = { 1, INT32_MAX, YP_NIL, YP_STIME },
|
||||
CONF_IO_FRLD_ZONES },
|
||||
{ C_RRSIG_PREREFRESH, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME },
|
||||
{ C_RRSIG_PREREFRESH, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME, DAYS(1) },
|
||||
CONF_IO_FRLD_ZONES },
|
||||
{ C_REPRO_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
|
||||
{ C_NSEC3, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
|
||||
|
|
|
|||
|
|
@ -338,8 +338,10 @@ int kdnssec_validation_ctx(conf_t *conf, kdnssec_ctx_t *ctx, const zone_contents
|
|||
if (conf != NULL) {
|
||||
conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner);
|
||||
conf_id_fix_default(&policy_id);
|
||||
conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id);
|
||||
ctx->policy->signing_threads = conf_int(&num_threads);
|
||||
conf_val_t val = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id);
|
||||
ctx->policy->signing_threads = conf_int(&val);
|
||||
val = conf_id_get(conf, C_POLICY, C_RRSIG_REFRESH, &policy_id);
|
||||
ctx->policy->rrsig_refresh_before = conf_int_alt(&val, true);
|
||||
} else {
|
||||
ctx->policy->signing_threads = MAX(dt_optimal_size(), 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_
|
|||
return ret;
|
||||
}
|
||||
|
||||
int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp)
|
||||
int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp, knot_time_t min_valid)
|
||||
{
|
||||
kctx->now = timestamp;
|
||||
int ret = kasp_zone_keys_from_rr(kctx->zone, &r->dnskey.rrs, false, &kctx->keytag_conflict);
|
||||
|
|
@ -224,12 +224,17 @@ int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timest
|
|||
return KNOT_ENOMEM;
|
||||
}
|
||||
|
||||
ret = knot_validate_rrsigs(&r->dnskey, &r->rrsig, sign_ctx, false);
|
||||
knot_time_t until = 0;
|
||||
ret = knot_validate_rrsigs(&r->dnskey, &r->rrsig, sign_ctx, false, &until);
|
||||
if (ret == KNOT_EOK && !knot_rrset_empty(&r->cdnskey)) {
|
||||
ret = knot_validate_rrsigs(&r->cdnskey, &r->rrsig, sign_ctx, false);
|
||||
ret = knot_validate_rrsigs(&r->cdnskey, &r->rrsig, sign_ctx, false, &until);
|
||||
}
|
||||
if (ret == KNOT_EOK && !knot_rrset_empty(&r->cds)) {
|
||||
ret = knot_validate_rrsigs(&r->cds, &r->rrsig, sign_ctx, false);
|
||||
ret = knot_validate_rrsigs(&r->cds, &r->rrsig, sign_ctx, false, &until);
|
||||
}
|
||||
|
||||
if (ret == KNOT_EOK && knot_time_lt(until, min_valid)) {
|
||||
ret = KNOT_ESOON_EXPIRE;
|
||||
}
|
||||
|
||||
zone_sign_ctx_free(sign_ctx);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ int key_records_dump(char **buf, size_t *buf_size, const key_records_t *r, bool
|
|||
int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx);
|
||||
|
||||
// WARNING this modifies 'kctx' with updated timestamp and with zone_keys from r->dnskey
|
||||
int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp);
|
||||
int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp, knot_time_t min_valid);
|
||||
|
||||
size_t key_records_serialized_size(const key_records_t *r);
|
||||
|
||||
|
|
|
|||
|
|
@ -300,9 +300,10 @@ static bool key_used(bool ksk, bool zsk, uint16_t type,
|
|||
int knot_validate_rrsigs(const knot_rrset_t *covered,
|
||||
const knot_rrset_t *rrsigs,
|
||||
zone_sign_ctx_t *sign_ctx,
|
||||
bool skip_crypto)
|
||||
bool skip_crypto,
|
||||
knot_time_t *valid_until)
|
||||
{
|
||||
if (covered == NULL || rrsigs == NULL || sign_ctx == NULL) {
|
||||
if (covered == NULL || rrsigs == NULL || sign_ctx == NULL || valid_until == NULL) {
|
||||
return KNOT_EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +320,8 @@ int knot_validate_rrsigs(const knot_rrset_t *covered,
|
|||
if (valid_signature_exists(covered, rrsigs, key->key, sign_ctx->sign_ctxs[i],
|
||||
sign_ctx->dnssec_ctx, 0, skip_crypto, &ret, &valid_at)) {
|
||||
valid_exists = true;
|
||||
knot_rdata_t *valid_rr = knot_rdataset_at(&rrsigs->rrs, valid_at);
|
||||
note_earliest_expiration(valid_rr, sign_ctx->dnssec_ctx->now, valid_until);
|
||||
}
|
||||
|
||||
knot_spin_lock(&sign_ctx->dnssec_ctx->stats->lock);
|
||||
|
|
@ -483,10 +486,15 @@ static int sign_node_rrsets(const zone_node_t *node,
|
|||
}
|
||||
|
||||
if (sign_ctx->dnssec_ctx->validation_mode) {
|
||||
result = knot_validate_rrsigs(&rrset, &rrsigs, sign_ctx, skip_crypto);
|
||||
knot_time_t until = 0;
|
||||
result = knot_validate_rrsigs(&rrset, &rrsigs, sign_ctx, skip_crypto, &until);
|
||||
if (result != KNOT_EOK) {
|
||||
hint->node = node->owner;
|
||||
hint->rrtype = rrset.type;
|
||||
} else if (knot_time_lt(until, sign_ctx->dnssec_ctx->now + sign_ctx->dnssec_ctx->policy->rrsig_refresh_before)) {
|
||||
hint->node = node->owner;
|
||||
hint->rrtype = rrset.type;
|
||||
hint->warning = KNOT_ESOON_EXPIRE;
|
||||
}
|
||||
} else if (sign_ctx->dnssec_ctx->rrsig_drop_existing) {
|
||||
result = force_resign_rrset(&rrset, &rrsigs,
|
||||
|
|
|
|||
|
|
@ -81,13 +81,15 @@ keyptr_dynarray_t knot_zone_sign_get_cdnskeys(const kdnssec_ctx_t *ctx,
|
|||
* \param rrsigs RRSIG with signatures.
|
||||
* \param sign_ctx Signing context (with keys == NULL)
|
||||
* \param skip_crypto Crypto operations might be skipped as they had been successful earlier.
|
||||
* \param valid_until End of soonest RRSIG validity.
|
||||
*
|
||||
* \return KNOT_E*
|
||||
*/
|
||||
int knot_validate_rrsigs(const knot_rrset_t *covered,
|
||||
const knot_rrset_t *rrsigs,
|
||||
zone_sign_ctx_t *sign_ctx,
|
||||
bool skip_crypto);
|
||||
bool skip_crypto,
|
||||
knot_time_t *valid_until);
|
||||
|
||||
/*!
|
||||
* \brief Update zone signatures and store performed changes in update.
|
||||
|
|
|
|||
|
|
@ -894,6 +894,23 @@ int zone_update_verify_digest(conf_t *conf, zone_update_t *update)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void log_validation_error(zone_update_t *update, const char *msg_valid,
|
||||
int ret, bool warning)
|
||||
{
|
||||
unsigned level = warning ? LOG_WARNING : LOG_ERR;
|
||||
|
||||
log_fmt_zone(level, LOG_SOURCE_ZONE, update->zone->name, NULL,
|
||||
"DNSSEC, %svalidation failed (%s)", msg_valid, knot_strerror(ret));
|
||||
|
||||
char type_str[16];
|
||||
knot_dname_txt_storage_t name_str;
|
||||
if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL &&
|
||||
knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) {
|
||||
log_fmt_zone(level, LOG_SOURCE_ZONE, update->zone->name, NULL,
|
||||
"DNSSEC, validation hint: %s %s", name_str, type_str);
|
||||
}
|
||||
}
|
||||
|
||||
int zone_update_commit(conf_t *conf, zone_update_t *update)
|
||||
{
|
||||
if (conf == NULL || update == NULL) {
|
||||
|
|
@ -947,20 +964,14 @@ int zone_update_commit(conf_t *conf, zone_update_t *update)
|
|||
size_t count = 0;
|
||||
ret = knot_dnssec_validate_zone(update, conf, 0, incr_valid, &count);
|
||||
if (ret != KNOT_EOK) {
|
||||
log_zone_error(update->zone->name, "DNSSEC, %svalidation failed (%s)",
|
||||
msg_valid, knot_strerror(ret));
|
||||
char type_str[16];
|
||||
knot_dname_txt_storage_t name_str;
|
||||
if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL &&
|
||||
knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) {
|
||||
log_zone_error(update->zone->name, "DNSSEC, validation hint: %s %s",
|
||||
name_str, type_str);
|
||||
}
|
||||
discard_adds_tree(update);
|
||||
log_validation_error(update, msg_valid, ret, false);
|
||||
if (conf->cache.srv_dbus_event & DBUS_EVENT_ZONE_INVALID) {
|
||||
systemd_emit_zone_invalid(update->zone->name);
|
||||
}
|
||||
discard_adds_tree(update);
|
||||
return ret;
|
||||
} else if (update->validation_hint.warning != KNOT_EOK) {
|
||||
log_validation_error(update, msg_valid, update->validation_hint.warning, true);
|
||||
} else {
|
||||
log_zone_info(update->zone->name, "DNSSEC, %svalidation successful, checked RRSIGs %zu",
|
||||
msg_valid, count);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ typedef struct {
|
|||
knot_dname_storage_t next;
|
||||
const knot_dname_t *node;
|
||||
uint16_t rrtype;
|
||||
int warning;
|
||||
} dnssec_validation_hint_t;
|
||||
|
||||
/*! \brief Structure for zone contents updating / querying. */
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ enum knot_error {
|
|||
KNOT_NO_PUBLIC_KEY,
|
||||
KNOT_NO_PRIVATE_KEY,
|
||||
KNOT_NO_READY_KEY,
|
||||
KNOT_ESOON_EXPIRE,
|
||||
|
||||
KNOT_ERROR_MAX = -501
|
||||
};
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ static const struct error errors[] = {
|
|||
{ KNOT_NO_PUBLIC_KEY, "no public key" },
|
||||
{ KNOT_NO_PRIVATE_KEY, "no private key" },
|
||||
{ KNOT_NO_READY_KEY, "no key ready for submission" },
|
||||
{ KNOT_ESOON_EXPIRE, "oncoming RRSIG expiration" },
|
||||
|
||||
/* Terminator */
|
||||
{ KNOT_ERROR, NULL }
|
||||
|
|
|
|||
|
|
@ -451,6 +451,7 @@ static void skr_import_header(zs_scanner_t *sc)
|
|||
// trailing header without timestamp
|
||||
next_timestamp = 0;
|
||||
}
|
||||
knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp;
|
||||
|
||||
// delete possibly existing conflicting offline records
|
||||
ctx->ret = kasp_db_delete_offline_records(
|
||||
|
|
@ -459,7 +460,7 @@ static void skr_import_header(zs_scanner_t *sc)
|
|||
|
||||
// store previous SKR
|
||||
if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) {
|
||||
ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp);
|
||||
ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts);
|
||||
if (ctx->ret != KNOT_EOK) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -490,9 +491,10 @@ static void skr_validate_header(zs_scanner_t *sc)
|
|||
// trailing header without timestamp
|
||||
next_timestamp = 0;
|
||||
}
|
||||
knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp;
|
||||
|
||||
if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) {
|
||||
int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp);
|
||||
int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts);
|
||||
if (ret != KNOT_EOK) { // ctx->ret untouched
|
||||
ERR2("invalid SignedKeyResponse for %"KNOT_TIME_PRINTF" (%s)",
|
||||
ctx->timestamp, knot_strerror(ret));
|
||||
|
|
|
|||
|
|
@ -1570,7 +1570,7 @@ class Knot(Server):
|
|||
have_policy = False
|
||||
for zone in sorted(self.zones):
|
||||
z = self.zones[zone]
|
||||
if not z.dnssec.enable:
|
||||
if not z.dnssec.enable and not z.dnssec.validate:
|
||||
continue
|
||||
|
||||
if (z.dnssec.shared_policy_with or z.name) != z.name:
|
||||
|
|
@ -1737,6 +1737,8 @@ class Knot(Server):
|
|||
|
||||
if z.dnssec.enable:
|
||||
s.item_str("dnssec-signing", "off" if z.dnssec.disable else "on")
|
||||
|
||||
if z.dnssec.enable or z.dnssec.validate:
|
||||
s.item_str("dnssec-policy", z.dnssec.shared_policy_with or z.name)
|
||||
|
||||
self._bool(s, "dnssec-validation", z.dnssec.validate)
|
||||
|
|
|
|||
Loading…
Reference in a new issue