bind9/bin/tests/system/nta/tests_nta.py
Aram Sargsyan 357331f886 Revert NTA flush on expire
Flushing the name when NTA expires causes problems for the ongoing
resolving process. Do not flush the name from the cache. Instead,
the resolver should do the flushing (this is planned to be merged
next).
2026-03-30 18:27:35 +00:00

420 lines
13 KiB
Python

# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
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)