]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
dnssec: implemented re-salt option on every ZSK roll occasion
authorLibor Peltan <libor.peltan@nic.cz>
Mon, 29 Nov 2021 16:44:49 +0000 (17:44 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Thu, 2 Dec 2021 11:17:30 +0000 (12:17 +0100)
doc/man/knot.conf.5in
doc/reference.rst
src/knot/conf/schema.c
src/knot/dnssec/kasp/policy.h
src/knot/dnssec/zone-events.c
src/knot/dnssec/zone-events.h
src/knot/dnssec/zone-keys.c
src/knot/dnssec/zone-keys.h
src/knot/events/handlers/nsec3resalt.c
src/knot/events/replan.c
tests-extra/tests/dnssec/zsk_rollover/test.py

index cac59ff779ed64b34278ab2e697c62d288b621b6..2584efb01ac78b7782ac0029b095d1f34b6b7e9b 100644 (file)
@@ -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
index 2cea76fffc9969c7a356623bfcc0179b45188da1..be1c86c11f2fbb15116df914c3ce7587063671d3 100644 (file)
@@ -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:
index 4cb321a9cf7256729dab8f7f37442c28f96eb023..44eb9326d40e04241204428178cb5815f45eeb87 100644 (file)
@@ -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,
index fcc38e146af419df93e4458ef38215247c2c3ca9..d53adb5acca60d6f0bec8d5070b2d1c42b6e59ab 100644 (file)
@@ -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
index b1e3db15bbb52a518e76323b4fa794e59ff57f55..ab2ca836fa2ac1f48e2b78f53034b59418675a8f 100644 (file)
@@ -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;
index 8320e3822cc0a384a19c4afadae2a5c805282fbf..b1c389d56a2ef43a9f304fefe8d70528374a171a 100644 (file)
@@ -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.
index e9d0725e0130dc465811da0a49d8703dfd4cc69b..31a9e895759677751b1fd012319a4b2e46cde949 100644 (file)
@@ -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;
+}
index fa1ba57cecc1897c2f349d459c98afa2ac08d8f3..6d72572fe5319127a61efca415df8c5cffb6b9a6 100644 (file)
@@ -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);
index f523d9d84f638fed5838801fdf10cf2947c8d395..6375ad017449ef6c077894ed128e45c473754dde 100644 (file)
@@ -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;
index 70966531defc1b4317f4657702e861a2c2c67faa..4c562e19528399374394c8949a0f57a47351cd90 100644 (file)
@@ -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);
+                               }
                        }
                }
 
index a15480a08ed9ef340ba96bddf19103e23db2bff4..9d45e51c580c41c440c5041e3898c3b9b4fbbd67 100644 (file)
@@ -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()