mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-27 20:25:55 -04:00
If the primary has been updated, but the secondary has not been
notified, the journal will go out of date. An 'rndc retransfer' causes
the zone to force an AXFR, removing and rebuilding zone and journal
files.
This test reproduces a bug that in such scenario, an NSEC3 signed zone
falls back to NSEC.
(cherry picked from commit be3e4c83d0)
165 lines
5 KiB
Python
165 lines
5 KiB
Python
# 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.
|
|
|
|
import os
|
|
|
|
from datetime import timedelta
|
|
|
|
import dns
|
|
import pytest
|
|
|
|
import isctest
|
|
|
|
pytestmark = pytest.mark.extra_artifacts(
|
|
[
|
|
"*.axfr",
|
|
"*.created",
|
|
"dig.out.*",
|
|
"rndc.reload.*",
|
|
"rndc.signing.*",
|
|
"update.out.*",
|
|
"verify.out.*",
|
|
"ns*/dsset-**",
|
|
"ns*/K*",
|
|
"ns*/settime.out.*",
|
|
"ns*/*.db",
|
|
"ns*/*.jbk",
|
|
"ns*/*.jnl",
|
|
"ns*/*.signed",
|
|
"ns*/keygen.out.*",
|
|
"ns3/named-*.conf",
|
|
]
|
|
)
|
|
|
|
ALGORITHM = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
SIZE = os.environ["DEFAULT_BITS"]
|
|
|
|
default_config = {
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
"ds-ttl": timedelta(days=1),
|
|
"max-zone-ttl": timedelta(days=1),
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
"publish-safety": timedelta(hours=1),
|
|
"retire-safety": timedelta(hours=1),
|
|
"signatures-refresh": timedelta(days=5),
|
|
"signatures-validity": timedelta(days=14),
|
|
"zone-propagation-delay": timedelta(minutes=5),
|
|
}
|
|
|
|
|
|
def check_auth_nsec(response):
|
|
rrs = []
|
|
for rrset in response.authority:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE):
|
|
rrs.append(rrset)
|
|
assert not rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE
|
|
)
|
|
assert len(rrs) != 0, "no NSEC records found in authority section"
|
|
|
|
|
|
def check_auth_nsec3(response, iterations=0, optout=0, salt="-"):
|
|
match = f"IN NSEC3 1 {optout} {iterations} {salt}"
|
|
rrs = []
|
|
|
|
for rrset in response.authority:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE):
|
|
assert match in rrset.to_text()
|
|
rrs.append(rrset)
|
|
assert not rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE
|
|
)
|
|
|
|
assert len(rrs) != 0, "no NSEC3 records found in authority section"
|
|
|
|
|
|
def check_nsec3param(response, match, saltlen):
|
|
rrs = []
|
|
salt = "-"
|
|
|
|
for rrset in response.answer:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3PARAM, dns.rdatatype.NONE):
|
|
assert match in rrset.to_text()
|
|
if saltlen == 0:
|
|
assert f"{match} -" in rrset.to_text()
|
|
else:
|
|
assert not f"{match} -" in rrset.to_text()
|
|
salt = rrset.to_text().split()[7]
|
|
|
|
rrs.append(rrset)
|
|
else:
|
|
assert rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC3PARAM
|
|
)
|
|
|
|
assert len(rrs) != 0
|
|
|
|
return salt
|
|
|
|
|
|
def check_nsec3_case(server, params, nsec3=True):
|
|
# Get test parameters.
|
|
zone = params["zone"]
|
|
fqdn = f"{zone}."
|
|
policy = params["policy"]
|
|
keydir = server.identifier
|
|
config = default_config
|
|
ttl = int(config["dnskey-ttl"].total_seconds())
|
|
expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=params["key-properties"])
|
|
|
|
# Test case.
|
|
isctest.log.info(f"check nsec case zone {zone} policy {policy}")
|
|
|
|
# Key files.
|
|
keys = isctest.kasp.keydir_to_keylist(zone, keydir)
|
|
if "external-keys" in params:
|
|
expected2 = isctest.kasp.policy_to_properties(ttl, keys=params["external-keys"])
|
|
for ek in expected2:
|
|
ek.private = False # noqa
|
|
ek.legacy = True # noqa
|
|
expected = expected + expected2
|
|
assert "external-keydir" in params
|
|
extkeys = isctest.kasp.keydir_to_keylist(zone, params["external-keydir"])
|
|
keys = keys + extkeys
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
isctest.kasp.check_apex(server, zone, keys, [])
|
|
|
|
query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM)
|
|
nsec3param_response = isctest.query.tcp(query, server.ip)
|
|
assert nsec3param_response.rcode() == dns.rcode.NOERROR
|
|
|
|
query = isctest.query.create(f"nosuchname.{fqdn}", dns.rdatatype.A)
|
|
response = isctest.query.tcp(query, server.ip)
|
|
assert response.rcode() == dns.rcode.NXDOMAIN
|
|
|
|
if nsec3:
|
|
# NSEC3
|
|
minimum = params.get("soa-minimum", 3600)
|
|
iterations = 0
|
|
optout = 0
|
|
saltlen = 0
|
|
if "nsec3param" in params:
|
|
optout = params["nsec3param"].get("optout", 0)
|
|
saltlen = params["nsec3param"].get("salt-length", 0)
|
|
|
|
match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}"
|
|
|
|
salt = check_nsec3param(nsec3param_response, match, saltlen)
|
|
|
|
check_auth_nsec3(response, iterations, optout, salt)
|
|
else:
|
|
# NSEC
|
|
assert len(nsec3param_response.answer) == 0
|
|
check_auth_nsec(nsec3param_response)
|
|
|
|
check_auth_nsec(response)
|