From: Matthijs Mekking Date: Thu, 28 Aug 2025 08:28:02 +0000 (+0200) Subject: Implement named-checkconf -k (check keys) X-Git-Tag: v9.21.14~24^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9fe520ece9121a81dc3e77d11a9a93ae29468399;p=thirdparty%2Fbind9.git Implement named-checkconf -k (check keys) With named-checkconf -k you can check your configuration including checking the dnssec-policy keys against the configured keystores. If there is a mismatch in the key files versus the policy, named-checkconf will fail. This is useful for running before migrating to dnssec-policy. For logging purposes, introduce a function that writes the identifying information about a policy key into a string. Allow a dnssec key to be initialized outside the keymgr code. Add 'log_errors' to 'cfg_kasp_fromconfig' to avoid duplicate error logs. --- diff --git a/bin/check/named-checkconf.c b/bin/check/named-checkconf.c index f9c80137190..a4da14d8e3b 100644 --- a/bin/check/named-checkconf.c +++ b/bin/check/named-checkconf.c @@ -57,7 +57,7 @@ usage(void); static void usage(void) { fprintf(stderr, - "usage: %s [-achijlvz] [-p [-x]] [-t directory] " + "usage: %s [-achijklvz] [-p [-x]] [-t directory] " "[named.conf]\n", isc_commandline_progname); exit(EXIT_SUCCESS); @@ -593,7 +593,7 @@ main(int argc, char **argv) { /* * Process memory debugging argument first. */ -#define CMDLINE_FLAGS "acdhijlm:nt:pvxz" +#define CMDLINE_FLAGS "acdhijklm:nt:pvxz" while ((c = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (c) { case 'm': @@ -638,6 +638,10 @@ main(int argc, char **argv) { nomerge = false; break; + case 'k': + checkflags |= BIND_CHECK_KEYS; + break; + case 'l': list_zones = true; break; diff --git a/bin/check/named-checkconf.rst b/bin/check/named-checkconf.rst index bdc7933a605..516756e9a6d 100644 --- a/bin/check/named-checkconf.rst +++ b/bin/check/named-checkconf.rst @@ -21,7 +21,7 @@ named-checkconf - named configuration file syntax checking tool Synopsis ~~~~~~~~ -:program:`named-checkconf` [**-achjlnvz**] [**-p** [**-x** ]] [**-t** directory] {filename} +:program:`named-checkconf` [**-achjklnvz**] [**-p** [**-x** ]] [**-t** directory] {filename} Description ~~~~~~~~~~~ @@ -56,6 +56,12 @@ Options When loading a zonefile, this option instructs :iscman:`named` to read the journal if it exists. +.. option:: -k + + Check the `dnssec-policy`'s DNSSEC keys against the key files in + the `key-directory`. This is useful when checking a `named.conf` + to ensure a DNSSEC policy matches the existing keys. + .. option:: -l This option lists all the configured zones. Each line of output contains the zone diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index ed8a244e599..5a1693fe6af 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -601,6 +601,9 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, const cfg_obj_t *keystores = NULL; dns_keystore_t *keystore = NULL; dns_keystorelist_t kslist; + unsigned int options = (ISCCFG_KASPCONF_CHECK_ALGORITHMS | + ISCCFG_KASPCONF_CHECK_KEYLIST | + ISCCFG_KASPCONF_LOG_ERRORS); ISC_LIST_INIT(kasplist); ISC_LIST_INIT(kslist); @@ -635,8 +638,8 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, continue; } - result = cfg_kasp_fromconfig(kconfig, NULL, true, mctx, &kslist, - &kasplist, &kasp); + result = cfg_kasp_fromconfig(kconfig, NULL, options, mctx, + &kslist, &kasplist, &kasp); if (result != ISC_R_SUCCESS) { fatal("failed to configure dnssec-policy '%s': %s", cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), diff --git a/bin/named/server.c b/bin/named/server.c index bb46d6e48ce..b82ffe82cb0 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -8052,6 +8052,9 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist, isc_result_t result = ISC_R_SUCCESS; dns_kasp_t *default_kasp = NULL; const cfg_obj_t *kasps = NULL; + unsigned int kaspopts = (ISCCFG_KASPCONF_CHECK_ALGORITHMS | + ISCCFG_KASPCONF_CHECK_KEYLIST | + ISCCFG_KASPCONF_LOG_ERRORS); APPLY_CONFIGURATION_SUBROUTINE_LOG; @@ -8063,7 +8066,7 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist, cfg_obj_t *kconfig = cfg_listelt_value(element); dns_kasp_t *kasp = NULL; - result = cfg_kasp_fromconfig(kconfig, default_kasp, true, + result = cfg_kasp_fromconfig(kconfig, default_kasp, kaspopts, isc_g_mctx, keystorelist, kasplist, &kasp); if (result != ISC_R_SUCCESS) { @@ -8091,7 +8094,7 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist, cfg_obj_t *kconfig = cfg_listelt_value(element); dns_kasp_t *kasp = NULL; - result = cfg_kasp_fromconfig(kconfig, default_kasp, true, + result = cfg_kasp_fromconfig(kconfig, default_kasp, kaspopts, isc_g_mctx, keystorelist, kasplist, &kasp); if (result != ISC_R_SUCCESS) { diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 999e08ddcba..eeb97b95ef0 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -31,6 +31,7 @@ #include #include +#include #include /* For storing a list of digest types */ @@ -137,6 +138,8 @@ struct dns_kasp { #define DNS_KASP_KEY_ROLE_KSK 0x01 #define DNS_KASP_KEY_ROLE_ZSK 0x02 +#define DNS_KASP_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + 64) + void dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp); /*%< @@ -764,6 +767,17 @@ dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey); *\li False, otherwise. */ +void +dns_kasp_key_format(dns_kasp_key_t *key, char *cp, unsigned int size); +/*%< + * Write the identifying information about the policy key (role, + * algorithm, tag range) into a string 'cp' of size 'size'. + * Requires: + * + *\li key != NULL + *\li cp != NULL + */ + bool dns_kasp_nsec3(dns_kasp_t *kasp); /*%< diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h index b8cbf2d3552..8fcc6dbfbbb 100644 --- a/lib/dns/include/dns/keymgr.h +++ b/lib/dns/include/dns/keymgr.h @@ -37,6 +37,21 @@ dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first); *\li 'kasp' is a valid DNSSEC policy. */ +void +dns_keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now, + bool csk); +/* + * Initialize this key's properties if not already present. A key created + * and derived from a dnssec-policy will have the required metadata available, + * otherwise these may be missing and need to be initialized. The key states + * will be initialized according to existing timing metadata. If 'csk' is + * set to true, the key is considered a combined signing key (CSK). + * + * Requires: + *\li 'key' is a valid DNSSEC key. + *\li 'kasp' is a valid DNSSEC policy. + */ + isc_result_t dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, isc_mem_t *mctx, dns_dnsseckeylist_t *keyring, diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 884e3e33ec8..76a59c225d0 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -559,6 +559,21 @@ dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey) { return true; } +void +dns_kasp_key_format(dns_kasp_key_t *key, char *cp, unsigned int size) { + REQUIRE(key != NULL); + REQUIRE(cp != NULL); + + char algstr[DNS_NAME_FORMATSIZE]; + bool csk = dns_kasp_key_ksk(key) && dns_kasp_key_zsk(key); + const char *rolestr = (csk ? "csk" + : (dns_kasp_key_ksk(key) ? "ksk" : "zsk")); + + dst_algorithm_format(key->algorithm, algstr, sizeof(algstr)); + snprintf(cp, size, "%s algorithm:%s length:%u tag-range:%u-%u", rolestr, + algstr, dns_kasp_key_size(key), key->tag_min, key->tag_max); +} + uint8_t dns_kasp_nsec3iter(dns_kasp_t *kasp) { REQUIRE(kasp != NULL); diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index 79ac71f8353..bb1f831673a 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -1635,16 +1635,9 @@ transition: return result; } -/* - * See if this key needs to be initialized with properties. A key created - * and derived from a dnssec-policy will have the required metadata available, - * otherwise these may be missing and need to be initialized. The key states - * will be initialized according to existing timing metadata. - * - */ -static void -keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now, - bool csk) { +void +dns_keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now, + bool csk) { bool ksk, zsk; isc_result_t ret; isc_stdtime_t active = 0, pub = 0, syncpub = 0, retire = 0, remove = 0; @@ -1926,7 +1919,7 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key, dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp)); dst_key_settime(dst_key, DST_TIME_CREATED, now); dns_dnsseckey_create(mctx, &dst_key, &new_key); - keymgr_key_init(new_key, kasp, now, csk); + dns_keymgr_key_init(new_key, kasp, now, csk); keycreated = true; } dst_key_setnum(new_key->key, DST_NUM_LIFETIME, lifetime); @@ -2174,7 +2167,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, ISC_LIST_FOREACH(*keyring, dkey, link) { bool found_match = false; - keymgr_key_init(dkey, kasp, now, numkeys == 1); + dns_keymgr_key_init(dkey, kasp, now, numkeys == 1); ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) { if (dns_kasp_key_match(kkey, dkey)) { @@ -2785,7 +2778,7 @@ dns_keymgr_offline(const dns_name_t *origin, dns_dnsseckeylist_t *keyring, continue; } - keymgr_key_init(dkey, kasp, now, false); + dns_keymgr_key_init(dkey, kasp, now, false); /* Get current metadata */ RETERR(dst_key_getstate(dkey->key, DST_KEY_DNSKEY, diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 3d03d1e10cc..385a85e6cc5 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1397,6 +1398,11 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, if (obj != NULL) { bool bad_kasp = false; bool bad_name = false; + unsigned int kaspopts = (ISCCFG_KASPCONF_CHECK_KEYLIST | + ISCCFG_KASPCONF_LOG_ERRORS); + if (check_algorithms) { + kaspopts |= ISCCFG_KASPCONF_CHECK_ALGORITHMS; + } if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) { bad_kasp = true; @@ -1422,8 +1428,8 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, } ret = cfg_kasp_fromconfig( - kconfig, NULL, check_algorithms, - mctx, &kslist, &list, &kasp); + kconfig, NULL, kaspopts, mctx, + &kslist, &list, &kasp); if (ret != ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) { result = ret; @@ -2815,29 +2821,29 @@ cleanup: static isc_result_t check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, - dns_name_t *zname, const char *name, const char *keydir, - isc_symtab_t *keydirs, isc_mem_t *mctx) { + dns_name_t *origin, const char *zname, const char *name, + const char *keydir, isc_symtab_t *keydirs, isc_mem_t *mctx, + bool check_keys) { const char *dir = keydir; isc_result_t ret, result = ISC_R_SUCCESS; - bool do_cleanup = false; bool done = false; bool keystore = false; const cfg_obj_t *kasps = NULL; + const cfg_obj_t *kaspobj = NULL; dns_kasp_t *kasp = NULL; dns_kasplist_t kasplist; const cfg_obj_t *keystores = NULL; dns_keystorelist_t kslist; + isc_time_t timenow; + isc_stdtime_t now; + + timenow = isc_time_now(); + now = isc_time_seconds(&timenow); - /* If no dnssec-policy or key-store, use the dir (key-directory) */ (void)cfg_map_get(config, "dnssec-policy", &kasps); (void)cfg_map_get(config, "key-store", &keystores); - if (kasps == NULL || keystores == NULL) { - goto check; - } - ISC_LIST_INIT(kasplist); ISC_LIST_INIT(kslist); - do_cleanup = true; /* * Build the keystore list. @@ -2854,7 +2860,7 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, */ CFG_LIST_FOREACH(kasps, element) { cfg_obj_t *kconfig = cfg_listelt_value(element); - const cfg_obj_t *kaspobj = NULL; + kaspobj = NULL; if (!cfg_obj_istuple(kconfig)) { continue; @@ -2865,7 +2871,7 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, continue; } - ret = cfg_kasp_fromconfig(kconfig, NULL, false, mctx, &kslist, + ret = cfg_kasp_fromconfig(kconfig, NULL, 0, mctx, &kslist, &kasplist, &kasp); if (ret != ISC_R_SUCCESS) { kasp = NULL; @@ -2876,6 +2882,7 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, if (kasp == NULL) { goto check; } + INSIST(kaspobj != NULL); /* Check key-stores of keys */ dns_kasp_freeze(kasp); @@ -2888,35 +2895,116 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, ret = keydirexist(zconfig, keystore ? "key-store directory" : "key-directory", - zname, dir, name, keydirs, mctx); + origin, dir, name, keydirs, mctx); + if (ret != ISC_R_SUCCESS) { + result = ret; + } + } + + if (check_keys) { + /* Find matching key files. */ + dns_dnsseckeylist_t keys; + int numkaspkeys = 0; + int numkeyfiles = 0; + + ISC_LIST_INIT(keys); + ret = dns_dnssec_findmatchingkeys(origin, kasp, keydir, &kslist, + now, mctx, &keys); if (ret != ISC_R_SUCCESS) { result = ret; } + + ISC_LIST_FOREACH(keys, dkey, link) { + numkeyfiles++; + } + + ISC_LIST_FOREACH(keys, dkey, link) { + bool found_match = false; + + dns_keymgr_key_init(dkey, kasp, now, numkeyfiles == 1); + + ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) { + if (dns_kasp_key_match(kkey, dkey)) { + found_match = true; + break; + } + } + + if (!found_match) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, + sizeof(keystr)); + cfg_obj_log(kaspobj, ISC_LOG_ERROR, + "zone '%s': key file '%s' does not " + "match dnssec-policy %s", + zname, keystr, + dns_kasp_getname(kasp)); + result = ISC_R_NOTFOUND; + } + } + + ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) { + bool found_match = false; + + numkaspkeys++; + + ISC_LIST_FOREACH(keys, dkey, link) { + if (dns_kasp_key_match(kkey, dkey)) { + found_match = true; + break; + } + } + + if (!found_match) { + char keystr[DNS_KASP_KEY_FORMATSIZE]; + dns_kasp_key_format(kkey, keystr, + sizeof(keystr)); + + cfg_obj_log( + kaspobj, ISC_LOG_ERROR, + "zone '%s': no key file found matching " + "dnssec-policy %s key:'%s'", + zname, dns_kasp_getname(kasp), keystr); + result = ISC_R_NOTFOUND; + } + } + + if (numkaspkeys != numkeyfiles) { + cfg_obj_log(kaspobj, ISC_LOG_ERROR, + "zone '%s': wrong number of key files (%d, " + "expected %d)", + zname, numkeyfiles, numkaspkeys); + result = ISC_R_FAILURE; + } + + ISC_LIST_FOREACH(keys, key, link) { + ISC_LIST_UNLINK(keys, key, link); + dns_dnsseckey_destroy(mctx, &key); + } } + dns_kasp_thaw(kasp); done = true; check: if (!done) { - ret = keydirexist(zconfig, "key-directory", zname, dir, name, + ret = keydirexist(zconfig, "key-directory", origin, dir, name, keydirs, mctx); if (ret != ISC_R_SUCCESS) { result = ret; } } - if (do_cleanup) { - if (kasp != NULL) { - dns_kasp_detach(&kasp); - } - ISC_LIST_FOREACH(kasplist, k, link) { - ISC_LIST_UNLINK(kasplist, k, link); - dns_kasp_detach(&k); - } - ISC_LIST_FOREACH(kslist, ks, link) { - ISC_LIST_UNLINK(kslist, ks, link); - dns_keystore_detach(&ks); - } + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + ISC_LIST_FOREACH(kasplist, k, link) { + ISC_LIST_UNLINK(kasplist, k, link); + dns_kasp_detach(&k); + } + ISC_LIST_FOREACH(kslist, ks, link) { + ISC_LIST_UNLINK(kslist, ks, link); + dns_keystore_detach(&ks); } return result; @@ -3046,6 +3134,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, bool has_dnssecpolicy = false; bool kasp_inlinesigning = false; bool inline_signing = false; + bool check_keys = (flags & BIND_CHECK_KEYS) != 0; const void *clauses = NULL; const char *option = NULL; const char *kaspname = NULL; @@ -3821,8 +3910,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ if (zname != NULL && keydirs != NULL) { if (has_dnssecpolicy) { - tresult = check_keydir(config, zconfig, zname, kaspname, - dir, keydirs, mctx); + tresult = check_keydir(config, zconfig, zname, znamestr, + kaspname, dir, keydirs, mctx, + check_keys); } else { tresult = keydirexist(zconfig, "key-directory", zname, dir, kaspname, keydirs, mctx); diff --git a/lib/isccfg/include/isccfg/check.h b/lib/isccfg/include/isccfg/check.h index f3980204d20..622308a012a 100644 --- a/lib/isccfg/include/isccfg/check.h +++ b/lib/isccfg/include/isccfg/check.h @@ -41,6 +41,11 @@ * Check the dnssec-policy DNSSEC algorithms against those * supported by the crypto provider. */ +#define BIND_CHECK_KEYS 0x00000004 +/*%< + * Check the dnssec-policy DNSSEC keys against the key files + * in the key stores. + */ isc_result_t isccfg_check_namedconf(const cfg_obj_t *config, unsigned int flags, diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h index 0a2e501bc76..14434f3e3a9 100644 --- a/lib/isccfg/include/isccfg/kaspconf.h +++ b/lib/isccfg/include/isccfg/kaspconf.h @@ -15,13 +15,17 @@ #include +#define ISCCFG_KASPCONF_CHECK_ALGORITHMS 0x01 +#define ISCCFG_KASPCONF_CHECK_KEYLIST 0x02 +#define ISCCFG_KASPCONF_LOG_ERRORS 0x04 + /*** *** Functions ***/ isc_result_t cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, - bool check_algorithms, isc_mem_t *mctx, + unsigned int options, isc_mem_t *mctx, dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist, dns_kasp_t **kaspp); /*%< @@ -34,8 +38,16 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, * * The 'keystorelist' is where to lookup key stores if KASP keys are using them. * - * If 'check_algorithms' is true then the dnssec-policy DNSSEC key - * algorithms are checked against those supported by the crypto provider. + * If 'options' has ISCCFG_KASPCONF_CHECK_ALGORITHMS set, then the dnssec-policy + * DNSSEC key algorithms are checked against those supported by the crypto + * provider. + * + * If 'options' has ISCCFG_KASPCONF_CHECK_KEYLIST set, then this function + * insists that the key list is not empty, unless the policy is "insecure" + * (then the key list must be empty). + * + * If 'options' has ISCCFG_KASPCONF_LOG_ERRORS set, then configuration errors + * and warnings are logged to the global logging context. * * Requires: * diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index d83e0af46b5..bee50de81e1 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -114,7 +114,7 @@ get_string(const cfg_obj_t **maps, const char *option) { */ static isc_result_t cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, - bool check_algorithms, bool offline_ksk, + bool check_algorithms, bool log_errors, bool offline_ksk, dns_keystorelist_t *keystorelist, uint32_t ksk_min_lifetime, uint32_t zsk_min_lifetime) { isc_result_t result; @@ -151,10 +151,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, key->role |= DNS_KASP_KEY_ROLE_ZSK; } else if (strcmp(rolestr, "csk") == 0) { if (offline_ksk) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: csk keys are not " - "allowed when offline-ksk is enabled"); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: csk keys " + "are not " + "allowed when offline-ksk " + "is enabled"); + } result = ISC_R_FAILURE; goto cleanup; } @@ -172,14 +175,20 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, result = dns_keystorelist_find(keystorelist, keydir, &key->keystore); if (result == ISC_R_NOTFOUND) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: keystore %s does not exist", - keydir); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: keystore %s does " + "not exist", + keydir); + } result = ISC_R_FAILURE; goto cleanup; } else if (result != ISC_R_SUCCESS) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: bad keystore %s", keydir); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: bad keystore %s", + keydir); + } result = ISC_R_FAILURE; goto cleanup; } @@ -192,9 +201,12 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, } if (key->lifetime > 0) { if (key->lifetime < 30 * (24 * 3600)) { - cfg_obj_log(obj, ISC_LOG_WARNING, - "dnssec-policy: key lifetime is " - "shorter than 30 days"); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_WARNING, + "dnssec-policy: key " + "lifetime is " + "shorter than 30 days"); + } } if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 && key->lifetime <= ksk_min_lifetime) @@ -207,10 +219,14 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, error = true; } if (error) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: key lifetime is " - "shorter than the time it takes to " - "do a rollover"); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: key " + "lifetime is " + "shorter than the time it " + "takes to " + "do a rollover"); + } result = ISC_R_FAILURE; goto cleanup; } @@ -222,9 +238,11 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, result = dst_algorithm_fromtext(&key->algorithm, (isc_textregion_t *)&alg); if (result != ISC_R_SUCCESS) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: bad algorithm %s", - alg.base); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: bad algorithm %s", + alg.base); + } result = DNS_R_BADALG; goto cleanup; } @@ -232,10 +250,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, (key->algorithm == DST_ALG_RSASHA1 || key->algorithm == DST_ALG_NSEC3RSASHA1)) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: algorithm %s not supported " - "in FIPS mode", - alg.base); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: algorithm %s not " + "supported " + "in FIPS mode", + alg.base); + } result = DNS_R_BADALG; goto cleanup; } @@ -243,9 +264,12 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, if (check_algorithms && !dst_algorithm_supported(key->algorithm)) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: algorithm %s not supported", - alg.base); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: algorithm %s not " + "supported", + alg.base); + } result = DNS_R_BADALG; goto cleanup; } @@ -253,10 +277,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, switch (key->algorithm) { case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: - cfg_obj_log(obj, ISC_LOG_WARNING, - "dnssec-policy: DNSSEC algorithm %s is " - "deprecated", - alg.base); + if (log_errors) { + cfg_obj_log( + obj, ISC_LOG_WARNING, + "dnssec-policy: DNSSEC algorithm %s is " + "deprecated", + alg.base); + } break; default: break; @@ -280,11 +307,15 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, min = DST_ALG_RSASHA512 ? 1024 : 512; } if (size < min || size > 4096) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: key with " - "algorithm %s has invalid " - "key length %u", - alg.base, size); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: " + "key with " + "algorithm %s has " + "invalid " + "key length %u", + alg.base, size); + } result = ISC_R_RANGE; goto cleanup; } @@ -293,11 +324,15 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, case DST_ALG_ECDSA384: case DST_ALG_ED25519: case DST_ALG_ED448: - cfg_obj_log(obj, ISC_LOG_WARNING, - "dnssec-policy: key algorithm %s " - "has predefined length; ignoring " - "length value %u", - alg.base, size); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_WARNING, + "dnssec-policy: key " + "algorithm %s " + "has predefined length; " + "ignoring " + "length value %u", + alg.base, size); + } default: break; } @@ -311,25 +346,31 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, obj = cfg_tuple_get(tagrange, "tag-min"); tag_min = cfg_obj_asuint32(obj); if (tag_min > 0xffff) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: tag-min " - "too big"); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: tag-min " + "too big"); + } result = ISC_R_RANGE; goto cleanup; } obj = cfg_tuple_get(tagrange, "tag-max"); tag_max = cfg_obj_asuint32(obj); if (tag_max > 0xffff) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: tag-max " - "too big"); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: tag-max " + "too big"); + } result = ISC_R_RANGE; goto cleanup; } if (tag_min >= tag_max) { - cfg_obj_log( - obj, ISC_LOG_ERROR, - "dnssec-policy: tag-min >= tag_max"); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: tag-min >= " + "tag_max"); + } result = ISC_R_RANGE; goto cleanup; } @@ -348,7 +389,8 @@ cleanup: } static isc_result_t -cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) { +cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, + bool log_errors) { unsigned int min_keysize = 4096; const cfg_obj_t *obj = NULL; uint32_t iter = DEFAULT_NSEC3PARAM_ITER; @@ -382,18 +424,22 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) { if (badalg > 0) { char algstr[DNS_SECALG_FORMATSIZE]; dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr)); - cfg_obj_log( - obj, ISC_LOG_ERROR, - "dnssec-policy: cannot use nsec3 with algorithm '%s'", - algstr); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: cannot use nsec3 with " + "algorithm '%s'", + algstr); + } return DNS_R_NSEC3BADALG; } if (iter != DEFAULT_NSEC3PARAM_ITER) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: nsec3 iterations value %u " - "not allowed, must be zero", - iter); + if (log_errors) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "dnssec-policy: nsec3 iterations value %u " + "not allowed, must be zero", + iter); + } return DNS_R_NSEC3ITERRANGE; } @@ -409,9 +455,12 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) { saltlen = cfg_obj_asuint32(obj); } if (saltlen > 0xff) { - cfg_obj_log(obj, ISC_LOG_ERROR, - "dnssec-policy: nsec3 salt length %u too high", - saltlen); + if (log_errors) { + cfg_obj_log( + obj, ISC_LOG_ERROR, + "dnssec-policy: nsec3 salt length %u too high", + saltlen); + } return DNS_R_NSEC3SALTRANGE; } @@ -420,7 +469,7 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) { } static isc_result_t -add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) { +add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest, bool log_errors) { isc_result_t result = ISC_R_SUCCESS; isc_textregion_t r; dns_dsdigest_t alg; @@ -430,21 +479,28 @@ add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) { r.length = strlen(str); result = dns_dsdigest_fromtext(&alg, &r); if (result != ISC_R_SUCCESS) { - cfg_obj_log(digest, ISC_LOG_ERROR, - "dnssec-policy: bad cds digest-type %s", str); + if (log_errors) { + cfg_obj_log(digest, ISC_LOG_ERROR, + "dnssec-policy: bad cds digest-type %s", + str); + } result = DNS_R_BADALG; } else if (!dst_ds_digest_supported(alg)) { - cfg_obj_log(digest, ISC_LOG_ERROR, - "dnssec-policy: unsupported cds " - "digest-type %s", - str); + if (log_errors) { + cfg_obj_log(digest, ISC_LOG_ERROR, + "dnssec-policy: unsupported cds " + "digest-type %s", + str); + } result = DST_R_UNSUPPORTEDALG; } else { if (alg == DNS_DSDIGEST_SHA1) { - cfg_obj_log( - digest, ISC_LOG_WARNING, - "dnssec-policy: deprecated CDS digest-type %s", - str); + if (log_errors) { + cfg_obj_log(digest, ISC_LOG_WARNING, + "dnssec-policy: deprecated CDS " + "digest-type %s", + str); + } } dns_kasp_adddigest(kasp, alg); } @@ -453,7 +509,7 @@ add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) { isc_result_t cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, - bool check_algorithms, isc_mem_t *mctx, + unsigned int options, isc_mem_t *mctx, dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist, dns_kasp_t **kaspp) { isc_result_t result; @@ -474,6 +530,10 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, uint32_t ipub = 0, iret = 0; uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0; bool offline_ksk = false, manual_mode = false; + bool check_algorithms = (options & ISCCFG_KASPCONF_CHECK_ALGORITHMS) != + 0; + bool check_keylist = (options & ISCCFG_KASPCONF_CHECK_KEYLIST) != 0; + bool log_errors = (options & ISCCFG_KASPCONF_LOG_ERRORS) != 0; REQUIRE(config != NULL); REQUIRE(kaspp != NULL && *kaspp == NULL); @@ -487,10 +547,12 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, result = dns_kasplist_find(kasplist, kaspname, &kasp); if (result == ISC_R_SUCCESS) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: duplicately named policy found '%s'", - kaspname); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: duplicately named policy " + "found '%s'", + kaspname); + } dns_kasp_detach(&kasp); return ISC_R_EXISTS; } @@ -526,42 +588,51 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, sigvalidity = get_duration(maps, "signatures-validity-dnskey", DNS_KASP_SIG_VALIDITY_DNSKEY); if (sigrefresh >= (sigvalidity * 0.9)) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: policy '%s' signatures-refresh must be " - "at most 90%% of the signatures-validity-dnskey", - kaspname); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' " + "signatures-refresh must be " + "at most 90%% of the " + "signatures-validity-dnskey", + kaspname); + } result = ISC_R_FAILURE; } dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity); if (sigjitter > sigvalidity) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: policy '%s' signatures-jitter cannot " - "be larger than signatures-validity-dnskey", - kaspname); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' " + "signatures-jitter cannot " + "be larger than signatures-validity-dnskey", + kaspname); + } result = ISC_R_FAILURE; } sigvalidity = get_duration(maps, "signatures-validity", DNS_KASP_SIG_VALIDITY); if (sigrefresh >= (sigvalidity * 0.9)) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: policy '%s' signatures-refresh must be " - "at most 90%% of the signatures-validity", - kaspname); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' " + "signatures-refresh must be " + "at most 90%% of the signatures-validity", + kaspname); + } result = ISC_R_FAILURE; } dns_kasp_setsigvalidity(kasp, sigvalidity); if (sigjitter > sigvalidity) { - cfg_obj_log( - config, ISC_LOG_ERROR, - "dnssec-policy: policy '%s' signatures-jitter cannot " - "be larger than signatures-validity", - kaspname); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: policy '%s' " + "signatures-jitter cannot " + "be larger than signatures-validity", + kaspname); + } result = ISC_R_FAILURE; } @@ -619,7 +690,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, (void)confget(maps, "cds-digest-types", &cds); if (cds != NULL) { CFG_LIST_FOREACH(cds, element) { - result = add_digest(kasp, cfg_listelt_value(element)); + result = add_digest(kasp, cfg_listelt_value(element), + log_errors); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -658,14 +730,16 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, CFG_LIST_FOREACH(keys, element) { cfg_obj_t *kobj = cfg_listelt_value(element); result = cfg_kaspkey_fromconfig( - kobj, kasp, check_algorithms, offline_ksk, - keystorelist, ksk_min_lifetime, + kobj, kasp, check_algorithms, log_errors, + offline_ksk, keystorelist, ksk_min_lifetime, zsk_min_lifetime); if (result != ISC_R_SUCCESS) { - cfg_obj_log(kobj, ISC_LOG_ERROR, - "dnssec-policy: failed to " - "configure keys (%s)", - isc_result_totext(result)); + if (log_errors) { + cfg_obj_log(kobj, ISC_LOG_ERROR, + "dnssec-policy: failed to " + "configure keys (%s)", + isc_result_totext(result)); + } goto cleanup; } } @@ -698,19 +772,22 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, if (role[i] != (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK)) { - cfg_obj_log(keys, ISC_LOG_ERROR, - "dnssec-policy: algorithm %zu " - "requires both KSK and ZSK roles", - i); + if (log_errors) { + cfg_obj_log(keys, ISC_LOG_ERROR, + "dnssec-policy: algorithm " + "%zu requires both KSK and " + "ZSK roles", + i); + } result = ISC_R_FAILURE; } - if (warn[i][0]) { + if (warn[i][0] && log_errors) { cfg_obj_log(keys, ISC_LOG_WARNING, "dnssec-policy: algorithm %zu has " "multiple keys with ZSK role", i); } - if (warn[i][1]) { + if (warn[i][1] && log_errors) { cfg_obj_log(keys, ISC_LOG_WARNING, "dnssec-policy: algorithm %zu has " "multiple keys with KSK role", @@ -744,20 +821,22 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, keystorelist, DNS_KEYSTORE_KEYDIRECTORY, &new_key->keystore); if (result != ISC_R_SUCCESS) { - cfg_obj_log(config, ISC_LOG_ERROR, - "dnssec-policy: failed to " - "find keystore (%s)", - isc_result_totext(result)); + if (log_errors) { + cfg_obj_log(config, ISC_LOG_ERROR, + "dnssec-policy: failed to " + "find keystore (%s)", + isc_result_totext(result)); + } goto cleanup; } dns_kasp_addkey(kasp, new_key); } } - if (strcmp(kaspname, "insecure") == 0) { + if (strcmp(kaspname, "insecure") == 0 && check_keylist) { /* "dnssec-policy insecure": key list must be empty */ INSIST(dns_kasp_keylist_empty(kasp)); - } else if (default_kasp != NULL) { + } else if (default_kasp != NULL && check_keylist) { /* There must be keys configured. */ INSIST(!(dns_kasp_keylist_empty(kasp))); } @@ -775,7 +854,7 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, } } else { dns_kasp_setnsec3(kasp, true); - result = cfg_nsec3param_fromconfig(nsec3, kasp); + result = cfg_nsec3param_fromconfig(nsec3, kasp, log_errors); if (result != ISC_R_SUCCESS) { goto cleanup; }