diff --git a/CHANGES b/CHANGES index 969e8e167a..1aa0727d09 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5950. [func] Implement a feature to set an Extended DNS Error (EDE) + code on responses modified by RPZ. [GL #3410] + 5949. [func] Add new isc_loopmgr API that runs the application event loops and completely replaces the isc_app API. Refactor the isc_taskmgr, isc_timermgr and diff --git a/bin/named/server.c b/bin/named/server.c index 899b0e027a..476c408b78 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -2395,6 +2396,18 @@ configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, *old_rpz_okp = false; } + obj = cfg_tuple_get(rpz_obj, "ede"); + if (!cfg_obj_isstring(obj)) { + zone->ede = 0; + } else { + str = cfg_obj_asstring(obj); + zone->ede = dns_rpz_str2ede(str); + INSIST(zone->ede != UINT16_MAX); + } + if (*old_rpz_okp && zone->ede != old->ede) { + *old_rpz_okp = false; + } + obj = cfg_tuple_get(rpz_obj, "add-soa"); if (cfg_obj_isvoid(obj)) { zone->addsoa = add_soa_default; diff --git a/bin/tests/system/checkconf/bad-rpz-ede.conf b/bin/tests/system/checkconf/bad-rpz-ede.conf new file mode 100644 index 0000000000..8d78f8df47 --- /dev/null +++ b/bin/tests/system/checkconf/bad-rpz-ede.conf @@ -0,0 +1,23 @@ +/* + * 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 "example.com." { + type primary; + file "example.com.zone"; +}; + +options { + response-policy { + zone "example.com." ede unsupported; + }; +}; diff --git a/bin/tests/system/checkconf/good-rpz-ede-none.conf b/bin/tests/system/checkconf/good-rpz-ede-none.conf new file mode 100644 index 0000000000..c0ffab91e2 --- /dev/null +++ b/bin/tests/system/checkconf/good-rpz-ede-none.conf @@ -0,0 +1,23 @@ +/* + * 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 "example.com." { + type primary; + file "example.com.zone"; +}; + +options { + response-policy { + zone "example.com." ede none; + }; +}; diff --git a/bin/tests/system/checkconf/good-rpz-ede.conf b/bin/tests/system/checkconf/good-rpz-ede.conf new file mode 100644 index 0000000000..616f9ee63e --- /dev/null +++ b/bin/tests/system/checkconf/good-rpz-ede.conf @@ -0,0 +1,23 @@ +/* + * 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 "example.com." { + type primary; + file "example.com.zone"; +}; + +options { + response-policy { + zone "example.com." ede filtered; + }; +}; diff --git a/bin/tests/system/rpz/ns3/named.conf.in b/bin/tests/system/rpz/ns3/named.conf.in index aafe7f20f4..b0f6804bd2 100644 --- a/bin/tests/system/rpz/ns3/named.conf.in +++ b/bin/tests/system/rpz/ns3/named.conf.in @@ -48,7 +48,7 @@ options { zone "bl-drop" policy drop; zone "bl-tcp-only" policy tcp-only; zone "bl.tld2"; - zone "manual-update-rpz"; + zone "manual-update-rpz" ede forged; zone "mixed-case-rpz"; } add-soa yes diff --git a/bin/tests/system/rpz/ns7/named.conf.in b/bin/tests/system/rpz/ns7/named.conf.in index 7effd3db41..40c385c32d 100644 --- a/bin/tests/system/rpz/ns7/named.conf.in +++ b/bin/tests/system/rpz/ns7/named.conf.in @@ -26,7 +26,7 @@ options { dnssec-validation yes; response-policy { - zone "policy2" add-soa no; + zone "policy2" add-soa no ede none; } qname-wait-recurse no nsip-enable yes nsdname-enable yes diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh index 9975b686cf..194527c1a8 100644 --- a/bin/tests/system/rpz/tests.sh +++ b/bin/tests/system/rpz/tests.sh @@ -848,6 +848,11 @@ EOF $PERL ../stop.pl --use-rndc --port ${CONTROLPORT} rpz ns3 restart 3 "rebuild-bl-rpz" + t=`expr $t + 1` + echo_i "checking the configured extended DNS error code (EDE) (${t})" + $DIG -p ${PORT} @$ns3 walled.tld2 > dig.out.$t + grep -F "EDE: 4 (Forged Answer)" dig.out.$t > /dev/null || setret "failed" + # reload a RPZ zone that is now deliberately broken. t=`expr $t + 1` echo_i "checking rpz failed update will keep previous rpz rules (${t})" @@ -860,6 +865,11 @@ EOF $DIG -p ${PORT} @$ns3 walled.tld2 > dig.out.$t.after grep "walled\.tld2\..*IN.*A.*10\.0\.0\.1" dig.out.$t.after > /dev/null || setret "failed" + t=`expr $t + 1` + echo_i "checking the default (unset) extended DNS error code (EDE) (${t})" + $DIG -p ${PORT} @$ns3 a6-2.tld2. A > dig.out.$t + grep -F "EDE: " dig.out.$t > /dev/null && setret "failed" + t=`expr $t + 1` echo_i "checking reload of a mixed-case RPZ zone (${t})" # First, a sanity check: the A6-2.TLD2.mixed-case-rpz RPZ record should @@ -907,20 +917,25 @@ EOF grep NXDOMAIN dig.out.${t} > /dev/null || setret "failed" t=`expr $t + 1` - echo_i "checking that "add-soa no" at rpz zone level works (${t})" + echo_i "checking that 'ede none' works same way as when \"ede\" is unset (${t})" + $DIG z.x.servfail -p ${PORT} @$ns7 > dig.out.${t} + grep -F "EDE: " dig.out.${t} > /dev/null && setret "failed" + + t=`expr $t + 1` + echo_i "checking that 'add-soa no' at rpz zone level works (${t})" $DIG z.x.servfail -p ${PORT} @$ns7 > dig.out.${t} grep SOA dig.out.${t} > /dev/null && setret "failed" if [ native = "$mode" ]; then t=`expr $t + 1` - echo_i "checking that "add-soa yes" at response-policy level works (${t})" + echo_i "checking that 'add-soa yes' at response-policy level works (${t})" $DIG walled.tld2 -p ${PORT} +noall +add @$ns3 > dig.out.${t} grep "^manual-update-rpz\..*SOA" dig.out.${t} > /dev/null || setret "failed" fi if [ native = "$mode" ]; then t=`expr $t + 1` - echo_i "checking that "add-soa unset" works (${t})" + echo_i "checking that 'add-soa unset' works (${t})" $DIG walled.tld2 -p ${PORT} +noall +add @$ns8 > dig.out.${t} grep "^manual-update-rpz\..*SOA" dig.out.${t} > /dev/null || setret "failed" fi diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index e031d6f36a..3b64f07bb4 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -5374,6 +5374,35 @@ with this zone file: example.com CNAME rpz-tcp-only. *.example.com CNAME rpz-tcp-only. +Response policy zones can be configured to set an Extended DNS Error (EDE) code +on the responses which have been modified by the response policy: + +:: + + response-policy { zone "badlist" ede filtered; }; + +The following settings are supported for the ``ede`` option: + +``none`` + No Extended DNS Error code is set (default). + +``forged`` + Extended DNS Error code 4 - Forged Answer. + +``blocked`` + Extended DNS Error code 15 - Blocked. + +``censored`` + Extended DNS Error code 16 - Censored. + +``filtered`` + Extended DNS Error code 17 - Filtered. + +``prohibited`` + Extended DNS Error code 18 - Prohibited. + +See :rfc:`8914` for more information about the Extended DNS Error codes. + RPZ can affect server performance. Each configured response policy zone requires the server to perform one to four additional database lookups before a query can be answered. For example, a DNS server with four diff --git a/doc/man/named.conf.5in b/doc/man/named.conf.5in index cbc565134c..47ef15b08e 100644 --- a/doc/man/named.conf.5in +++ b/doc/man/named.conf.5in @@ -312,7 +312,7 @@ options { resolver\-query\-timeout ; resolver\-retry\-interval ; response\-padding { ; ... } block\-size ; - response\-policy { zone [ add\-soa ] [ log ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ policy ( cname | disabled | drop | given | no\-op | nodata | nxdomain | passthru | tcp\-only ) ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ]; ... } [ add\-soa ] [ break\-dnssec ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ min\-ns\-dots ] [ nsip\-wait\-recurse ] [ nsdname\-wait\-recurse ] [ qname\-wait\-recurse ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ dnsrps\-enable ] [ dnsrps\-options { } ]; + response\-policy { zone [ add\-soa ] [ log ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ policy ( cname | disabled | drop | given | no\-op | nodata | nxdomain | passthru | tcp\-only ) ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ ede ]; ... } [ add\-soa ] [ break\-dnssec ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ min\-ns\-dots ] [ nsip\-wait\-recurse ] [ nsdname\-wait\-recurse ] [ qname\-wait\-recurse ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ dnsrps\-enable ] [ dnsrps\-options { } ]; reuseport ; root\-delegation\-only [ exclude { ; ... } ]; root\-key\-sentinel ; @@ -590,7 +590,7 @@ view [ ] { resolver\-query\-timeout ; resolver\-retry\-interval ; response\-padding { ; ... } block\-size ; - response\-policy { zone [ add\-soa ] [ log ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ policy ( cname | disabled | drop | given | no\-op | nodata | nxdomain | passthru | tcp\-only ) ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ]; ... } [ add\-soa ] [ break\-dnssec ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ min\-ns\-dots ] [ nsip\-wait\-recurse ] [ nsdname\-wait\-recurse ] [ qname\-wait\-recurse ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ dnsrps\-enable ] [ dnsrps\-options { } ]; + response\-policy { zone [ add\-soa ] [ log ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ policy ( cname | disabled | drop | given | no\-op | nodata | nxdomain | passthru | tcp\-only ) ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ ede ]; ... } [ add\-soa ] [ break\-dnssec ] [ max\-policy\-ttl ] [ min\-update\-interval ] [ min\-ns\-dots ] [ nsip\-wait\-recurse ] [ nsdname\-wait\-recurse ] [ qname\-wait\-recurse ] [ recursive\-only ] [ nsip\-enable ] [ nsdname\-enable ] [ dnsrps\-enable ] [ dnsrps\-options { } ]; root\-delegation\-only [ exclude { ; ... } ]; root\-key\-sentinel ; rrset\-order { [ class ] [ type ] [ name ] ; ... }; diff --git a/doc/misc/options b/doc/misc/options index 1681be1514..6b0609394f 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -255,7 +255,7 @@ options { resolver-query-timeout ; resolver-retry-interval ; response-padding { ; ... } block-size ; - response-policy { zone [ add-soa ] [ log ] [ max-policy-ttl ] [ min-update-interval ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only ) ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ]; ... } [ add-soa ] [ break-dnssec ] [ max-policy-ttl ] [ min-update-interval ] [ min-ns-dots ] [ nsip-wait-recurse ] [ nsdname-wait-recurse ] [ qname-wait-recurse ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ dnsrps-enable ] [ dnsrps-options { } ]; + response-policy { zone [ add-soa ] [ log ] [ max-policy-ttl ] [ min-update-interval ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only ) ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ ede ]; ... } [ add-soa ] [ break-dnssec ] [ max-policy-ttl ] [ min-update-interval ] [ min-ns-dots ] [ nsip-wait-recurse ] [ nsdname-wait-recurse ] [ qname-wait-recurse ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ dnsrps-enable ] [ dnsrps-options { } ]; reuseport ; root-delegation-only [ exclude { ; ... } ]; root-key-sentinel ; @@ -533,7 +533,7 @@ view [ ] { resolver-query-timeout ; resolver-retry-interval ; response-padding { ; ... } block-size ; - response-policy { zone [ add-soa ] [ log ] [ max-policy-ttl ] [ min-update-interval ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only ) ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ]; ... } [ add-soa ] [ break-dnssec ] [ max-policy-ttl ] [ min-update-interval ] [ min-ns-dots ] [ nsip-wait-recurse ] [ nsdname-wait-recurse ] [ qname-wait-recurse ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ dnsrps-enable ] [ dnsrps-options { } ]; + response-policy { zone [ add-soa ] [ log ] [ max-policy-ttl ] [ min-update-interval ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only ) ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ ede ]; ... } [ add-soa ] [ break-dnssec ] [ max-policy-ttl ] [ min-update-interval ] [ min-ns-dots ] [ nsip-wait-recurse ] [ nsdname-wait-recurse ] [ qname-wait-recurse ] [ recursive-only ] [ nsip-enable ] [ nsdname-enable ] [ dnsrps-enable ] [ dnsrps-options { } ]; root-delegation-only [ exclude { ; ... } ]; root-key-sentinel ; rrset-order { [ class ] [ type ] [ name ] ; ... }; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index f24d02c02d..2ddd90b270 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -4958,6 +4960,49 @@ check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj, return (result); } +static isc_result_t +check_rpz(const cfg_obj_t *rpz_obj, isc_log_t *logctx) { + const cfg_listelt_t *element; + const cfg_obj_t *obj, *nameobj, *edeobj; + const char *zonename; + isc_result_t result = ISC_R_SUCCESS, tresult; + dns_fixedname_t fixed; + dns_name_t *name = dns_fixedname_initname(&fixed); + + obj = cfg_tuple_get(rpz_obj, "zone list"); + + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + nameobj = cfg_tuple_get(obj, "zone name"); + zonename = cfg_obj_asstring(nameobj); + + tresult = dns_name_fromstring(name, zonename, 0, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", zonename); + if (result == ISC_R_SUCCESS) { + result = tresult; + continue; + } + } + + edeobj = cfg_tuple_get(obj, "ede"); + if (edeobj != NULL && cfg_obj_isstring(edeobj)) { + const char *str = cfg_obj_asstring(edeobj); + + if (dns_rpz_str2ede(str) == UINT16_MAX) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "unsupported EDE type '%s'", str); + result = ISC_R_FAILURE; + } + } + } + + return (result); +} + static isc_result_t check_catz(const cfg_obj_t *catz_obj, const char *viewname, isc_mem_t *mctx, isc_log_t *logctx) { @@ -5214,6 +5259,19 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions, } } + /* + * Check response-policy configuration. + */ + if (opts != NULL) { + obj = NULL; + if ((cfg_map_get(opts, "response-policy", &obj) == + ISC_R_SUCCESS) && + (check_rpz(obj, logctx) != ISC_R_SUCCESS)) + { + result = ISC_R_FAILURE; + } + } + /* * Check catalog-zones configuration. */ diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h index c46168d538..4bd421b22f 100644 --- a/lib/dns/include/dns/rpz.h +++ b/lib/dns/include/dns/rpz.h @@ -143,6 +143,7 @@ struct dns_rpz_zone { dns_name_t cname; /* override value for ..._CNAME */ dns_ttl_t max_policy_ttl; dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ + uint16_t ede; /* Extended DNS Error */ uint32_t min_update_interval; /* minimal interval between * updates */ @@ -379,6 +380,9 @@ dns_rpz_str2policy(const char *str); const char * dns_rpz_policy2str(dns_rpz_policy_t policy); +uint16_t +dns_rpz_str2ede(const char *str); + dns_rpz_policy_t dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, dns_name_t *selfname); diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c index 4e78b57609..cfeac54dea 100644 --- a/lib/dns/rpz.c +++ b/lib/dns/rpz.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -278,6 +279,32 @@ dns_rpz_policy2str(dns_rpz_policy_t policy) { return (str); } +uint16_t +dns_rpz_str2ede(const char *str) { + static struct { + const char *str; + uint16_t ede; + } tbl[] = { + { "none", 0 }, + { "forged", DNS_EDE_FORGEDANSWER }, + { "blocked", DNS_EDE_BLOCKED }, + { "censored", DNS_EDE_CENSORED }, + { "filtered", DNS_EDE_FILTERED }, + { "prohibited", DNS_EDE_PROHIBITED }, + }; + unsigned int n; + + if (str == NULL) { + return (UINT16_MAX); + } + for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n) { + if (!strcasecmp(tbl[n].str, str)) { + return (tbl[n].ede); + } + } + return (UINT16_MAX); +} + /* * Return the bit number of the highest set bit in 'zbit'. * (for example, 0x01 returns 0, 0xFF returns 7, etc.) diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 64d0ddb494..481bc9827b 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1833,6 +1833,7 @@ static cfg_tuplefielddef_t rpz_zone_fields[] = { { "recursive-only", &cfg_type_boolean, 0 }, { "nsip-enable", &cfg_type_boolean, 0 }, { "nsdname-enable", &cfg_type_boolean, 0 }, + { "ede", &cfg_type_ustring, 0 }, { NULL, NULL, 0 } }; static cfg_type_t cfg_type_rpz_tuple = { "rpz tuple", cfg_parse_kv_tuple, diff --git a/lib/ns/query.c b/lib/ns/query.c index c16cac61a2..d1c969ca16 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -7267,6 +7268,12 @@ query_checkrpz(query_ctx_t *qctx, isc_result_t result) { UNREACHABLE(); } + if (qctx->rpz_st->m.rpz->ede != 0 && + qctx->rpz_st->m.rpz->ede != UINT16_MAX) { + ns_client_extendederror(qctx->client, + qctx->rpz_st->m.rpz->ede, NULL); + } + /* * Turn off DNSSEC because the results of a * response policy zone cannot verify.