]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
dnssec: implemented multi-keystore option...
authorLibor Peltan <libor.peltan@nic.cz>
Tue, 27 May 2025 11:34:03 +0000 (13:34 +0200)
committerDaniel Salzman <daniel.salzman@nic.cz>
Tue, 1 Jul 2025 06:42:04 +0000 (08:42 +0200)
...useful e.g. for hsm-to-pem migration

14 files changed:
doc/reference.rst
src/knot/conf/schema.c
src/knot/dnssec/context.c
src/knot/dnssec/context.h
src/knot/dnssec/kasp/kasp_zone.c
src/knot/dnssec/kasp/kasp_zone.h
src/knot/dnssec/kasp/policy.h
src/knot/dnssec/zone-keys.c
src/knot/dnssec/zone-keys.h
src/knot/zone/backup.c
src/utils/keymgr/functions.c
src/utils/keymgr/keystore.c
tests-extra/tests/dnssec/keystores/test.py [new file with mode: 0644]
tests-extra/tools/dnstest/server.py

index b6b8f814f415942a503cbdb34020ef0988d913d8..d660293d636075f26a5f38b4d55ad456f17b4555 100644 (file)
@@ -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<keystore_id>` 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<Data and metadata backup>` 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
index fe1eee5cdf2da9cba609658d29bd5134385cf052..1db67b716fd4feead9e616da113e6724994dce21 100644 (file)
@@ -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 },
index fbed953256409a97583bfe0e307afd5348bf3b51..27e311095c420ab0e82ea32e03570d9ab2f9bd56 100644 (file)
@@ -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);
 
index 48ee5087c5050a726e269258f87883aa036ca4a5..0e7e8eccdfced8b2314bd8b1d6d461bfb63f0d37 100644 (file)
@@ -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;
 
index 227d512e9ba805e88ca42c74baf4a7049be9cf1e..808c2c663a288bf60fb65e8fb26729a0b073dc8e 100644 (file)
@@ -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);
index 778493ef37a68238a8110c66b7010a731195f398..1395df886c119503d4e6c33dd9ace5be22644f7b 100644 (file)
@@ -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,
index 54031c3dfef696a38583bd424265ee2d9fd7e4cc..7f9db32609990b634c207067eda553dcd03e0c92 100644 (file)
@@ -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;
index 570b27e561a5a9222febf0f2f3df1b6a40ca775b..2a4b305e31b3978d9044ed5c6c3c04ddf67efddf 100644 (file)
@@ -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:
index 583e954c6e4f04c9fe98537fab08dd7ebedb23ce..0474d527fd53a2cadcb6e06b160af2c094cb1a6c 100644 (file)
@@ -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.
  *
index 449daab4a550384437f5b089fd6849c1a83635df..bb1a0d70f98b1f70be5ec838c90c0c5d011e5858 100644 (file)
@@ -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;
 }
 
index 311b8ea5cab574185e42036cd114d64376095c15..760114e67d7e9f9d2d7a13ceb45d40741183c9cb 100644 (file)
@@ -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)
index 9d598e461cdba525c6d0980476f8d41166591776..06f841dffb921ee5cec53e1b7175d12703e7d47f 100644 (file)
@@ -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 (file)
index 0000000..664a399
--- /dev/null
@@ -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()
index 8a8bde2da58d7fc8b87aafb82e3891f110709bbf..1e1f4710ab699e40b28f712f6d7f6d2e6d19a989 100644 (file)
@@ -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)