diff --git a/bin/tests/system/nta/ns1/named.conf.j2 b/bin/tests/system/nta/ns1/named.conf.j2 new file mode 100644 index 0000000000..bd1ccc4081 --- /dev/null +++ b/bin/tests/system/nta/ns1/named.conf.j2 @@ -0,0 +1,36 @@ +/* + * 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. + */ + +// NS1 + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; + dnssec-validation yes; + /* test that we can turn off trust-anchor-telemetry */ + trust-anchor-telemetry no; +}; + +zone "." { + type primary; + file "root.db.signed"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/nta/ns1/root.db.in b/bin/tests/system/nta/ns1/root.db.in new file mode 100644 index 0000000000..34f777330d --- /dev/null +++ b/bin/tests/system/nta/ns1/root.db.in @@ -0,0 +1,24 @@ +; 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 300 +. IN SOA gson.nominum.com. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +example. NS ns2.example. +ns2.example. A 10.53.0.2 diff --git a/bin/tests/system/nta/ns1/sign.sh b/bin/tests/system/nta/ns1/sign.sh new file mode 100644 index 0000000000..243503d85d --- /dev/null +++ b/bin/tests/system/nta/ns1/sign.sh @@ -0,0 +1,37 @@ +#!/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. + +# shellcheck source=conf.sh +. ../../conf.sh + +set -e + +zone=. +infile=root.db.in +zonefile=root.db + +(cd ../ns2 && $SHELL sign.sh) + +cp "../ns2/dsset-example." . + +ksk=$("$KEYGEN" -q -fk -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") +zsk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") + +cat "$infile" "$ksk.key" "$zsk.key" >"$zonefile" + +"$SIGNER" -g -o "$zone" "$zonefile" >/dev/null 2>&1 + +# Configure the resolving server with a static key. +keyfile_to_static_ds "$ksk" >trusted.conf +cp trusted.conf ../ns4/trusted.conf +cp trusted.conf ../ns9/trusted.conf diff --git a/bin/tests/system/nta/ns2/corp.db b/bin/tests/system/nta/ns2/corp.db new file mode 100644 index 0000000000..b2912bc6e3 --- /dev/null +++ b/bin/tests/system/nta/ns2/corp.db @@ -0,0 +1,23 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 30 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 30 ; minimum (1 hour) + ) + NS ns2 +ns2 A 10.53.0.2 + +www A 10.0.0.1 diff --git a/bin/tests/system/nta/ns2/example.db.in b/bin/tests/system/nta/ns2/example.db.in new file mode 100644 index 0000000000..f72258f63c --- /dev/null +++ b/bin/tests/system/nta/ns2/example.db.in @@ -0,0 +1,35 @@ +; 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 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns2 + NS ns3 +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +; A secure subdomain +secure NS ns3.secure +ns3.secure A 10.53.0.3 + +; A secure subdomain we're going to inject bogus data into +bogus NS ns.bogus +ns.bogus A 10.53.0.3 + +; A subdomain with a corrupt DS +badds NS ns.badds +ns.badds A 10.53.0.3 diff --git a/bin/tests/system/nta/ns2/named.conf.j2 b/bin/tests/system/nta/ns2/named.conf.j2 new file mode 100644 index 0000000000..9bfbcde9d2 --- /dev/null +++ b/bin/tests/system/nta/ns2/named.conf.j2 @@ -0,0 +1,50 @@ +/* + * 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. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + notify yes; + notify-delay 1; + dnssec-validation no; + minimal-responses no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type primary; + file "example.db.signed"; + allow-update { any; }; +}; + +zone "corp" { + type primary; + file "corp.db"; +}; diff --git a/bin/tests/system/nta/ns2/sign.sh b/bin/tests/system/nta/ns2/sign.sh new file mode 100644 index 0000000000..5eb698e842 --- /dev/null +++ b/bin/tests/system/nta/ns2/sign.sh @@ -0,0 +1,38 @@ +#!/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. + +# shellcheck source=conf.sh +. ../../conf.sh + +set -e + +# Sign child zones (served by ns3). +(cd ../ns3 && $SHELL sign.sh) + +# The "example." zone. +zone=example. +infile=example.db.in +zonefile=example.db + +# Get the DS records for the "example." zone. +for subdomain in bogus badds secure; do + cp "../ns3/dsset-$subdomain.example." . +done + +# Sign the "example." zone. +keyname1=$("$KEYGEN" -q -a "$ALTERNATIVE_ALGORITHM" -b "$ALTERNATIVE_BITS" -f KSK "$zone") +keyname2=$("$KEYGEN" -q -a "$ALTERNATIVE_ALGORITHM" -b "$ALTERNATIVE_BITS" "$zone") + +cat "$infile" "$keyname1.key" "$keyname2.key" >"$zonefile" + +"$SIGNER" -g -o "$zone" -k "$keyname1" "$zonefile" "$keyname2" >/dev/null 2>&1 diff --git a/bin/tests/system/nta/ns3/bogus.example.db.in b/bin/tests/system/nta/ns3/bogus.example.db.in new file mode 100644 index 0000000000..0feb441cdd --- /dev/null +++ b/bin/tests/system/nta/ns3/bogus.example.db.in @@ -0,0 +1,27 @@ +; 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 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 +z A 10.0.0.26 diff --git a/bin/tests/system/nta/ns3/named.conf.j2 b/bin/tests/system/nta/ns3/named.conf.j2 new file mode 100644 index 0000000000..3c3f256ca4 --- /dev/null +++ b/bin/tests/system/nta/ns3/named.conf.j2 @@ -0,0 +1,62 @@ +/* + * 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 + +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; + notify yes; + dnssec-validation no; + minimal-responses no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type secondary; + primaries { 10.53.0.2; }; + file "example.bk"; +}; + +zone "secure.example" { + type primary; + file "secure.example.db.signed"; + allow-update { any; }; +}; + +zone "bogus.example" { + type primary; + file "bogus.example.db.signed"; + allow-update { any; }; +}; + +zone "badds.example" { + type primary; + file "badds.example.db.signed"; + allow-update { any; }; +}; diff --git a/bin/tests/system/nta/ns3/secure.example.db.in b/bin/tests/system/nta/ns3/secure.example.db.in new file mode 100644 index 0000000000..182329bf42 --- /dev/null +++ b/bin/tests/system/nta/ns3/secure.example.db.in @@ -0,0 +1,30 @@ +; 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 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; 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 +c A 10.0.0.3 +d A 10.0.0.4 +e A 10.0.0.5 +f A 10.0.0.6 +g A 10.0.0.7 +z A 10.0.0.26 diff --git a/bin/tests/system/nta/ns3/sign.sh b/bin/tests/system/nta/ns3/sign.sh new file mode 100644 index 0000000000..5e5405abed --- /dev/null +++ b/bin/tests/system/nta/ns3/sign.sh @@ -0,0 +1,62 @@ +#!/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. + +# shellcheck source=conf.sh +. ../../conf.sh + +set -e + +# a validly signed zone +zone=secure.example. +infile=secure.example.db.in +zonefile=secure.example.db + +keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -D -o "$zone" "$zonefile" >/dev/null +cat "$zonefile" "$zonefile".signed >"$zonefile".tmp +mv "$zonefile".tmp "$zonefile".signed + +# a zone that we'll add bogus data to +zone=bogus.example. +infile=bogus.example.db.in +zonefile=bogus.example.db + +keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -o "$zone" "$zonefile" >/dev/null + +{ + echo "a.bogus.example. A 10.0.0.22" + echo "b.bogus.example. A 10.0.0.23" + echo "c.bogus.example. A 10.0.0.23" +} >>bogus.example.db.signed + +# +# A zone with a bad DS in the parent +# (sourced from bogus.example.db.in) +# +zone=badds.example. +infile=bogus.example.db.in +zonefile=badds.example.db + +keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -P -o "$zone" "$zonefile" >/dev/null +sed -e 's/bogus/badds/g' dsset-badds.example. diff --git a/bin/tests/system/nta/ns4/named.conf.j2 b/bin/tests/system/nta/ns4/named.conf.j2 new file mode 100644 index 0000000000..87f70e2f21 --- /dev/null +++ b/bin/tests/system/nta/ns4/named.conf.j2 @@ -0,0 +1,53 @@ +/* + * 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. + */ + +// NS4 + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion yes; + minimal-responses no; + + nta-lifetime 12s; + nta-recheck 9s; + validate-except { corp; }; + + dnssec-validation yes; +}; + +include "trusted.conf"; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +zone "corp" { + type static-stub; + server-addresses { 10.53.0.2; }; +}; diff --git a/bin/tests/system/nta/ns9/named.conf.j2 b/bin/tests/system/nta/ns9/named.conf.j2 new file mode 100644 index 0000000000..cdbe7ec8ea --- /dev/null +++ b/bin/tests/system/nta/ns9/named.conf.j2 @@ -0,0 +1,40 @@ +/* + * 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. + */ + +// NS9 + +options { + query-source address 10.53.0.9; + notify-source 10.53.0.9; + transfer-source 10.53.0.9; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.9; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation yes; + forward only; + forwarders { 10.53.0.4; }; + servfail-ttl 0; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.9 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/nta/setup.sh b/bin/tests/system/nta/setup.sh new file mode 100644 index 0000000000..4a4db2dd0d --- /dev/null +++ b/bin/tests/system/nta/setup.sh @@ -0,0 +1,22 @@ +#!/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. + +# shellcheck source=conf.sh +. ../conf.sh + +set -e + +( + cd ns1 + $SHELL sign.sh +) diff --git a/bin/tests/system/nta/tests_nta.py b/bin/tests/system/nta/tests_nta.py new file mode 100644 index 0000000000..ece8db6729 --- /dev/null +++ b/bin/tests/system/nta/tests_nta.py @@ -0,0 +1,420 @@ +# 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. + +from re import compile as Re + +import os +import time + +import isctest + + +def active(blob): + return len([x for x in blob.splitlines() if " expiry" in x]) + + +# global start-time variable +# pylint: disable=global-statement +START = 0 + + +def test_initial(): + m = isctest.query.create("a.bogus.example.", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + + m = isctest.query.create("badds.example.", "SOA") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + + m = isctest.query.create("a.secure.example.", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + +def test_nta_validate_except(servers): + ns4 = servers["ns4"] + response = ns4.rndc("secroots -") + assert Re("^corp: permanent") in response.out + + # check insecure local domain works with validate-except + m = isctest.query.create("www.corp", "NS") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + +def test_nta_bogus_lifetimes(servers): + ns4 = servers["ns4"] + + # no nta lifetime specified: + response = ns4.rndc("nta -l '' foo", raise_on_exception=False) + assert "'nta' failed: bad ttl" in response.err + + # bad nta lifetime: + response = ns4.rndc("nta -l garbage foo", raise_on_exception=False) + assert "'nta' failed: bad ttl" in response.err + + # excessive nta lifetime: + response = ns4.rndc("nta -l 7d1h foo", raise_on_exception=False) + assert "'nta' failed: out of range" in response.err + + +def test_nta_install(servers): + global START + + ns4 = servers["ns4"] + ns4.rndc("nta -f -l 20s bogus.example") + ns4.rndc("nta badds.example") + + # NTAs should persist after reconfig + ns4.reconfigure() + + response = ns4.rndc("nta -d") + assert len(response.out.splitlines()) == 3 + + ns4.rndc("nta secure.example") + ns4.rndc("nta fakenode.secure.example") + with ns4.watch_log_from_here() as watcher: + ns4.rndc("reload") + watcher.wait_for_line("all zones loaded") + + response = ns4.rndc("nta -d") + assert len(response.out.splitlines()) == 5 + + START = time.time() + + +def test_nta_behavior(servers): + assert START, "test_nta_behavior must be run as part of the full NTA test" + + m = isctest.query.create("a.bogus.example.", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + m = isctest.query.create("badds.example.", "SOA") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + m = isctest.query.create("a.secure.example.", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + m = isctest.query.create("a.fakenode.secure.example.", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noadflag(res) + + ns4 = servers["ns4"] + response = ns4.rndc("secroots -") + assert Re("^bogus.example: expiry") in response.out + assert Re("^badds.example: expiry") in response.out + assert Re("^secure.example: expiry") in response.out + assert Re("^fakenode.secure.example: expiry") in response.out + + # secure.example and badds.example used the default nta-duration + # (configured as 12s in ns4/named1.conf), but the nta recheck interval + # is configured to 9s, so at t=10 the NTAs for secure.example and + # fakenode.secure.example should both be lifted, while badds.example + # should still be going. + delay = START + 10 - time.time() + if delay > 0: + time.sleep(delay) + + m = isctest.query.create("b.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + m = isctest.query.create("b.fakenode.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.nxdomain(res) + isctest.check.adflag(res) + + m = isctest.query.create("badds.example.", "SOA") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + # bogus.example was set to expire in 20s, so at t=13 + # it should still be NTA'd, but badds.example used the default + # lifetime of 12s, so it should revert to SERVFAIL now. + delay = START + 13 - time.time() + if delay > 0: + time.sleep(delay) + + response = ns4.rndc("nta -d") + assert active(response.out) <= 2 + + response = ns4.rndc("secroots -") + assert Re("bogus.example: expiry") in response.out + assert Re("badds.example: expiry") not in response.out + + m = isctest.query.create("b.bogus.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + + m = isctest.query.create("a.badds.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + isctest.check.noadflag(res) + + m = isctest.query.create("c.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + # at t=21, all the NTAs should have expired. + delay = START + 21 - time.time() + if delay > 0: + time.sleep(delay) + + response = ns4.rndc("nta -d") + assert active(response.out) == 0 + + m = isctest.query.create("d.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + m = isctest.query.create("c.bogus.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + isctest.check.noadflag(res) + + +def test_nta_removals(servers): + ns4 = servers["ns4"] + ns4.rndc("nta badds.example") + + response = ns4.rndc("nta -d") + assert Re("^badds.example/_default: expiry") in response.out + + m = isctest.query.create("a.badds.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + response = ns4.rndc("nta -remove badds.example") + assert "Negative trust anchor removed: badds.example" in response.out + + response = ns4.rndc("nta -d") + assert Re("^badds.example/_default: expiry") not in response.out + + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + isctest.check.noadflag(res) + + # remove non-existent NTA three times + ns4.rndc("nta -r foo") + ns4.rndc("nta -remove foo") + response = ns4.rndc("nta -r foo") + assert "not found" in response.out + + +def test_nta_restarts(servers): + global START + assert START, "test_nta_restarts must be run as part of the full NTA test" + + # test NTA persistence across restarts + ns4 = servers["ns4"] + response = ns4.rndc("nta -d") + assert active(response.out) == 0 + + START = time.time() + ns4.rndc("nta -f -l 30s bogus.example") + ns4.rndc("nta -f -l 10s badds.example") + response = ns4.rndc("nta -d") + assert active(response.out) == 2 + + # stop the server + ns4.stop() + + # wait 14s before restarting. badds.example's NTA (lifetime=10s) should + # have expired, and bogus.example should still be running. + delay = START + 14 - time.time() + if delay > 0: + time.sleep(delay) + ns4.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + + response = ns4.rndc("nta -d") + assert active(response.out) == 1 + assert Re("^bogus.example/_default: expiry") in response.out + + m = isctest.query.create("a.badds.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.servfail(res) + + m = isctest.query.create("a.bogus.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + ns4.rndc("nta -r bogus.example") + + +def test_nta_regular(servers): + global START + assert START, "test_nta_regular must be run as part of the full NTA test" + + # check "regular" attribute in NTA file + ns4 = servers["ns4"] + + response = ns4.rndc("nta -d") + assert active(response.out) == 0 + + # secure.example validates with AD=1 + m = isctest.query.create("a.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + # stop the server, update _default.nta, restart + ns4.stop() + now = time.localtime() + future = str(now.tm_year + 20) + "0101010000" + with open("ns4/_default.nta", "w", encoding="utf-8") as f: + f.write(f"secure.example. regular {future}") + + ns4.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + + # NTA active; secure.example. should now return an AD=0 answer. + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + # nta-recheck is configured as 9s, so at t=12 the NTA for + # secure.example. should be lifted as it is not a "forced" NTA. + START = time.mktime(now) + delay = START + 12 - time.time() + if delay > 0: + time.sleep(delay) + + response = ns4.rndc("nta -d") + assert active(response.out) == 0 + + # NTA lifted; secure.example. flush the cache to trigger a new query, + # and it should now return an AD=1 answer. + ns4.rndc("flushtree secure.example") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + +def test_nta_forced(servers): + global START + assert START, "test_nta_regular must be run as part of the full NTA test" + + # check "forced" attribute in NTA file + ns4 = servers["ns4"] + + # just to be certain, clean up any existing NTA first + ns4.rndc("nta -r secure.example") + + response = ns4.rndc("nta -d") + assert active(response.out) == 0 + + # secure.example validates with AD=1 + m = isctest.query.create("a.secure.example", "A") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.adflag(res) + + # stop the server, update _default.nta, restart + ns4.stop() + now = time.localtime() + future = str(now.tm_year + 20) + "0101010000" + with open("ns4/_default.nta", "w", encoding="utf-8") as f: + f.write(f"secure.example. forced {future}") + + ns4.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + + # NTA active; secure.example. should now return an AD=0 answer + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + # nta-recheck is configured as 9s. at t=12 the NTA for + # secure.example. should NOT be lifted as it is "forced". + START = time.mktime(now) + delay = START + 12 - time.time() + if delay > 0: + time.sleep(delay) + + # NTA lifted; secure.example. should still return an AD=0 answer + ns4.rndc("flushtree secure.example") + res = isctest.query.tcp(m, "10.53.0.4") + isctest.check.noerror(res) + isctest.check.noadflag(res) + + +def test_nta_clamping(servers): + ns4 = servers["ns4"] + + # clean up any existing NTA + ns4.rndc("nta -r secure.example") + + # stop the server, update _default.nta, restart + ns4.stop() + now = time.localtime() + future = str(now.tm_year + 20) + "0101010000" + with open("ns4/_default.nta", "w", encoding="utf-8") as f: + f.write(f"secure.example. forced {future}") + + ns4.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + + # check that NTA lifetime read from file is clamped to 1 week. + response = ns4.rndc("nta -d") + assert active(response.out) == 1 + + nta = next((s for s in response.out.splitlines() if " expiry" in s), None) + assert nta is not None + + nta = nta.split(" ") + expiry = f"{nta[2]} {nta[3]}" + then = time.mktime(time.strptime(expiry, "%d-%b-%Y %H:%M:%S.000")) + nextweek = time.mktime(now) + (86400 * 7) + + # normally there's no more than a few seconds difference between the + # clamped expiration date and the calculated date for next week, + # but add a 3600 second fudge factor to allow for daylight savings + # changes. + assert abs(nextweek - then < 3610) + + # remove the NTA + ns4.rndc("nta -r secure.example") + + +def test_nta_forward(servers): + ns9 = servers["ns9"] + + m = isctest.query.create("badds.example", "SOA") + res = isctest.query.tcp(m, "10.53.0.9") + isctest.check.servfail(res) + isctest.check.empty_answer(res) + isctest.check.noadflag(res) + + # add NTA and expect resolution to succeed + ns9.rndc("nta badds.example") + res = isctest.query.tcp(m, "10.53.0.9") + isctest.check.noerror(res) + isctest.check.rr_count_eq(res.answer, 2) + isctest.check.noadflag(res) + + # remove NTA and expect resolution to fail again + ns9.rndc("nta -remove badds.example") + res = isctest.query.tcp(m, "10.53.0.9") + isctest.check.servfail(res) + isctest.check.empty_answer(res) + isctest.check.noadflag(res)