From: Matthijs Mekking Date: Thu, 28 Aug 2025 12:48:07 +0000 (+0200) Subject: Change checkconf to include built-in dnssec-policy X-Git-Tag: v9.21.14~24^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=dcd49f2eadcff03d7eab11a6aa371c4a62b7c67a;p=thirdparty%2Fbind9.git Change checkconf to include built-in dnssec-policy 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. --- 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 index 00000000000..b6d1300a36d --- /dev/null +++ b/bin/tests/system/checkconf-keys/bad-default-algorithm.conf.j2 @@ -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 index 00000000000..3bb56f4d6ea --- /dev/null +++ b/bin/tests/system/checkconf-keys/bad-default-kz.conf.j2 @@ -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"; +}; diff --git a/bin/tests/system/checkconf-keys/named.conf.j2 b/bin/tests/system/checkconf-keys/named.conf.j2 index 92bf50ae0a8..5bdfcbd6c4c 100644 --- a/bin/tests/system/checkconf-keys/named.conf.j2 +++ b/bin/tests/system/checkconf-keys/named.conf.j2 @@ -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"; diff --git a/bin/tests/system/checkconf-keys/setup.sh b/bin/tests/system/checkconf-keys/setup.sh index 08a79a636ab..633aac55d99 100644 --- a/bin/tests/system/checkconf-keys/setup.sh +++ b/bin/tests/system/checkconf-keys/setup.sh @@ -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 diff --git a/bin/tests/system/checkconf-keys/tests_checkconf_keys.py b/bin/tests/system/checkconf-keys/tests_checkconf_keys.py index bc8406c981a..c89f156f825 100644 --- a/bin/tests/system/checkconf-keys/tests_checkconf_keys.py +++ b/bin/tests/system/checkconf-keys/tests_checkconf_keys.py @@ -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 diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 385a85e6cc5..e3563a55226 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -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); } diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h index 14434f3e3a9..99b551f95bc 100644 --- a/lib/isccfg/include/isccfg/kaspconf.h +++ b/lib/isccfg/include/isccfg/kaspconf.h @@ -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, diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index bee50de81e1..3c226899771 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -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,