diff --git a/bin/check/check-tool.c b/bin/check/check-tool.c index b7ca8a8936..68abccbb13 100644 --- a/bin/check/check-tool.c +++ b/bin/check/check-tool.c @@ -91,6 +91,7 @@ dns_zoneopt_t zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_CHECKMX | #endif /* if CHECK_SIBLING */ DNS_ZONEOPT_CHECKSVCB | DNS_ZONEOPT_CHECKWILDCARD | DNS_ZONEOPT_WARNMXCNAME | DNS_ZONEOPT_WARNSRVCNAME; +dns_zoneopt_t zonemd_options; static isc_symtab_t *symtab = NULL; @@ -614,7 +615,9 @@ setup_logging(FILE *errout) { logconfig, "default_stderr", ISC_LOG_TOFILEDESC, ISC_LOG_DYNAMIC, ISC_LOGDESTINATION_FILE(errout), 0, ISC_LOGCATEGORY_DEFAULT, ISC_LOGMODULE_DEFAULT); - + if (debug > 1) { + isc_log_setdebuglevel(debug - 1); + } return ISC_R_SUCCESS; } @@ -666,6 +669,7 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_zone_setclass(zone, rdclass); dns_zone_setoption(zone, zone_options, true); dns_zone_setoption(zone, DNS_ZONEOPT_NOMERGE, nomerge); + dns_zone_setoption(zone, zonemd_options, true); dns_zone_setmaxttl(zone, maxttl); diff --git a/bin/check/check-tool.h b/bin/check/check-tool.h index 7e8f946268..fadc514a93 100644 --- a/bin/check/check-tool.h +++ b/bin/check/check-tool.h @@ -24,6 +24,7 @@ #include #include #include +#include isc_result_t setup_logging(FILE *errout); @@ -45,3 +46,4 @@ extern bool docheckmx; extern bool docheckns; extern bool dochecksrv; extern dns_zoneopt_t zone_options; +extern dns_zoneopt_t zonemd_options; diff --git a/bin/check/named-checkzone.c b/bin/check/named-checkzone.c index 98a18ad78b..c78c686cf3 100644 --- a/bin/check/named-checkzone.c +++ b/bin/check/named-checkzone.c @@ -66,7 +66,7 @@ usage(void); static void usage(void) { fprintf(stderr, - "usage: %s [-djqvD] [-c class] " + "usage: %s [-djqvDz] [-c class] " "[-f inputformat] [-F outputformat] [-J filename] " "[-s (full|relative)] [-t directory] [-w directory] " "[-k (ignore|warn|fail)] [-m (ignore|warn|fail)] " @@ -136,8 +136,8 @@ main(int argc, char **argv) { isc_commandline_errprint = false; while ((c = isc_commandline_parse(argc, argv, - "c:df:hi:jJ:k:L:l:m:n:qr:s:t:o:vw:C:" - "DF:M:R:S:T:W:")) != EOF) + "c:df:hi:jJ:k:L:l:m:n:qr:s:t:o:vw:z:" + "C:DF:M:R:S:T:W:")) != EOF) { switch (c) { case 'c': @@ -414,6 +414,18 @@ main(int argc, char **argv) { } break; + case 'z': + if (ARGCMP("fail")) { + zone_options |= DNS_ZONEOPT_ZONEMD_CHECK; + } else if (ARGCMP("ignore")) { + zone_options &= ~DNS_ZONEOPT_ZONEMD_CHECK; + } else { + fprintf(stderr, "invalid argument to -Z: %s\n", + isc_commandline_argument); + exit(EXIT_FAILURE); + } + break; + case '?': if (isc_commandline_option != '?') { fprintf(stderr, "%s: invalid argument -%c\n", diff --git a/bin/check/named-checkzone.rst b/bin/check/named-checkzone.rst index a58cfb7a14..5ea54713c2 100644 --- a/bin/check/named-checkzone.rst +++ b/bin/check/named-checkzone.rst @@ -23,7 +23,7 @@ named-checkzone - zone file validation tool Synopsis ~~~~~~~~ -:program:`named-checkzone` [**-d**] [**-h**] [**-j**] [**-q**] [**-v**] [**-c** class] [**-C** mode] [**-f** format] [**-F** format] [**-J** filename] [**-i** mode] [**-k** mode] [**-m** mode] [**-M** mode] [**-n** mode] [**-l** ttl] [**-L** serial] [**-o** filename] [**-r** mode] [**-R** mode] [**-s** style] [**-S** mode] [**-t** directory] [**-T** mode] [**-w** directory] [**-D**] [**-W** mode] {zonename} {filename} +:program:`named-checkzone` [**-d**] [**-h**] [**-j**] [**-q**] [**-v**] [**-c** class] [**-C** mode] [**-f** format] [**-F** format] [**-J** filename] [**-i** mode] [**-k** mode] [**-m** mode] [**-M** mode] [**-n** mode] [**-l** ttl] [**-L** serial] [**-o** filename] [**-r** mode] [**-R** mode] [**-s** style] [**-S** mode] [**-t** directory] [**-T** mode] [**-w** directory] [**-D**] [**-W** mode] [**-z** mode] {zonename} {filename} Description ~~~~~~~~~~~ @@ -220,6 +220,12 @@ Options wildcard matching algorithm (:rfc:`4592`). Possible modes are ``warn`` (the default) and ``ignore``. +.. option:: -z mode + + This option specifies whether to check the contents of a zone against + the cryptographic hash in a ZONEMD record (:rfc:`8976`). + Possible modes are ``fail`` and ``ignore`` (the default). + .. option:: zonename This indicates the domain name of the zone being checked. diff --git a/bin/check/named-compilezone.rst b/bin/check/named-compilezone.rst index c067826194..5bd3099dff 100644 --- a/bin/check/named-compilezone.rst +++ b/bin/check/named-compilezone.rst @@ -23,7 +23,7 @@ named-compilezone - zone file converting tool Synopsis ~~~~~~~~ -:program:`named-compilezone` [**-d**] [**-h**] [**-j**] [**-q**] [**-v**] [**-c** class] [**-C** mode] [**-f** format] [**-F** format] [**-J** filename] [**-i** mode] [**-k** mode] [**-m** mode] [**-M** mode] [**-n** mode] [**-l** ttl] [**-L** serial] [**-r** mode] [**-R** mode] [**-s** style] [**-S** mode] [**-t** directory] [**-T** mode] [**-w** directory] [**-D**] [**-W** mode] {**-o** filename} {zonename} {filename} +:program:`named-compilezone` [**-d**] [**-h**] [**-j**] [**-q**] [**-v**] [**-c** class] [**-C** mode] [**-f** format] [**-F** format] [**-J** filename] [**-i** mode] [**-k** mode] [**-m** mode] [**-M** mode] [**-n** mode] [**-l** ttl] [**-L** serial] [**-r** mode] [**-R** mode] [**-s** style] [**-S** mode] [**-t** directory] [**-T** mode] [**-w** directory] [**-D**] [**-W** mode] [**-z** mode] {**-o** filename} {zonename} {filename} Description ~~~~~~~~~~~ @@ -224,6 +224,18 @@ Options wildcard matching algorithm (:rfc:`4592`). Possible modes are ``warn`` and ``ignore`` (the default). +.. option:: -z option + + This option specifies whether to check the contents of a zone against + the cryptographic hash in a ZONEMD record (:rfc:`8976`). This is + off by default. Up to four options can be active simultaneously; + these options are: ``check`` (check the contents of the zone against + the ZONEMD hash if a ZONEMD record is present), ``required`` (reject + a zone if ZONEMD is *not* present), ``dnssec-only`` (reject a zone if + ZONEMD is present but the zone is not DNSSEC-signed), and + ``accept-expired`` (allow expired RRSIG records when verifying the + ZONEMD hash). + .. option:: zonename This indicates the domain name of the zone being checked. diff --git a/bin/named/config.c b/bin/named/config.c index cb58713c8a..5f8bcec5a0 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -256,6 +256,9 @@ options {\n\ try-tcp-refresh yes; /* BIND 8 compat */\n\ zero-no-soa-ttl yes;\n\ zone-statistics terse;\n\ + check-zonemd yes;\n\ + check-zonemd-dnssec yes;\n\ + require-zonemd no;\n\ };\n\ " diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index beba0aba46..15ea43a692 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1494,6 +1494,24 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, dns_zone_setrad(zone, rad); } } + + obj = NULL; + result = named_config_get(maps, "check-zonemd", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEMD_CHECK, + cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "check-zonemd-dnssec", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEMD_DNSSEC, + cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "require-zonemd", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEMD_REQUIRED, + cfg_obj_asboolean(obj)); } else if (ztype == dns_zone_redirect) { dns_zone_setnotifytype(zone, dns_notifytype_no); diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 0592c64bf7..f4d68c2abc 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -2725,6 +2725,31 @@ Boolean Options the ``dohpath`` parameter exists when the ``alpn`` indicates that it should be present. The default is ``yes``. +.. namedconf:statement:: check-zonemd + :tags: zone + :short: Specifies whether to check zone contents against the ZONEMD record. + + This option indicates that a zone's contents should be checked + against the cryptographic hash in a ZONEMD record, if such a record + is present, when it is loaded or transferred. This helps to ensure + integrity of the zone data. The default is ``yes``. + +.. namedconf:statement:: check-zonemd-dnssec + :tags: zone + :short: Require DNSSEC signatures for ZONEMD records. + + When this option is set to the default value of ``yes``, zones + containing ZONEMD records will be rejected if they are not + DNSSEC-signed. (Note that this is not optional for zones of type + ``mirror``: if the option is disabled, it will be silently ignored.) + +.. namedconf:statement:: require-zonemd + :tags: zone + :short: Controls whether a ZONEMD record is required to be present in the zone. + + This indicates that a ZONEMD record **must** be present in a + zone when it is loaded or transferred. The default is ``no``. + .. namedconf:statement:: zero-no-soa-ttl :tags: zone, query, server :short: Specifies whether to set the time to live (TTL) of the SOA record to zero, when returning authoritative negative responses to SOA queries. @@ -6829,17 +6854,18 @@ Zone Types :short: Contains a DNSSEC-validated duplicate of the main data for a zone. A mirror zone is similar to a zone of :any:`type secondary`, except its - data is subject to DNSSEC validation before being used in answers. - Validation is applied to the entire zone during the zone transfer - process, and again when the zone file is loaded from disk upon - restarting :iscman:`named`. If validation of a new version of a mirror zone - fails, a retransfer is scheduled; in the meantime, the most recent - correctly validated version of that zone is used until it either - expires or a newer version validates correctly. If no usable zone - data is available for a mirror zone, due to either transfer failure - or expiration, traditional DNS recursion is used to look up the - answers instead. Mirror zones cannot be used in a view that does not - have recursion enabled. + data is subject to cryptographic validation before being used in + answers. The zone contents must be verified by a signed and + validated ZONEMD record, or else DNSSEC validation must be applied + to the entire zone, both during the zone transfer process and again when + the zone file is loaded from disk upon restarting :iscman:`named`. + If validation of a new version of a mirror zone fails, a retransfer is + scheduled; in the meantime, the most recent correctly validated version + of that zone is used until it either expires or a newer version + validates correctly. If no usable zone data is available for a mirror + zone, due to either transfer failure or expiration, traditional DNS + recursion is used to look up the answers instead. Mirror zones cannot be + used in a view that does not have recursion enabled. Answers coming from a mirror zone look almost exactly like answers from a zone of :any:`type secondary`, with the notable exceptions that @@ -7102,6 +7128,15 @@ Zone Options :any:`check-sibling` See the description of :any:`check-sibling` in :ref:`boolean_options`. +:any:`check-zonemd` + See the description of :any:`check-zonemd` in :ref:`boolean_options`. + +:any:`check-zonemd-dnssec` + See the description of :any:`check-zonemd-dnssec` in :ref:`boolean_options`. + +:any:`require-zonemd` + See the description of :any:`require-zonemd` in :ref:`boolean_options`. + :any:`zero-no-soa-ttl` See the description of :any:`zero-no-soa-ttl` in :ref:`boolean_options`. @@ -7387,6 +7422,7 @@ Zone Options :any:`send-report-channel` See the description of :any:`send-report-channel` in :namedconf:ref:`options`. + .. _zone_templates: Zone Templates diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index aa193235a7..176e8cae09 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -7,6 +7,8 @@ zone [ ] { allow-update-forwarding { ; ... }; also-notify [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; check-names ( fail | warn | ignore ); + check-zonemd ; + check-zonemd-dnssec ; database ; file ; ixfr-from-differences ; @@ -38,6 +40,7 @@ zone [ ] { request-expire ; request-ixfr ; request-ixfr-max-diffs ; + require-zonemd ; template ; transfer-source ( | * ); transfer-source-v6 ( | * ); diff --git a/doc/misc/options b/doc/misc/options index 3215fc7af7..ab74bc0e9b 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -96,6 +96,8 @@ options { check-srv-cname ( fail | warn | ignore ); check-svcb ; check-wildcard ; + check-zonemd ; + check-zonemd-dnssec ; clients-per-query ; cookie-algorithm ( siphash24 ); cookie-secret ; // may occur multiple times @@ -258,6 +260,7 @@ options { request-nsid ; request-zoneversion ; require-server-cookie ; + require-zonemd ; resolver-query-timeout ; resolver-use-dns64 ; response-padding { ; ... } block-size ; @@ -380,6 +383,8 @@ template { check-srv-cname ( fail | warn | ignore ); check-svcb ; check-wildcard ; + check-zonemd ; + check-zonemd-dnssec ; checkds ( explicit | ); database ; dlz ; @@ -431,6 +436,7 @@ template { request-expire ; request-ixfr ; request-ixfr-max-diffs ; + require-zonemd ; send-report-channel ; serial-update-method ( date | increment | unixtime ); server-addresses { ( | ); ... }; @@ -492,6 +498,8 @@ view [ ] { check-srv-cname ( fail | warn | ignore ); check-svcb ; check-wildcard ; + check-zonemd ; + check-zonemd-dnssec ; clients-per-query ; deny-answer-addresses { ; ... } [ except-from { ; ... } ]; deny-answer-aliases { ; ... } [ except-from { ; ... } ]; @@ -630,6 +638,7 @@ view [ ] { request-nsid ; request-zoneversion ; require-server-cookie ; + require-zonemd ; resolver-query-timeout ; resolver-use-dns64 ; response-padding { ; ... } block-size ; diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index dd1b94756b..e18c44789e 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -15,6 +15,8 @@ zone [ ] { check-srv-cname ( fail | warn | ignore ); check-svcb ; check-wildcard ; + check-zonemd ; + check-zonemd-dnssec ; checkds ( explicit | ); database ; dlz ; @@ -54,6 +56,7 @@ zone [ ] { parental-source ( | * ); parental-source-v6 ( | * ); provide-zoneversion ; + require-zonemd ; send-report-channel ; serial-update-method ( date | increment | unixtime ); sig-signing-nodes ; diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index 7529112a33..251a8831e1 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -7,6 +7,8 @@ zone [ ] { allow-update-forwarding { ; ... }; also-notify [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; check-names ( fail | warn | ignore ); + check-zonemd ; + check-zonemd-dnssec ; checkds ( explicit | ); database ; dlz ; @@ -55,6 +57,7 @@ zone [ ] { request-expire ; request-ixfr ; request-ixfr-max-diffs ; + require-zonemd ; send-report-channel ; sig-signing-nodes ; sig-signing-signatures ; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 193121b920..b91b1005de 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -96,14 +96,20 @@ typedef enum { DNS_ZONEOPT_LOGREPORTS = 1 << 23, /* Log error-reporting queries */ DNS_ZONEOPT_DNSKEYKSKONLY = 1 << 24, /*%< dnssec-dnskey-kskonly */ DNS_ZONEOPT_CHECKDUPRR = 1 << 25, /*%< check-dup-records */ - DNS_ZONEOPT_CHECKDUPRRFAIL = 1 << 26, /*%< fatal check-dup-records - * failures */ - DNS_ZONEOPT_CHECKSPF = 1 << 27, /*%< check SPF records */ - DNS_ZONEOPT_CHECKTTL = 1 << 28, /*%< check max-zone-ttl */ - DNS_ZONEOPT_AUTOEMPTY = 1 << 29, /*%< automatic empty zone */ - DNS_ZONEOPT_CHECKSVCB = 1 << 30, /*%< check SVBC records */ - DNS_ZONEOPT_ZONEVERSION = 1U << 31, /*%< enable zoneversion */ - DNS_ZONEOPT_FULLSIGN = 1ULL << 32, /*%< fully sign zone */ + DNS_ZONEOPT_CHECKDUPRRFAIL = 1 << 26, /*%< fatal check-dup-records + * failures */ + DNS_ZONEOPT_CHECKSPF = 1 << 27, /*%< check SPF records */ + DNS_ZONEOPT_CHECKTTL = 1 << 28, /*%< check max-zone-ttl */ + DNS_ZONEOPT_AUTOEMPTY = 1 << 29, /*%< automatic empty zone */ + DNS_ZONEOPT_CHECKSVCB = 1 << 30, /*%< check SVBC records */ + DNS_ZONEOPT_ZONEVERSION = 1U << 31, /*%< enable zoneversion */ + DNS_ZONEOPT_FULLSIGN = 1ULL << 32, /*%< fully sign zone */ + DNS_ZONEOPT_ZONEMD_CHECK = 1ULL << 33, /*%< zonemd check */ + DNS_ZONEOPT_ZONEMD_DNSSEC = 1ULL << 34, /*%< zonemd DNSSEC only */ + DNS_ZONEOPT_ZONEMD_REQUIRED = 1ULL << 35, /*%< zonemd required */ + DNS_ZONEOPT_ZONEMD_NOEXPIRED = 1ULL << 36, /*%< don't allow expired + * RRSIG when verifying + * zonemd */ DNS_ZONEOPT___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ } dns_zoneopt_t; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 67824c75e8..2aba833e08 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2410,6 +2410,12 @@ static cfg_clausedef_t zone_clauses[] = { { "zone-statistics", &cfg_type_zonestat, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT }, + { "check-zonemd", &cfg_type_boolean, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR }, + { "check-zonemd-dnssec", &cfg_type_boolean, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR }, + { "require-zonemd", &cfg_type_boolean, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR }, { NULL, NULL, 0 } };