From: Libor Peltan Date: Mon, 29 Nov 2021 16:44:49 +0000 (+0100) Subject: dnssec: implemented re-salt option on every ZSK roll occasion X-Git-Tag: v3.3.dev~264^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=89662f91bfaff58a46de45f08f497378d67270ff;p=thirdparty%2Fknot-dns.git dnssec: implemented re-salt option on every ZSK roll occasion --- diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in index cac59ff779..2584efb01a 100644 --- a/doc/man/knot.conf.5in +++ b/doc/man/knot.conf.5in @@ -1511,6 +1511,9 @@ A validity period of newly issued salt field. .sp Zero value means infinity. .sp +Special value \fI\-1\fP triggers re\-salt every time when active ZSK changes. +This optimizes the number of big changes to the zone. +.sp \fIDefault:\fP 30 days .SS signing\-threads .sp diff --git a/doc/reference.rst b/doc/reference.rst index 2cea76fffc..be1c86c11f 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -1648,6 +1648,9 @@ A validity period of newly issued salt field. Zero value means infinity. +Special value *-1* triggers re-salt every time when active ZSK changes. +This optimizes the number of big changes to the zone. + *Default:* 30 days .. _policy_signing-threads: diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index 4cb321a9cf..44eb9326d4 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -375,7 +375,7 @@ static const yp_item_t desc_policy[] = { { C_NSEC3_ITER, YP_TINT, YP_VINT = { 0, UINT16_MAX, 10 }, CONF_IO_FRLD_ZONES }, { C_NSEC3_OPT_OUT, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, { C_NSEC3_SALT_LEN, YP_TINT, YP_VINT = { 0, UINT8_MAX, 8 }, CONF_IO_FRLD_ZONES }, - { C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { 0, UINT32_MAX, DAYS(30), YP_STIME }, + { C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { -1, UINT32_MAX, DAYS(30), YP_STIME }, CONF_IO_FRLD_ZONES }, { C_SIGNING_THREADS, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } }, { C_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES, diff --git a/src/knot/dnssec/kasp/policy.h b/src/knot/dnssec/kasp/policy.h index fcc38e146a..d53adb5acc 100644 --- a/src/knot/dnssec/kasp/policy.h +++ b/src/knot/dnssec/kasp/policy.h @@ -109,7 +109,7 @@ typedef struct { // NSEC3 bool nsec3_enabled; bool nsec3_opt_out; - uint32_t nsec3_salt_lifetime; // like knot_time_t + int64_t nsec3_salt_lifetime; // like knot_time_t uint16_t nsec3_iterations; uint8_t nsec3_salt_length; // zone diff --git a/src/knot/dnssec/zone-events.c b/src/knot/dnssec/zone-events.c index b1e3db15bb..ab2ca836fa 100644 --- a/src/knot/dnssec/zone-events.c +++ b/src/knot/dnssec/zone-events.c @@ -66,7 +66,8 @@ static int generate_salt(dnssec_binary_t *salt, uint16_t length) return KNOT_EOK; } -int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_time_t *when_resalt) +int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, bool soa_rrsigs_ok, + knot_time_t *salt_changed, knot_time_t *when_resalt) { int ret = KNOT_EOK; @@ -74,11 +75,13 @@ int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_ return KNOT_EOK; } - if (ctx->zone->nsec3_salt.size != ctx->policy->nsec3_salt_length || ctx->zone->nsec3_salt_created == 0) { + if (ctx->policy->nsec3_salt_lifetime < 0 && !soa_rrsigs_ok) { + *when_resalt = ctx->now; + } else if (ctx->zone->nsec3_salt.size != ctx->policy->nsec3_salt_length || ctx->zone->nsec3_salt_created == 0) { *when_resalt = ctx->now; } else if (knot_time_cmp(ctx->now, ctx->zone->nsec3_salt_created) < 0) { return KNOT_EINVAL; - } else { + } else if (ctx->policy->nsec3_salt_lifetime > 0) { *when_resalt = knot_time_plus(ctx->zone->nsec3_salt_created, ctx->policy->nsec3_salt_lifetime); } @@ -98,7 +101,9 @@ int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_ *salt_changed = ctx->now; } // continue to planning next resalt even if NOK - *when_resalt = knot_time_plus(ctx->now, ctx->policy->nsec3_salt_lifetime); + if (ctx->policy->nsec3_salt_lifetime > 0) { + *when_resalt = knot_time_plus(ctx->now, ctx->policy->nsec3_salt_lifetime); + } } return ret; @@ -140,16 +145,6 @@ int knot_dnssec_zone_sign(zone_update_t *update, goto done; } - // perform nsec3resalt if pending - if (roll_flags & KEY_ROLL_ALLOW_NSEC3RESALT) { - result = knot_dnssec_nsec3resalt(&ctx, &reschedule->last_nsec3resalt, &reschedule->next_nsec3resalt); - if (result != KNOT_EOK) { - log_zone_error(zone_name, "DNSSEC, failed to update NSEC3 salt (%s)", - knot_strerror(result)); - goto done; - } - } - ctx.rrsig_drop_existing = flags & ZONE_SIGN_DROP_SIGNATURES; conf_val_t val = conf_zone_get(conf, C_ZONEMD_GENERATE, zone_name); @@ -176,6 +171,18 @@ int knot_dnssec_zone_sign(zone_update_t *update, goto done; } + // perform nsec3resalt if pending + if (roll_flags & KEY_ROLL_ALLOW_NSEC3RESALT) { + knot_rdataset_t *rrsig = node_rdataset(update->new_cont->apex, KNOT_RRTYPE_RRSIG); + bool issbaz = is_soa_signed_by_all_zsks(&keyset, rrsig); + result = knot_dnssec_nsec3resalt(&ctx, issbaz, &reschedule->last_nsec3resalt, &reschedule->next_nsec3resalt); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to update NSEC3 salt (%s)", + knot_strerror(result)); + goto done; + } + } + log_zone_info(zone_name, "DNSSEC, signing started"); knot_time_t next_resign = 0; diff --git a/src/knot/dnssec/zone-events.h b/src/knot/dnssec/zone-events.h index 8320e3822c..b1c389d56a 100644 --- a/src/knot/dnssec/zone-events.h +++ b/src/knot/dnssec/zone-events.h @@ -103,12 +103,14 @@ int knot_dnssec_sign_update(zone_update_t *update, conf_t *conf); * proper DNSSEC chain. * * \param ctx zone signing context + * \param soa_rrsigs_ok Zone is signed by current active ZSKs. * \param salt_changed output if KNOT_EOK: when was the salt last changed? (either ctx->now or 0) * \param when_resalt output: timestamp when next resalt takes place * * \return KNOT_E* */ -int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_time_t *when_resalt); +int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, bool soa_rrsigs_ok, + knot_time_t *salt_changed, knot_time_t *when_resalt); /*! * \brief When DNSSEC signing failed, re-plan on this time. diff --git a/src/knot/dnssec/zone-keys.c b/src/knot/dnssec/zone-keys.c index e9d0725e01..31a9e89575 100644 --- a/src/knot/dnssec/zone-keys.c +++ b/src/knot/dnssec/zone-keys.c @@ -715,3 +715,41 @@ int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner, *key = new_key; return KNOT_EOK; } + +static bool soa_signed_by_key(const zone_key_t *key, const knot_rdataset_t *apex_rrsig) +{ + assert(key != NULL); + if (apex_rrsig == NULL) { + return false; + } + uint16_t keytag = dnssec_key_get_keytag(key->key); + + knot_rdata_t *rr = apex_rrsig->rdata; + for (int i = 0; i < apex_rrsig->count; i++) { + if (knot_rrsig_type_covered(rr) == KNOT_RRTYPE_SOA && + knot_rrsig_key_tag(rr) == keytag) { + return true; + } + rr = knot_rdataset_next(rr); + } + + return false; +} + +int is_soa_signed_by_all_zsks(const zone_keyset_t *keyset, + const knot_rdataset_t *apex_rrsig) +{ + if (keyset == NULL || keyset->count == 0) { + return false; + } + + for (size_t i = 0; i < keyset->count; i++) { + const zone_key_t *key = &keyset->keys[i]; + if (key->is_zsk && key->is_active && + !soa_signed_by_key(key, apex_rrsig)) { + return false; + } + } + + return true; +} diff --git a/src/knot/dnssec/zone-keys.h b/src/knot/dnssec/zone-keys.h index fa1ba57cec..6d72572fe5 100644 --- a/src/knot/dnssec/zone-keys.h +++ b/src/knot/dnssec/zone-keys.h @@ -202,3 +202,12 @@ void zone_sign_ctx_free(zone_sign_ctx_t *ctx); */ int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner, const uint8_t *rdata, size_t rdlen); + +/*! + * \brief Tell if apex SOA is signed by all active ZSKs. + * + * \param keyset Zone key set. + * \param apex_rrsig Apex RRSIG RRSet. + */ +int is_soa_signed_by_all_zsks(const zone_keyset_t *keyset, + const knot_rdataset_t *apex_rrsig); diff --git a/src/knot/events/handlers/nsec3resalt.c b/src/knot/events/handlers/nsec3resalt.c index f523d9d84f..6375ad0174 100644 --- a/src/knot/events/handlers/nsec3resalt.c +++ b/src/knot/events/handlers/nsec3resalt.c @@ -29,7 +29,7 @@ int event_nsec3resalt(conf_t *conf, zone_t *zone) return ret; } - ret = knot_dnssec_nsec3resalt(&kctx, &salt_changed, &next_resalt); + ret = knot_dnssec_nsec3resalt(&kctx, true, &salt_changed, &next_resalt); if (ret == KNOT_EOK && salt_changed != 0) { zone_events_schedule_now(zone, ZONE_EVENT_DNSSEC); zone->timers.last_resalt = kctx.now; diff --git a/src/knot/events/replan.c b/src/knot/events/replan.c index 70966531de..4c562e1952 100644 --- a/src/knot/events/replan.c +++ b/src/knot/events/replan.c @@ -130,7 +130,9 @@ void replan_from_timers(conf_t *conf, zone_t *zone) resalt = now; } else { val = conf_id_get(conf, C_POLICY, C_NSEC3_SALT_LIFETIME, &policy); - resalt = zone->timers.last_resalt + conf_int(&val); + if (conf_int(&val) > 0) { + resalt = zone->timers.last_resalt + conf_int(&val); + } } } diff --git a/tests-extra/tests/dnssec/zsk_rollover/test.py b/tests-extra/tests/dnssec/zsk_rollover/test.py index a15480a08e..9d45e51c58 100644 --- a/tests-extra/tests/dnssec/zsk_rollover/test.py +++ b/tests-extra/tests/dnssec/zsk_rollover/test.py @@ -7,6 +7,21 @@ Check of automatic ZSK rollover with changing zone TTLs. from dnstest.utils import * from dnstest.test import Test +def get_salt(server, zone): + resp = server.dig(zone[0].name, "NSEC3PARAM") + return resp.resp.answer[0].to_rdataset()[0].to_text().split()[-1] + +last_salt = "" + +def check_salt(server, zone, shall_differ): + global last_salt + salt = get_salt(server, zone) + if shall_differ != (salt != last_salt): + msg_not = " " if shall_differ else " not " + detail_log("Salt %s, Last salt %s, shall%sdiffer" % (salt, last_salt, msg_not)) + set_err("NSEC3 salt shall%sdiffer" % msg_not) + last_salt = salt + def dnskey_count(server, zone): return server.dig(zone[0].name, "DNSKEY", dnssec=False).count("DNSKEY") @@ -47,17 +62,25 @@ master.dnssec(zone).manual = False master.dnssec(zone).dnskey_ttl = 3 master.dnssec(zone).zsk_lifetime = 16 master.dnssec(zone).propagation_delay = 3 +master.dnssec(zone).nsec3 = True +master.dnssec(zone).nsec3_salt_lifetime = -1 t.start() +master.zone_wait(zone) +check_salt(master, zone, True) + wait4key(t, master, zone, 3, -1, 6, 20, "ZSK publish") # new ZSK published old_key = zsk_keytag(master, zone) +check_salt(master, zone, False) wait4key(t, master, zone, 3, old_key, 4, 8, "ZSK switch") # active ZSK switched +check_salt(master, zone, True) up = master.update(zone) up.delete("longttl.example.com.", "A") # zone max TTL decreases up.send() master.ctl("zone-sign") wait4key(t, master, zone, 2, old_key, 9, 14, "ZSK remove") # old ZSK removed +check_salt(master, zone, False) t.end()