Test retransfer with NSEC3 policy

If the primary has been updated, but the secondary has not been
notified, the journal will go out of date. An 'rndc retransfer' causes
the zone to force an AXFR, removing and rebuilding zone and journal
files.

This test reproduces a bug that in such scenario, an NSEC3 signed zone
falls back to NSEC.

(cherry picked from commit be3e4c83d0)
This commit is contained in:
Matthijs Mekking 2025-11-06 17:32:51 +01:00 committed by Matthijs Mekking (GitLab job 6518807)
parent 76594d641f
commit c501fa900e
7 changed files with 260 additions and 3 deletions

View file

@ -35,9 +35,7 @@ pytestmark = pytest.mark.extra_artifacts(
"ns*/*.jnl",
"ns*/*.signed",
"ns*/keygen.out.*",
"ns3/named-common.conf",
"ns3/named-fips.conf",
"ns3/named-rsasha1.conf",
"ns3/named-*.conf",
]
)

View file

@ -46,3 +46,13 @@ zone "nsec3-xfr-inline.kasp" {
dnssec-policy "nsec3";
};
{% endif %}{# nsec3-xfr-inline.kasp #}
{% if "retransfer.kasp" in zones %}
zone "retransfer.kasp" {
type primary;
file "retransfer.kasp.db";
notify explicit;
also-notify { 10.53.0.3; };
allow-transfer { any; };
};
{% endif %}{# nsec3-xfr-inline.kasp #}

View file

@ -0,0 +1,31 @@
; 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 retransfer.kasp.
$TTL 300
retransfer.kasp. IN SOA mname1. . (
@serial@ ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
NS ns2
ns2 A 10.53.0.2
ns3 A 10.53.0.3
a A 10.0.0.1
b A 10.0.0.2
c A 10.0.0.3

View file

@ -0,0 +1,49 @@
/*
* 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.
*/
remote-servers "ns2" {
10.53.0.2 port @PORT@;
};
remote-servers "ns4" {
10.53.0.4 port @PORT@;
};
dnssec-policy "nsec3rsa256" {
cdnskey no;
keys {
ksk lifetime unlimited algorithm RSASHA256 2048;
zsk lifetime P90D algorithm RSASHA256 2048;
};
max-zone-ttl P2D;
signatures-refresh P8D;
nsec3param;
};
{% if "retransfer.kasp" in zones %}
zone "retransfer.kasp" {
type secondary;
primaries { "ns2"; };
file "retransfer.kasp.db";
allow-transfer { any; };
allow-notify { any; };
also-notify { "ns4"; };
notify explicit;
dnssec-policy "nsec3rsa256";
inline-signing yes;
sig-signing-signatures 100;
checkds no;
};
{% endif %}{# retransfer.kasp #}

View file

@ -15,6 +15,7 @@
include "named-common.conf";
include "named-fips.conf";
include "named-retransfer.conf";
{% if RSASHA1_SUPPORTED == "1" %}
include "named-rsasha1.conf";

View file

@ -0,0 +1,39 @@
/*
* 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; };
allow-transfer { any; };
recursion no;
dnssec-validation no;
};
remote-servers "ns3" {
10.53.0.3 port @PORT@;
};
zone "retransfer.kasp" {
type secondary;
file "retransfer.kasp.db";
primaries { "ns3"; };
allow-notify { any; };
notify no;
};

View file

@ -0,0 +1,129 @@
# 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
from datetime import timedelta
import dns.update
import pytest
pytest.importorskip("dns", minversion="2.0.0")
import isctest
import isctest.mark
from isctest.vars.algorithms import RSASHA256
from nsec3.common import (
pytestmark,
check_auth_nsec3,
check_nsec3param,
)
DNSKEY_TTL = int(timedelta(hours=1).total_seconds())
ZSK_LIFETIME = int(timedelta(days=90).total_seconds())
# include the following zones when rendering named configs
ZONES = {
"retransfer.kasp",
}
def bootstrap():
return {
"zones": ZONES,
}
def perform_nsec3_tests(server, params):
# Get test parameters.
zone = params["zone"]
fqdn = f"{zone}."
policy = params["policy"]
keydir = server.identifier
minimum = params.get("soa-minimum", 3600)
expected = isctest.kasp.policy_to_properties(
ttl=DNSKEY_TTL, keys=params["key-properties"]
)
iterations = 0
optout = 0
saltlen = 0
match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}"
# Test case.
isctest.log.info(f"check nsec3 case zone {zone} policy {policy}")
# First make sure the zone is properly signed.
isctest.kasp.wait_keymgr_done(server, zone)
keys = isctest.kasp.keydir_to_keylist(zone, keydir)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if k.is_zsk()]
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_dnssec_verify(server, zone)
isctest.kasp.check_apex(server, zone, ksks, zsks)
query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM)
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
assert response.rcode() == dns.rcode.NOERROR
salt = check_nsec3param(response, match, saltlen)
query = isctest.query.create(f"nosuchname.{fqdn}", dns.rdatatype.A)
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
assert response.rcode() == dns.rcode.NXDOMAIN
check_auth_nsec3(response, iterations, optout, salt)
return salt
def test_nsec3_retransfer(servers, templates):
ns2 = servers["ns2"]
ns3 = servers["ns3"]
params = {
"zone": "retransfer.kasp",
"policy": "nsec3rsa256",
"key-properties": [
f"ksk 0 {RSASHA256.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
f"zsk {ZSK_LIFETIME} {RSASHA256.number} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured",
],
}
zone = params["zone"]
salt = perform_nsec3_tests(ns3, params)
# Stop primary.
ns2.stop()
# Update the zone.
serial = 10
templates.render(f"{ns2.identifier}/{zone}.db", {"serial": serial})
with ns2.watch_log_from_here() as watcher:
ns2.start(["--noclean", "--restart", "--port", os.environ["PORT"]])
watcher.wait_for_line("all zones loaded")
# Test NSEC3 and NSEC3PARAM is the same after retransfer.
isctest.log.info(f"check zone {zone} after retransfer has salt {salt}")
prevsalt = salt
# Retransfer zone, NSEC3 should stay the same.
with ns3.watch_log_from_here() as watcher:
ns3.rndc(f"retransfer {zone}")
# When sending notifies, the zone should be up to date.
watcher.wait_for_line(f"zone {zone}/IN (signed): sending notify to 10.53.0.4")
salt = perform_nsec3_tests(ns3, params)
assert prevsalt == salt