]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Change checkconf to include built-in dnssec-policy
authorMatthijs Mekking <matthijs@isc.org>
Thu, 28 Aug 2025 12:48:07 +0000 (14:48 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Wed, 24 Sep 2025 15:03:06 +0000 (17:03 +0200)
The configuration should also take into account the built-in
DNSSEC policies when verifying the keys in the key-directory match the
given policy. Update the code accordingly and add some good and
failure test cases.

bin/tests/system/checkconf-keys/bad-default-algorithm.conf.j2 [new file with mode: 0644]
bin/tests/system/checkconf-keys/bad-default-kz.conf.j2 [new file with mode: 0644]
bin/tests/system/checkconf-keys/named.conf.j2
bin/tests/system/checkconf-keys/setup.sh
bin/tests/system/checkconf-keys/tests_checkconf_keys.py
lib/isccfg/check.c
lib/isccfg/include/isccfg/kaspconf.h
lib/isccfg/kaspconf.c

diff --git a/bin/tests/system/checkconf-keys/bad-default-algorithm.conf.j2 b/bin/tests/system/checkconf-keys/bad-default-algorithm.conf.j2
new file mode 100644 (file)
index 0000000..b6d1300
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+zone "bad-default-algorithm.example" {
+       type primary;
+       file "bad-default-algorithm.example.db";
+       dnssec-policy "default";
+};
diff --git a/bin/tests/system/checkconf-keys/bad-default-kz.conf.j2 b/bin/tests/system/checkconf-keys/bad-default-kz.conf.j2
new file mode 100644 (file)
index 0000000..3bb56f4
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+zone "bad-default-kz.example" {
+       type primary;
+       file "bad-default-kz.example.db";
+       dnssec-policy "default";
+};
index 92bf50ae0a8d2abfb79a8b8cec18f149e57fc07e..5bdfcbd6c4cdeac6581854ea820c500619a79ff1 100644 (file)
@@ -53,6 +53,12 @@ dnssec-policy "keystores-kz" {
        };
 };
 
+zone "default.example" {
+       type primary;
+       file "default.example.db";
+       dnssec-policy "default";
+};
+
 zone "alternative.kz.example" {
        type primary;
        file "alternative.kz.example.db";
index 08a79a636ab5c67f7ededf0c5912a701ce32abda..633aac55d99c7de10fb4f2ffc93a781553f5cf35 100644 (file)
@@ -19,6 +19,19 @@ set -e
 mkdir ksk
 mkdir zsk
 
+zone="default.example"
+cp template.db.in "${zone}.db"
+$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.1
+
+zone="bad-default-kz.example"
+cp template.db.in "${zone}.db"
+$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.1
+$KEYGEN -a 13 $zone 2>keygen.out.$zone.2
+
+zone="bad-default-algorithm.example"
+cp template.db.in "${zone}.db"
+$KEYGEN -a 8 -fK $zone 2>keygen.out.$zone.1
+
 zone="alternative.kz.example"
 cp template.db.in "${zone}.db"
 $KEYGEN -a RSASHA256 -b 2048 $zone 2>keygen.out.$zone.1
index bc8406c981afa1728bdb2ac3b7c17f5feb394ac0..c89f156f825909db560daacab1114e6cd25c030e 100644 (file)
@@ -121,3 +121,42 @@ def test_dnssecpolicy_keystore():
         f"zone '{zone}': no key file found matching dnssec-policy default-kz key:'zsk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
         in err
     )
+
+    # Mismatch algorithm (default policy)
+    zone = "bad-default-algorithm.example"
+    out = isctest.run.cmd(
+        [CHECKCONF, "-k", "bad-default-algorithm.conf"], raise_on_exception=False
+    )
+    err = out.stdout.decode("utf-8")
+    keys = isctest.kasp.keydir_to_keylist(zone)
+    assert len(keys) == 1
+    assert (
+        f"zone '{zone}': key file '{zone}/RSASHA256/{keys[0].tag}' does not match dnssec-policy default"
+        in err
+    )
+    assert (
+        f"zone '{zone}': no key file found matching dnssec-policy default key:'csk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
+        in err
+    )
+
+    # Mismatch role (default policy)
+    zone = "bad-default-kz.example"
+    out = isctest.run.cmd(
+        [CHECKCONF, "-k", "bad-default-kz.conf"], raise_on_exception=False
+    )
+    err = out.stdout.decode("utf-8")
+    keys = isctest.kasp.keydir_to_keylist(zone)
+    assert len(keys) == 2
+    assert (
+        f"zone '{zone}': key file '{zone}/ECDSAP256SHA256/{keys[0].tag}' does not match dnssec-policy default"
+        in err
+    )
+    assert (
+        f"zone '{zone}': key file '{zone}/ECDSAP256SHA256/{keys[1].tag}' does not match dnssec-policy default"
+        in err
+    )
+    assert (
+        f"zone '{zone}': no key file found matching dnssec-policy default key:'csk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
+        in err
+    )
+    assert f"zone '{zone}': wrong number of key files (2, expected 1)" in err
index 385a85e6cc58113099d64bd5f3ef9cf2366d272b..e3563a55226423a41f23fc62de38551491e6277f 100644 (file)
@@ -2826,10 +2826,11 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
             bool check_keys) {
        const char *dir = keydir;
        isc_result_t ret, result = ISC_R_SUCCESS;
-       bool done = false;
        bool keystore = false;
        const cfg_obj_t *kasps = NULL;
        const cfg_obj_t *kaspobj = NULL;
+       const cfg_obj_t *obj = NULL;
+       dns_kasp_t *default_kasp = NULL;
        dns_kasp_t *kasp = NULL;
        dns_kasplist_t kasplist;
        const cfg_obj_t *keystores = NULL;
@@ -2855,37 +2856,86 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
        (void)cfg_keystore_fromconfig(NULL, mctx, &kslist, NULL);
 
        /*
-        * Look for the dnssec-policy by name, which is the dnssec-policy
-        * for the zone in question.
+        * dnssec-policy "default".
+        */
+       ret = cfg_kasp_builtinconfig(mctx, "default", &kslist, &kasplist,
+                                    &default_kasp);
+       if (ret != ISC_R_SUCCESS) {
+               cfg_obj_log(config, ISC_LOG_ERROR,
+                           "failed to load the 'default' dnssec-policy: %s",
+                           isc_result_totext(ret));
+               result = ret;
+               goto check;
+       }
+       dns_kasp_freeze(default_kasp);
+
+       /*
+        * dnssec-policy "insecure".
+        */
+       ret = cfg_kasp_builtinconfig(mctx, "insecure", &kslist, &kasplist,
+                                    &kasp);
+       if (ret != ISC_R_SUCCESS) {
+               cfg_obj_log(config, ISC_LOG_ERROR,
+                           "failed to load the 'insecure' dnssec-policy: %s",
+                           isc_result_totext(ret));
+               result = ret;
+               goto check;
+       }
+       dns_kasp_freeze(kasp);
+       dns_kasp_detach(&kasp);
+
+       /*
+        * Configured dnssec-policy clauses.
         */
        CFG_LIST_FOREACH(kasps, element) {
                cfg_obj_t *kconfig = cfg_listelt_value(element);
-               kaspobj = NULL;
+               obj = NULL;
 
                if (!cfg_obj_istuple(kconfig)) {
                        continue;
                }
 
-               kaspobj = cfg_tuple_get(kconfig, "name");
-               if (strcmp(name, cfg_obj_asstring(kaspobj)) != 0) {
-                       continue;
-               }
-
-               ret = cfg_kasp_fromconfig(kconfig, NULL, 0, mctx, &kslist,
-                                         &kasplist, &kasp);
+               obj = cfg_tuple_get(kconfig, "name");
+               ret = cfg_kasp_fromconfig(kconfig, default_kasp, 0, mctx,
+                                         &kslist, &kasplist, &kasp);
                if (ret != ISC_R_SUCCESS) {
-                       kasp = NULL;
+                       result = ret;
+                       goto check;
                }
 
-               break;
+               if (strcmp(name, cfg_obj_asstring(obj)) == 0) {
+                       kaspobj = obj;
+                       dns_kasp_freeze(kasp);
+               }
+               dns_kasp_detach(&kasp);
        }
-       if (kasp == NULL) {
+
+       /*
+        * Look for the dnssec-policy by name, which is the dnssec-policy
+        * for the zone in question.
+        */
+       ret = dns_kasplist_find(&kasplist, name, &kasp);
+       if (ret != ISC_R_SUCCESS) {
+               cfg_obj_log(config, ISC_LOG_ERROR,
+                           "no dnssec-policy found for zone '%s'", zname);
+               result = ISC_R_NOTFOUND;
                goto check;
        }
-       INSIST(kaspobj != NULL);
+       INSIST(kasp != NULL);
+
+       if (kaspobj == NULL) {
+               kaspobj = kasps == NULL ? config : kasps;
+       }
+
+       if (strcmp(name, "insecure") == 0 || strcmp(name, "default") == 0) {
+               ret = keydirexist(zconfig, "key-directory", origin, dir, name,
+                                 keydirs, mctx);
+               if (ret != ISC_R_SUCCESS) {
+                       result = ret;
+               }
+       }
 
        /* Check key-stores of keys */
-       dns_kasp_freeze(kasp);
        ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
                dns_keystore_t *kks = dns_kasp_key_keystore(kkey);
                dir = dns_keystore_directory(kks, keydir);
@@ -2983,18 +3033,10 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
                }
        }
 
-       dns_kasp_thaw(kasp);
-       done = true;
-
 check:
-       if (!done) {
-               ret = keydirexist(zconfig, "key-directory", origin, dir, name,
-                                 keydirs, mctx);
-               if (ret != ISC_R_SUCCESS) {
-                       result = ret;
-               }
+       if (default_kasp != NULL) {
+               dns_kasp_detach(&default_kasp);
        }
-
        if (kasp != NULL) {
                dns_kasp_detach(&kasp);
        }
index 14434f3e3a92aca1e8511202ca7df4b6c910398d..99b551f95bcda879f0f7e0c15e9e14574cd3aaba 100644 (file)
@@ -67,6 +67,30 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
  *\li  Other errors are possible.
  */
 
+isc_result_t
+cfg_kasp_builtinconfig(isc_mem_t *mctx, const char *name,
+                      dns_keystorelist_t *keystorelist,
+                      dns_kasplist_t *kasplist, dns_kasp_t **kaspp);
+/*%<
+ * Create built-in KASP.
+ *
+ * If a 'kasplist' is provided, a lookup happens and if a KASP already exists
+ * with the same name, no new KASP is created, and no attach to 'kaspp' happens.
+ *
+ * Requires:
+ *
+ *\li  'mctx' is a valid memory context.
+ *
+ *\li  kaspp != NULL && *kaspp == NULL
+ *
+ * Returns:
+ *
+ *\li  #ISC_R_SUCCESS  If creating and configuring the KASP succeeds.
+ *\li  #ISC_R_EXISTS   If 'kasplist' already has the default policy.
+ *
+ *\li  Other errors are possible.
+ */
+
 isc_result_t
 cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx,
                        dns_keystorelist_t *keystorelist,
index bee50de81e12b39c0ccca2c8a8edb47b953fc801..3c226899771dda398d212c6d5bdb930c4a1df188 100644 (file)
@@ -570,10 +570,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
        /* Now configure. */
        INSIST(DNS_KASP_VALID(kasp));
 
-       if (config != NULL) {
-               koptions = cfg_tuple_get(config, "options");
-               maps[i++] = koptions;
-       }
+       koptions = cfg_tuple_get(config, "options");
+       maps[i++] = koptions;
        maps[i] = NULL;
 
        /* Configuration: Signatures */
@@ -877,6 +875,100 @@ cleanup:
        return result;
 }
 
+isc_result_t
+cfg_kasp_builtinconfig(isc_mem_t *mctx, const char *name,
+                      dns_keystorelist_t *keystorelist,
+                      dns_kasplist_t *kasplist, dns_kasp_t **kaspp) {
+       isc_result_t result;
+       dns_kasp_t *kasp = NULL;
+
+       REQUIRE(kaspp != NULL && *kaspp == NULL);
+       REQUIRE(strcmp(name, "default") == 0 || strcmp(name, "insecure") == 0);
+
+       result = dns_kasplist_find(kasplist, name, &kasp);
+       if (result == ISC_R_SUCCESS) {
+               dns_kasp_detach(&kasp);
+               return ISC_R_EXISTS;
+       }
+       if (result != ISC_R_NOTFOUND) {
+               return result;
+       }
+       result = ISC_R_SUCCESS;
+
+       /* No kasp with configured name was found in list, create new one. */
+       INSIST(kasp == NULL);
+       dns_kasp_create(mctx, name, &kasp);
+       INSIST(kasp != NULL);
+
+       /* Now configure. */
+       INSIST(DNS_KASP_VALID(kasp));
+
+       /* Configuration: Signatures */
+       dns_kasp_setsigjitter(kasp, parse_duration(DNS_KASP_SIG_JITTER));
+       dns_kasp_setsigrefresh(kasp, parse_duration(DNS_KASP_SIG_REFRESH));
+       dns_kasp_setsigvalidity_dnskey(
+               kasp, parse_duration(DNS_KASP_SIG_VALIDITY_DNSKEY));
+       dns_kasp_setsigvalidity(kasp, parse_duration(DNS_KASP_SIG_VALIDITY));
+
+       /* Configuration: Zone settings */
+       dns_kasp_setinlinesigning(kasp, true);
+       dns_kasp_setmanualmode(kasp, false);
+       dns_kasp_setzonemaxttl(kasp, parse_duration(DNS_KASP_ZONE_MAXTTL));
+       dns_kasp_setzonepropagationdelay(
+               kasp, parse_duration(DNS_KASP_ZONE_PROPDELAY));
+
+       /* Configuration: Parent settings */
+       dns_kasp_setdsttl(kasp, parse_duration(DNS_KASP_DS_TTL));
+       dns_kasp_setparentpropagationdelay(
+               kasp, parse_duration(DNS_KASP_PARENT_PROPDELAY));
+
+       /* Configuration: Keys */
+       dns_kasp_setofflineksk(kasp, false);
+       dns_kasp_setcdnskey(kasp, true);
+       dns_kasp_adddigest(kasp, DNS_DSDIGEST_SHA256);
+       dns_kasp_setdnskeyttl(kasp, parse_duration(DNS_KASP_KEY_TTL));
+       dns_kasp_setpublishsafety(kasp,
+                                 parse_duration(DNS_KASP_PUBLISH_SAFETY));
+       dns_kasp_setretiresafety(kasp, parse_duration(DNS_KASP_RETIRE_SAFETY));
+
+       dns_kasp_setpurgekeys(kasp, parse_duration(DNS_KASP_PURGE_KEYS));
+
+       if (strcmp(name, "default") == 0) {
+               dns_kasp_key_t *new_key = NULL;
+               dns_kasp_key_create(kasp, &new_key);
+               new_key->role |= DNS_KASP_KEY_ROLE_KSK;
+               new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
+               new_key->lifetime = 0;
+               new_key->algorithm = DST_ALG_ECDSA256;
+               new_key->length = 256;
+               result = dns_keystorelist_find(keystorelist,
+                                              DNS_KEYSTORE_KEYDIRECTORY,
+                                              &new_key->keystore);
+               if (result != ISC_R_SUCCESS) {
+                       goto cleanup;
+               }
+               dns_kasp_addkey(kasp, new_key);
+       }
+
+       /* Configuration: Denial of existence */
+       dns_kasp_setnsec3(kasp, false);
+
+       /* Append it to the list for future lookups. */
+       ISC_LIST_APPEND(*kasplist, kasp, link);
+       INSIST(!(ISC_LIST_EMPTY(*kasplist)));
+
+       /* Success: Attach the kasp to the pointer and return. */
+       dns_kasp_attach(kasp, kaspp);
+
+       /* Don't detach as kasp is on '*kasplist' */
+       return ISC_R_SUCCESS;
+
+cleanup:
+       /* Something bad happened, detach (destroys kasp) and return error. */
+       dns_kasp_detach(&kasp);
+       return result;
+}
+
 isc_result_t
 cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx,
                        dns_keystorelist_t *keystorelist,