Commit 54f688c1 authored by Libor Peltan's avatar Libor Peltan Committed by Daniel Salzman

dnssec on slave: saving 'last signed' SOA serial...

...to avoid signing the same serial twice differently
forcing increment of serial if the same master serial signed twice
parent 1e4e21f0
......@@ -36,6 +36,7 @@ typedef enum {
KASPDBKEY_NSEC3SALT = 0x3,
KASPDBKEY_NSEC3TIME = 0x4,
KASPDBKEY_MASTERSERIAL = 0x5,
KASPDBKEY_LASTSIGNEDSERIAL = 0x6,
} keyclass_t;
static const knot_db_api_t *db_api = NULL;
......@@ -566,39 +567,38 @@ int kasp_db_load_nsec3salt(kasp_db_t *db, const knot_dname_t *zone_name,
return ret;
}
int kasp_db_store_master_serial(kasp_db_t *db, const knot_dname_t *zone_name,
uint32_t master_serial)
int kasp_db_store_serial(kasp_db_t *db, const knot_dname_t *zone_name,
kaspdb_serial_t serial_type, uint32_t serial)
{
if (db == NULL || db->keys_db == NULL || zone_name == NULL) {
return KNOT_EINVAL;
}
uint32_t ms_be = htobe32(master_serial);
uint32_t be = htobe32(serial);
with_txn(KEYS_RW, NULL);
knot_db_val_t key = make_key(KASPDBKEY_MASTERSERIAL, zone_name, NULL);
knot_db_val_t val = { .len = sizeof(uint32_t), .data = &ms_be };
knot_db_val_t key = make_key((keyclass_t)serial_type, zone_name, NULL);
knot_db_val_t val = { .len = sizeof(uint32_t), .data = &be };
ret = db_api->insert(txn, &key, &val, 0);
free_key(&key);
with_txn_end(NULL);
return ret;
}
int kasp_db_load_master_serial(kasp_db_t *db, const knot_dname_t *zone_name,
uint32_t *master_serial)
int kasp_db_load_serial(kasp_db_t *db, const knot_dname_t *zone_name,
kaspdb_serial_t serial_type, uint32_t *serial)
{
if (db == NULL || db->keys_db == NULL ||
zone_name == NULL || master_serial == NULL) {
if (db == NULL || db->keys_db == NULL || zone_name == NULL || serial == NULL) {
return KNOT_EINVAL;
}
with_txn(KEYS_RO, NULL);
knot_db_val_t key = make_key(KASPDBKEY_MASTERSERIAL, zone_name, NULL), val = { 0 };
knot_db_val_t key = make_key((keyclass_t)serial_type, zone_name, NULL), val = { 0 };
ret = db_api->find(txn, &key, &val, 0);
free_key(&key);
if (ret == KNOT_EOK) {
if (val.len == sizeof(uint32_t)) {
*master_serial = be32toh(*(uint32_t *)val.data);
}
else {
*serial = be32toh(*(uint32_t *)val.data);
} else {
ret = KNOT_EMALF;
}
}
......
......@@ -26,6 +26,11 @@
typedef struct kasp_db kasp_db_t;
typedef enum { // the enum values MUST match those from keyclass_t !!
KASPDB_SERIAL_MASTER = 0x5,
KASPDB_SERIAL_LASTSIGNED = 0x6,
} kaspdb_serial_t;
/*!
* \brief Returns kasp_db_t singleton, to be used for signing all zones.
*
......@@ -162,28 +167,30 @@ int kasp_db_load_nsec3salt(kasp_db_t *db, const knot_dname_t *zone_name,
dnssec_binary_t *nsec3salt, knot_time_t *salt_created);
/*!
* \brief Store SOA serial number of master.
* \brief Store SOA serial number of master or last signed serial.
*
* \param db KASP db
* \param zone_name zone name
* \param master_serial new master's serial
* \param serial_type kind of serial to be stored
* \param serial new serial to be stored
*
* \return KNOT_E*
*/
int kasp_db_store_master_serial(kasp_db_t *db, const knot_dname_t *zone_name,
uint32_t master_serial);
int kasp_db_store_serial(kasp_db_t *db, const knot_dname_t *zone_name,
kaspdb_serial_t serial_type, uint32_t serial);
/*!
* \brief Load saved SOA serial number of master.
* \brief Load saved SOA serial number of master or last signed serial.
*
* \param db KASP db
* \param zone_name zone name
* \param master_serial output if KNOT_EOK: master's serial
* \param serial_type kind of serial to be loaded
* \param master_serial output if KNOT_EOK: desired serial number
*
* \return KNOT_E* (KNOT_ENOENT if not stored before)
*/
int kasp_db_load_master_serial(kasp_db_t *db, const knot_dname_t *zone_name,
uint32_t *master_serial);
int kasp_db_load_serial(kasp_db_t *db, const knot_dname_t *zone_name,
kaspdb_serial_t serial_type, uint32_t *serial);
/*!
* \brief For given policy name, obtain last generated key.
......
......@@ -223,9 +223,10 @@ static int axfr_finalize(struct refresh_data *data)
uint32_t master_serial = zone_contents_serial(new_zone);
uint32_t local_serial = zone_contents_serial(data->zone->contents);
bool have_lastsigned = zone_get_lastsigned_serial(data->zone, &local_serial);
uint32_t old_serial = local_serial;
bool bootstrap = (data->soa == NULL);
if (!bootstrap && serial_compare(master_serial, local_serial) <= 0) {
if ((!bootstrap || have_lastsigned) && serial_compare(master_serial, local_serial) <= 0) {
conf_val_t val = conf_zone_get(data->conf, C_SERIAL_POLICY, data->zone->name);
local_serial = serial_next(local_serial, conf_opt(&val));
zone_contents_set_soa_serial(new_zone, local_serial);
......@@ -257,6 +258,9 @@ static int axfr_finalize(struct refresh_data *data)
if (dnssec_enable) {
ret = zone_set_master_serial(data->zone, master_serial);
if (ret == KNOT_EOK) {
ret = zone_set_lastsigned_serial(data->zone, zone_contents_serial(new_zone));
}
if (ret != KNOT_EOK) {
log_zone_warning(data->zone->name,
"unable to save master serial, future transfers might be broken");
......@@ -406,11 +410,16 @@ static int ixfr_finalize(struct refresh_data *data)
uint32_t master_serial;
(void)zone_get_master_serial(data->zone, &master_serial);
uint32_t local_serial = zone_contents_serial(data->zone->contents);
uint32_t lastsigned_serial = local_serial;
bool have_lastsigned = zone_get_lastsigned_serial(data->zone, &lastsigned_serial);
uint32_t old_serial = local_serial;
changeset_t *chs = NULL;
WALK_LIST(chs, data->ixfr.changesets) {
master_serial = knot_soa_serial(&chs->soa_to->rrs);
knot_soa_serial_set(&chs->soa_from->rrs, local_serial);
if (have_lastsigned && serial_compare(lastsigned_serial, local_serial) >= 0) {
local_serial = lastsigned_serial;
}
if (serial_compare(master_serial, local_serial) <= 0) {
conf_val_t val = conf_zone_get(data->conf, C_SERIAL_POLICY, data->zone->name);
local_serial = serial_next(local_serial, conf_opt(&val));
......@@ -461,6 +470,10 @@ static int ixfr_finalize(struct refresh_data *data)
if (ret == KNOT_EOK) {
if (dnssec_enable && !EMPTY_LIST(data->ixfr.changesets)) {
ret = zone_set_master_serial(data->zone, master_serial);
if (ret == KNOT_EOK) {
ret = zone_set_lastsigned_serial(data->zone,
zone_contents_serial(up.zone->contents));
}
if (ret != KNOT_EOK) {
log_zone_warning(data->zone->name,
"unable to save master serial, future transfers might be broken");
......
......@@ -640,7 +640,7 @@ int zone_set_master_serial(zone_t *zone, uint32_t serial)
{
int ret = kasp_db_open(*kaspdb());
if (ret == KNOT_EOK) {
ret = kasp_db_store_master_serial(*kaspdb(), zone->name, serial);
ret = kasp_db_store_serial(*kaspdb(), zone->name, KASPDB_SERIAL_MASTER, serial);
}
return ret;
}
......@@ -655,10 +655,31 @@ int zone_get_master_serial(zone_t *zone, uint32_t *serial)
if (ret != KNOT_EOK) {
return ret;
}
ret = kasp_db_load_master_serial(*kaspdb(), zone->name, serial);
ret = kasp_db_load_serial(*kaspdb(), zone->name, KASPDB_SERIAL_MASTER, serial);
if (ret == KNOT_ENOENT) {
*serial = zone_contents_serial(zone->contents);
return KNOT_EOK;
}
return ret;
}
int zone_set_lastsigned_serial(zone_t *zone, uint32_t serial)
{
int ret = kasp_db_open(*kaspdb());
if (ret == KNOT_EOK) {
ret = kasp_db_store_serial(*kaspdb(), zone->name, KASPDB_SERIAL_LASTSIGNED, serial);
}
return ret;
}
bool zone_get_lastsigned_serial(zone_t *zone, uint32_t *serial)
{
if (!kasp_db_exists(*kaspdb())) {
return false;
}
int ret = kasp_db_open(*kaspdb());
if (ret == KNOT_EOK) {
ret = kasp_db_load_serial(*kaspdb(), zone->name, KASPDB_SERIAL_LASTSIGNED, serial);
}
return (ret == KNOT_EOK);
}
......@@ -187,4 +187,8 @@ int zone_set_master_serial(zone_t *zone, uint32_t serial);
int zone_get_master_serial(zone_t *zone, uint32_t *serial);
int zone_set_lastsigned_serial(zone_t *zone, uint32_t serial);
bool zone_get_lastsigned_serial(zone_t *zone, uint32_t *serial);
/*! @} */
#!/usr/bin/env python3
'''Test for signing on a slave Knot w/o zonefile and journal'''
import shutil
from dnstest.utils import *
from dnstest.test import Test
t = Test()
master = t.server("knot")
slave = t.server("knot")
slave.zonefile_sync = "-1"
zone = t.zone_rnd(1, dnssec=False)
t.link(zone, master, slave)
slave.dnssec(zone).enable = True
t.start()
t.sleep(5)
soa1 = slave.dig(zone[0].name, "SOA", dnssec=True, bufsize=4096)
soa1serial = str(soa1.resp.answer[0].to_rdataset()).split()[5]
detail_log("soa1serial "+soa1serial)
soa1rrsig_expire = str(soa1.resp.answer[1].to_rdataset()).split()[7]
detail_log("soa1rrsig_exp "+soa1rrsig_expire)
slave.stop()
try:
shutil.rmtree(os.path.join(slave.dir, "timers"))
shutil.rmtree(os.path.join(slave.dir, "journal"))
except:
pass
slave.start()
t.sleep(5)
soa2 = slave.dig(zone[0].name, "SOA", dnssec=True, bufsize=4096)
soa2serial = str(soa2.resp.answer[0].to_rdataset()).split()[5]
detail_log("soa2serial "+soa2serial)
soa2rrsig_expire = str(soa2.resp.answer[1].to_rdataset()).split()[7]
detail_log("soa2rrsig_exp "+soa2rrsig_expire)
if soa2rrsig_expire == soa1rrsig_expire:
set_err("Zone not resigned, test error")
if soa2serial == soa1serial:
set_err("Serial not incremented on AXFR")
up = master.update(zone)
up.add("hahahahahah", 3600, "A", "1.2.3.4")
up.send()
t.sleep(5)
msoa3 = master.dig(zone[0].name, "SOA", dnssec=False)
msoa3serial = str(msoa3.resp.answer[0].to_rdataset()).split()[5]
detail_log("msoa3serial "+msoa3serial)
soa4 = slave.dig(zone[0].name, "SOA", dnssec=True, bufsize=4096)
soa4serial = str(soa4.resp.answer[0].to_rdataset()).split()[5]
detail_log("soa4serial "+soa4serial)
if msoa3serial != soa2serial:
set_err("Serial incremented unexpectedly, test error")
if soa4serial == soa2serial:
set_err("Serial not incremented on IXFR")
t.end()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment