Introduce the concept of broken catalog zones

The DNS catalog zones draft version 5 document describes various
situations when a catalog zones must be considered as "broken" and
not be processed.

Implement those checks in catz.c and add corresponding system tests.

(cherry picked from commit a8228d5f19)
This commit is contained in:
Aram Sargsyan 2022-03-17 14:43:18 +00:00
parent 9a11c7a570
commit 6539f73e3a
9 changed files with 291 additions and 19 deletions

View file

@ -19,6 +19,7 @@ rm -f ns*/named.run
rm -f ns*/named.run.prev
rm -f ns1/*dom*example.db
rm -f ns2/__catz__*db
rm -f ns2/catalog-bad*.db
rm -f ns2/named.conf.tmp
rm -f ns3/dom2.example.db ns3/dom13.example.db ns3/dom14.example.db ns3/dom17.example.db ns3/dom18.example.db
rm -f nsupdate.out.*

View file

@ -0,0 +1,13 @@
; 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.
@ 3600 SOA . . 1 86400 3600 86400 3600
@ 3600 IN NS invalid.

View file

@ -0,0 +1,14 @@
; 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.
@ 3600 SOA . . 1 86400 3600 86400 3600
@ 3600 IN NS invalid.
version IN TXT "99"

View file

@ -0,0 +1,15 @@
; 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.
@ 3600 SOA . . 1 86400 3600 86400 3600
@ 3600 IN NS invalid.
version IN TXT "1"
version IN TXT "2"

View file

@ -61,6 +61,36 @@ zone "catalog4.example" {
/* catalog5 is missing on purpose */
# No "version" property
zone "catalog-bad1.example" {
type primary;
file "catalog-bad1.example.db";
allow-transfer { any; };
allow-update { any; };
also-notify { 10.53.0.2; };
notify explicit;
};
# Unsupported "version" property
zone "catalog-bad2.example" {
type primary;
file "catalog-bad2.example.db";
allow-transfer { any; };
allow-update { any; };
also-notify { 10.53.0.2; };
notify explicit;
};
# Two RRs in TXT RRset for the "version" property
zone "catalog-bad3.example" {
type primary;
file "catalog-bad3.example.db";
allow-transfer { any; };
allow-update { any; };
also-notify { 10.53.0.2; };
notify explicit;
};
key tsig_key. {
secret "LSAnCU+Z";
algorithm hmac-md5;

View file

@ -43,6 +43,15 @@ options {
#T1 default-masters { 10.53.0.1; };
#T2 zone "catalog5.example"
#T2 default-primaries { 10.53.0.1; };
zone "catalog-bad1.example"
default-masters { 10.53.0.1; }
in-memory yes;
zone "catalog-bad2.example"
default-masters { 10.53.0.1; }
in-memory yes;
zone "catalog-bad3.example"
default-masters { 10.53.0.1; }
in-memory yes;
};
};
@ -83,6 +92,24 @@ zone "catalog4.example" {
primaries { 10.53.0.1; };
};
zone "catalog-bad1.example" {
type secondary;
file "catalog-bad1.example.db";
primaries { 10.53.0.1; };
};
zone "catalog-bad2.example" {
type secondary;
file "catalog-bad2.example.db";
primaries { 10.53.0.1; };
};
zone "catalog-bad3.example" {
type secondary;
file "catalog-bad3.example.db";
primaries { 10.53.0.1; };
};
key tsig_key. {
secret "LSAnCU+Z";
algorithm hmac-md5;

View file

@ -61,6 +61,24 @@ zone "catalog4.example" {
primaries { 10.53.0.1; };
};
zone "catalog-bad1.example" {
type secondary;
file "catalog-bad1.example.db";
primaries { 10.53.0.1; };
};
zone "catalog-bad2.example" {
type secondary;
file "catalog-bad2.example.db";
primaries { 10.53.0.1; };
};
zone "catalog-bad3.example" {
type secondary;
file "catalog-bad3.example.db";
primaries { 10.53.0.1; };
};
key tsig_key. {
secret "LSAnCU+Z";
algorithm hmac-md5;

View file

@ -80,6 +80,36 @@ wait_for_no_zonefile() (
status=0
n=0
##########################################################################
n=$((n+1))
echo_i "checking that catalog-bad1.example (with no version) has failed to load ($n)"
ret=0
wait_for_message ns2/named.run "catz: zone 'catalog-bad1.example' has no 'version' record" &&
wait_for_message ns2/named.run "catz: new catalog zone 'catalog-bad1.example' is broken and will not be processed" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "checking that catalog-bad2.example (with unsupported version) has failed to load ($n)"
ret=0
wait_for_message ns2/named.run "catz: zone 'catalog-bad2.example' unsupported version '99'" &&
wait_for_message ns2/named.run "catz: new catalog zone 'catalog-bad2.example' is broken and will not be processed" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "checking that catalog-bad3.example (with two supported version records) has failed to load ($n)"
ret=0
wait_for_message ns2/named.run "catz: 'version' property TXT RRset contains more than one record, which is invalid" &&
wait_for_message ns2/named.run "catz: invalid record in catalog zone - version.catalog-bad3.example IN TXT (failure) - ignoring" &&
wait_for_message ns2/named.run "catz: zone 'catalog-bad3.example' version is not set" &&
wait_for_message ns2/named.run "catz: new catalog zone 'catalog-bad3.example' is broken and will not be processed" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
##########################################################################
echo_i "Testing adding/removing of domain in catalog zone"
n=$((n+1))
@ -145,6 +175,8 @@ wait_for_zonefile "ns2/zonedir/__catz___default_catalog1.example_dom1.example.db
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
n=$((n+1))
echo_i "update dom1.example. ($n)"
ret=0
@ -176,6 +208,8 @@ test -f ns2/zonedir/__catz___default_catalog1.example_dom1.example.db.jnl || ret
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
n=$((n+1))
echo_i "update catalog zone serial ($n)"
ret=0
@ -202,6 +236,8 @@ retry_quiet 10 wait_for_soa_equal_20 || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
n=$((n+1))
echo_i "update dom1.example. again ($n)"
ret=0
@ -259,6 +295,8 @@ wait_for_no_zonefile "ns2/zonedir/__catz___default_catalog1.example_dom1.example
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
##########################################################################
echo_i "Testing various simple operations on domains, including using multiple catalog zones and garbage in zone"
n=$((n+1))
@ -296,6 +334,7 @@ ret=0
$NSUPDATE -d <<END >> nsupdate.out.test$n 2>&1 || ret=1
server 10.53.0.1 ${PORT}
update add 636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR dom2.example.
update add coo.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN TXT "catalog2.example."
update add b901f492f3ebf6c1e5b597e51766f02f0479eb03.zones.catalog1.example. 3600 IN PTR dom3.example.
update add e721433b6160b450260d4f54b3ec8bab30cb3b83.zones.catalog1.example. 3600 IN NS foo.bar.
update add trash.catalog1.example. 3600 IN A 1.2.3.4
@ -322,10 +361,11 @@ END
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "waiting for secondary to sync up ($n)"
ret=0
wait_for_message ns2/named.run "catz: adding zone 'dom2.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "catz: adding zone 'dom3.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "catz: adding zone 'dom4.example' from catalog 'catalog2.example'" &&
wait_for_message ns2/named.run "transfer of 'dom4.example/IN' from 10.53.0.1#${EXTRAPORT1}: Transfer status: success" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@ -338,7 +378,6 @@ wait_for_soa @10.53.0.2 dom4.example. dig.out.test$n || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "checking that dom3.example. is not served by primary ($n)"
ret=0
@ -365,8 +404,6 @@ status=$((status+ret))
n=$((n+1))
echo_i "waiting for secondary to sync up ($n)"
ret=0
wait_for_message ns2/named.run "catz: adding zone 'dom2.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "catz: adding zone 'dom3.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "transfer of 'dom2.example/IN' from 10.53.0.1#${PORT}: Transfer status: success" &&
wait_for_message ns2/named.run "transfer of 'dom3.example/IN' from 10.53.0.1#${PORT}: Transfer status: success" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@ -381,6 +418,43 @@ status=$((status+ret))
nextpart ns2/named.run >/dev/null
# The member zone's PTR RRset must have only one record in it.
# Check that adding a second record to the RRset is caught and such a
# catalog zone is not processed.
n=$((n+1))
echo_i "adding domain dom4-reused-label.example. to catalog2 zone, reusing a label ($n)"
ret=0
$NSUPDATE -d <<END >> nsupdate.out.test$n 2>&1 || ret=1
server 10.53.0.3 ${PORT}
update add de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example. 3600 IN PTR dom4-reused-label.example.
send
END
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "waiting for secondary to sync up, and checking that the reused label has been caught ($n)"
ret=0
wait_for_message ns2/named.run "de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example IN PTR (failure)" &&
wait_for_message ns2/named.run "catz: new catalog zone 'catalog2.example' is broken and will not be processed" || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
n=$((n+1))
echo_i "deleting domain dom4-reused-label.example. from catalog2 zone ($n)"
ret=0
$NSUPDATE -d <<END >> nsupdate.out.test$n 2>&1 || ret=1
server 10.53.0.3 ${PORT}
update delete de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example. 3600 IN PTR dom4-reused-label.example.
send
END
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
nextpart ns2/named.run >/dev/null
n=$((n+1))
echo_i "adding domain dom2.example. to catalog2 zone to test change of ownership ($n)"
ret=0
@ -615,6 +689,7 @@ ret=0
$NSUPDATE -d <<END >> nsupdate.out.test$n 2>&1 || ret=1
server 10.53.0.1 ${PORT}
update delete 636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR dom2.example.
update delete coo.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN TXT "catalog2.example."
update delete b901f492f3ebf6c1e5b597e51766f02f0479eb03.zones.catalog1.example. 3600 IN PTR dom3.example.
update delete e721433b6160b450260d4f54b3ec8bab30cb3b83.zones.catalog1.example. 3600 IN NS foo.bar.
update delete trash.catalog1.example. 3600 IN A 1.2.3.4
@ -2083,7 +2158,7 @@ status=$((status+ret))
n=$((n+1))
echo_i "waiting for secondary to sync up ($n)"
ret=0
wait_for_message ns2/named.run "catz: unknown record in catalog zone - primaries.dom17.zones.catalog1.example IN A(failure) - ignoring" &&
wait_for_message ns2/named.run "catz: invalid record in catalog zone - primaries.dom17.zones.catalog1.example IN A (failure) - ignoring" &&
wait_for_message ns2/named.run "catz: adding zone 'dom17.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "catz: adding zone 'dom18.example' from catalog 'catalog1.example'" &&
wait_for_message ns2/named.run "transfer of 'dom17.example/IN' from 10.53.0.1#${PORT}: Transfer status: success" &&
@ -2174,7 +2249,7 @@ status=$((status+ret))
n=$((n+1))
echo_i "waiting for secondary to sync up ($n)"
ret=0
wait_for_message ns2/named.run "catz: unknown record in catalog zone - primaries.ext.dom18.zones.catalog2.example IN A(failure) - ignoring" &&
wait_for_message ns2/named.run "catz: invalid record in catalog zone - primaries.ext.dom18.zones.catalog2.example IN A (failure) - ignoring" &&
wait_for_message ns2/named.run "catz: adding zone 'dom17.example' from catalog 'catalog2.example'" &&
wait_for_message ns2/named.run "catz: adding zone 'dom18.example' from catalog 'catalog2.example'" &&
wait_for_message ns2/named.run "transfer of 'dom17.example/IN' from 10.53.0.3#${PORT}: Transfer status: success" &&

View file

@ -94,6 +94,7 @@ struct dns_catz_zone {
bool active;
bool db_registered;
bool broken;
isc_refcount_t refs;
};
@ -1077,6 +1078,15 @@ catz_process_coo(dns_catz_zone_t *zone, dns_label_t *mhash,
return (ISC_R_FAILURE);
}
if (dns_rdataset_count(value) != 1) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: 'coo' property PTR RRset contains "
"more than one record, which is invalid");
zone->broken = true;
return (ISC_R_FAILURE);
}
result = dns_rdataset_first(value);
if (result != ISC_R_SUCCESS) {
return (result);
@ -1135,24 +1145,32 @@ catz_process_zones_entry(dns_catz_zone_t *zone, dns_rdataset_t *value,
dns_rdata_ptr_t ptr;
dns_catz_entry_t *entry = NULL;
/*
* We only take -first- value, as mhash must be
* different.
*/
if (value->type != dns_rdatatype_ptr) {
if (value->rdclass != dns_rdataclass_in ||
value->type != dns_rdatatype_ptr) {
return (ISC_R_FAILURE);
}
if (dns_rdataset_count(value) != 1) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: member zone PTR RRset contains "
"more than one record, which is invalid");
zone->broken = true;
return (ISC_R_FAILURE);
}
result = dns_rdataset_first(value);
if (result != ISC_R_SUCCESS) {
return (ISC_R_FAILURE);
return (result);
}
dns_rdata_init(&rdata);
dns_rdataset_current(value, &rdata);
result = dns_rdata_tostruct(&rdata, &ptr, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
return (result);
}
result = isc_ht_find(zone->entries, mhash->base, mhash->length,
(void **)&entry);
@ -1198,6 +1216,15 @@ catz_process_version(dns_catz_zone_t *zone, dns_rdataset_t *value) {
return (ISC_R_FAILURE);
}
if (dns_rdataset_count(value) != 1) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: 'version' property TXT RRset contains "
"more than one record, which is invalid");
zone->broken = true;
return (ISC_R_FAILURE);
}
result = dns_rdataset_first(value);
if (result != ISC_R_SUCCESS) {
return (result);
@ -1207,7 +1234,9 @@ catz_process_version(dns_catz_zone_t *zone, dns_rdataset_t *value) {
dns_rdataset_current(value, &rdata);
result = dns_rdata_tostruct(&rdata, &rdatatxt, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
return (result);
}
result = dns_rdata_txt_first(&rdatatxt);
if (result != ISC_R_SUCCESS) {
@ -1239,6 +1268,13 @@ catz_process_version(dns_catz_zone_t *zone, dns_rdataset_t *value) {
cleanup:
dns_rdata_freestruct(&rdatatxt);
if (result != ISC_R_SUCCESS) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: invalid record for the catalog "
"zone version property");
zone->broken = true;
}
return (result);
}
@ -1682,6 +1718,14 @@ dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone,
REQUIRE(DNS_CATZ_ZONE_VALID(zone));
REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC));
if (rdataset->rdclass != dns_rdataclass_in) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
"catz: RR found which has a non-IN class");
zone->broken = true;
return (ISC_R_FAILURE);
}
nrres = dns_name_fullcompare(src_name, &zone->name, &order, &nlabels);
if (nrres == dns_namereln_equal) {
if (rdataset->type == dns_rdatatype_soa) {
@ -2059,8 +2103,10 @@ dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
dns_rdatasetiter_t *rdsiter = NULL;
dns_rdataset_t rdataset;
char bname[DNS_NAME_FORMATSIZE];
char cname[DNS_NAME_FORMATSIZE];
bool is_vers_processed = false;
uint32_t vers;
uint32_t catz_vers;
REQUIRE(DNS_DB_VALID(db));
REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
@ -2137,13 +2183,13 @@ dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
result = dns_dbiterator_seek(it, name);
if (result != ISC_R_SUCCESS) {
dns_dbiterator_destroy(&it);
dns_catz_zone_detach(&newzone);
dns_db_closeversion(db, &oldzone->dbversion, false);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
"catz: zone '%s' has no 'version' record (%s)",
bname, isc_result_totext(result));
return;
newzone->broken = true;
goto final;
}
name = dns_fixedname_initname(&fixname);
@ -2189,7 +2235,6 @@ dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
result = dns_catz_update_process(catzs, newzone, name,
&rdataset);
if (result != ISC_R_SUCCESS) {
char cname[DNS_NAME_FORMATSIZE];
char typebuf[DNS_RDATATYPE_FORMATSIZE];
char classbuf[DNS_RDATACLASS_FORMATSIZE];
@ -2203,8 +2248,8 @@ dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER,
ISC_LOG_WARNING,
"catz: unknown record in catalog "
"zone - %s %s %s(%s) - ignoring",
"catz: invalid record in catalog "
"zone - %s %s %s (%s) - ignoring",
cname, classbuf, typebuf,
isc_result_totext(result));
}
@ -2230,6 +2275,40 @@ dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
ISC_LOG_DEBUG(3),
"catz: update_from_db: iteration finished");
/*
* Check catalog zone version compatibilites.
*/
catz_vers = (newzone->version == DNS_CATZ_VERSION_UNDEFINED)
? oldzone->version
: newzone->version;
if (catz_vers == DNS_CATZ_VERSION_UNDEFINED) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: zone '%s' version is not set", bname);
newzone->broken = true;
} else if (catz_vers != 1 && catz_vers != 2) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
"catz: zone '%s' unsupported version "
"'%" PRIu32 "'",
bname, catz_vers);
newzone->broken = true;
} else {
oldzone->version = catz_vers;
}
final:
if (newzone->broken) {
dns_name_format(name, cname, DNS_NAME_FORMATSIZE);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
"catz: new catalog zone '%s' is broken and "
"will not be processed",
bname);
dns_catz_zone_detach(&newzone);
return;
}
/*
* Finally merge new zone into old zone.
*/