check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
+def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None):
+ """
+ Test an RRset below the apex and verify it is updated and signed correctly.
+ """
+ response = _query(server, qname, qtype, tsig=tsig)
+
+ if response.rcode() != dns.rcode.NOERROR:
+ return False
+
+ rrtype = dns.rdatatype.to_text(qtype)
+ match = f"{qname} {DEFAULT_TTL} IN {rrtype} {rdata}"
+ rrsigs = []
+ for rrset in response.answer:
+ if rrset.match(
+ dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
+ ):
+ rrsigs.append(rrset)
+ elif not match in rrset.to_text():
+ return False
+
+ if len(rrsigs) == 0:
+ return False
+
+ # Zone is updated, ready to verify the signatures.
+ check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
+
+ return True
+
+
def next_key_event_equals(server, zone, next_event):
if next_event is None:
# No next key event check.
# infinite loops if there is an error.
n=$((n + 1))
echo_i "waiting for kasp signing changes to take effect ($n)"
+ret=0
_wait_for_done_apexnsec() {
while read -r zone; do
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
-#
-# Zone: default.kasp.
-#
set_keytimes_csk_policy() {
# The first key is immediately published and activated.
created=$(key_get KEY1 CREATED)
# Key lifetime is unlimited, so not setting RETIRED and REMOVED.
}
-# Check the zone with default kasp policy has loaded and is signed.
-set_zone "default.kasp"
-set_policy "default" "1" "3600"
-set_server "ns3" "10.53.0.3"
# Key properties.
set_keyrole "KEY1" "csk"
set_keylifetime "KEY1" "0"
set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY1" "STATE_DS" "hidden"
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-set_keytimes_csk_policy
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Trigger a keymgr run. Make sure the key files are not touched if there are
-# no modifications to the key metadata.
-n=$((n + 1))
-echo_i "make sure key files are untouched if metadata does not change ($n)"
-ret=0
-basefile=$(key_get KEY1 BASEFILE)
-privkey_stat=$(key_get KEY1 PRIVKEY_STAT)
-pubkey_stat=$(key_get KEY1 PUBKEY_STAT)
-state_stat=$(key_get KEY1 STATE_STAT)
-
-nextpart $DIR/named.run >/dev/null
-rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
-wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
-privkey_stat2=$(key_stat "${basefile}.private")
-pubkey_stat2=$(key_stat "${basefile}.key")
-state_stat2=$(key_stat "${basefile}.state")
-test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
-test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
-test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "again ($n)"
-ret=0
-
-nextpart $DIR/named.run >/dev/null
-rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
-wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
-privkey_stat2=$(key_stat "${basefile}.private")
-pubkey_stat2=$(key_stat "${basefile}.key")
-state_stat2=$(key_stat "${basefile}.state")
-test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
-test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
-test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Update zone.
-n=$((n + 1))
-echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
-ret=0
-cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
-rndccmd 10.53.0.3 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed"
-
update_is_signed() {
ip_a=$1
ip_d=$2
fi
}
-retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Move the private key file, a rekey event should not introduce replacement
-# keys.
-ret=0
-echo_i "test that if private key files are inaccessible this doesn't trigger a rollover ($n)"
-basefile=$(key_get KEY1 BASEFILE)
-mv "${basefile}.private" "${basefile}.offline"
-rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
-wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1
-mv "${basefile}.offline" "${basefile}.private"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Nothing has changed.
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-set_keytimes_csk_policy
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
#
# A zone with special characters.
#
from datetime import timedelta
+import dns
import pytest
import isctest
"K*.cmp",
"K*.key",
"K*.state",
+ "*.axfr",
"*.created",
"dig.out*",
"keyevent.out.*",
"unused.key-*",
"verify.out.*",
"zone.out.*",
- "ns*/K*.private",
"ns*/K*.key",
+ "ns*/K*.offline",
+ "ns*/K*.private",
"ns*/K*.state",
"ns*/*.db",
"ns*/*.db.infile",
"ns*/*.db.signed",
+ "ns*/*.db.signed.tmp",
"ns*/*.jbk",
"ns*/*.jnl",
+ "ns*/*.zsk1",
+ "ns*/*.zsk2",
"ns*/dsset-*",
"ns*/keygen.out.*",
"ns*/keys",
"ns*/signer.out.*",
"ns*/zones",
"ns*/policies/*.conf",
- "ns*/*.zsk1",
- "ns*/*.zsk2",
"ns3/legacy-keys.*",
"ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
]
)
+def check_all(server, zone, policy, ksks, zsks, tsig=None):
+ isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy)
+ isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig)
+ isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig)
+ isctest.kasp.check_dnssec_verify(server, zone)
+
+
+def set_keytimes_default_policy(kp):
+ # The first key is immediately published and activated.
+ kp.timing["Generated"] = kp.key.get_timing("Created")
+ kp.timing["Published"] = kp.timing["Generated"]
+ kp.timing["Active"] = kp.timing["Generated"]
+ # The DS can be published if the DNSKEY and RRSIG records are
+ # OMNIPRESENT. This happens after max-zone-ttl (1d) plus
+ # plus zone-propagation-delay (300s).
+ kp.timing["PublishCDS"] = kp.timing["Published"] + timedelta(days=1, seconds=300)
+ # Key lifetime is unlimited, so not setting 'Retired' nor 'Removed'.
+ kp.timing["DNSKEYChange"] = kp.timing["Published"]
+ kp.timing["DSChange"] = kp.timing["Published"]
+ kp.timing["KRRSIGChange"] = kp.timing["Active"]
+ kp.timing["ZRRSIGChange"] = kp.timing["Active"]
+
+
+def test_kasp_default(servers):
+ server = servers["ns3"]
+
+ # check the zone with default kasp policy has loaded and is signed.
+ isctest.log.info("check a zone with the default policy is signed")
+ zone = "default.kasp"
+ policy = "default"
+
+ # Key properties.
+ # DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait.
+ keyprops = [
+ "csk 0 13 256 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
+ ]
+ expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+ keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
+ isctest.kasp.check_zone_is_signed(server, zone)
+ isctest.kasp.check_keys(zone, keys, expected)
+ set_keytimes_default_policy(expected[0])
+ isctest.kasp.check_keytimes(keys, expected)
+ check_all(server, zone, policy, keys, [])
+
+ # Trigger a keymgr run. Make sure the key files are not touched if there
+ # are no modifications to the key metadata.
+ isctest.log.info(
+ "check that key files are untouched if there are no metadata changes"
+ )
+ key = keys[0]
+ privkey_stat = os.stat(key.privatefile)
+ pubkey_stat = os.stat(key.keyfile)
+ state_stat = os.stat(key.statefile)
+
+ with server.watch_log_from_here() as watcher:
+ server.rndc(f"loadkeys {zone}", log=False)
+ watcher.wait_for_line(f"keymgr: {zone} done")
+
+ assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
+ assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
+ assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
+
+ # again
+ with server.watch_log_from_here() as watcher:
+ server.rndc(f"loadkeys {zone}", log=False)
+ watcher.wait_for_line(f"keymgr: {zone} done")
+
+ assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
+ assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
+ assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
+
+ # modify unsigned zone file and check that new record is signed.
+ isctest.log.info("check that an updated zone signs the new record")
+ shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
+ server.rndc(f"reload {zone}", log=False)
+
+ def update_is_signed():
+ parts = update.split()
+ qname = parts[0]
+ qtype = dns.rdatatype.from_text(parts[1])
+ rdata = parts[2]
+ return isctest.kasp.verify_update_is_signed(
+ server, zone, qname, qtype, rdata, keys, []
+ )
+
+ expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
+ for update in expected_updates:
+ isctest.run.retry_with_timeout(update_is_signed, timeout=5)
+
+ # Move the private key file, a rekey event should not introduce
+ # replacement keys.
+ isctest.log.info("check that missing private key doesn't trigger rollover")
+ shutil.move(f"{key.privatefile}", f"{key.path}.offline")
+ expectmsg = "zone_rekey:zone_verifykeys failed: some key files are missing"
+ with server.watch_log_from_here() as watcher:
+ server.rndc(f"loadkeys {zone}", log=False)
+ watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}")
+ # Nothing has changed.
+ expected[0].properties["private"] = False
+ isctest.kasp.check_keys(zone, keys, expected)
+ isctest.kasp.check_keytimes(keys, expected)
+ check_all(server, zone, policy, keys, [])
+
+
def test_kasp_dnssec_keygen():
def keygen(zone, policy, keydir=None):
if keydir is None:
lifetime = {
"P1Y": int(timedelta(days=365).total_seconds()),
"P30D": int(timedelta(days=30).total_seconds()),
- "P6M": int(timedelta(days=31*6).total_seconds()),
+ "P6M": int(timedelta(days=31 * 6).total_seconds()),
}
keyprops = [
f"csk {lifetime['P1Y']} 13 256",
f"zsk {lifetime['P30D']} 8 2048",
f"zsk {lifetime['P6M']} 8 3072",
]
- keydir="keys"
+ keydir = "keys"
out = keygen("kasp", "kasp", keydir)
keys = isctest.kasp.keystr_to_keylist(out, keydir)
expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)