diff --git a/bin/tests/system/isctest/instance.py b/bin/tests/system/isctest/instance.py index 268dcf09d0..d6400c5e39 100644 --- a/bin/tests/system/isctest/instance.py +++ b/bin/tests/system/isctest/instance.py @@ -154,7 +154,9 @@ class NamedInstance: return response - def nsupdate(self, update_msg: dns.message.Message): + def nsupdate( + self, update_msg: dns.message.Message, expected_rcode=dns.rcode.NOERROR + ): """ Issue a dynamic update to a server's zone. """ @@ -168,12 +170,14 @@ class NamedInstance: self.ip, self.ports.dns, timeout=3, - expected_rcode=dns.rcode.NOERROR, + expected_rcode=expected_rcode, ) except dns.exception.Timeout as exc: msg = f"update timeout for {zone}" raise dns.exception.Timeout(msg) from exc - debug(f"update of zone {zone} to server {self.ip} successful") + debug( + f"update of zone {zone} to server {self.ip} finished with {expected_rcode}" + ) return response def watch_log_from_start( diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 0fac77ff45..604a4f8669 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -1609,5 +1609,5 @@ def wait_keymgr_done(server: NamedInstance, zone: str, reconfig: bool = False) - messages.append("received control channel command 'reconfig'") messages.append("apply_configuration") messages.append(f"keymgr: {zone} done") - with server.watch_log_from_start() as watcher: + with server.watch_log_from_start(timeout=60) as watcher: watcher.wait_for_sequence(messages) diff --git a/bin/tests/system/nsec3/common.py b/bin/tests/system/nsec3/common.py new file mode 100644 index 0000000000..678cc4cbed --- /dev/null +++ b/bin/tests/system/nsec3/common.py @@ -0,0 +1,167 @@ +# 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-common.conf", + "ns3/named-fips.conf", + "ns3/named-rsasha1.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) diff --git a/bin/tests/system/nsec3/ns2/named.conf.in b/bin/tests/system/nsec3/ns2/named.conf.j2 similarity index 92% rename from bin/tests/system/nsec3/ns2/named.conf.in rename to bin/tests/system/nsec3/ns2/named.conf.j2 index 924e9d26cb..904abbf81d 100644 --- a/bin/tests/system/nsec3/ns2/named.conf.in +++ b/bin/tests/system/nsec3/ns2/named.conf.j2 @@ -39,8 +39,10 @@ controls { inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +{% if "nsec3-xfr-inline.kasp" in zones %} zone "nsec3-xfr-inline.kasp" { type primary; file "nsec3-xfr-inline.kasp.db"; dnssec-policy "nsec3"; }; +{% endif %}{# nsec3-xfr-inline.kasp #} diff --git a/bin/tests/system/nsec3/ns3/named-common.conf.j2 b/bin/tests/system/nsec3/ns3/named-common.conf.j2 new file mode 100644 index 0000000000..5dc8a7a552 --- /dev/null +++ b/bin/tests/system/nsec3/ns3/named-common.conf.j2 @@ -0,0 +1,52 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +dnssec-policy "nsec" { + // no need to change configuration: if no 'nsec3param' is set, + // NSEC will be used; +}; + +dnssec-policy "nsec3" { + nsec3param; +}; + +dnssec-policy "optout" { + nsec3param optout yes; +}; + +dnssec-policy "nsec3-other" { + nsec3param iterations 0 optout yes salt-length 8; +}; + diff --git a/bin/tests/system/nsec3/ns3/named-fips.conf.in b/bin/tests/system/nsec3/ns3/named-fips.conf.in deleted file mode 100644 index 4ed7cc0427..0000000000 --- a/bin/tests/system/nsec3/ns3/named-fips.conf.in +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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. - */ - -// NS3 - -dnssec-policy "nsec" { - // no need to change configuration: if no 'nsec3param' is set, - // NSEC will be used; -}; - -dnssec-policy "nsec3" { - nsec3param; -}; - -dnssec-policy "optout" { - nsec3param optout yes; -}; - -dnssec-policy "nsec3-other" { - nsec3param iterations 0 optout yes salt-length 8; -}; - -options { - query-source address 10.53.0.3; - notify-source 10.53.0.3; - transfer-source 10.53.0.3; - port @PORT@; - pid-file "named.pid"; - listen-on { 10.53.0.3; }; - listen-on-v6 { none; }; - allow-transfer { any; }; - recursion no; - dnssec-validation no; -}; - -key rndc_key { - secret "1234abcd8765"; - algorithm @DEFAULT_HMAC@; -}; - -controls { - inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; -}; - -/* This zone starts with NSEC, but will be reconfigured to use NSEC3. */ -zone "nsec-to-nsec3.kasp" { - type primary; - file "nsec-to-nsec3.kasp.db"; - dnssec-policy "nsec"; -}; - -/* These zones use the default NSEC3 settings. */ -zone "nsec3.kasp" { - type primary; - file "nsec3.kasp.db"; - dnssec-policy "nsec3"; -}; - -zone "nsec3-dynamic.kasp" { - type primary; - file "nsec3-dynamic.kasp.db"; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* This zone uses non-default NSEC3 settings. */ -zone "nsec3-other.kasp" { - type primary; - file "nsec3-other.kasp.db"; - dnssec-policy "nsec3-other"; -}; - -/* These zones will be reconfigured to use other NSEC3 settings. */ -zone "nsec3-change.kasp" { - type primary; - file "nsec3-change.kasp.db"; - dnssec-policy "nsec3"; -}; - -zone "nsec3-dynamic-change.kasp" { - type primary; - file "nsec3-dynamic-change.kasp.db"; - inline-signing no; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* The zone will be reconfigured to use opt-out. */ -zone "nsec3-to-optout.kasp" { - type primary; - file "nsec3-to-optout.kasp.db"; - dnssec-policy "nsec3"; -}; - -/* The zone will be reconfigured to disable opt-out. */ -zone "nsec3-from-optout.kasp" { - type primary; - file "nsec3-from-optout.kasp.db"; - dnssec-policy "optout"; -}; - -/* The zone starts with NSEC3, but will be reconfigured to use NSEC. */ -zone "nsec3-to-nsec.kasp" { - type primary; - file "nsec3-to-nsec.kasp.db"; - dnssec-policy "nsec3"; -}; - -/* The zone fails to load, this should not prevent shutdown. */ -zone "nsec3-fails-to-load.kasp" { - type primary; - file "nsec3-fails-to-load.kasp.db"; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* These zones switch from dynamic to inline-signing or vice versa. */ -zone "nsec3-dynamic-to-inline.kasp" { - type primary; - file "nsec3-dynamic-to-inline.kasp.db"; - inline-signing no; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -zone "nsec3-inline-to-dynamic.kasp" { - type primary; - file "nsec3-inline-to-dynamic.kasp.db"; - dnssec-policy "nsec3"; -}; - -/* Test adding a NSEC3 record to an inline-signing dnssec-policy zone. */ -zone "nsec3-dynamic-update-inline.kasp" { - type primary; - file "nsec3-dynamic-update-inline.kasp.db"; - allow-update { any; }; - dnssec-policy "nsec"; -}; - -zone "nsec3-xfr-inline.kasp" { - type secondary; - file "nsec3-xfr-inline.kasp.db"; - dnssec-policy "nsec"; - primaries { 10.53.0.2; }; -}; diff --git a/bin/tests/system/nsec3/ns3/named-fips.conf.j2 b/bin/tests/system/nsec3/ns3/named-fips.conf.j2 new file mode 100644 index 0000000000..8074646fa6 --- /dev/null +++ b/bin/tests/system/nsec3/ns3/named-fips.conf.j2 @@ -0,0 +1,179 @@ +/* + * 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. + */ + +{% set reconfiged = reconfiged | default(False) %} +{% set nsec_to_nsec3 = "nsec" if not reconfiged else "nsec3" %} +{% set nsec3_to_nsec = "nsec3" if not reconfiged else "nsec" %} +{% set nsec3_change = "nsec3" if not reconfiged else "nsec3-other" %} +{% set nsec3_from_optout = "optout" if not reconfiged else "nsec3" %} +{% set nsec3_to_optout = "nsec3" if not reconfiged else "optout" %} + +{% if "nsec-to-nsec3.kasp" in zones %} +/* This zone starts with NSEC, but will be reconfigured to use NSEC3. */ +zone "nsec-to-nsec3.kasp" { + type primary; + file "nsec-to-nsec3.kasp.db"; + dnssec-policy "@nsec_to_nsec3@"; +}; +{% endif %}{# nsec-to-nsec3.kasp #} + +{% if "nsec3.kasp" in zones %} +/* These zones use the default NSEC3 settings. */ +zone "nsec3.kasp" { + type primary; + file "nsec3.kasp.db"; + dnssec-policy "nsec3"; +}; +{% endif %}{# nsec3.kasp #} + +{% if "nsec3-dynamic.kasp" in zones %} +zone "nsec3-dynamic.kasp" { + type primary; + file "nsec3-dynamic.kasp.db"; + dnssec-policy "nsec3"; + allow-update { any; }; +}; +{% endif %}{# nsec3-dynamic.kasp #} + +{% if "nsec3-other.kasp" in zones %} +/* This zone uses non-default NSEC3 settings. */ +zone "nsec3-other.kasp" { + type primary; + file "nsec3-other.kasp.db"; + dnssec-policy "nsec3-other"; +}; +{% endif %}{# nsec3-other.kasp #} + +{% if "nsec3-change.kasp" in zones %} +/* These zones will be reconfigured to use other NSEC3 settings. */ +zone "nsec3-change.kasp" { + type primary; + file "nsec3-change.kasp.db"; + dnssec-policy "@nsec3_change@"; +}; +{% endif %}{# nsec3-change.kasp #} + +{% if "nsec3-dynamic-change.kasp" in zones %} +zone "nsec3-dynamic-change.kasp" { + type primary; + file "nsec3-dynamic-change.kasp.db"; + inline-signing no; + dnssec-policy "@nsec3_change@"; + allow-update { any; }; +}; +{% endif %}{# nsec3-dynamic-change.kasp #} + +{% if "nsec3-to-optout.kasp" in zones %} +/* The zone will be reconfigured to use opt-out. */ +zone "nsec3-to-optout.kasp" { + type primary; + file "nsec3-to-optout.kasp.db"; + dnssec-policy "@nsec3_to_optout@"; +}; +{% endif %}{# nsec3-to-optout.kasp #} + +{% if "nsec3-from-optout.kasp" in zones %} +/* The zone will be reconfigured to disable opt-out. */ +zone "nsec3-from-optout.kasp" { + type primary; + file "nsec3-from-optout.kasp.db"; + dnssec-policy "@nsec3_from_optout@"; +}; +{% endif %}{# nsec3-from-optout.kasp #} + +{% if "nsec3-to-nsec.kasp" in zones %} +/* The zone starts with NSEC3, but will be reconfigured to use NSEC. */ +zone "nsec3-to-nsec.kasp" { + type primary; + file "nsec3-to-nsec.kasp.db"; + dnssec-policy "@nsec3_to_nsec@"; +}; +{% endif %}{# nsec3-to-nsec.kasp #} + +{% if "nsec3-fails-to-load.kasp" in zones %} +/* + * The zone fails to load, this should not prevent shutdown. + * The zone is fixed after a reconfig. + */ +zone "nsec3-fails-to-load.kasp" { + type primary; + file "nsec3-fails-to-load.kasp.db"; + dnssec-policy "nsec3"; + allow-update { any; }; +}; +{% endif %}{# nsec3-fails-to-load.kasp #} + +{% if "nsec3-dynamic-to-inline.kasp" in zones %} +/* These zones switch from dynamic to inline-signing or vice versa. */ +zone "nsec3-dynamic-to-inline.kasp" { + type primary; + file "nsec3-dynamic-to-inline.kasp.db"; + dnssec-policy "nsec3"; +{% if not reconfiged %} + allow-update { any; }; + inline-signing no; +{% endif %}{# not reconfiged #} +}; +{% endif %}{# nsec3-dynamic-to-inline.kasp #} + +{% if "nsec3-inline-to-dynamic.kasp" in zones %} +zone "nsec3-inline-to-dynamic.kasp" { + type primary; + file "nsec3-inline-to-dynamic.kasp.db"; + dnssec-policy "nsec3"; +{% if reconfiged %} + allow-update { any; }; + inline-signing no; +{% endif %}{# reconfiged #} +}; +{% endif %}{# nsec3-inline-to-dynamic.kasp #} + +{% if "nsec3-ent.kasp" in zones %} +/* + * This zone will have an empty nonterminal node added and a node deleted. + */ +zone "nsec3-ent.kasp" { + type primary; + file "nsec3-ent.kasp.db"; + dnssec-policy "nsec3"; + inline-signing yes; +}; +{% endif %}{# nsec3-ent.kasp #} + +{% if not reconfiged %} + +{% if "nsec3-dynamic-update-inline.kasp" in zones %} +/* + * Test adding a NSEC3 record to an inline-signing dnssec-policy zone. + */ +zone "nsec3-dynamic-update-inline.kasp" { + type primary; + file "nsec3-dynamic-update-inline.kasp.db"; + allow-update { any; }; + dnssec-policy "nsec"; +}; +{% endif %}{# nsec3-dynamic-update-inline.kasp #} + +{% if "nsec3-xfr-inline.kasp" in zones %} +/* + * This zone will have an empty nonterminal node added and a node deleted. + */ +zone "nsec3-xfr-inline.kasp" { + type secondary; + file "nsec3-xfr-inline.kasp.db"; + dnssec-policy "nsec"; + primaries { 10.53.0.2; }; +}; +{% endif %}{# nsec3-xfr-inline.kasp #} + +{% endif %}{# not reconfiged #} diff --git a/bin/tests/system/nsec3/ns3/named2.conf.in b/bin/tests/system/nsec3/ns3/named-rsasha1.conf.j2 similarity index 71% rename from bin/tests/system/nsec3/ns3/named2.conf.in rename to bin/tests/system/nsec3/ns3/named-rsasha1.conf.j2 index a883940f31..7c47ae2d54 100644 --- a/bin/tests/system/nsec3/ns3/named2.conf.in +++ b/bin/tests/system/nsec3/ns3/named-rsasha1.conf.j2 @@ -11,9 +11,9 @@ * information regarding copyright ownership. */ -// NS3 - -include "named-fips.conf"; +{% set reconfiged = reconfiged | default(False) %} +{% set rsasha1_to_nsec3 = "rsasha1" if not reconfiged else "nsec3" %} +{% set nsec3_to_rsasha1 = "nsec3" if not reconfiged else "rsasha1" %} dnssec-policy "rsasha1" { keys { @@ -21,6 +21,8 @@ dnssec-policy "rsasha1" { }; }; + +{% if "rsasha1-to-nsec3.kasp" in zones %} /* * This zone starts with NSEC, but will be reconfigured to use NSEC3. * This should work despite the incompatible RSAHSHA1 algorithm, @@ -29,10 +31,11 @@ dnssec-policy "rsasha1" { zone "rsasha1-to-nsec3.kasp" { type primary; file "rsasha1-to-nsec3.kasp.db"; - //dnssec-policy "rsasha1"; - dnssec-policy "nsec3"; + dnssec-policy "@rsasha1_to_nsec3@"; }; +{% endif %}{# rsasha1-to-nsec3.kasp #} +{% if "rsasha1-to-nsec3-wait.kasp" in zones %} /* * This zone starts with NSEC, but will be reconfigured to use NSEC3. * This should block because RSASHA1 is not compatible with NSEC3, @@ -41,10 +44,11 @@ zone "rsasha1-to-nsec3.kasp" { zone "rsasha1-to-nsec3-wait.kasp" { type primary; file "rsasha1-to-nsec3-wait.kasp.db"; - //dnssec-policy "rsasha1"; - dnssec-policy "nsec3"; + dnssec-policy "@rsasha1_to_nsec3@"; }; +{% endif %}{# rsasha1-to-nsec3-wait.kasp #} +{% if "nsec3-to-rsasha1.kasp" in zones %} /* * This zone starts with NSEC3, but will be reconfigured to use NSEC with an * NSEC only algorithm. This should work despite the incompatible RSAHSHA1 @@ -53,10 +57,11 @@ zone "rsasha1-to-nsec3-wait.kasp" { zone "nsec3-to-rsasha1.kasp" { type primary; file "nsec3-to-rsasha1.kasp.db"; - //dnssec-policy "nsec3"; - dnssec-policy "rsasha1"; + dnssec-policy "@nsec3_to_rsasha1@"; }; +{% endif %}{# nsec3-to-rsasha1.kasp #} +{% if "nsec3-to-rsasha1-ds.kasp" in zones %} /* * This zone starts with NSEC3, but will be reconfigured to use NSEC with an * NSEC only algorithm. This should also be fine because we are allowed @@ -66,6 +71,6 @@ zone "nsec3-to-rsasha1.kasp" { zone "nsec3-to-rsasha1-ds.kasp" { type primary; file "nsec3-to-rsasha1-ds.kasp.db"; - //dnssec-policy "nsec3"; - dnssec-policy "rsasha1"; + dnssec-policy "@nsec3_to_rsasha1@"; }; +{% endif %}{# nsec3-to-rsasha1-ds.kasp #} diff --git a/bin/tests/system/nsec3/ns3/named.conf.j2 b/bin/tests/system/nsec3/ns3/named.conf.j2 new file mode 100644 index 0000000000..7dd06ad83c --- /dev/null +++ b/bin/tests/system/nsec3/ns3/named.conf.j2 @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// NS3 + +include "named-common.conf"; +include "named-fips.conf"; + +{% if RSASHA1_SUPPORTED == "1" %} +include "named-rsasha1.conf"; +{% endif %} diff --git a/bin/tests/system/nsec3/ns3/named1.conf.in b/bin/tests/system/nsec3/ns3/named1.conf.in deleted file mode 100644 index 9b1235e36f..0000000000 --- a/bin/tests/system/nsec3/ns3/named1.conf.in +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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. - */ - -// NS3 - -include "named-fips.conf"; - -dnssec-policy "rsasha1" { - keys { - csk lifetime unlimited algorithm rsasha1; - }; -}; - -/* - * This zone starts with NSEC, but will be reconfigured to use NSEC3. - * This should work despite the incompatible RSAHSHA1 algorithm, - * because the DS is still in hidden state. - */ -zone "rsasha1-to-nsec3.kasp" { - type primary; - file "rsasha1-to-nsec3.kasp.db"; - dnssec-policy "rsasha1"; -}; - -/* - * This zone starts with NSEC, but will be reconfigured to use NSEC3. - * This should block because RSASHA1 is not compatible with NSEC3, - * and the DS is published. - */ -zone "rsasha1-to-nsec3-wait.kasp" { - type primary; - file "rsasha1-to-nsec3-wait.kasp.db"; - dnssec-policy "rsasha1"; -}; - -/* - * This zone starts with NSEC3, but will be reconfigured to use NSEC with an - * NSEC only algorithm. This should work despite the incompatible RSAHSHA1 - * algorithm, because the DS is still in hidden state. - */ -zone "nsec3-to-rsasha1.kasp" { - type primary; - file "nsec3-to-rsasha1.kasp.db"; - dnssec-policy "nsec3"; -}; - -/* - * This zone starts with NSEC3, but will be reconfigured to use NSEC with an - * NSEC only algorithm. This should also be fine because we are allowed - * to change to NSEC with any algorithm, then we can also publish the new - * DNSKEY and signatures of the RSASHA1 algorithm. - */ -zone "nsec3-to-rsasha1-ds.kasp" { - type primary; - file "nsec3-to-rsasha1-ds.kasp.db"; - dnssec-policy "nsec3"; -}; diff --git a/bin/tests/system/nsec3/ns3/named2-fips.conf.in b/bin/tests/system/nsec3/ns3/named2-fips.conf.in deleted file mode 100644 index 2c9a2b7e20..0000000000 --- a/bin/tests/system/nsec3/ns3/named2-fips.conf.in +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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. - */ - -// NS3 - -dnssec-policy "nsec" { - // no need to change configuration: if no 'nsec3param' is set, - // NSEC will be used; -}; - -dnssec-policy "nsec3" { - nsec3param; -}; - -dnssec-policy "optout" { - nsec3param optout yes; -}; - -dnssec-policy "nsec3-other" { - nsec3param iterations 0 optout yes salt-length 8; -}; - -options { - query-source address 10.53.0.3; - notify-source 10.53.0.3; - transfer-source 10.53.0.3; - port @PORT@; - pid-file "named.pid"; - listen-on { 10.53.0.3; }; - listen-on-v6 { none; }; - allow-transfer { any; }; - recursion no; - dnssec-validation no; -}; - -key rndc_key { - secret "1234abcd8765"; - algorithm @DEFAULT_HMAC@; -}; - -controls { - inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; -}; - -/* This zone starts with NSEC, but will be reconfigured to use NSEC3. */ -zone "nsec-to-nsec3.kasp" { - type primary; - file "nsec-to-nsec3.kasp.db"; - //dnssec-policy "nsec"; - dnssec-policy "nsec3"; -}; - -/* These zones use the default NSEC3 settings. */ -zone "nsec3.kasp" { - type primary; - file "nsec3.kasp.db"; - dnssec-policy "nsec3"; -}; - -zone "nsec3-dynamic.kasp" { - type primary; - file "nsec3-dynamic.kasp.db"; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* This zone uses non-default NSEC3 settings. */ -zone "nsec3-other.kasp" { - type primary; - file "nsec3-other.kasp.db"; - dnssec-policy "nsec3-other"; -}; - -/* These zone will be reconfigured to use other NSEC3 settings. */ -zone "nsec3-change.kasp" { - type primary; - file "nsec3-change.kasp.db"; - //dnssec-policy "nsec3"; - dnssec-policy "nsec3-other"; -}; - -zone "nsec3-dynamic-change.kasp" { - type primary; - file "nsec3-dynamic-change.kasp.db"; - //dnssec-policy "nsec3"; - inline-signing no; - dnssec-policy "nsec3-other"; - allow-update { any; }; -}; - -/* The zone will be reconfigured to use opt-out. */ -zone "nsec3-to-optout.kasp" { - type primary; - file "nsec3-to-optout.kasp.db"; - //dnssec-policy "nsec3"; - dnssec-policy "optout"; -}; - -/* The zone will be reconfigured to disable opt-out. */ -zone "nsec3-from-optout.kasp" { - type primary; - file "nsec3-from-optout.kasp.db"; - //dnssec-policy "optout"; - dnssec-policy "nsec3"; -}; - -/* The zone starts with NSEC3, but will be reconfigured to use NSEC. */ -zone "nsec3-to-nsec.kasp" { - type primary; - file "nsec3-to-nsec.kasp.db"; - //dnssec-policy "nsec3"; - dnssec-policy "nsec"; -}; - -/* The zone fails to load, but is fixed after a reload. */ -zone "nsec3-fails-to-load.kasp" { - type primary; - file "nsec3-fails-to-load.kasp.db"; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* These zones switch from dynamic to inline-signing or vice versa. */ -zone "nsec3-dynamic-to-inline.kasp" { - type primary; - file "nsec3-dynamic-to-inline.kasp.db"; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -zone "nsec3-inline-to-dynamic.kasp" { - type primary; - file "nsec3-inline-to-dynamic.kasp.db"; - inline-signing no; - dnssec-policy "nsec3"; - allow-update { any; }; -}; - -/* - * This zone will have an empty nonterminal node added and a node deleted. - */ -zone "nsec3-ent.kasp" { - type primary; - file "nsec3-ent.kasp.db"; - dnssec-policy "nsec3"; - inline-signing yes; -}; diff --git a/bin/tests/system/nsec3/ns3/nsec3-ent.kasp.db.j2 b/bin/tests/system/nsec3/ns3/nsec3-ent.kasp.db.j2 new file mode 100644 index 0000000000..5348dcfafa --- /dev/null +++ b/bin/tests/system/nsec3/ns3/nsec3-ent.kasp.db.j2 @@ -0,0 +1,41 @@ +; 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. + +{% set serial = serial | default(1) %} + +$ORIGIN nsec3-ent.kasp. +$TTL 300 +nsec3-ent.kasp. IN SOA mname1. . ( + @serial@ ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 + +{% if serial == 1 %} +c A 10.0.0.3 +{% endif %} + +{% if serial == 2 %} +d A 10.0.0.3 +{% endif %} + +{% if serial == 3 %} +c A 10.0.0.3 +x.y.z A 10.0.0.4 +{% endif %} diff --git a/bin/tests/system/nsec3/ns3/setup.sh b/bin/tests/system/nsec3/ns3/setup.sh index 32ddf5e9c5..5b7053e08d 100644 --- a/bin/tests/system/nsec3/ns3/setup.sh +++ b/bin/tests/system/nsec3/ns3/setup.sh @@ -26,8 +26,7 @@ setup() { for zn in nsec-to-nsec3 nsec3 nsec3-other nsec3-change nsec3-to-nsec \ nsec3-to-optout nsec3-from-optout nsec3-dynamic \ nsec3-dynamic-change nsec3-dynamic-to-inline \ - nsec3-inline-to-dynamic nsec3-dynamic-update-inline \ - nsec3-ent; do + nsec3-inline-to-dynamic nsec3-dynamic-update-inline; do setup "${zn}.kasp" done diff --git a/bin/tests/system/nsec3/setup.sh b/bin/tests/system/nsec3/setup.sh index 56c3ac2eef..1ddb23c55a 100644 --- a/bin/tests/system/nsec3/setup.sh +++ b/bin/tests/system/nsec3/setup.sh @@ -16,19 +16,11 @@ set -e -copy_setports ns2/named.conf.in ns2/named.conf ( cd ns2 $SHELL setup.sh ) -if [ $RSASHA1_SUPPORTED = 0 ]; then - copy_setports ns3/named-fips.conf.in ns3/named.conf -else - copy_setports ns3/named-fips.conf.in ns3/named-fips.conf - # includes named-fips.conf - cp ns3/named1.conf.in ns3/named.conf -fi ( cd ns3 $SHELL setup.sh diff --git a/bin/tests/system/nsec3/tests.sh b/bin/tests/system/nsec3/tests.sh deleted file mode 100644 index bcb1144ada..0000000000 --- a/bin/tests/system/nsec3/tests.sh +++ /dev/null @@ -1,623 +0,0 @@ -#!/bin/sh - -# 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. - -set -e - -# shellcheck source=conf.sh -. ../conf.sh -# shellcheck source=kasp.sh -. ../kasp.sh - -# Log errors and increment $ret. -log_error() { - echo_i "error: $1" - ret=$((ret + 1)) -} - -# Call dig with default options. -dig_with_opts() { - $DIG +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@" -} - -# Call rndc. -rndccmd() { - "$RNDC" -c ../_common/rndc.conf -p "$CONTROLPORT" -s "$@" -} - -# Set zone name ($1) and policy ($2) for testing nsec3. -# Also set the expected number of keys ($3) and DNSKEY TTL ($4). -set_zone_policy() { - ZONE=$1 - POLICY=$2 - NUM_KEYS=$3 - DNSKEY_TTL=$4 - KEYFILE_TTL=$4 - # The CDS digest type in these tests are all the default, - # which is SHA-256 (2). - CDS_SHA256="yes" - CDS_SHA384="no" -} -# Set expected NSEC3 parameters: flags ($1) and salt length ($2). -set_nsec3param() { - FLAGS=$1 - SALTLEN=$2 - # Reset salt. - SALT="" -} - -# Set expected default dnssec-policy keys values. -set_key_default_values() { - key_clear $1 - - set_keyrole $1 "csk" - set_keylifetime $1 "0" - set_keyalgorithm $1 "13" "ECDSAP256SHA256" "256" - set_keysigning $1 "yes" - set_zonesigning $1 "yes" - - set_keystate $1 "GOAL" "omnipresent" - set_keystate $1 "STATE_DNSKEY" "rumoured" - set_keystate $1 "STATE_KRRSIG" "rumoured" - set_keystate $1 "STATE_ZRRSIG" "rumoured" - set_keystate $1 "STATE_DS" "hidden" -} - -# Set expected rsasha1 dnssec-policy keys values. -set_key_rsasha1_values() { - key_clear $1 - - set_keyrole $1 "csk" - set_keylifetime $1 "0" - set_keyalgorithm $1 "5" "RSASHA1" "2048" - set_keysigning $1 "yes" - set_zonesigning $1 "yes" - - set_keystate $1 "GOAL" "omnipresent" - set_keystate $1 "STATE_DNSKEY" "rumoured" - set_keystate $1 "STATE_KRRSIG" "rumoured" - set_keystate $1 "STATE_ZRRSIG" "rumoured" - set_keystate $1 "STATE_DS" "hidden" -} - -# Update the key states. -set_key_states() { - set_keystate $1 "GOAL" "$2" - set_keystate $1 "STATE_DNSKEY" "$3" - set_keystate $1 "STATE_KRRSIG" "$4" - set_keystate $1 "STATE_ZRRSIG" "$5" - set_keystate $1 "STATE_DS" "$6" -} - -# The apex NSEC3PARAM record indicates that it is signed. -_wait_for_nsec3param() { - dig_with_opts +noquestion "@${SERVER}" "$ZONE" NSEC3PARAM >"dig.out.test$n.wait" || return 1 - grep "${ZONE}\..*IN.*NSEC3PARAM 1 0 0.*${SALT}" "dig.out.test$n.wait" >/dev/null || return 1 - grep "${ZONE}\..*IN.*RRSIG" "dig.out.test$n.wait" >/dev/null || return 1 - return 0 -} -# The apex NSEC record indicates that it is signed. -_wait_for_nsec() { - dig_with_opts +noquestion "@${SERVER}" "$ZONE" NSEC >"dig.out.test$n.wait" || return 1 - grep "NS SOA" "dig.out.test$n.wait" >/dev/null || return 1 - grep "${ZONE}\..*IN.*RRSIG" "dig.out.test$n.wait" >/dev/null || return 1 - grep "${ZONE}\..*IN.*NSEC3PARAM" "dig.out.test$n.wait" >/dev/null && return 1 - return 0 -} - -# Wait for the zone to be signed. -wait_for_zone_is_signed() { - n=$((n + 1)) - ret=0 - echo_i "wait for ${ZONE} to be signed with $1 ($n)" - - if [ "$1" = "nsec3" ]; then - retry_quiet 10 _wait_for_nsec3param || log_error "wait for ${ZONE} to be signed failed" - else - retry_quiet 10 _wait_for_nsec || log_error "wait for ${ZONE} to be signed failed" - fi - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Test: check DNSSEC verify -_check_dnssec_verify() { - dig_with_opts @$SERVER "${ZONE}" AXFR >"dig.out.test$n.axfr.$ZONE" || return 1 - $VERIFY -z -o "$ZONE" "dig.out.test$n.axfr.$ZONE" >"verify.out.test$n.$ZONE" 2>&1 || return 1 - return 0 -} - -# Test: check NSEC in answers -_check_nsec_nsec3param() { - dig_with_opts +noquestion @$SERVER "${ZONE}" NSEC3PARAM >"dig.out.test$n.nsec3param.$ZONE" || return 1 - grep "NSEC3PARAM" "dig.out.test$n.nsec3param.$ZONE" >/dev/null && return 1 - return 0 -} - -_check_nsec_nxdomain() { - dig_with_opts @$SERVER "nosuchname.${ZONE}" >"dig.out.test$n.nxdomain.$ZONE" || return 1 - grep "${ZONE}.*IN.*NSEC.*NS.*SOA.*RRSIG.*NSEC.*DNSKEY" "dig.out.test$n.nxdomain.$ZONE" >/dev/null || return 1 - grep "NSEC3" "dig.out.test$n.nxdomain.$ZONE" >/dev/null && return 1 - return 0 -} - -check_nsec() { - wait_for_zone_is_signed "nsec" - - n=$((n + 1)) - echo_i "check DNSKEY rrset is signed correctly for zone ${ZONE} ($n)" - ret=0 - check_keys - retry_quiet 10 _check_apex_dnskey || log_error "bad DNSKEY RRset for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - n=$((n + 1)) - echo_i "verify DNSSEC for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_dnssec_verify || log_error "DNSSEC verify failed for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check NSEC3PARAM response for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_nsec_nsec3param || log_error "unexpected NSEC3PARAM in response for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check NXDOMAIN response for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_nsec_nxdomain || log_error "bad NXDOMAIN response for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Test: check NSEC3 parameters in answers -_check_nsec3_nsec3param() { - dig_with_opts +noquestion @$SERVER "${ZONE}" NSEC3PARAM >"dig.out.test$n.nsec3param.$ZONE" || return 1 - grep "${ZONE}.*0.*IN.*NSEC3PARAM.*1.*0.*0.*${SALT}" "dig.out.test$n.nsec3param.$ZONE" >/dev/null || return 1 - - if [ -z "$SALT" ]; then - SALT=$(awk '$4 == "NSEC3PARAM" { print $8 }' dig.out.test$n.nsec3param.$ZONE) - fi - return 0 -} - -_check_nsec3_nxdomain() { - dig_with_opts @$SERVER "nosuchname.${ZONE}" >"dig.out.test$n.nxdomain.$ZONE" || return 1 - grep ".*\.${ZONE}.*IN.*NSEC3.*1.${FLAGS}.*0.*${SALT}" "dig.out.test$n.nxdomain.$ZONE" >/dev/null || return 1 - return 0 -} - -check_nsec3() { - wait_for_zone_is_signed "nsec3" - - n=$((n + 1)) - echo_i "check that NSEC3PARAM 1 0 0 ${SALT} is published zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_nsec3_nsec3param || log_error "bad NSEC3PARAM response for ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check NXDOMAIN response has correct NSEC3 1 ${FLAGS} 0 ${SALT} for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_nsec3_nxdomain || log_error "bad NXDOMAIN response for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - n=$((n + 1)) - echo_i "verify DNSSEC for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_dnssec_verify || log_error "DNSSEC verify failed for zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -start_time="$(TZ=UTC date +%s)" -status=0 -n=0 - -key_clear "KEY1" -key_clear "KEY2" -key_clear "KEY3" -key_clear "KEY4" - -# Zone: nsec-to-nsec3.kasp. -set_zone_policy "nsec-to-nsec3.kasp" "nsec" 1 3600 -set_server "ns3" "10.53.0.3" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec - -if [ $RSASHA1_SUPPORTED = 1 ]; then - # Zone: rsasha1-to-nsec3.kasp. - set_zone_policy "rsasha1-to-nsec3.kasp" "rsasha1" 1 3600 - set_server "ns3" "10.53.0.3" - set_key_rsasha1_values "KEY1" - echo_i "initial check zone ${ZONE}" - check_nsec - - # Zone: rsasha1-to-nsec3-wait.kasp. - set_zone_policy "rsasha1-to-nsec3-wait.kasp" "rsasha1" 1 3600 - set_server "ns3" "10.53.0.3" - set_key_rsasha1_values "KEY1" - set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" - echo_i "initial check zone ${ZONE}" - check_nsec - - # Zone: nsec3-to-rsasha1.kasp. - set_zone_policy "nsec3-to-rsasha1.kasp" "nsec3" 1 3600 - set_server "ns3" "10.53.0.3" - set_key_rsasha1_values "KEY1" - echo_i "initial check zone ${ZONE}" - check_nsec3 - - # Zone: nsec3-to-rsasha1-ds.kasp. - set_zone_policy "nsec3-to-rsasha1-ds.kasp" "nsec3" 1 3600 - set_server "ns3" "10.53.0.3" - set_key_rsasha1_values "KEY1" - set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" - echo_i "initial check zone ${ZONE}" - check_nsec3 -fi - -# Zone: nsec3.kasp. -set_zone_policy "nsec3.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-dynamic.kasp. -set_zone_policy "nsec3-dynamic.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-change.kasp. -set_zone_policy "nsec3-change.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Test that NSEC3PARAM TTL is equal to SOA MINIMUM. -n=$((n + 1)) -echo_i "check TTL of NSEC3PARAM in zone $ZONE is equal to SOA MINIMUM ($n)" -ret=0 -dig_with_opts +noquestion "@${SERVER}" "$ZONE" NSEC3PARAM >"dig.out.test$n" || ret=1 -grep "${ZONE}\..*3600.*IN.*NSEC3PARAM" "dig.out.test$n" >/dev/null || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# Update SOA MINIMUM. -cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db" -rndccmd $SERVER reload $ZONE >rndc.reload.test$n.$ZONE || log_error "failed to call rndc reload $ZONE" -_wait_for_new_soa() { - dig_with_opts +noquestion "@${SERVER}" "$ZONE" SOA >"dig.out.soa.test$n" || return 1 - grep "${ZONE}\..*IN.*SOA.*mname1..*..*20.*20.*.1814400.*900" "dig.out.soa.test$n" >/dev/null || return 1 -} -retry_quiet 10 _wait_for_new_soa || log_error "failed to update SOA record in zone $ZONE" - -# Zone: nsec3-dynamic-change.kasp. -set_zone_policy "nsec3-dynamic-change.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-dynamic-to-inline.kasp. -set_zone_policy "nsec3-dynamic-to-inline.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-inline-to-dynamic.kasp. -set_zone_policy "nsec3-inline-to-dynamic.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-to-nsec.kasp. -set_zone_policy "nsec3-to-nsec.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-to-optout.kasp. -set_zone_policy "nsec3-to-optout.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-from-optout.kasp. -set_zone_policy "nsec3-from-optout.kasp" "optout" 1 3600 -set_nsec3param "1" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-other.kasp. -set_zone_policy "nsec3-other.kasp" "nsec3-other" 1 3600 -set_nsec3param "1" "8" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-xfr-inline.kasp. -# This is a secondary zone, where the primary is signed with NSEC3 but -# the dnssec-policy dictates NSEC. -set_zone_policy "nsec3-xfr-inline.kasp" "nsec" 1 3600 -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec - -# Zone: nsec3-dynamic-update-inline.kasp. -set_zone_policy "nsec3-dynamic-update-inline.kasp" "nsec" 1 3600 -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec - -n=$((n + 1)) -echo_i "dynamic update dnssec-policy zone ${ZONE} with NSEC3 ($n)" -ret=0 -$NSUPDATE >update.out.$ZONE.test$n 2>&1 <"dig.out.nsec3param.test$n" || ret=1 -grep "${ZONE}\..*900.*IN.*NSEC3PARAM" "dig.out.nsec3param.test$n" >/dev/null || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# Using rndc signing -nsec3param (should fail) -echo_i "use rndc signing -nsec3param ${ZONE} to change NSEC3 settings" -rndccmd $SERVER signing -nsec3param 1 1 12 ffff $ZONE >rndc.signing.test$n.$ZONE || log_error "failed to call rndc signing -nsec3param $ZONE" -grep "zone uses dnssec-policy, use rndc dnssec command instead" rndc.signing.test$n.$ZONE >/dev/null || log_error "rndc signing -nsec3param should fail" -check_nsec3 - -# Zone: nsec3-dynamic-change.kasp. (reconfigured) -set_zone_policy "nsec3-dynamic-change.kasp" "nsec3-other" 1 3600 -set_nsec3param "1" "8" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} after reconfig" -check_nsec3 - -# Zone: nsec3-dynamic-to-inline.kasp. (same) -set_zone_policy "nsec3-dynamic-to-inline.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} after reconfig" -check_nsec3 - -# Zone: nsec3-inline-to-dynamic.kasp. (same) -set_zone_policy "nsec3-inline-to-dynamic.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "initial check zone ${ZONE}" -check_nsec3 - -# Zone: nsec3-to-nsec.kasp. (reconfigured) -set_zone_policy "nsec3-to-nsec.kasp" "nsec" 1 3600 -set_nsec3param "1" "8" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} after reconfig" -check_nsec - -# Zone: nsec3-to-optout.kasp. (reconfigured) -# DISABLED: -# There is a bug in the nsec3param building code that thinks when the -# optout bit is changed, the chain already exists. [GL #2216] -#set_zone_policy "nsec3-to-optout.kasp" "optout" 1 3600 -#set_nsec3param "1" "0" -#set_key_default_values "KEY1" -#echo_i "check zone ${ZONE} after reconfig" -#check_nsec3 - -# Zone: nsec3-from-optout.kasp. (reconfigured) -# DISABLED: -# There is a bug in the nsec3param building code that thinks when the -# optout bit is changed, the chain already exists. [GL #2216] -#set_zone_policy "nsec3-from-optout.kasp" "nsec3" 1 3600 -#set_nsec3param "0" "0" -#set_key_default_values "KEY1" -#echo_i "check zone ${ZONE} after reconfig" -#check_nsec3 - -# Zone: nsec3-other.kasp. (same) -set_zone_policy "nsec3-other.kasp" "nsec3-other" 1 3600 -set_nsec3param "1" "8" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} after reconfig" -check_nsec3 - -# Test NSEC3 and NSEC3PARAM is the same after restart -set_zone_policy "nsec3.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} before restart" -check_nsec3 - -# Restart named, NSEC3 should stay the same. -ret=0 -echo "stop ns3" -stop_server --use-rndc --port ${CONTROLPORT} ${DIR} || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -ret=0 -echo "start ns3" -start_server --noclean --restart --port ${PORT} ${DIR} -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -prevsalt="${SALT}" -set_zone_policy "nsec3.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -SALT="${prevsalt}" -echo_i "check zone ${ZONE} after restart has salt ${SALT}" -check_nsec3 - -# Zone: nsec3-fails-to-load.kasp. (should be fixed after reload) -cp ns3/template.db.in ns3/nsec3-fails-to-load.kasp.db -rndc_reload ns3 10.53.0.3 - -set_zone_policy "nsec3-fails-to-load.kasp" "nsec3" 1 3600 -set_nsec3param "0" "0" -set_key_default_values "KEY1" -echo_i "check zone ${ZONE} after reload" -check_nsec3 - -# Zone: nsec3-ent.kasp (regression test for #5108) -n=$((n + 1)) -echo_i "check query for newly empty name does not crash ($n)" -set_zone_policy "nsec3-ent.kasp" -set_server "ns3" "10.53.0.3" -# confirm the pre-existing name still exists -dig_with_opts +noquestion "@${SERVER}" c.$ZONE >"dig.out.$ZONE.test$n.1" || ret=1 -grep "c\.nsec3-ent\.kasp\..*IN.*A.*10\.0\.0\.3" "dig.out.$ZONE.test$n.1" >/dev/null || ret=1 -# remove a name, bump the SOA, and reload -sed -e 's/1 *; serial/2/' -e '/^c/d' ns3/template.db.in >ns3/nsec3-ent.kasp.db -rndc_reload ns3 10.53.0.3 -# try the query again -dig_with_opts +noquestion "@${SERVER}" c.$ZONE >"dig.out.$ZONE.test$n.2" || ret=1 -grep "status: NXDOMAIN" "dig.out.$ZONE.test$n.2" >/dev/null || ret=1 -if [ "$ret" -ne 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -n=$((n + 1)) -echo_i "check queries for new names below ENT do not crash ($n)" -set_zone_policy "nsec3-ent.kasp" -set_server "ns3" "10.53.0.3" -# confirm the ENT name does not exist yet -dig_with_opts +noquestion "@${SERVER}" x.y.z.$ZONE >"dig.out.$ZONE.test$n.1" || ret=1 -grep "status: NXDOMAIN" "dig.out.$ZONE.test$n.1" >/dev/null || ret=1 -# add a name with an ENT, bump the SOA, and reload ensuring the time stamp changes -sleep 1 -sed -e 's/1 *; serial/3/' ns3/template.db.in >ns3/nsec3-ent.kasp.db -echo "x.y.z A 10.0.0.4" >>ns3/nsec3-ent.kasp.db -rndc_reload ns3 10.53.0.3 -# try the query again -dig_with_opts +noquestion "@${SERVER}" x.y.z.$ZONE >"dig.out.$ZONE.test$n.2" || ret=1 -grep "x\.y\.z\.nsec3-ent\.kasp\..*IN.*A.*10\.0\.0\.4" "dig.out.$ZONE.test$n.2" >/dev/null || ret=1 -if [ "$ret" -ne 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/nsec3/tests_nsec3_change.py b/bin/tests/system/nsec3/tests_nsec3_change.py new file mode 100644 index 0000000000..a65a1789be --- /dev/null +++ b/bin/tests/system/nsec3/tests_nsec3_change.py @@ -0,0 +1,117 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import os +import shutil +import time + +import dns.update +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import isctest +import isctest.mark +from isctest.vars.algorithms import RSASHA1 +from nsec3.common import ( + ALGORITHM, + SIZE, + default_config, + pytestmark, + check_nsec3_case, +) + + +# include the following zones when rendering named configs +ZONES = { + "nsec3-change.kasp", +} + + +def bootstrap(): + return { + "zones": ZONES, + } + + +@pytest.fixture(scope="module", autouse=True) +def after_servers_start(ns3, templates): + + def check_soa_update(): + query = isctest.query.create(fqdn, dns.rdatatype.SOA) + response = isctest.query.tcp(query, ns3.ip, attempts=1, timeout=2) + rrset = response.get_rrset( + response.answer, + dns.name.from_text(fqdn), + dns.rdataclass.IN, + dns.rdatatype.SOA, + ) + return rrset[0].minimum == 900 + + nsdir = ns3.identifier + + zone = "nsec3-change.kasp" + fqdn = f"{zone}." + isctest.kasp.wait_keymgr_done(ns3, zone) + + shutil.copyfile(f"{nsdir}/template2.db.in", f"{nsdir}/{zone}.db") + ns3.rndc(f"reload {zone}") + + isctest.run.retry_with_timeout(check_soa_update, timeout=10) + # After reconfig, the NSEC3PARAM TTL should match the new SOA MINIMUM. + + # Reconfigure. + data = { + "reconfiged": True, + "zones": ZONES, + } + templates.render(f"{nsdir}/named-fips.conf", data) + templates.render(f"{nsdir}/named-rsasha1.conf", data) + + # Wait for the NSEC3 chain is finished rebuilding. + messages = [ + f"zone {zone}/IN (signed): generated salt", + f"zone_nsec3chain: zone {zone}/IN (signed): enter", + f"add {zone}. 900 IN NSEC3PARAM 1 0 0", + f"zone_needdump: zone {zone}/IN (signed): enter", + ] + with ns3.watch_log_from_start() as watcher: + ns3.reconfigure() + watcher.wait_for_sequence(messages) + + +def test_nsec3_case(ns3): + # Get test parameters. + params = { + "zone": "nsec3-change.kasp", + "policy": "nsec3", + "soa-minimum": 900, + "nsec3param": { + "optout": 1, + "salt-length": 8, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + } + zone = params["zone"] + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone, reconfig=True) + + # Test case. + check_nsec3_case(ns3, params) + + # Using rndc signing -nsec3param (should fail) + isctest.log.info(f"use rndc signing -nsec3param {zone} to change NSEC3 settings") + response = ns3.rndc(f"signing -nsec3param 1 1 12 ffff {zone}") + assert "zone uses dnssec-policy, use rndc dnssec command instead" in response diff --git a/bin/tests/system/nsec3/tests_nsec3_initial.py b/bin/tests/system/nsec3/tests_nsec3_initial.py new file mode 100644 index 0000000000..6ef80343a9 --- /dev/null +++ b/bin/tests/system/nsec3/tests_nsec3_initial.py @@ -0,0 +1,300 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import os + +import dns.update +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import isctest +import isctest.mark +from isctest.vars.algorithms import RSASHA1 +from nsec3.common import ( + ALGORITHM, + SIZE, + default_config, + pytestmark, + check_nsec3_case, +) + + +# include the following zones when rendering named configs +ZONES = { + "nsec-to-nsec3.kasp", + "nsec3-xfr-inline.kasp", + "nsec3-dynamic-update-inline.kasp", + "nsec3.kasp", + "nsec3-dynamic.kasp", + "nsec3-change.kasp", + "nsec3-dynamic-change.kasp", + "nsec3-dynamic-to-inline.kasp", + "nsec3-inline-to-dynamic.kasp", + "nsec3-to-nsec.kasp", + "nsec3-to-optout.kasp", + "nsec3-from-optout.kasp", + "nsec3-other.kasp", +} + +if os.environ["RSASHA1_SUPPORTED"] == "1": + ZONES.update( + { + "rsasha1-to-nsec3.kasp", + "rsasha1-to-nsec3-wait.kasp", + "nsec3-to-rsasha1.kasp", + "nsec3-to-rsasha1-ds.kasp", + } + ) + + +def bootstrap(): + return { + "zones": ZONES, + } + + +@pytest.mark.parametrize( + "params", + [ + pytest.param( + { + "zone": "nsec-to-nsec3.kasp", + "policy": "nsec", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec-to-nsec3.kasp", + ), + pytest.param( + { + "zone": "rsasha1-to-nsec3.kasp", + "policy": "rsasha1", + "key-properties": [ + f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="rsasha1-to-nsec3.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "rsasha1-to-nsec3-wait.kasp", + "policy": "rsasha1", + "key-properties": [ + f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + ], + }, + id="rsasha1-to-nsec3-wait.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + # This is a secondary zone, where the primary is signed with + # NSEC3 but the dnssec-policy dictates NSEC. + "zone": "nsec3-xfr-inline.kasp", + "policy": "nsec", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + "external-keys": [ + f"csk 0 {ALGORITHM} {SIZE}", + ], + "external-keydir": "ns2", + }, + id="nsec3-xfr-inline.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic-update-inline.kasp", + "policy": "nsec", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic-update-inline.kasp", + ), + ], +) +def test_nsec_case(ns3, params): + # Get test parameters. + zone = params["zone"] + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone) + + # Test case. + check_nsec3_case(ns3, params, nsec3=False) + + # Extra test for nsec3-dynamic-update-inline.kasp. + if zone == "nsec3-dynamic-update-inline.kasp": + isctest.log.info(f"dynamic update dnssec-policy zone {zone} with NSEC3") + update_msg = dns.update.UpdateMessage(zone) + update_msg.add( + f"04O18462RI5903H8RDVL0QDT5B528DUJ.{zone}.", + 3600, + "NSEC3", + "0 0 0 408A4B2D412A4E95 1JMDDPMTFF8QQLIOINSIG4CR9OTICAOC A RRSIG", + ) + + with ns3.watch_log_from_here() as watcher: + ns3.nsupdate(update_msg, expected_rcode=dns.rcode.REFUSED) + watcher.wait_for_line( + f"updating zone '{zone}/IN': update failed: explicit NSEC3 updates are not allowed in secure zones (REFUSED)" + ) + + +@pytest.mark.parametrize( + "params", + [ + pytest.param( + { + "zone": "nsec3-to-rsasha1.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-rsasha1.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3-to-rsasha1-ds.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + ], + }, + id="nsec3-to-rsasha1-ds.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic.kasp", + ), + pytest.param( + { + "zone": "nsec3-change.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-change.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic-change.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic-change.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic-to-inline.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic-to-inline.kasp", + ), + pytest.param( + { + "zone": "nsec3-inline-to-dynamic.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-inline-to-dynamic.kasp", + ), + pytest.param( + { + "zone": "nsec3-to-nsec.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-nsec.kasp", + ), + pytest.param( + { + "zone": "nsec3-to-optout.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-optout.kasp", + ), + pytest.param( + { + "zone": "nsec3-from-optout.kasp", + "policy": "optout", + "nsec3param": { + "optout": 1, + "salt-length": 0, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-from-optout.kasp", + ), + pytest.param( + { + "zone": "nsec3-other.kasp", + "policy": "nsec3-other", + "nsec3param": { + "optout": 1, + "salt-length": 8, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-other.kasp", + ), + ], +) +def test_nsec3_case(ns3, params): + # Get test parameters. + zone = params["zone"] + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone) + + # Test case. + check_nsec3_case(ns3, params) diff --git a/bin/tests/system/nsec3/tests_nsec3_reconfig.py b/bin/tests/system/nsec3/tests_nsec3_reconfig.py new file mode 100644 index 0000000000..47cd77ecf2 --- /dev/null +++ b/bin/tests/system/nsec3/tests_nsec3_reconfig.py @@ -0,0 +1,365 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import os +import shutil +import time + +import dns.update +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import isctest +import isctest.mark +from isctest.vars.algorithms import RSASHA1 +from nsec3.common import ( + ALGORITHM, + SIZE, + default_config, + pytestmark, + check_nsec3_case, +) + + +# include the following zones when rendering named configs +ZONES = { + "nsec3-to-nsec.kasp", + "nsec-to-nsec3.kasp", + "nsec3.kasp", + "nsec3-dynamic.kasp", + "nsec3-dynamic-change.kasp", + "nsec3-dynamic-to-inline.kasp", + "nsec3-inline-to-dynamic.kasp", + # "nsec3-to-optout.kasp", + # "nsec3-from-optout.kasp", + "nsec3-other.kasp", + "nsec3-ent.kasp", +} + +if os.environ["RSASHA1_SUPPORTED"] == "1": + ZONES.update( + { + "rsasha1-to-nsec3-wait.kasp", + "nsec3-to-rsasha1.kasp", + "nsec3-to-rsasha1-ds.kasp", + "rsasha1-to-nsec3.kasp", + } + ) + + +def bootstrap(): + return { + "zones": ZONES, + } + + +@pytest.fixture(scope="module", autouse=True) +def after_servers_start(ns3, templates): + # First make sure all zones are properly signed. Here we specifically need + # to wait until all zones have finished key management before we can + # reconfigure the server, because changing the DNSSEC policy relies on + # zones having finished applying their initial policy. + for zone in ZONES: + isctest.kasp.wait_keymgr_done(ns3, zone) + + # Ensure rsasha1-to-nsec3-wait.kasp is fully signed prior to reconfig. + with_rsasha1 = "RSASHA1_SUPPORTED" + assert with_rsasha1 in os.environ, f"{with_rsasha1} env variable undefined" + if os.getenv(with_rsasha1) == "1": + zone = "rsasha1-to-nsec3-wait.kasp" + isctest.kasp.check_dnssec_verify(ns3, zone) + + # Reconfigure. + data = { + "reconfiged": True, + "zones": ZONES, + } + templates.render(f"{ns3.identifier}/named-fips.conf", data) + templates.render(f"{ns3.identifier}/named-rsasha1.conf", data) + ns3.reconfigure() + + +@pytest.mark.parametrize( + "params", + [ + pytest.param( + { + "zone": "rsasha1-to-nsec3-wait.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {RSASHA1.number} 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="rsasha1-to-nsec3-wait.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3-to-rsasha1.kasp", + "policy": "rsasha1", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:unretentive ds:hidden", + f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-rsasha1.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3-to-rsasha1-ds.kasp", + "policy": "rsasha1", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-rsasha1-ds.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3-to-nsec.kasp", + "policy": "nsec", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-to-nsec.kasp", + ), + ], +) +def test_nsec_case(ns3, params): + zone = params["zone"] + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone, reconfig=True) + + # Test case. + check_nsec3_case(ns3, params, nsec3=False) + + +@pytest.mark.parametrize( + "params", + [ + pytest.param( + { + "zone": "nsec-to-nsec3.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec-to-nsec3.kasp", + ), + pytest.param( + { + "zone": "rsasha1-to-nsec3.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {RSASHA1.number} 2048 goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:unretentive ds:hidden", + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="rsasha1-to-nsec3.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "nsec3.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic-change.kasp", + "policy": "nsec3-other", + "nsec3param": { + "optout": 1, + "salt-length": 8, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic-change.kasp", + ), + pytest.param( + { + "zone": "nsec3-dynamic-to-inline.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-dynamic-to-inline.kasp", + ), + pytest.param( + { + "zone": "nsec3-inline-to-dynamic.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-inline-to-dynamic.kasp", + ), + # DISABLED: + # There is a bug in the nsec3param building code that thinks when the + # optout bit is changed, the chain already exists. [GL #2216] + # pytest.param( + # { + # "zone": "nsec3-to-optout.kasp", + # "policy": "nsec3", + # "nsec3param": { + # "optout": 1, + # "salt-length": 0, + # }, + # "key-properties": [ + # f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + # ], + # }, + # id="nsec3-to-optout.kasp", + # ), + # DISABLED: + # There is a bug in the nsec3param building code that thinks when the + # optout bit is changed, the chain already exists. [GL #2216] + # pytest.param( + # { + # "zone": "nsec3-from-optout.kasp", + # "policy": "optout", + # "key-properties": [ + # f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + # ], + # }, + # id="nsec3-from-optout.kasp", + # ), + pytest.param( + { + "zone": "nsec3-other.kasp", + "policy": "nsec3-other", + "nsec3param": { + "optout": 1, + "salt-length": 8, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-other.kasp", + ), + ], +) +def test_nsec3_case(ns3, params): + # Get test parameters. + zone = params["zone"] + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone, reconfig=True) + + # Test case. + check_nsec3_case(ns3, params) + + +def test_nsec3_ent(ns3, templates): + # Zone: nsec3-ent.kasp (regression test for #5108) + params = { + "zone": "nsec3-ent.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + } + + zone = params["zone"] + fqdn = f"{zone}." + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone, reconfig=True) + + # Test case. + check_nsec3_case(ns3, params) + + # Test empty non-terminals do not trigger a crash. + isctest.log.info("check query for newly empty name does not crash") + + # confirm the pre-existing name still exists + query = isctest.query.create(f"c.{fqdn}", dns.rdatatype.A) + response = isctest.query.tcp(query, ns3.ip, ns3.ports.dns, timeout=3) + assert response.rcode() == dns.rcode.NOERROR + + match = "10.0.0.3" + rrset = response.get_rrset( + response.answer, + dns.name.from_text(f"c.{fqdn}"), + dns.rdataclass.IN, + dns.rdatatype.A, + ) + assert rrset is not None, "no A records found in answer section" + assert match in str(rrset[0]) + + # remove a name, bump the SOA, and reload + templates.render(f"{ns3.identifier}/nsec3-ent.kasp.db", {"serial": 2}) + + with ns3.watch_log_from_here() as watcher: + ns3.rndc(f"reload {zone}") + watcher.wait_for_line(f"zone {zone}/IN (signed): sending notifies") + + # try the query again + query = isctest.query.create(f"c.{fqdn}", dns.rdatatype.A) + response = isctest.query.tcp(query, ns3.ip, ns3.ports.dns, timeout=3) + assert response.rcode() == dns.rcode.NXDOMAIN + + isctest.log.info("check queries for new names below ENT do not crash") + + # confirm the ENT name does not exist yet + query = isctest.query.create(f"x.y.z.{fqdn}", dns.rdatatype.A) + response = isctest.query.tcp(query, ns3.ip, ns3.ports.dns, timeout=3) + assert response.rcode() == dns.rcode.NXDOMAIN + + # add a name with an ENT, bump the SOA, and reload ensuring the time stamp changes + templates.render(f"{ns3.identifier}/nsec3-ent.kasp.db", {"serial": 3}) + + with ns3.watch_log_from_here() as watcher: + ns3.rndc(f"reload {zone}") + watcher.wait_for_line(f"zone {zone}/IN (signed): sending notifies") + + # try the query again + query = isctest.query.create(f"x.y.z.{fqdn}", dns.rdatatype.A) + response = isctest.query.tcp(query, ns3.ip, ns3.ports.dns, timeout=3) + assert response.rcode() == dns.rcode.NOERROR + + match = "10.0.0.4" + rrset = response.get_rrset( + response.answer, + dns.name.from_text(f"x.y.z.{fqdn}"), + dns.rdataclass.IN, + dns.rdatatype.A, + ) + assert rrset is not None, "no A records found in answer section" + assert match in str(rrset[0]) diff --git a/bin/tests/system/nsec3/tests_nsec3_reload.py b/bin/tests/system/nsec3/tests_nsec3_reload.py new file mode 100644 index 0000000000..b3fb860288 --- /dev/null +++ b/bin/tests/system/nsec3/tests_nsec3_reload.py @@ -0,0 +1,64 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import os +import shutil +import time + +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import isctest +from nsec3.common import ( + ALGORITHM, + SIZE, + check_nsec3_case, +) + + +# include the following zones when rendering named configs +ZONES = { + "nsec3-fails-to-load.kasp", +} + + +def bootstrap(): + return { + "zones": ZONES, + } + + +def test_nsec3_case(ns3): + # Get test parameters. + params = { + "zone": "nsec3-fails-to-load.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + } + zone = params["zone"] + + # nsec3-fails-to-load.kasp. fails to load (should be fixed after reload). + zone = "nsec3-fails-to-load.kasp" + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line(f"zone {zone}/IN (unsigned): not loaded due to errors.") + + shutil.copyfile(f"{ns3.identifier}/template.db.in", f"{ns3.identifier}/{zone}.db") + ns3.rndc(f"reload {zone}") + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(ns3, zone) + + # Test case. + check_nsec3_case(ns3, params) diff --git a/bin/tests/system/nsec3/tests_nsec3_restart.py b/bin/tests/system/nsec3/tests_nsec3_restart.py new file mode 100644 index 0000000000..e1e76c4f98 --- /dev/null +++ b/bin/tests/system/nsec3/tests_nsec3_restart.py @@ -0,0 +1,112 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import os + +import dns.update +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import isctest +import isctest.mark +from nsec3.common import ( + ALGORITHM, + SIZE, + default_config, + pytestmark, + check_nsec3_case, + check_nsec3param, +) + + +# include the following zones when rendering named configs +ZONES = { + "nsec3.kasp", + "nsec3-other.kasp", +} + + +def bootstrap(): + return { + "zones": ZONES, + } + + +def perform_nsec3_tests(server, params): + # Get test parameters. + zone = params["zone"] + fqdn = f"{zone}." + + # First make sure the zone is properly signed. + isctest.kasp.wait_keymgr_done(server, zone) + + # Test case. + check_nsec3_case(server, params) + + # Return salt. + minimum = params.get("soa-minimum", 3600) + iterations = 0 + saltlen = 0 + if "nsec3param" in params: + saltlen = params["nsec3param"].get("salt-length", 0) + + match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}" + + query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM) + response = isctest.query.tcp(query, server.ip) + return check_nsec3param(response, match, saltlen) + + +@pytest.mark.parametrize( + "params", + [ + pytest.param( + { + "zone": "nsec3.kasp", + "policy": "nsec3", + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3.kasp", + ), + pytest.param( + { + "zone": "nsec3-other.kasp", + "policy": "nsec3-other", + "nsec3param": { + "optout": 1, + "salt-length": 8, + }, + "key-properties": [ + f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="nsec3-other.kasp", + ), + ], +) +def test_nsec3_case(ns3, params): + zone = params["zone"] + salt = perform_nsec3_tests(ns3, params) + + # Test NSEC3 and NSEC3PARAM is the same after restart + isctest.log.info(f"check zone {zone} after restart has salt {salt}") + prevsalt = salt + + # Restart named, NSEC3 should stay the same. + ns3.stop() + ns3.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + + salt = perform_nsec3_tests(ns3, params) + assert prevsalt == salt diff --git a/bin/tests/system/nsec3/tests_sh_nsec3.py b/bin/tests/system/nsec3/tests_sh_nsec3.py deleted file mode 100644 index 90476f2518..0000000000 --- a/bin/tests/system/nsec3/tests_sh_nsec3.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 pytest - -pytestmark = pytest.mark.extra_artifacts( - [ - "*.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-fips.conf", - ] -) - - -def test_nsec3(run_tests_sh): - run_tests_sh()