From: Libor Peltan Date: Tue, 27 May 2025 11:34:03 +0000 (+0200) Subject: dnssec: implemented multi-keystore option... X-Git-Tag: v3.5.0~53^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=415f5bf88ee9d541f432008f791148218521958b;p=thirdparty%2Fknot-dns.git dnssec: implemented multi-keystore option... ...useful e.g. for hsm-to-pem migration --- diff --git a/doc/reference.rst b/doc/reference.rst index b6b8f814f4..d660293d63 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -2008,7 +2008,7 @@ DNSSEC policy configuration. policy: - id: STR - keystore: keystore_id + keystore: keystore_id ... manual: BOOL single-type-signing: BOOL algorithm: rsasha1 | rsasha1-nsec3-sha1 | rsasha256 | rsasha512 | ecdsap256sha256 | ecdsap384sha384 | ed25519 | ed448 @@ -2055,11 +2055,19 @@ keystore A :ref:`reference` to a keystore holding private key material for zones. -*Default:* an imaginary keystore with all default values +If multiple keystores are specified, private keys for signing are looked up in +all of them. But newly generated keys are stored in the first one in the specified order. + +.. NOTE:: + If multiple keystores are configured and a zone is being restored + with the :ref:`back up` feature, all restored + private keys are stored into the first referenced keystore. .. NOTE:: A configured keystore called "default" won't be used unless explicitly referenced. +*Default:* an imaginary keystore with all default values + .. _policy_manual: manual diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index fe1eee5cdf..1db67b716f 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -397,7 +397,7 @@ static const yp_item_t desc_dnskey_sync[] = { static const yp_item_t desc_policy[] = { { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, - { C_KEYSTORE, YP_TREF, YP_VREF = { C_KEYSTORE }, CONF_IO_FRLD_ZONES, + { C_KEYSTORE, YP_TREF, YP_VREF = { C_KEYSTORE }, YP_FMULTI | CONF_IO_FRLD_ZONES, { check_ref_dflt } }, { C_MANUAL, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, { C_SINGLE_TYPE_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, diff --git a/src/knot/dnssec/context.c b/src/knot/dnssec/context.c index fbed953256..27e311095c 100644 --- a/src/knot/dnssec/context.c +++ b/src/knot/dnssec/context.c @@ -227,8 +227,7 @@ int kdnssec_ctx_init(conf_t *conf, kdnssec_ctx_t *ctx, const knot_dname_t *zone_ conf_id_fix_default(&policy_id); policy_load(ctx->policy, conf, &policy_id, ctx->zone->dname); - ret = zone_init_keystore(conf, &policy_id, NULL, &ctx->keystore, - &ctx->keystore_type, &ctx->policy->key_label); + ret = zone_init_keystore(conf, &policy_id, NULL, &ctx->keystores); if (ret != KNOT_EOK) { goto init_error; } @@ -287,7 +286,7 @@ void kdnssec_ctx_deinit(kdnssec_ctx_t *ctx) free(ctx->policy); } key_records_clear(&ctx->offline_records); - dnssec_keystore_deinit(ctx->keystore); + zone_deinit_keystore(&ctx->keystores); kasp_zone_free(&ctx->zone); free(ctx->kasp_zone_path); diff --git a/src/knot/dnssec/context.h b/src/knot/dnssec/context.h index 48ee5087c5..0e7e8eccdf 100644 --- a/src/knot/dnssec/context.h +++ b/src/knot/dnssec/context.h @@ -28,8 +28,7 @@ typedef struct { knot_lmdb_db_t *kasp_db; knot_kasp_zone_t *zone; knot_kasp_policy_t *policy; - dnssec_keystore_t *keystore; - unsigned keystore_type; + knot_kasp_keystore_t *keystores; char *kasp_zone_path; diff --git a/src/knot/dnssec/kasp/kasp_zone.c b/src/knot/dnssec/kasp/kasp_zone.c index 227d512e9b..808c2c663a 100644 --- a/src/knot/dnssec/kasp/kasp_zone.c +++ b/src/knot/dnssec/kasp/kasp_zone.c @@ -303,9 +303,25 @@ void free_key_params(key_params_t *parm) } } +void zone_deinit_keystore(knot_kasp_keystore_t **keystores) +{ + if (keystores != NULL && *keystores != NULL) { + for (size_t i = 0; i < (*keystores)[0].count; i++) { + dnssec_keystore_deinit((*keystores)[i].keystore); + } + free(*keystores); + *keystores = NULL; + } +} + int zone_init_keystore(conf_t *conf, conf_val_t *policy_id, conf_val_t *keystore_id, - dnssec_keystore_t **keystore, unsigned *backend, bool *key_label) + knot_kasp_keystore_t **keystores) { + if (keystores == NULL || *keystores != NULL || + (bool)(policy_id == NULL) == (bool)(keystore_id == NULL)) { + return KNOT_EINVAL; + } + char *zone_path = conf_db(conf, C_KASP_DB); if (zone_path == NULL) { return KNOT_ENOMEM; @@ -322,21 +338,34 @@ int zone_init_keystore(conf_t *conf, conf_val_t *policy_id, conf_val_t *keystore conf_id_fix_default(keystore_id); } - conf_val_t val = conf_id_get(conf, C_KEYSTORE, C_BACKEND, keystore_id); - unsigned _backend = conf_opt(&val); + size_t ks_count = conf_val_count(keystore_id); + *keystores = calloc(ks_count, sizeof(**keystores)); + if (*keystores == NULL) { + free(zone_path); + return KNOT_ENOMEM; + } - val = conf_id_get(conf, C_KEYSTORE, C_CONFIG, keystore_id); - const char *config = conf_str(&val); + int ret = KNOT_EOK; + for (size_t i = 0; i < ks_count && ret == KNOT_EOK; i++) { + knot_kasp_keystore_t *ks = *keystores + i; - if (key_label != NULL) { + conf_val_t val = conf_id_get(conf, C_KEYSTORE, C_BACKEND, keystore_id); + ks->backend = conf_opt(&val); val = conf_id_get(conf, C_KEYSTORE, C_KEY_LABEL, keystore_id); - *key_label = conf_bool(&val); - } + ks->key_label = conf_bool(&val); + ks->count = ks_count; - int ret = keystore_load(config, _backend, zone_path, keystore); + val = conf_id_get(conf, C_KEYSTORE, C_CONFIG, keystore_id); + const char *config = conf_str(&val); + ret = keystore_load(config, ks->backend, zone_path, &ks->keystore); - if (backend != NULL) { - *backend = _backend; + if (ks_count > 1) { // Don't try to iterate if not multivalued. + conf_val_next(keystore_id); + } + } + + if (ret != KNOT_EOK) { + zone_deinit_keystore(keystores); } free(zone_path); diff --git a/src/knot/dnssec/kasp/kasp_zone.h b/src/knot/dnssec/kasp/kasp_zone.h index 778493ef37..1395df886c 100644 --- a/src/knot/dnssec/kasp/kasp_zone.h +++ b/src/knot/dnssec/kasp/kasp_zone.h @@ -36,8 +36,10 @@ void kasp_zone_free(knot_kasp_zone_t **zone); void free_key_params(key_params_t *parm); +void zone_deinit_keystore(knot_kasp_keystore_t **keystores); + int zone_init_keystore(conf_t *conf, conf_val_t *policy_id, conf_val_t *keystore_id, - dnssec_keystore_t **keystore, unsigned *backend, bool *key_label); + knot_kasp_keystore_t **keystores); int kasp_zone_keys_from_rr(knot_kasp_zone_t *zone, const knot_rdataset_t *zone_dnskey, diff --git a/src/knot/dnssec/kasp/policy.h b/src/knot/dnssec/kasp/policy.h index 54031c3dfe..7f9db32609 100644 --- a/src/knot/dnssec/kasp/policy.h +++ b/src/knot/dnssec/kasp/policy.h @@ -9,6 +9,7 @@ #include "contrib/time.h" #include "libdnssec/key.h" +#include "libdnssec/keystore.h" #include "knot/conf/conf.h" /*! @@ -53,6 +54,13 @@ typedef struct { bool is_zsk; } knot_kasp_key_t; +typedef struct { + dnssec_keystore_t *keystore; + unsigned backend; + bool key_label; + size_t count; /*!< Number of keystores configured. */ +} knot_kasp_keystore_t; + /*! * Parent for DS checks. */ @@ -119,7 +127,6 @@ typedef struct { bool has_dnskey_sync; bool offline_ksk; bool incremental; - bool key_label; unsigned unsafe; uint32_t keytag_remain; uint32_t keytag_modulo; diff --git a/src/knot/dnssec/zone-keys.c b/src/knot/dnssec/zone-keys.c index 570b27e561..2a4b305e31 100644 --- a/src/knot/dnssec/zone-keys.c +++ b/src/knot/dnssec/zone-keys.c @@ -106,15 +106,17 @@ static int generate_keytag_unconflict(kdnssec_ctx_t *ctx, const char *label = NULL; char label_buf[sizeof(knot_dname_txt_storage_t) + 16]; - if (ctx->policy->key_label && + if (ctx->keystores[0].key_label && knot_dname_to_str(label_buf, ctx->zone->dname, sizeof(label_buf)) != NULL) { const char *key_type = (flags & DNSKEY_GENERATE_KSK) ? " KSK" : " ZSK" ; strlcat(label_buf, key_type, sizeof(label_buf)); label = label_buf; } + assert(ctx->keystores != NULL && ctx->keystores[0].count > 0 && ctx->keystores[0].keystore != NULL); + for (size_t i = 0; i < GENERATE_KEYTAG_ATTEMPTS; i++) { - int ret = generate_dnssec_key(ctx->keystore, ctx->zone->dname, label, + int ret = generate_dnssec_key(ctx->keystores[0].keystore, ctx->zone->dname, label, ctx->policy->algorithm, size, flags, id, key); if (ret != KNOT_EOK) { @@ -126,7 +128,7 @@ static int generate_keytag_unconflict(kdnssec_ctx_t *ctx, return KNOT_EOK; } - (void)dnssec_keystore_remove(ctx->keystore, *id); + (void)dnssec_keystore_remove(ctx->keystores[0].keystore, *id); dnssec_key_free(*key); free(*id); } @@ -141,7 +143,7 @@ int kdnssec_generate_key(kdnssec_ctx_t *ctx, kdnssec_generate_flags_t flags, { assert(ctx); assert(ctx->zone); - assert(ctx->keystore); + assert(ctx->keystores); assert(ctx->policy); normalize_generate_flags(&flags); @@ -172,7 +174,7 @@ int kdnssec_generate_key(kdnssec_ctx_t *ctx, kdnssec_generate_flags_t flags, r = kasp_zone_append(ctx->zone, key); free(key); if (r != KNOT_EOK) { - (void)dnssec_keystore_remove(ctx->keystore, id); + (void)dnssec_keystore_remove(ctx->keystores[0].keystore, id); dnssec_key_free(dnskey); free(id); return r; @@ -215,7 +217,8 @@ int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr) { assert(ctx); assert(ctx->zone); - assert(ctx->keystore); + assert(ctx->keystores); + assert(ctx->keystores[0].count > 0); assert(ctx->policy); ssize_t key_index = key_ptr - ctx->zone->keys; @@ -231,7 +234,10 @@ int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr) } if (!key_still_used_in_keystore && !key_ptr->is_pub_only) { - ret = dnssec_keystore_remove(ctx->keystore, key_ptr->id); + ret = DNSSEC_ENOENT; + for (size_t i = 0; i < ctx->keystores[0].count && ret == DNSSEC_ENOENT; i++) { + ret = dnssec_keystore_remove(ctx->keystores[i].keystore, key_ptr->id); + } if (ret != KNOT_EOK) { return ret; } @@ -447,6 +453,19 @@ static int walk_algorithms(kdnssec_ctx_t *ctx, zone_keyset_t *keyset) return KNOT_EOK; } +int kdnssec_load_private(knot_kasp_keystore_t *keystores, const char *id, + dnssec_key_t *key, unsigned *backend) +{ + int ret = DNSSEC_ENOENT; + for (size_t i = 0; i < keystores[0].count && ret == DNSSEC_ENOENT; i++) { + if (backend != NULL) { + *backend = keystores[i].backend; + } + ret = dnssec_keystore_get_private(keystores[i].keystore, id, key); + } + return ret; +} + /*! * \brief Load private keys for active keys. */ @@ -460,7 +479,7 @@ static int load_private_keys(kdnssec_ctx_t *ctx, zone_keyset_t *keyset) if (!key->is_active && !key->is_ksk_active_plus && !key->is_zsk_active_plus) { continue; } - int ret = dnssec_keystore_get_private(ctx->keystore, key->id, key->key); + int ret = kdnssec_load_private(ctx->keystores, key->id, key->key, NULL); switch (ret) { case DNSSEC_EOK: case DNSSEC_KEY_ALREADY_PRESENT: diff --git a/src/knot/dnssec/zone-keys.h b/src/knot/dnssec/zone-keys.h index 583e954c6e..0474d527fd 100644 --- a/src/knot/dnssec/zone-keys.h +++ b/src/knot/dnssec/zone-keys.h @@ -112,6 +112,19 @@ int kdnssec_share_key(kdnssec_ctx_t *ctx, const knot_dname_t *from_zone, const c */ int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr); +/*! + * \brief Load private key for given ID by searching all configured keystores. + * + * \param keystores Array of keystores. + * \param id Key ID to search. + * \param key Libdnssec key structure to be filled with private material. + * \param backend Optional: backend of the keystore where found. + * + * \return DNSSEC_E* + */ +int kdnssec_load_private(knot_kasp_keystore_t *keystores, const char *id, + dnssec_key_t *key, unsigned *backend); + /*! * \brief Load zone keys and init cryptographic context. * diff --git a/src/knot/zone/backup.c b/src/knot/zone/backup.c index 449daab4a5..bb1a0d70f9 100644 --- a/src/knot/zone/backup.c +++ b/src/knot/zone/backup.c @@ -23,6 +23,7 @@ #include "knot/ctl/commands.h" #include "knot/dnssec/kasp/kasp_zone.h" #include "knot/dnssec/kasp/keystore.h" +#include "knot/dnssec/zone-keys.h" #include "knot/journal/journal_metadata.h" #include "knot/server/server.h" #include "knot/zone/backup_dir.h" @@ -215,7 +216,8 @@ static char *dir_file(const char *dir_name, const char *file_name) return sprintf_alloc("%s/%s", dir_name, basename); } -static int backup_key(key_params_t *parm, dnssec_keystore_t *from, dnssec_keystore_t *to) +static int backup_key(key_params_t *parm, const knot_dname_t *zname, + knot_kasp_keystore_t *from, knot_kasp_keystore_t *to) { dnssec_key_t *key = NULL; int ret = dnssec_key_new(&key); @@ -224,9 +226,22 @@ static int backup_key(key_params_t *parm, dnssec_keystore_t *from, dnssec_keysto } dnssec_key_set_algorithm(key, parm->algorithm); - ret = dnssec_keystore_get_private(from, parm->id, key); + dnssec_keystore_t *to_pem = NULL; + for (size_t i = 0; i < to[0].count; i++) { + if (to[i].backend == KEYSTORE_BACKEND_PEM) { + to_pem = to[i].keystore; + break; + } + } + + unsigned backend; + ret = kdnssec_load_private(from, parm->id, key, &backend); if (ret == DNSSEC_EOK) { - ret = dnssec_keystore_set_private(to, key); + if (backend == KEYSTORE_BACKEND_PKCS11 || to_pem == NULL) { + log_zone_notice(zname, "private keys from PKCS #11 are not subject of backup/restore, skipping them"); + } else { + ret = dnssec_keystore_set_private(to[0].keystore, key); + } } dnssec_key_free(key); @@ -352,29 +367,34 @@ done: static int backup_keystore(conf_t *conf, zone_t *zone, zone_backup_ctx_t *ctx) { - dnssec_keystore_t *from = NULL, *to = NULL; + knot_kasp_keystore_t *from = NULL, *to = NULL; conf_val_t policy_id = get_zone_policy(conf, zone->name); - unsigned backend_type = 0; - int ret = zone_init_keystore(conf, &policy_id, NULL, &from, &backend_type, NULL); + int ret = zone_init_keystore(conf, &policy_id, NULL, &from); if (ret != KNOT_EOK) { LOG_FAIL("keystore init"); return ret; } - if (backend_type == KEYSTORE_BACKEND_PKCS11) { - log_zone_notice(zone->name, "private keys from PKCS #11 are not subject of backup/restore"); - (void)dnssec_keystore_deinit(from); - return KNOT_EOK; + + to = calloc(1, sizeof(*to)); + if (to == NULL) { + LOG_FAIL("out of memory"); + zone_deinit_keystore(&from); + return KNOT_ENOMEM; } + dnssec_keystore_t *to_ks; char kasp_dir[strlen(ctx->backup_dir) + 6]; (void)snprintf(kasp_dir, sizeof(kasp_dir), "%s/keys", ctx->backup_dir); - ret = keystore_load("keys", KEYSTORE_BACKEND_PEM, kasp_dir, &to); + ret = keystore_load("keys", KEYSTORE_BACKEND_PEM, kasp_dir, &to_ks); if (ret != KNOT_EOK) { LOG_FAIL("keystore load"); goto done; } + to->keystore = to_ks; + to->count = 1; + to->backend = KEYSTORE_BACKEND_PEM; BACKUP_SWAP(ctx, from, to); @@ -390,7 +410,7 @@ static int backup_keystore(conf_t *conf, zone_t *zone, zone_backup_ctx_t *ctx) WALK_LIST(n, key_params) { key_params_t *parm = n->d; if (ret == KNOT_EOK && !parm->is_pub_only) { - ret = backup_key(parm, from, to); + ret = backup_key(parm, zone->name, from, to); } free_key_params(parm); } @@ -400,8 +420,8 @@ static int backup_keystore(conf_t *conf, zone_t *zone, zone_backup_ctx_t *ctx) ptrlist_deep_free(&key_params, NULL); done: - (void)dnssec_keystore_deinit(to); - (void)dnssec_keystore_deinit(from); + zone_deinit_keystore(&to); + zone_deinit_keystore(&from); return ret; } diff --git a/src/utils/keymgr/functions.c b/src/utils/keymgr/functions.c index 311b8ea5ca..760114e67d 100644 --- a/src/utils/keymgr/functions.c +++ b/src/utils/keymgr/functions.c @@ -416,7 +416,7 @@ int keymgr_import_bind(kdnssec_ctx_t *ctx, const char *import_file, bool pub_onl bind_privkey_free(&bpriv); - ret = dnssec_keystore_import(ctx->keystore, &pem, &keyid); + ret = dnssec_keystore_import(ctx->keystores[0].keystore, &pem, &keyid); dnssec_binary_free(&pem); if (ret != DNSSEC_EOK) { goto fail; @@ -536,7 +536,7 @@ static int import_key(kdnssec_ctx_t *ctx, unsigned backend, const char *param, } // put pem to keystore - ret = dnssec_keystore_import(ctx->keystore, &pem, &keyid); + ret = dnssec_keystore_import(ctx->keystores[0].keystore, &pem, &keyid); dnssec_binary_free(&pem); if (ret != DNSSEC_EOK) { err_import_key(keyid, param); @@ -560,7 +560,7 @@ static int import_key(kdnssec_ctx_t *ctx, unsigned backend, const char *param, dnssec_key_set_algorithm(key, ctx->policy->algorithm); // fill key structure from keystore (incl. pubkey from privkey computation) - ret = dnssec_keystore_get_private(ctx->keystore, keyid, key); + ret = kdnssec_load_private(ctx->keystores, keyid, key, NULL); if (ret != DNSSEC_EOK) { err_import_key(keyid, ""); goto fail; @@ -606,7 +606,7 @@ int keymgr_import_pkcs11(kdnssec_ctx_t *ctx, char *key_id, int argc, char *argv[ return DNSSEC_INVALID_KEY_ID; } - if (ctx->keystore_type != KEYSTORE_BACKEND_PKCS11) { + if (ctx->keystores[0].backend != KEYSTORE_BACKEND_PKCS11) { knot_dname_txt_storage_t dname_str; (void)knot_dname_to_str(dname_str, ctx->zone->dname, sizeof(dname_str)); ERR2("not a PKCS #11 keystore for zone %s", dname_str); @@ -1007,7 +1007,7 @@ static int key_sort(const void *a, const void *b) static bool key_missing(kdnssec_ctx_t *ctx, const knot_kasp_key_t *key) { return !key->is_pub_only && DNSSEC_EOK != - dnssec_keystore_get_private(ctx->keystore, key->id, key->key); + kdnssec_load_private(ctx->keystores, key->id, key->key, NULL); } int keymgr_list_keys(kdnssec_ctx_t *ctx, keymgr_list_params_t *params) diff --git a/src/utils/keymgr/keystore.c b/src/utils/keymgr/keystore.c index 9d598e461c..06f841dffb 100644 --- a/src/utils/keymgr/keystore.c +++ b/src/utils/keymgr/keystore.c @@ -198,26 +198,28 @@ static int init_keystore(dnssec_keystore_t **store, const char *keystore_id, id.blob = (const uint8_t *)keystore_id; id.blob_len = len; - unsigned backend; - bool key_label; + knot_kasp_keystore_t *keystores = NULL; - int ret = zone_init_keystore(conf(), NULL, &id, store, &backend, &key_label); + int ret = zone_init_keystore(conf(), NULL, &id, &keystores); if (ret != KNOT_EOK) { ERR2("failed to open '%s' keystore (%s)", keystore_id, knot_strerror(ret)); return ret; } + assert(keystores[0].count == 1); + *store = keystores[0].keystore; if (strcmp(keystore_id, DFLT_ID) == 0) { printf("Keystore default"); } else { printf("Keystore id '%s'", keystore_id); }; - printf(", type %s", (backend == KEYSTORE_BACKEND_PEM ? "PEM" : "PKCS #11")); + printf(", type %s", (keystores[0].backend == KEYSTORE_BACKEND_PEM ? "PEM" : "PKCS #11")); if (threads > 0) { printf(", threads %u", threads); } printf("\n\n"); + free(keystores); return KNOT_EOK; } diff --git a/tests-extra/tests/dnssec/keystores/test.py b/tests-extra/tests/dnssec/keystores/test.py new file mode 100644 index 0000000000..664a399d95 --- /dev/null +++ b/tests-extra/tests/dnssec/keystores/test.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +""" +Check of multi-keystore operation. +""" + +import os +import random +import shutil +from dnstest.utils import * +from dnstest.keys import Keymgr +from dnstest.test import Test + +def check_key_count(server, keystore, expected): + ksdir = os.path.join(server.keydir, keystore) + try: + files = len([name for name in os.listdir(ksdir)]) + except FileNotFoundError: + files = 0 + compare(files, expected, "privkey count in %s" % ksdir) + +t = Test() + +server = t.server("knot") +zone = t.zone("catalog.") # has zero TTL => faster key rollovers +t.link(zone, server) + +server.dnssec(zone).enable = True +server.dnssec(zone).propagation_delay = 1 +server.dnssec(zone).keystores = [ "keys1", "keys2" ] + +t.start() +serial = server.zone_wait(zone) + +check_key_count(server, "keys1", 2) +check_key_count(server, "keys2", 0) + +server.dnssec(zone).keystores = [ "keys2", "keys1" ] +server.gen_confile() +server.reload() +server.ctl("zone-key-rollover %s zsk" % zone[0].name) + +serial += 2 # wait for three increments which is whole ZSK rollover +serial = server.zone_wait(zone, serial) + +check_key_count(server, "keys1", 1) +check_key_count(server, "keys2", 1) + +backup_dir = os.path.join(server.dir, "backup1") +server.ctl("zone-backup +backupdir %s %s" % (backup_dir, zone[0].name), wait=True) +shutil.rmtree(os.path.join(server.keydir, "keys1")) +shutil.rmtree(os.path.join(server.keydir, "keys2")) +server.ctl("zone-restore +backupdir %s %s" % (backup_dir, zone[0].name), wait=True) + +check_key_count(server, "keys1", 0) +check_key_count(server, "keys2", 2) # restore puts all keys to first configured keystore no matter where they were at backup + +server.ctl("zone-sign %s" % zone[0].name, wait=True) # check that signing still works after restore +serial = server.zone_wait(zone, serial) + +server.flush(zone[0], wait=True) +server.zone_verify(zone[0]) + +t.end() diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py index 8a8bde2da5..1e1f4710ab 100644 --- a/tests-extra/tools/dnstest/server.py +++ b/tests-extra/tools/dnstest/server.py @@ -45,6 +45,7 @@ class ZoneDnssec(object): self.validate = None self.disable = None # create the policy in config, but set dnssec-signing: off self.manual = None + self.keystores = [] self.single_type_signing = None self.alg = None self.ksk_size = None @@ -1786,6 +1787,20 @@ class Knot(Server): if have_dnskeysync: s.end() + have_keystore = False + for zone in sorted(self.zones): + z = self.zones[zone] + if not z.dnssec.keystores: + continue + if not have_keystore: + s.begin("keystore") + have_keystore = True + for ks in z.dnssec.keystores: + s.id_item("id", ks) + s.item("config", ks) + if have_keystore: + s.end() + have_policy = False for zone in sorted(self.zones): z = self.zones[zone] @@ -1801,6 +1816,8 @@ class Knot(Server): s.id_item("id", z.name) self._bool(s, "manual", z.dnssec.manual) self._bool(s, "single-type-signing", z.dnssec.single_type_signing) + if z.dnssec.keystores: + s.item("keystore", "[ %s ]" % ", ".join(z.dnssec.keystores)) self._str(s, "algorithm", z.dnssec.alg) self._str(s, "ksk-size", z.dnssec.ksk_size) self._str(s, "zsk-size", z.dnssec.zsk_size)