diff --git a/src/knot/ctl/commands.c b/src/knot/ctl/commands.c index cb27333f09d191677493a99f952efe888da1b5ea..ce8d943b9ad4ebfcec32b0e8e47db1d6fc14a9c3 100644 --- a/src/knot/ctl/commands.c +++ b/src/knot/ctl/commands.c @@ -30,6 +30,7 @@ #include "knot/nameserver/query_module.h" #include "knot/updates/zone-update.h" #include "knot/zone/backup.h" +#include "knot/zone/digest.h" #include "knot/zone/timers.h" #include "knot/zone/zonedb-load.h" #include "knot/zone/zonefile.h" @@ -694,17 +695,26 @@ static int zone_txn_commit(zone_t *zone, _unused_ ctl_args_t *args) // Sign update. conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); bool dnssec_enable = conf_bool(&val); + val = conf_zone_get(conf(), C_ZONEMD_GENERATE, zone->name); + unsigned digest_alg = conf_opt(&val); if (dnssec_enable) { zone_sign_reschedule_t resch = { 0 }; bool full = (zone->control_update->flags & UPDATE_FULL); zone_sign_roll_flags_t rflags = KEY_ROLL_ALLOW_ALL; ret = (full ? knot_dnssec_zone_sign(zone->control_update, conf(), 0, rflags, 0, &resch) : knot_dnssec_sign_update(zone->control_update, conf(), &resch)); - if (ret != KNOT_EOK) { - zone_control_clear(zone); - return ret; - } event_dnssec_reschedule(conf(), zone, &resch, false); + } else if (digest_alg != ZONE_DIGEST_NONE) { + if (zone_update_to(zone->control_update) == NULL) { + ret = zone_update_increment_soa(zone->control_update, conf()); + } + if (ret == KNOT_EOK) { + ret = zone_update_add_digest(zone->control_update, digest_alg, false); + } + } + if (ret != KNOT_EOK) { + zone_control_clear(zone); + return ret; } ret = zone_update_commit(conf(), zone->control_update); diff --git a/src/knot/events/handlers/load.c b/src/knot/events/handlers/load.c index ba69008bcbf6506c61398b0adabb64e9023eeedc..93f10b5df6cca2d5d3242b82422a9a52a0897a04 100644 --- a/src/knot/events/handlers/load.c +++ b/src/knot/events/handlers/load.c @@ -23,6 +23,7 @@ #include "knot/dnssec/zone-events.h" #include "knot/events/handlers.h" #include "knot/events/replan.h" +#include "knot/zone/digest.h" #include "knot/zone/serial.h" #include "knot/zone/zone-diff.h" #include "knot/zone/zone-load.h" @@ -272,6 +273,9 @@ load_end: } } + val = conf_zone_get(conf, C_ZONEMD_GENERATE, zone->name); + unsigned digest_alg = conf_opt(&val); + // Sign zone using DNSSEC if configured. zone_sign_reschedule_t dnssec_refresh = { 0 }; if (dnssec_enable) { @@ -285,6 +289,16 @@ load_end: "'zonefile-load: difference' should be set to avoid malformed " "IXFR after manual zone file update"); } + } else if (digest_alg != ZONE_DIGEST_NONE) { + if (zone_update_to(&up) == NULL || middle_serial == zone->zonefile.serial) { + ret = zone_update_increment_soa(&up, conf); + } + if (ret == KNOT_EOK) { + ret = zone_update_add_digest(&up, digest_alg, false); + } + if (ret != KNOT_EOK) { + goto cleanup; + } } // If the change is only automatically incremented SOA serial, make it no change. diff --git a/src/knot/events/handlers/refresh.c b/src/knot/events/handlers/refresh.c index 88c8daf270efac8719a6198c9448747e11608770..80f01bd0269d4edd6f502e9cf4cfa6e96a87e8e1 100644 --- a/src/knot/events/handlers/refresh.c +++ b/src/knot/events/handlers/refresh.c @@ -30,6 +30,7 @@ #include "knot/query/requestor.h" #include "knot/updates/changesets.h" #include "knot/zone/adjust.h" +#include "knot/zone/digest.h" #include "knot/zone/serial.h" #include "knot/zone/zone.h" #include "knot/zone/zonefile.h" @@ -256,14 +257,20 @@ static int axfr_finalize(struct refresh_data *data) return ret; } + val = conf_zone_get(data->conf, C_ZONEMD_GENERATE, data->zone->name); + unsigned digest_alg = conf_opt(&val); + if (dnssec_enable) { zone_sign_reschedule_t resch = { 0 }; ret = knot_dnssec_zone_sign(&up, data->conf, ZONE_SIGN_KEEP_SERIAL, KEY_ROLL_ALLOW_ALL, 0, &resch); - if (ret != KNOT_EOK) { - zone_update_clear(&up); - return ret; - } event_dnssec_reschedule(data->conf, data->zone, &resch, true); + } else if (digest_alg != ZONE_DIGEST_NONE) { + assert(zone_update_to(&up) != NULL); + ret = zone_update_add_digest(&up, digest_alg, false); + } + if (ret != KNOT_EOK) { + zone_update_clear(&up); + return ret; } ret = zone_update_commit(data->conf, &up); @@ -516,14 +523,20 @@ static int ixfr_finalize(struct refresh_data *data) return ret; } + val = conf_zone_get(data->conf, C_ZONEMD_GENERATE, data->zone->name); + unsigned digest_alg = conf_opt(&val); + if (dnssec_enable) { zone_sign_reschedule_t resch = { 0 }; ret = knot_dnssec_sign_update(&up, data->conf, &resch); - if (ret != KNOT_EOK) { - zone_update_clear(&up); - return ret; - } event_dnssec_reschedule(data->conf, data->zone, &resch, true); + } else if (digest_alg != ZONE_DIGEST_NONE) { + assert(zone_update_to(&up) != NULL); + ret = zone_update_add_digest(&up, digest_alg, false); + } + if (ret != KNOT_EOK) { + zone_update_clear(&up); + return ret; } ret = zone_update_commit(data->conf, &up); diff --git a/src/knot/events/handlers/update.c b/src/knot/events/handlers/update.c index 43e130060f7ca2f2e782307b938c4d362298c698..adad434beba1b80417c8cdf7d0edd9bbad56b359 100644 --- a/src/knot/events/handlers/update.c +++ b/src/knot/events/handlers/update.c @@ -22,6 +22,7 @@ #include "knot/query/capture.h" #include "knot/query/requestor.h" #include "knot/updates/ddns.h" +#include "knot/zone/digest.h" #include "knot/zone/zone.h" #include "libdnssec/random.h" #include "libknot/libknot.h" @@ -157,15 +158,24 @@ static int process_normal(conf_t *conf, zone_t *zone, list_t *requests) // Sign update. conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, zone->name); bool dnssec_enable = conf_bool(&val); + val = conf_zone_get(conf, C_ZONEMD_GENERATE, zone->name); + unsigned digest_alg = conf_opt(&val); if (dnssec_enable) { zone_sign_reschedule_t resch = { 0 }; ret = knot_dnssec_sign_update(&up, conf, &resch); - if (ret != KNOT_EOK) { - zone_update_clear(&up); - set_rcodes(requests, KNOT_RCODE_SERVFAIL); - return ret; - } event_dnssec_reschedule(conf, zone, &resch, false); // false since we handle NOTIFY after processing ddns queue + } else if (digest_alg != ZONE_DIGEST_NONE) { + if (zone_update_to(&up) == NULL) { + ret = zone_update_increment_soa(&up, conf); + } + if (ret == KNOT_EOK) { + ret = zone_update_add_digest(&up, digest_alg, false); + } + } + if (ret != KNOT_EOK) { + zone_update_clear(&up); + set_rcodes(requests, KNOT_RCODE_SERVFAIL); + return ret; } // Apply changes. diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index 0529dec5fa5a86d045c953e5a83c9be96f57cc31..25eb2579d8545484a97d2a765e4667bdb930d2ff 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-update.c @@ -882,24 +882,6 @@ int zone_update_commit(conf_t *conf, zone_update_t *update) conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name); bool dnssec = conf_bool(&val); - val = conf_zone_get(conf, C_ZONEMD_GENERATE, update->zone->name); - unsigned digest_alg = conf_opt(&val); - bool do_digest = (digest_alg != ZONE_DIGEST_NONE && !dnssec); // in case of DNSSEC, digest is part of signing routine - if (do_digest && !(update->flags & UPDATE_FULL) && zone_update_to(update) == NULL) { - // cold start, decide if (digest & bump SOA) or NOOP - // yes, computing hash twice, but in rare situation: cold start & exists & invalid - if (zone_contents_digest_exists(update->new_cont, digest_alg, false)) { - do_digest = false; - } else { - ret = zone_update_increment_soa(update, conf); - } - } - if (do_digest && ret == KNOT_EOK) { - ret = zone_update_add_digest(update, digest_alg, false); - } - if (ret != KNOT_EOK) { - return ret; - } conf_val_t thr = conf_zone_get(conf, C_ADJUST_THR, update->zone->name); if ((update->flags & (UPDATE_HYBRID | UPDATE_FULL))) { diff --git a/tests-extra/tests/zone/zonemd_flush/test.py b/tests-extra/tests/zone/zonemd_flush/test.py index 7af8d7ef4bb2b599dcee67c54c07636191e2675d..f1a1e3a5602bf85150188dfdfb9ac35d800eec00 100644 --- a/tests-extra/tests/zone/zonemd_flush/test.py +++ b/tests-extra/tests/zone/zonemd_flush/test.py @@ -2,9 +2,13 @@ '''Flushing the zone after ZONEMD generation.''' +import random + from dnstest.test import Test from dnstest.utils import * +t = Test() + def has_zonemd(server, zone, alg): zfn = server.zones[zone.name].zfile.path with open(zfn) as zf: @@ -15,31 +19,82 @@ def has_zonemd(server, zone, alg): return False def check_zonemd(server, zone, alg): + t.sleep(2) for z in zone: if not has_zonemd(server, z, alg): set_err("NO ZONEMD in %s" % z.name) -t = Test() +def del_zonemd1(server, zone): + zf = server.zones[zone.name].zfile + zf.update_soa() + + with open(zf.path, "r+") as f: + lines = f.readlines() + f.seek(0) + for line in lines: + if "ZONEMD" not in line: + f.write(line) + f.truncate() + +def del_zonemd(server, zone): + for z in zone: + del_zonemd1(server, z) + +# NOTE parameter "serials" is updated +def check_serial_incr(server, zones, serials, expect_incr, msg): + new_serials = server.zones_wait(zones, serials) + for z in zones: + if new_serials[z.name] != serials[z.name] + expect_incr: + err_str = "%s: zone %s serial incremented by %d" % (msg, z.name, new_serials[z.name] - serial[z.name]); + detail_log(err_str) + set_err(err_str) + serials[z.name] = new_serials[z.name] master = t.server("knot") +slave = t.server("knot") zone = t.zone_rnd(2, dnssec=False, records=10) -t.link(zone, master) +t.link(zone, master, slave, ixfr=random.choice([True, False])) master.zonefile_sync = 0 master.zonemd_generate = "zonemd-sha384" +slave.zonemd_verify = True t.start() -serial = master.zones_wait(zone) -t.sleep(4) +serial = slave.zones_wait(zone) check_zonemd(master, zone, "1") master.zonemd_generate = "zonemd-sha512" master.gen_confile() master.reload() -master.zones_wait(zone, serial) -t.sleep(4) +check_serial_incr(slave, zone, serial, 1, "alg change") +check_zonemd(master, zone, "2") + +del_zonemd(master, zone) +master.ctl("zone-reload") +check_serial_incr(slave, zone, serial, 2, "ZONEMD removed") +check_zonemd(master, zone, "2") + +for z in zone: + master.random_ddns(z, allow_empty=False) +check_serial_incr(slave, zone, serial, 1, "DDNS") + +for z in zone: + # BUMP SOA serial by 3 thru DDNS + resp = master.dig(z.name, "SOA") + soa = resp.resp.answer[0].to_rdataset()[0].to_text() + fields = soa.split() + fields[2] = str(int(fields[2]) + 3) + up = master.update(z) + up.add(z.name, 3600, "SOA", ' '.join(fields)) + up.send("NOERROR") +check_serial_incr(slave, zone, serial, 3, "SOA DDNS") + +for z in zone: + master.zones[z.name].zfile.update_rnd() +master.ctl("zone-reload") +check_serial_incr(slave, zone, serial, 2, "ZF reload") check_zonemd(master, zone, "2") t.end()