mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
[9.20] fix: usr: Reconfigure NSEC3 opt-out zone to NSEC causes zone to be invalid
A zone that is signed with NSEC3, opt-out enabled, and then reconfigured to use NSEC, causes the zone to be published with missing NSEC records. This has been fixed. Closes #5679 Backport of MR !11359 Merge branch 'backport-5679-nsec3-optout-to-nsec-9.20' into 'bind-9.20' See merge request isc-projects/bind9!11401
This commit is contained in:
commit
1d0e19c612
6 changed files with 154 additions and 100 deletions
|
|
@ -11,6 +11,9 @@
|
|||
* information regarding copyright ownership.
|
||||
*/
|
||||
|
||||
{% set reconfiged = reconfiged | default(False) %}
|
||||
{% set policy = "optout" if not reconfiged else "nsec" %}
|
||||
|
||||
options {
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
|
|
@ -33,9 +36,22 @@ dnssec-policy "optout" {
|
|||
nsec3param iterations 0 optout yes salt-length 0;
|
||||
};
|
||||
|
||||
dnssec-policy "nsec" {
|
||||
keys {
|
||||
csk lifetime unlimited algorithm ecdsa256;
|
||||
};
|
||||
};
|
||||
|
||||
zone "test" {
|
||||
type primary;
|
||||
file "test.db";
|
||||
dnssec-policy "optout";
|
||||
inline-signing yes;
|
||||
};
|
||||
|
||||
zone "small.test" {
|
||||
type primary;
|
||||
file "small.test.db";
|
||||
dnssec-policy "@policy@";
|
||||
inline-signing yes;
|
||||
};
|
||||
|
|
|
|||
25
bin/tests/system/optout/ns2/small.test.db
Normal file
25
bin/tests/system/optout/ns2/small.test.db
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
; 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.
|
||||
|
||||
$TTL 3600
|
||||
@ IN SOA ns2.small.test. hostmaster.small.test. 1 7200 3600 24796800 3600
|
||||
IN NS ns2
|
||||
|
||||
ns2 IN A 10.53.0.2
|
||||
|
||||
a IN A 127.0.0.1
|
||||
|
||||
dname IN DNAME branch.example.
|
||||
under.dname IN TXT "occluded"
|
||||
|
||||
$GENERATE 1-10 child$ IN NS ns.example.
|
||||
|
||||
child5 IN DS 7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697 CDA01CB2
|
||||
|
|
@ -17,6 +17,9 @@ ns2 IN A 10.53.0.2
|
|||
|
||||
a IN A 127.0.0.1
|
||||
|
||||
dname IN DNAME branch.example.
|
||||
under.dname IN TXT "occluded"
|
||||
|
||||
$GENERATE 1-50000 child$ IN NS ns.example.
|
||||
|
||||
child303 IN DS 7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697 CDA01CB2
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# 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.
|
||||
|
||||
. ../conf.sh
|
||||
|
|
@ -94,14 +94,51 @@ def verify_zone(zone, transfer):
|
|||
|
||||
def test_optout(ns2):
|
||||
zone = "test"
|
||||
expect_nsec3param = True
|
||||
|
||||
# Wait until the provided zone is signed and then verify its DNSSEC data.
|
||||
def check_nsec3param():
|
||||
response = do_query(ns2, zone, "NSEC3PARAM")
|
||||
return has_nsec3param(zone, response)
|
||||
if expect_nsec3param:
|
||||
return has_nsec3param(zone, response)
|
||||
return not has_nsec3param(zone, response)
|
||||
|
||||
# check zone is fully signed.
|
||||
isctest.run.retry_with_timeout(check_nsec3param, timeout=300)
|
||||
isctest.run.retry_with_timeout(check_nsec3param, timeout=100)
|
||||
|
||||
# check if zone if DNSSEC valid.
|
||||
transfer = do_xfr(ns2, zone)
|
||||
assert verify_zone(zone, transfer)
|
||||
|
||||
|
||||
def test_optout_to_nsec(ns2, templates):
|
||||
zone = "small.test"
|
||||
expect_nsec3param = True
|
||||
|
||||
# Wait until the provided zone is signed and then verify its DNSSEC data.
|
||||
def check_nsec3param():
|
||||
response = do_query(ns2, zone, "NSEC3PARAM")
|
||||
if expect_nsec3param:
|
||||
return has_nsec3param(zone, response)
|
||||
return not has_nsec3param(zone, response)
|
||||
|
||||
# check zone is fully signed.
|
||||
isctest.run.retry_with_timeout(check_nsec3param, timeout=100)
|
||||
|
||||
# check if zone if DNSSEC valid.
|
||||
transfer = do_xfr(ns2, zone)
|
||||
assert verify_zone(zone, transfer)
|
||||
|
||||
# reconfigure to NSEC.
|
||||
data = {
|
||||
"reconfiged": True,
|
||||
}
|
||||
templates.render(f"{ns2.identifier}/named.conf", data)
|
||||
ns2.reconfigure()
|
||||
|
||||
# wait until NSEC3PARAM is removed.
|
||||
expect_nsec3param = False
|
||||
isctest.run.retry_with_timeout(check_nsec3param, timeout=100)
|
||||
|
||||
# check if zone if DNSSEC valid.
|
||||
transfer = do_xfr(ns2, zone)
|
||||
|
|
|
|||
155
lib/dns/zone.c
155
lib/dns/zone.c
|
|
@ -7627,6 +7627,58 @@ cleanup:
|
|||
return result;
|
||||
}
|
||||
|
||||
typedef struct seen {
|
||||
bool rr;
|
||||
bool soa;
|
||||
bool ns;
|
||||
bool nsec;
|
||||
bool nsec3;
|
||||
bool ds;
|
||||
bool dname;
|
||||
} seen_t;
|
||||
|
||||
static isc_result_t
|
||||
allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
||||
dns_rdatasetiter_t **iterp, seen_t *seen) {
|
||||
isc_result_t result;
|
||||
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
||||
|
||||
*seen = (seen_t){};
|
||||
|
||||
RETERR(dns_db_allrdatasets(db, node, version, 0, 0, iterp));
|
||||
|
||||
for (result = dns_rdatasetiter_first(*iterp); result == ISC_R_SUCCESS;
|
||||
result = dns_rdatasetiter_next(*iterp))
|
||||
{
|
||||
dns_rdatasetiter_current(*iterp, &rdataset);
|
||||
|
||||
if (rdataset.type == dns_rdatatype_rrsig) {
|
||||
dns_rdataset_disassociate(&rdataset);
|
||||
continue;
|
||||
}
|
||||
|
||||
(*seen).rr = true;
|
||||
|
||||
if (rdataset.type == dns_rdatatype_soa) {
|
||||
(*seen).soa = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ns) {
|
||||
(*seen).ns = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ds) {
|
||||
(*seen).ds = true;
|
||||
} else if (rdataset.type == dns_rdatatype_dname) {
|
||||
(*seen).dname = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec) {
|
||||
(*seen).nsec = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec3) {
|
||||
(*seen).nsec3 = true;
|
||||
}
|
||||
|
||||
dns_rdataset_disassociate(&rdataset);
|
||||
}
|
||||
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
||||
dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3,
|
||||
|
|
@ -7643,13 +7695,13 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
bool offlineksk = false;
|
||||
isc_buffer_t buffer;
|
||||
unsigned char data[1024];
|
||||
bool seen_soa, seen_ns, seen_rr, seen_nsec, seen_nsec3, seen_ds;
|
||||
seen_t seen;
|
||||
|
||||
if (zone->kasp != NULL) {
|
||||
offlineksk = dns_kasp_offlineksk(zone->kasp);
|
||||
}
|
||||
|
||||
result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator);
|
||||
result = allrdatasets(db, node, version, &iterator, &seen);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
if (result == ISC_R_NOTFOUND) {
|
||||
result = ISC_R_SUCCESS;
|
||||
|
|
@ -7659,36 +7711,13 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
|
||||
dns_rdataset_init(&rdataset);
|
||||
isc_buffer_init(&buffer, data, sizeof(data));
|
||||
seen_rr = seen_soa = seen_ns = seen_nsec = seen_nsec3 = seen_ds = false;
|
||||
for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS;
|
||||
result = dns_rdatasetiter_next(iterator))
|
||||
{
|
||||
dns_rdatasetiter_current(iterator, &rdataset);
|
||||
if (rdataset.type == dns_rdatatype_soa) {
|
||||
seen_soa = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ns) {
|
||||
seen_ns = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ds) {
|
||||
seen_ds = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec) {
|
||||
seen_nsec = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec3) {
|
||||
seen_nsec3 = true;
|
||||
}
|
||||
if (rdataset.type != dns_rdatatype_rrsig) {
|
||||
seen_rr = true;
|
||||
}
|
||||
dns_rdataset_disassociate(&rdataset);
|
||||
}
|
||||
if (result != ISC_R_NOMORE) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Going from insecure to NSEC3.
|
||||
* Don't generate NSEC3 records for NSEC3 records.
|
||||
*/
|
||||
if (build_nsec3 && !seen_nsec3 && seen_rr) {
|
||||
bool unsecure = !seen_ds && seen_ns && !seen_soa;
|
||||
if (build_nsec3 && !seen.nsec3 && seen.rr) {
|
||||
bool unsecure = !seen.ds && seen.ns && !seen.soa;
|
||||
CHECK(dns_nsec3_addnsec3s(db, version, name, nsecttl, unsecure,
|
||||
diff));
|
||||
(*signatures)--;
|
||||
|
|
@ -7697,7 +7726,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
* Going from insecure to NSEC.
|
||||
* Don't generate NSEC records for NSEC3 records.
|
||||
*/
|
||||
if (build_nsec && !seen_nsec3 && !seen_nsec && seen_rr) {
|
||||
if (build_nsec && !seen.nsec3 && !seen.nsec && seen.rr) {
|
||||
/*
|
||||
* Build a NSEC record except at the origin.
|
||||
*/
|
||||
|
|
@ -7739,7 +7768,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name,
|
|||
}
|
||||
}
|
||||
|
||||
if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds &&
|
||||
if (seen.ns && !seen.soa && rdataset.type != dns_rdatatype_ds &&
|
||||
rdataset.type != dns_rdatatype_nsec)
|
||||
{
|
||||
goto next_rdataset;
|
||||
|
|
@ -8437,8 +8466,7 @@ zone_nsec3chain(dns_zone_t *zone) {
|
|||
unsigned int nkeys = 0;
|
||||
uint32_t nodes;
|
||||
bool unsecure = false;
|
||||
bool seen_soa, seen_ns, seen_dname, seen_ds;
|
||||
bool seen_nsec, seen_nsec3, seen_rr;
|
||||
seen_t seen;
|
||||
dns_rdatasetiter_t *iterator = NULL;
|
||||
bool buildnsecchain;
|
||||
bool updatensec = false;
|
||||
|
|
@ -8606,45 +8634,27 @@ zone_nsec3chain(dns_zone_t *zone) {
|
|||
/*
|
||||
* Check to see if this is a bottom of zone node.
|
||||
*/
|
||||
result = dns_db_allrdatasets(db, node, version, 0, 0,
|
||||
&iterator);
|
||||
result = allrdatasets(db, node, version, &iterator, &seen);
|
||||
if (result == ISC_R_NOTFOUND) {
|
||||
/* Empty node? */
|
||||
goto next_addnode;
|
||||
}
|
||||
CHECK(result);
|
||||
|
||||
seen_soa = seen_ns = seen_dname = seen_ds = seen_nsec = false;
|
||||
for (result = dns_rdatasetiter_first(iterator);
|
||||
result == ISC_R_SUCCESS;
|
||||
result = dns_rdatasetiter_next(iterator))
|
||||
{
|
||||
dns_rdatasetiter_current(iterator, &rdataset);
|
||||
INSIST(rdataset.type != dns_rdatatype_nsec3);
|
||||
if (rdataset.type == dns_rdatatype_soa) {
|
||||
seen_soa = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ns) {
|
||||
seen_ns = true;
|
||||
} else if (rdataset.type == dns_rdatatype_dname) {
|
||||
seen_dname = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ds) {
|
||||
seen_ds = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec) {
|
||||
seen_nsec = true;
|
||||
}
|
||||
dns_rdataset_disassociate(&rdataset);
|
||||
}
|
||||
INSIST(!seen.nsec3);
|
||||
|
||||
dns_rdatasetiter_destroy(&iterator);
|
||||
/*
|
||||
* Is there a NSEC chain than needs to be cleaned up?
|
||||
*/
|
||||
if (seen_nsec) {
|
||||
if (seen.nsec) {
|
||||
nsec3chain->seen_nsec = true;
|
||||
}
|
||||
if (seen_ns && !seen_soa && !seen_ds) {
|
||||
|
||||
if (seen.ns && !seen.soa && !seen.ds) {
|
||||
unsecure = true;
|
||||
}
|
||||
if ((seen_ns && !seen_soa) || seen_dname) {
|
||||
if ((seen.ns && !seen.soa) || seen.dname) {
|
||||
delegation = true;
|
||||
}
|
||||
|
||||
|
|
@ -8803,7 +8813,7 @@ zone_nsec3chain(dns_zone_t *zone) {
|
|||
|
||||
if (first) {
|
||||
dnssec_log(zone, ISC_LOG_DEBUG(3),
|
||||
"zone_nsec3chain:buildnsecchain = %u\n",
|
||||
"zone_nsec3chain:buildnsecchain = %u",
|
||||
buildnsecchain);
|
||||
}
|
||||
|
||||
|
|
@ -8868,42 +8878,19 @@ zone_nsec3chain(dns_zone_t *zone) {
|
|||
/*
|
||||
* Check to see if this is a bottom of zone node.
|
||||
*/
|
||||
result = dns_db_allrdatasets(db, node, version, 0, 0,
|
||||
&iterator);
|
||||
result = allrdatasets(db, node, version, &iterator, &seen);
|
||||
if (result == ISC_R_NOTFOUND) {
|
||||
/* Empty node? */
|
||||
goto next_removenode;
|
||||
}
|
||||
CHECK(result);
|
||||
|
||||
seen_soa = seen_ns = seen_dname = seen_nsec3 = seen_nsec =
|
||||
seen_rr = false;
|
||||
for (result = dns_rdatasetiter_first(iterator);
|
||||
result == ISC_R_SUCCESS;
|
||||
result = dns_rdatasetiter_next(iterator))
|
||||
{
|
||||
dns_rdatasetiter_current(iterator, &rdataset);
|
||||
if (rdataset.type == dns_rdatatype_soa) {
|
||||
seen_soa = true;
|
||||
} else if (rdataset.type == dns_rdatatype_ns) {
|
||||
seen_ns = true;
|
||||
} else if (rdataset.type == dns_rdatatype_dname) {
|
||||
seen_dname = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec) {
|
||||
seen_nsec = true;
|
||||
} else if (rdataset.type == dns_rdatatype_nsec3) {
|
||||
seen_nsec3 = true;
|
||||
} else if (rdataset.type != dns_rdatatype_rrsig) {
|
||||
seen_rr = true;
|
||||
}
|
||||
dns_rdataset_disassociate(&rdataset);
|
||||
}
|
||||
dns_rdatasetiter_destroy(&iterator);
|
||||
|
||||
if (!seen_rr || seen_nsec3 || seen_nsec) {
|
||||
if (!seen.rr || seen.nsec3 || seen.nsec) {
|
||||
goto next_removenode;
|
||||
}
|
||||
if ((seen_ns && !seen_soa) || seen_dname) {
|
||||
if ((seen.ns && !seen.soa) || seen.dname) {
|
||||
delegation = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue