mirror of
https://gitlab.nic.cz/knot/knot-dns.git
synced 2026-05-28 04:02:31 -04:00
external validation: implemented basic functionality
This commit is contained in:
parent
6264449184
commit
8ccc39d8dc
17 changed files with 243 additions and 1 deletions
|
|
@ -454,6 +454,11 @@ static const yp_item_t desc_policy[] = {
|
|||
{ NULL }
|
||||
};
|
||||
|
||||
static const yp_item_t desc_external[] = {
|
||||
{ C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ZONE_ITEMS(FLAGS) \
|
||||
{ C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR }, FLAGS }, \
|
||||
{ C_FILE, YP_TSTR, YP_VNONE, FLAGS }, \
|
||||
|
|
@ -479,6 +484,7 @@ static const yp_item_t desc_policy[] = {
|
|||
{ C_IXFR_FROM_AXFR, YP_TBOOL, YP_VNONE }, \
|
||||
{ C_ZONE_MAX_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, SSIZE_MAX, YP_SSIZE }, FLAGS }, \
|
||||
{ C_ADJUST_THR, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } }, \
|
||||
{ C_EXTERNAL_VLDT, YP_TREF, YP_VREF = { C_EXTERNAL }, FLAGS, { check_ref_dflt } }, \
|
||||
{ C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE, FLAGS }, \
|
||||
{ C_DNSSEC_VALIDATION, YP_TBOOL, YP_VNONE, FLAGS }, \
|
||||
{ C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \
|
||||
|
|
@ -535,6 +541,7 @@ const yp_item_t conf_schema[] = {
|
|||
{ C_SBM, YP_TGRP, YP_VGRP = { desc_submission }, YP_FMULTI },
|
||||
{ C_DNSKEY_SYNC, YP_TGRP, YP_VGRP = { desc_dnskey_sync }, YP_FMULTI, { check_dnskey_sync } },
|
||||
{ C_POLICY, YP_TGRP, YP_VGRP = { desc_policy }, YP_FMULTI, { check_policy } },
|
||||
{ C_EXTERNAL, YP_TGRP, YP_VGRP = { desc_external }, YP_FMULTI, { check_external } },
|
||||
{ C_TPL, YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI, { check_template } },
|
||||
{ C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI | CONF_IO_FZONE, { check_zone } },
|
||||
{ C_INCL, YP_TSTR, YP_VNONE, CONF_IO_FDIFF_ZONES | CONF_IO_FRLD_ALL, { include_file } },
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@
|
|||
#define C_ECS "\x12""edns-client-subnet"
|
||||
#define C_EXPIRE_MAX_INTERVAL "\x13""expire-max-interval"
|
||||
#define C_EXPIRE_MIN_INTERVAL "\x13""expire-min-interval"
|
||||
#define C_EXTERNAL "\x08""external"
|
||||
#define C_EXTERNAL_VLDT "\x13""external-validation"
|
||||
#define C_FILE "\x04""file"
|
||||
#define C_GLOBAL_MODULE "\x0D""global-module"
|
||||
#define C_ID "\x02""id"
|
||||
|
|
|
|||
|
|
@ -851,6 +851,12 @@ int check_policy(
|
|||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
int check_external(
|
||||
knotd_conf_check_args_t *args)
|
||||
{
|
||||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
int check_key(
|
||||
knotd_conf_check_args_t *args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ int check_policy(
|
|||
knotd_conf_check_args_t *args
|
||||
);
|
||||
|
||||
int check_external(
|
||||
knotd_conf_check_args_t *args
|
||||
);
|
||||
|
||||
int check_key(
|
||||
knotd_conf_check_args_t *args
|
||||
);
|
||||
|
|
|
|||
|
|
@ -933,6 +933,12 @@ static int zone_txn_commit_l(zone_t *zone, _unused_ ctl_args_t *args)
|
|||
return KNOT_TXN_ENOTEXISTS;
|
||||
}
|
||||
|
||||
if (zone->control_update->flags & UPDATE_WFEV) {
|
||||
zone->control_update->flags |= UPDATE_EVOK;
|
||||
knot_sem_post(&zone->control_update->external);
|
||||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
int ret = zone_update_semcheck(conf(), zone->control_update);
|
||||
if (ret != KNOT_EOK) {
|
||||
return ret; // Recoverable error.
|
||||
|
|
@ -1005,6 +1011,12 @@ static int zone_txn_abort(zone_t *zone, _unused_ ctl_args_t *args)
|
|||
return KNOT_TXN_ENOTEXISTS;
|
||||
}
|
||||
|
||||
if (zone->control_update->flags & UPDATE_WFEV) {
|
||||
knot_sem_post(&zone->control_update->external);
|
||||
pthread_mutex_unlock(&zone->cu_lock);
|
||||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
zone_control_clear(zone);
|
||||
|
||||
pthread_mutex_unlock(&zone->cu_lock);
|
||||
|
|
@ -1953,7 +1965,7 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd)
|
|||
|
||||
static void check_zone_txn(zone_t *zone, const knot_dname_t **exists)
|
||||
{
|
||||
if (zone->control_update != NULL) {
|
||||
if (zone->control_update != NULL && !(zone->control_update->flags & UPDATE_WFEV)) {
|
||||
*exists = zone->name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ int event_expire(conf_t *conf, zone_t *zone)
|
|||
synchronize_rcu();
|
||||
|
||||
pthread_mutex_lock(&zone->cu_lock);
|
||||
assert(zone->control_update == NULL || !(zone->control_update->flags & UPDATE_WFEV));
|
||||
zone_control_clear(zone);
|
||||
pthread_mutex_unlock(&zone->cu_lock);
|
||||
|
||||
|
|
|
|||
|
|
@ -667,6 +667,10 @@ static int ixfr_finalize(struct refresh_data *data)
|
|||
ret = zone_update_commit(data->conf, &up);
|
||||
if (ret != KNOT_EOK) {
|
||||
zone_update_clear(&up);
|
||||
if (ret == KNOT_EEXTERNAL) {
|
||||
data->fallback_axfr = false;
|
||||
data->fallback->remote = false;
|
||||
}
|
||||
IXFRIN_LOG(LOG_WARNING, data,
|
||||
"failed to store changes (%s)", knot_strerror(ret));
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1039,6 +1039,13 @@ int server_start(server_t *server, bool answering)
|
|||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
static void zonedb_shutdown(server_t *server)
|
||||
{
|
||||
if (server->zone_db != NULL) {
|
||||
knot_zonedb_foreach(server->zone_db, zone_shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
void server_wait(server_t *server)
|
||||
{
|
||||
if (server == NULL) {
|
||||
|
|
@ -1404,6 +1411,8 @@ void server_stop(server_t *server)
|
|||
|
||||
/* Stop scheduler. */
|
||||
evsched_stop(&server->sched);
|
||||
/* Shut down zones. */
|
||||
zonedb_shutdown(server);
|
||||
/* Interrupt background workers. */
|
||||
worker_pool_stop(server->workers);
|
||||
|
||||
|
|
@ -1621,6 +1630,7 @@ void server_update_zones(conf_t *conf, server_t *server, reload_t mode)
|
|||
/* Suspend adding events to worker pool queue, wait for queued events. */
|
||||
log_debug("suspending zone events");
|
||||
evsched_pause(&server->sched);
|
||||
zonedb_shutdown(server);
|
||||
worker_pool_wait(server->workers);
|
||||
log_debug("suspended zone events");
|
||||
|
||||
|
|
|
|||
|
|
@ -915,6 +915,42 @@ int zone_update_verify_digest(conf_t *conf, zone_update_t *update)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int zone_update_external(conf_t *conf, zone_update_t *update)
|
||||
{
|
||||
(void)conf;
|
||||
pthread_mutex_lock(&update->zone->cu_lock);
|
||||
|
||||
if (update->zone->control_update != NULL) {
|
||||
assert(update->zone->control_update == update);
|
||||
assert(!(update->flags & UPDATE_WFEV));
|
||||
pthread_mutex_unlock(&update->zone->cu_lock);
|
||||
return KNOT_EOK; // real control update never waits for external validation
|
||||
}
|
||||
|
||||
if (zone_get_flag(update->zone, ZONE_SHUT_DOWN, false)) {
|
||||
pthread_mutex_unlock(&update->zone->cu_lock);
|
||||
return KNOT_EEXTERNAL;
|
||||
}
|
||||
|
||||
update->zone->control_update = update;
|
||||
update->flags |= UPDATE_WFEV;
|
||||
knot_sem_init(&update->external, 0);
|
||||
pthread_mutex_unlock(&update->zone->cu_lock);
|
||||
|
||||
log_zone_notice(update->zone->name, "waiting for external validation");
|
||||
|
||||
knot_sem_wait(&update->external);
|
||||
|
||||
pthread_mutex_lock(&update->zone->cu_lock);
|
||||
update->zone->control_update = NULL;
|
||||
pthread_mutex_unlock(&update->zone->cu_lock);
|
||||
|
||||
knot_sem_post(&update->external);
|
||||
knot_sem_destroy(&update->external);
|
||||
|
||||
return (update->flags & UPDATE_EVOK) ? KNOT_EOK : KNOT_EEXTERNAL;
|
||||
}
|
||||
|
||||
int zone_update_commit(conf_t *conf, zone_update_t *update)
|
||||
{
|
||||
if (conf == NULL || update == NULL) {
|
||||
|
|
@ -977,6 +1013,15 @@ int zone_update_commit(conf_t *conf, zone_update_t *update)
|
|||
}
|
||||
}
|
||||
|
||||
val = conf_zone_get(conf, C_EXTERNAL_VLDT, update->zone->name);
|
||||
if (val.code == KNOT_EOK) {
|
||||
ret = zone_update_external(conf, update, &val);
|
||||
if (ret != KNOT_EOK) {
|
||||
discard_adds_tree(update);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = update_catalog(conf, update);
|
||||
if (ret != KNOT_EOK) {
|
||||
log_zone_error(update->zone->name, "failed to process catalog zone (%s)", knot_strerror(ret));
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ typedef struct zone_update {
|
|||
changeset_t extra_ch; /*!< Extra changeset to store just diff btwn zonefile and result. */
|
||||
apply_ctx_t *a_ctx; /*!< Context for applying changesets. */
|
||||
uint32_t flags; /*!< Zone update flags. */
|
||||
knot_sem_t external; /*!< Lock for external validation. */
|
||||
dnssec_validation_hint_t validation_hint;
|
||||
} zone_update_t;
|
||||
|
||||
|
|
@ -50,6 +51,8 @@ typedef enum {
|
|||
UPDATE_CHANGED_NSEC = 1 << 7, /*!< This incremental update affects NSEC or NSEC3 nodes in zone. */
|
||||
UPDATE_NO_CHSET = 1 << 8, /*!< Avoid using changeset and serialize to journal from diff of bi-nodes. */
|
||||
UPDATE_SIGNED_FULL = 1 << 9, /*!< Full (non-incremental) zone sign took place during this update. */
|
||||
UPDATE_WFEV = 1 << 10, /*!< Update waiting for external validation. */
|
||||
UPDATE_EVOK = 1 << 11, /*!< External validation accepted the update. */
|
||||
} zone_update_flags_t;
|
||||
|
||||
/*!
|
||||
|
|
@ -268,6 +271,18 @@ int zone_update_semcheck(conf_t *conf, zone_update_t *update);
|
|||
*/
|
||||
int zone_update_verify_digest(conf_t *conf, zone_update_t *update);
|
||||
|
||||
/*!
|
||||
* \brief Wait for external validation.
|
||||
*
|
||||
* \param conf Configuration.
|
||||
* \param update Zone update.
|
||||
*
|
||||
* \retval KNOT_EEXTERNAL External validation failed.
|
||||
* \retval KNOT_EOK External validation succeeded.
|
||||
* \return KNOT_E*
|
||||
*/
|
||||
int zone_update_external(conf_t *conf, zone_update_t *update);
|
||||
|
||||
/*!
|
||||
* \brief Commits all changes to the zone, signs it, saves changes to journal.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -200,6 +200,16 @@ void zone_control_clear(zone_t *zone)
|
|||
zone->control_update = NULL;
|
||||
}
|
||||
|
||||
void zone_shutdown(zone_t *zone)
|
||||
{
|
||||
pthread_mutex_lock(&zone->cu_lock);
|
||||
if (zone->control_update != NULL && (zone->control_update->flags & UPDATE_WFEV)) {
|
||||
knot_sem_post(&zone->control_update->external);
|
||||
}
|
||||
zone_set_flag(zone, ZONE_SHUT_DOWN);
|
||||
pthread_mutex_unlock(&zone->cu_lock);
|
||||
}
|
||||
|
||||
void zone_free(zone_t **zone_ptr)
|
||||
{
|
||||
if (zone_ptr == NULL || *zone_ptr == NULL) {
|
||||
|
|
@ -226,6 +236,7 @@ void zone_free(zone_t **zone_ptr)
|
|||
knot_sem_destroy(&zone->cow_lock);
|
||||
|
||||
/* Control update. */
|
||||
assert(zone->control_update == NULL || !(zone->control_update->flags & UPDATE_WFEV));
|
||||
zone_control_clear(zone);
|
||||
|
||||
free(zone->catalog_gen);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ typedef enum {
|
|||
ZONE_XFR_FROZEN = 1 << 7, /*!< Outgoing AXFR/IXFR temporarily disabled. */
|
||||
ZONE_USER_FLUSH = 1 << 8, /*!< User-triggered flush. */
|
||||
ZONE_LAST_SIGN_OK = 1 << 9, /*!< Last full-sign event finished OK. */
|
||||
ZONE_SHUT_DOWN = 1 << 10, /*!< Zone events are shutting down. */
|
||||
} zone_flag_t;
|
||||
|
||||
/*!
|
||||
|
|
@ -148,6 +149,13 @@ typedef struct zone
|
|||
*/
|
||||
zone_t* zone_new(const knot_dname_t *name);
|
||||
|
||||
/*!
|
||||
* \brief Declare that zone is shutting down.
|
||||
*
|
||||
* \param zone Zone to be shut down.
|
||||
*/
|
||||
void zone_shutdown(zone_t *zone);
|
||||
|
||||
/*!
|
||||
* \brief Deallocates the zone structure.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -533,6 +533,8 @@ static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server, reload_t mod
|
|||
it = knot_zonedb_iter_begin(db_new);
|
||||
while (!knot_zonedb_iter_finished(it)) {
|
||||
zone_t *z = knot_zonedb_iter_val(it);
|
||||
zone_unset_flag(z, ZONE_SHUT_DOWN);
|
||||
|
||||
conf_val_t val = conf_zone_get(conf, C_REVERSE_GEN, z->name);
|
||||
while (val.code == KNOT_EOK) {
|
||||
const knot_dname_t *forw_name = conf_dname(&val);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ enum knot_error {
|
|||
KNOT_EBACKUPDATA,
|
||||
KNOT_ECPUCOMPAT,
|
||||
KNOT_EMODINVAL,
|
||||
KNOT_EEXTERNAL,
|
||||
|
||||
KNOT_GENERAL_ERROR = -900,
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ static const struct error errors[] = {
|
|||
{ KNOT_EBACKUPDATA, "requested data not in backup" },
|
||||
{ KNOT_ECPUCOMPAT, "incompatible CPU architecture" },
|
||||
{ KNOT_EMODINVAL, "invalid module" },
|
||||
{ KNOT_EEXTERNAL, "external validation failed" },
|
||||
|
||||
{ KNOT_GENERAL_ERROR, "unknown general error" },
|
||||
|
||||
|
|
|
|||
95
tests-extra/tests/zone/external_vldt/test.py
Normal file
95
tests-extra/tests/zone/external_vldt/test.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Test of external zone validation.
|
||||
"""
|
||||
|
||||
from dnstest.utils import *
|
||||
from dnstest.test import Test
|
||||
import random
|
||||
|
||||
t = Test()
|
||||
|
||||
master = t.server("knot")
|
||||
slave = t.server("knot")
|
||||
zone = t.zone_rnd(1, records=40)
|
||||
t.link(zone, master, slave)
|
||||
|
||||
def log_count_expect(server, pattern, expct):
|
||||
fnd = server.log_search_count(pattern)
|
||||
if fnd != expct:
|
||||
detail_log("LOG SEARCH COUNT '%s' found %d expected %d" % (pattern, fnd, expct))
|
||||
set_err("LOG SEARCH COUNT %d != %d" % (fnd, expct))
|
||||
|
||||
ZONE = zone[0].name
|
||||
LOG = "for external validation"
|
||||
|
||||
slave.async_start = True
|
||||
slave.zones[ZONE].external = True # TODO this will be a list or dict once 'external' secation has any fields
|
||||
|
||||
master.dnssec(zone[0]).enable = random.choice([False, True])
|
||||
|
||||
t.start()
|
||||
serial = master.zone_wait(zone)
|
||||
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, LOG, 1)
|
||||
resp = slave.dig(ZONE, "SOA")
|
||||
resp.check(rcode="SERVFAIL")
|
||||
resp.check_count(0, "SOA")
|
||||
|
||||
slave.ctl("zone-commit " + ZONE)
|
||||
t.sleep(2)
|
||||
resp = slave.dig(ZONE, "SOA")
|
||||
resp.check_soa_serial(serial)
|
||||
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
serial = master.zone_wait(zone, serial)
|
||||
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, LOG, 2)
|
||||
slave.ctl("zone-abort " + ZONE)
|
||||
t.sleep(2)
|
||||
resp = slave.dig(ZONE, "SOA")
|
||||
resp.check_soa_serial(serial - 1)
|
||||
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
serial = master.zone_wait(zone, serial)
|
||||
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, LOG, 3)
|
||||
slave.ctl("zone-commit " + ZONE)
|
||||
t.sleep(2)
|
||||
resp = slave.dig(ZONE, "SOA")
|
||||
resp.check_soa_serial(serial)
|
||||
|
||||
slave.ctl("zone-freeze " + ZONE)
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
serial = master.zone_wait(zone, serial)
|
||||
|
||||
slave.zonemd_generate = "zonemd-sha512"
|
||||
slave.gen_confile()
|
||||
slave.ctl("zone-thaw " + ZONE)
|
||||
t.sleep(1)
|
||||
slave.reload()
|
||||
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
serial = master.zone_wait(zone, serial)
|
||||
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, LOG, 5)
|
||||
slave.ctl("zone-commit " + ZONE)
|
||||
t.sleep(2)
|
||||
resp = slave.dig(ZONE, "SOA")
|
||||
resp.check_soa_serial(serial)
|
||||
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
serial = master.zone_wait(zone, serial)
|
||||
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, LOG, 6)
|
||||
slave.stop()
|
||||
t.sleep(2)
|
||||
log_count_expect(slave, "shutting down", 1)
|
||||
|
||||
t.end()
|
||||
|
|
@ -102,6 +102,7 @@ class Zone(object):
|
|||
self.journal_content = journal_content # journal contents
|
||||
self.modules = []
|
||||
self.reverse_from = None
|
||||
self.external = None
|
||||
self.dnssec = ZoneDnssec()
|
||||
self.catalog_role = ZoneCatalogRole.NONE
|
||||
self.catalog_gen_name = None # Generated catalog name for this member
|
||||
|
|
@ -216,6 +217,7 @@ class Server(object):
|
|||
self.zone_size_limit = None
|
||||
self.serial_policy = None
|
||||
self.auto_acl = None
|
||||
self.async_start = None
|
||||
self.provide_ixfr = None
|
||||
self.master_pin_tol = None
|
||||
self.quic_log = None
|
||||
|
|
@ -1543,6 +1545,7 @@ class Knot(Server):
|
|||
self._str(s, "remote-pool-limit", str(random.randint(0,6)))
|
||||
self._str(s, "remote-retry-delay", str(random.choice([0, 1, 5])))
|
||||
self._bool(s, "automatic-acl", self.auto_acl)
|
||||
self._bool(s, "async-start", self.async_start)
|
||||
if self.cert_key_file:
|
||||
s.item_str("key-file", self.cert_key_file[0])
|
||||
s.item_str("cert-file", self.cert_key_file[1])
|
||||
|
|
@ -1787,6 +1790,18 @@ class Knot(Server):
|
|||
if have_dnskeysync:
|
||||
s.end()
|
||||
|
||||
have_external = False
|
||||
for zone in sorted(self.zones):
|
||||
z = self.zones[zone]
|
||||
if not z.external:
|
||||
continue
|
||||
if not have_external:
|
||||
s.begin("external")
|
||||
have_external = True
|
||||
s.id_item("id", z.name)
|
||||
if have_external:
|
||||
s.end()
|
||||
|
||||
have_keystore = False
|
||||
for zone in sorted(self.zones):
|
||||
z = self.zones[zone]
|
||||
|
|
@ -1958,6 +1973,9 @@ class Knot(Server):
|
|||
self._str(s, "expire-min-interval", z.expire_min)
|
||||
self._str(s, "expire-max-interval", z.expire_max)
|
||||
|
||||
if z.external:
|
||||
s.item("external-validation", z.name)
|
||||
|
||||
if self.zonefile_load is not None:
|
||||
s.item_str("zonefile-load", self.zonefile_load)
|
||||
elif z.ixfr:
|
||||
|
|
|
|||
Loading…
Reference in a new issue