From: Matthijs Mekking Date: Thu, 2 Apr 2026 08:06:08 +0000 (+0200) Subject: Add fast test case X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=784a441e2d8eb86d32d5e79202d03fc4a6838289;p=thirdparty%2Fbind9.git Add fast test case This specific test case triggered a bug where the SKR included bundles with unsigned DNSKEY RRsets (signatures where omitted because the inception time was equal to the inactive time of the key). --- diff --git a/bin/tests/system/ksr/ns1/named.conf.j2 b/bin/tests/system/ksr/ns1/named.conf.j2 index 0c7d2e62aeb..0fb2df4fba8 100644 --- a/bin/tests/system/ksr/ns1/named.conf.j2 +++ b/bin/tests/system/ksr/ns1/named.conf.j2 @@ -81,6 +81,25 @@ dnssec-policy "ksk-roll" { }; }; +dnssec-policy "fast" { + offline-ksk yes; + keys { + ksk lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + zsk lifetime 8792 algorithm @DEFAULT_ALGORITHM@; + }; + dnskey-ttl 439; + max-zone-ttl 4396; + zone-propagation-delay 439; + signatures-validity 6; + signatures-validity-dnskey 6; + signatures-refresh 2; + signatures-jitter 0; + publish-safety 1; + retire-safety 1; + parent-ds-ttl 5; + parent-propagation-delay 5; +}; + dnssec-policy "invalid-skr" { offline-ksk yes; keys { diff --git a/bin/tests/system/ksr/ns1/setup.sh b/bin/tests/system/ksr/ns1/setup.sh index e8c932b3924..482bafa9e88 100644 --- a/bin/tests/system/ksr/ns1/setup.sh +++ b/bin/tests/system/ksr/ns1/setup.sh @@ -28,3 +28,4 @@ cp template.db.in unlimited.test.db cp template.db.in two-tone.test.db cp template.db.in ksk-roll.test.db cp template.db.in invalid-skr.test.db +cp template.db.in fast.test.db diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index 3e0925bfab3..0d71fdae9c8 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -20,6 +20,7 @@ import pytest from isctest.kasp import KeyTimingMetadata from isctest.vars.algorithms import Algorithm +from rollover.common import TIMEDELTA import isctest @@ -27,6 +28,7 @@ pytestmark = pytest.mark.extra_artifacts( [ "K*", "common.test.*", + "fast.test.*", "future.test.*", "in-the-middle.test.*", "ksk-roll.test.*", @@ -43,6 +45,11 @@ pytestmark = pytest.mark.extra_artifacts( "ns1/common.test.db.signed", "ns1/common.test.db.signed.jnl", "ns1/common.test.skr.2", + "ns1/fast.test.db", + "ns1/fast.test.db.jbk", + "ns1/fast.test.db.signed", + "ns1/fast.test.db.signed.jnl", + "ns1/fast.test.skr.1", "ns1/future.test.db", "ns1/future.test.db.jbk", "ns1/future.test.db.signed", @@ -86,6 +93,30 @@ pytestmark = pytest.mark.extra_artifacts( ] ) +CONFIG = { + "dnskey-ttl": TIMEDELTA["PT1H"], + "ds-ttl": TIMEDELTA["P1D"], + "max-zone-ttl": TIMEDELTA["P1D"], + "parent-propagation-delay": TIMEDELTA["PT1H"], + "publish-safety": TIMEDELTA["PT1H"], + "retire-safety": TIMEDELTA["PT1H"], + "signatures-refresh": TIMEDELTA["P5D"], + "signatures-validity": TIMEDELTA["P14D"], + "zone-propagation-delay": TIMEDELTA["PT5M"], +} + +FASTCONFIG = { + "dnskey-ttl": timedelta(seconds=439), + "ds-ttl": TIMEDELTA["PT1H"], + "max-zone-ttl": timedelta(seconds=4396), + "parent-propagation-delay": timedelta(seconds=5), + "publish-safety": timedelta(seconds=1), + "retire-safety": timedelta(seconds=1), + "signatures-refresh": timedelta(seconds=2), + "signatures-validity": timedelta(seconds=6), + "zone-propagation-delay": timedelta(seconds=439), +} + def between(value, start, end): if value is None or start is None or end is None: @@ -116,9 +147,14 @@ def ksr(zone, policy, action, options="", raise_on_exception=True, to_file=""): return cmd +def sign_delay(config): + return config["signatures-validity"] - config["signatures-refresh"] + + def check_keys( keys, lifetime, + config, alg=None, size=None, offset=0, @@ -144,7 +180,12 @@ def check_keys( active = retired # published: dnskey-ttl + publish-safety + propagation - published = active - timedelta(hours=2, minutes=5) + pubtime = ( + config["dnskey-ttl"] + + config["publish-safety"] + + config["zone-propagation-delay"] + ) + published = active - pubtime # retired: zsk-lifetime if lifetime is not None: @@ -152,10 +193,22 @@ def check_keys( if key.is_ksk(): # removed: ttlds + retire-safety + parent-propagation - removed = retired + timedelta(days=1, hours=2) + remtime = ( + config["ds-ttl"] + + config["retire-safety"] + + config["parent-propagation-delay"] + ) + removed = retired + remtime else: # removed: ttlsig + retire-safety + sign-delay + propagation - removed = retired + timedelta(days=10, hours=1, minutes=5) + remtime = ( + config["max-zone-ttl"] + + config["retire-safety"] + + config["zone-propagation-delay"] + + sign_delay(config) + ) + removed = retired + remtime + else: retired = None removed = None @@ -167,8 +220,8 @@ def check_keys( state_ds = "hidden" if retired is None or between(now, published, retired): goal = "omnipresent" - pubdelay = published + timedelta(hours=2, minutes=5) - signdelay = active + timedelta(days=10, hours=1, minutes=5) + pubdelay = published + pubtime + signdelay = active + sign_delay(config) if between(now, published, pubdelay): state_dnskey = "rumoured" @@ -261,19 +314,19 @@ def check_cds_bundle(bundle_keys, bundle_lines, expected_cds): assert count == len(bundle_lines) -def check_rrsig_bundle(bundle_keys, bundle_lines, zone, rrtype, sigend, sigstart): +def check_rrsig_bundle(bundle_keys, bundle_lines, zone, rrtype, sigend, sigstart, ttl): count = 0 for key in bundle_keys: found = False alg = key.get_dnsalg() - expect = f"{zone}. 3600 IN RRSIG {rrtype} {alg} 2 3600 {sigend} {sigstart} {key.tag} {zone}." + expect = f"{zone}. {ttl} IN RRSIG {rrtype} {alg} 2 {ttl} {sigend} {sigstart} {key.tag} {zone}." # there must be a signature of this ksk for line in bundle_lines: rrsig = " ".join(line.split()) if expect in rrsig: found = True count += 1 - assert found + assert found, f"Expected string not found: {expect}" assert count == len(bundle_keys) assert count == len(bundle_lines) @@ -331,6 +384,7 @@ def check_keysigningrequest(path, zsks, start, end): def check_signedkeyresponse( path, + config, zone, ksks, zsks, @@ -345,6 +399,8 @@ def check_signedkeyresponse( line_no = 0 next_bundle = end + 1 + dnskey_ttl = int(config["dnskey-ttl"].total_seconds()) + inception = start while inception < end: # A single signed key response may consist of: @@ -358,7 +414,7 @@ def check_signedkeyresponse( # ;; RRSIG(CDS) (one per active key in ksks) sigstart = inception - timedelta(hours=1) # clockskew - sigend = inception + timedelta(days=14) # sig-validity + sigend = inception + config["signatures-validity"] next_bundle = sigend + refresh # ignore empty lines @@ -440,7 +496,9 @@ def check_signedkeyresponse( bundle_lines.append(lines[line_no]) line_no += 1 - check_rrsig_bundle(bundle_keys, bundle_lines, zone, "DNSKEY", sigend, sigstart) + check_rrsig_bundle( + bundle_keys, bundle_lines, zone, "DNSKEY", sigend, sigstart, dnskey_ttl + ) # expect cdnskey have_cdnskey = False @@ -489,7 +547,13 @@ def check_signedkeyresponse( line_no += 1 check_rrsig_bundle( - bundle_keys, bundle_lines, zone, "CDNSKEY", sigend, sigstart + bundle_keys, + bundle_lines, + zone, + "CDNSKEY", + sigend, + sigstart, + dnskey_ttl, ) # expect cds @@ -540,7 +604,9 @@ def check_signedkeyresponse( bundle_lines.append(lines[line_no]) line_no += 1 - check_rrsig_bundle(bundle_keys, bundle_lines, zone, "CDS", sigend, sigstart) + check_rrsig_bundle( + bundle_keys, bundle_lines, zone, "CDS", sigend, sigstart, dnskey_ttl + ) inception = next_bundle @@ -598,7 +664,7 @@ def test_ksr_common(ns1): ksks = isctest.kasp.keystr_to_keylist(cmd.out, kskdir) assert len(ksks) == 1 - check_keys(ksks, None) + check_keys(ksks, None, CONFIG) # check that 'dnssec-ksr keygen' pregenerates right amount of keys cmd = ksr(zone, policy, "keygen", options="-i now -e +1y") @@ -606,7 +672,7 @@ def test_ksr_common(ns1): assert len(zsks) == 2 lifetime = timedelta(days=31 * 6) - check_keys(zsks, lifetime) + check_keys(zsks, lifetime, CONFIG) # check that 'dnssec-ksr keygen' pregenerates right amount of keys # in the given key directory @@ -616,7 +682,7 @@ def test_ksr_common(ns1): assert len(zsks) == 2 lifetime = timedelta(days=31 * 6) - check_keys(zsks, lifetime) + check_keys(zsks, lifetime, CONFIG) for key in zsks: privatefile = f"{key.path}.private" @@ -649,7 +715,7 @@ def test_ksr_common(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {now} -e +1y", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, now, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, now, until, refresh) # common test cases (2) n = 2 @@ -699,7 +765,7 @@ def test_ksr_common(ns1): cmd = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y") overlapping_zsks2 = isctest.kasp.keystr_to_keylist(cmd.out, zskdir) assert len(overlapping_zsks2) == 4 - check_keys(overlapping_zsks2, lifetime) + check_keys(overlapping_zsks2, lifetime, CONFIG) for index, key in enumerate(overlapping_zsks2): assert overlapping_zsks[index] == key @@ -740,6 +806,7 @@ def test_ksr_common(ns1): ) check_signedkeyresponse( skr_fname, + CONFIG, zone, ksks, overlapping_zsks, @@ -768,7 +835,7 @@ def test_ksr_common(ns1): # - dnssec_verify isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys - check_keys(overlapping_zsks, lifetime, with_state=True) + check_keys(overlapping_zsks, lifetime, CONFIG, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, overlapping_zsks, offline_ksk=True) # - check subdomain @@ -787,7 +854,7 @@ def test_ksr_lastbundle(ns1): ksks = isctest.kasp.keystr_to_keylist(cmd.out, kskdir) assert len(ksks) == 1 - check_keys(ksks, None, offset=offset) + check_keys(ksks, None, CONFIG, offset=offset) # check that 'dnssec-ksr keygen' pregenerates right amount of keys zskdir = "ns1" @@ -796,7 +863,7 @@ def test_ksr_lastbundle(ns1): assert len(zsks) == 2 lifetime = timedelta(days=31 * 6) - check_keys(zsks, lifetime, offset=offset) + check_keys(zsks, lifetime, CONFIG, offset=offset) # check that 'dnssec-ksr request' creates correct ksr then = zsks[0].get_timing("Created") + offset @@ -821,7 +888,7 @@ def test_ksr_lastbundle(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {then} -e +1d", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, then, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, then, until, refresh) # add zone ns1.rndc( @@ -841,7 +908,7 @@ def test_ksr_lastbundle(ns1): # - dnssec_verify isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys - check_keys(zsks, lifetime, offset=offset, with_state=True) + check_keys(zsks, lifetime, CONFIG, offset=offset, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain @@ -864,7 +931,7 @@ def test_ksr_inthemiddle(ns1): ksks = isctest.kasp.keystr_to_keylist(cmd.out, kskdir) assert len(ksks) == 1 - check_keys(ksks, None, offset=offset) + check_keys(ksks, None, CONFIG, offset=offset) # check that 'dnssec-ksr keygen' pregenerates right amount of keys zskdir = "ns1" @@ -873,7 +940,7 @@ def test_ksr_inthemiddle(ns1): assert len(zsks) == 4 lifetime = timedelta(days=31 * 6) - check_keys(zsks, lifetime, offset=offset) + check_keys(zsks, lifetime, CONFIG, offset=offset) # check that 'dnssec-ksr request' creates correct ksr then = zsks[0].get_timing("Created") @@ -899,7 +966,7 @@ def test_ksr_inthemiddle(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {then} -e +1y", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, then, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, then, until, refresh) # add zone ns1.rndc( @@ -919,7 +986,7 @@ def test_ksr_inthemiddle(ns1): # - dnssec_verify isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys - check_keys(zsks, lifetime, offset=offset, with_state=True) + check_keys(zsks, lifetime, CONFIG, offset=offset, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain @@ -1014,7 +1081,7 @@ def test_ksr_unlimited(ns1): ksks = isctest.kasp.keystr_to_keylist(cmd.out, kskdir) assert len(ksks) == 1 - check_keys(ksks, None) + check_keys(ksks, None, CONFIG) # check that 'dnssec-ksr keygen' pregenerates right amount of keys zskdir = "ns1" @@ -1023,7 +1090,7 @@ def test_ksr_unlimited(ns1): assert len(zsks) == 1 lifetime = None - check_keys(zsks, lifetime) + check_keys(zsks, lifetime, CONFIG) # check that 'dnssec-ksr request' creates correct ksr now = zsks[0].get_timing("Created") @@ -1050,6 +1117,7 @@ def test_ksr_unlimited(ns1): ) check_signedkeyresponse( skr_fname, + CONFIG, zone, ksks, zsks, @@ -1072,6 +1140,7 @@ def test_ksr_unlimited(ns1): ) check_signedkeyresponse( skr_fname, + CONFIG, zone, ksks, zsks, @@ -1091,7 +1160,7 @@ def test_ksr_unlimited(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {now} -e +4y", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, now, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, now, until, refresh) # add zone ns1.rndc( @@ -1111,7 +1180,7 @@ def test_ksr_unlimited(ns1): # - dnssec_verify isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys - check_keys(zsks, lifetime, with_state=True) + check_keys(zsks, lifetime, CONFIG, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain @@ -1141,11 +1210,11 @@ def test_ksr_twotone(ns1): assert len(ksks_defalg) == 1 assert len(ksks_altalg) == 1 - check_keys(ksks_defalg, None) + check_keys(ksks_defalg, None, CONFIG) alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") - check_keys(ksks_altalg, None, alg, size) + check_keys(ksks_altalg, None, CONFIG, alg, size) # check that 'dnssec-ksr keygen' pregenerates right amount of keys zskdir = "ns1" @@ -1171,12 +1240,12 @@ def test_ksr_twotone(ns1): assert len(zsks_altalg) == 3 lifetime = timedelta(days=31 * 3) - check_keys(zsks_defalg, lifetime) + check_keys(zsks_defalg, lifetime, CONFIG) alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") lifetime = timedelta(days=31 * 5) - check_keys(zsks_altalg, lifetime, alg, size) + check_keys(zsks_altalg, lifetime, CONFIG, alg, size) # check that 'dnssec-ksr request' creates correct ksr now = zsks[0].get_timing("Created") @@ -1201,7 +1270,7 @@ def test_ksr_twotone(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {now} -e +1y", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, now, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, now, until, refresh) # add zone ns1.rndc( @@ -1222,12 +1291,12 @@ def test_ksr_twotone(ns1): isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys lifetime = timedelta(days=31 * 3) - check_keys(zsks_defalg, lifetime, with_state=True) + check_keys(zsks_defalg, lifetime, CONFIG, with_state=True) alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") lifetime = timedelta(days=31 * 5) - check_keys(zsks_altalg, lifetime, alg, size, with_state=True) + check_keys(zsks_altalg, lifetime, CONFIG, alg, size, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain @@ -1246,7 +1315,7 @@ def test_ksr_kskroll(ns1): assert len(ksks) == 2 lifetime = timedelta(days=31 * 6) - check_keys(ksks, lifetime) + check_keys(ksks, lifetime, CONFIG) # check that 'dnssec-ksr keygen' pregenerates right amount of keys zskdir = "ns1" @@ -1254,7 +1323,7 @@ def test_ksr_kskroll(ns1): zsks = isctest.kasp.keystr_to_keylist(cmd.out, zskdir) assert len(zsks) == 1 - check_keys(zsks, None) + check_keys(zsks, None, CONFIG) # check that 'dnssec-ksr request' creates correct ksr now = zsks[0].get_timing("Created") @@ -1279,7 +1348,81 @@ def test_ksr_kskroll(ns1): options=f"-K {kskdir} -f {ksr_fname} -i {now} -e +1y", to_file=skr_fname, ) - check_signedkeyresponse(skr_fname, zone, ksks, zsks, now, until, refresh) + check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, now, until, refresh) + + # add zone + ns1.rndc( + f"addzone {zone} " + + "{ type primary; file " + + f'"{zone}.db"; dnssec-policy {policy}; ' + + "};", + ) + + # import skr + shutil.copyfile(skr_fname, f"ns1/{skr_fname}") + ns1.rndc(f"skr -import {skr_fname} {zone}") + + # test zone is correctly signed + # - check rndc dnssec -status output + isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy, verbose=True) + # - dnssec_verify + isctest.kasp.check_dnssec_verify(ns1, zone) + # - check keys + check_keys(zsks, None, CONFIG, with_state=True) + # - check apex + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) + # - check subdomain + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) + + +def test_ksr_fast(ns1): + zone = "fast.test" + policy = "fast" + n = 1 + + # create ksk + kskdir = "ns1/offline" + cmd = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1h -o") + ksks = isctest.kasp.keystr_to_keylist(cmd.out, kskdir) + assert len(ksks) == 1 + + check_keys(ksks, None, FASTCONFIG) + + # check that 'dnssec-ksr keygen' pregenerates right amount of keys + zskdir = "ns1" + cmd = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1h") + zsks = isctest.kasp.keystr_to_keylist(cmd.out, zskdir) + assert len(zsks) == 1 + + lifetime = timedelta(seconds=8792) + check_keys(zsks, lifetime, FASTCONFIG) + + # check that 'dnssec-ksr request' creates correct ksr + now = zsks[0].get_timing("Created") + until = now + timedelta(hours=1) + ksr_fname = f"{zone}.ksr.{n}" + ksr( + zone, + policy, + "request", + options=f"-K {zskdir} -i {now} -e +1h", + to_file=ksr_fname, + ) + check_keysigningrequest(ksr_fname, zsks, now, until) + + # check that 'dnssec-ksr sign' creates correct skr + refresh = -2 + skr_fname = f"{zone}.skr.{n}" + ksr( + zone, + policy, + "sign", + options=f"-K {kskdir} -f {ksr_fname} -i {now} -e +1h", + to_file=skr_fname, + ) + check_signedkeyresponse( + skr_fname, FASTCONFIG, zone, ksks, zsks, now, until, refresh + ) # add zone ns1.rndc( @@ -1299,7 +1442,7 @@ def test_ksr_kskroll(ns1): # - dnssec_verify isctest.kasp.check_dnssec_verify(ns1, zone) # - check keys - check_keys(zsks, None, with_state=True) + check_keys(zsks, lifetime, FASTCONFIG, with_state=True) # - check apex isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain