mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-29 05:28:00 +00:00
This commit deals with converting the test cases related to the default dnssec-policy. This requires a new method 'check_update_is_signed'. This method will be used in future tests as well, and checks if an expected record is in the zone and is properly signed. Remove the counterparts for the newly added test from the kasp shell tests script.
373 lines
12 KiB
Python
373 lines
12 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.
|
|
|
|
import os
|
|
import shutil
|
|
|
|
from datetime import timedelta
|
|
|
|
import dns
|
|
import pytest
|
|
|
|
import isctest
|
|
from isctest.kasp import (
|
|
KeyProperties,
|
|
KeyTimingMetadata,
|
|
)
|
|
|
|
pytestmark = pytest.mark.extra_artifacts(
|
|
[
|
|
"K*.private",
|
|
"K*.backup",
|
|
"K*.cmp",
|
|
"K*.key",
|
|
"K*.state",
|
|
"*.axfr",
|
|
"*.created",
|
|
"dig.out*",
|
|
"keyevent.out.*",
|
|
"keygen.out.*",
|
|
"keys",
|
|
"published.test*",
|
|
"python.out.*",
|
|
"retired.test*",
|
|
"rndc.dnssec.*.out.*",
|
|
"rndc.zonestatus.out.*",
|
|
"rrsig.out.*",
|
|
"created.key-*",
|
|
"unused.key-*",
|
|
"verify.out.*",
|
|
"zone.out.*",
|
|
"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*/ksk",
|
|
"ns*/ksk/K*",
|
|
"ns*/zsk",
|
|
"ns*/zsk",
|
|
"ns*/zsk/K*",
|
|
"ns*/named-fips.conf",
|
|
"ns*/settime.out.*",
|
|
"ns*/signer.out.*",
|
|
"ns*/zones",
|
|
"ns*/policies/*.conf",
|
|
"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:
|
|
keydir = "."
|
|
|
|
keygen_command = [
|
|
os.environ.get("KEYGEN"),
|
|
"-K",
|
|
keydir,
|
|
"-k",
|
|
policy,
|
|
"-l",
|
|
"kasp.conf",
|
|
zone,
|
|
]
|
|
|
|
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
# check that 'dnssec-keygen -k' (configured policy) creates valid files.
|
|
lifetime = {
|
|
"P1Y": int(timedelta(days=365).total_seconds()),
|
|
"P30D": int(timedelta(days=30).total_seconds()),
|
|
"P6M": int(timedelta(days=31 * 6).total_seconds()),
|
|
}
|
|
keyprops = [
|
|
f"csk {lifetime['P1Y']} 13 256",
|
|
f"ksk {lifetime['P1Y']} 8 2048",
|
|
f"zsk {lifetime['P30D']} 8 2048",
|
|
f"zsk {lifetime['P6M']} 8 3072",
|
|
]
|
|
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)
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
# check that 'dnssec-keygen -k' (default policy) creates valid files.
|
|
keyprops = ["csk 0 13 256"]
|
|
out = keygen("kasp", "default")
|
|
keys = isctest.kasp.keystr_to_keylist(out)
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
# check that 'dnssec-settime' by default does not edit key state file.
|
|
key = keys[0]
|
|
shutil.copyfile(key.privatefile, f"{key.privatefile}.backup")
|
|
shutil.copyfile(key.keyfile, f"{key.keyfile}.backup")
|
|
shutil.copyfile(key.statefile, f"{key.statefile}.backup")
|
|
|
|
created = key.get_timing("Created")
|
|
publish = key.get_timing("Publish") + timedelta(hours=1)
|
|
settime = [
|
|
os.environ.get("SETTIME"),
|
|
"-P",
|
|
str(publish),
|
|
key.path,
|
|
]
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
isctest.check.file_contents_equal(f"{key.statefile}", f"{key.statefile}.backup")
|
|
assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
|
|
assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
|
|
|
|
# check that 'dnssec-settime -s' also sets publish time metadata and
|
|
# states in key state file.
|
|
now = KeyTimingMetadata.now()
|
|
goal = "omnipresent"
|
|
dnskey = "rumoured"
|
|
krrsig = "rumoured"
|
|
zrrsig = "omnipresent"
|
|
ds = "hidden"
|
|
keyprops = [
|
|
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
|
]
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
expected[0].timing = {
|
|
"Generated": created,
|
|
"Published": now,
|
|
"Active": created,
|
|
"DNSKEYChange": now,
|
|
"KRRSIGChange": now,
|
|
"ZRRSIGChange": now,
|
|
"DSChange": now,
|
|
}
|
|
|
|
settime = [
|
|
os.environ.get("SETTIME"),
|
|
"-s",
|
|
"-P",
|
|
str(now),
|
|
"-g",
|
|
goal,
|
|
"-k",
|
|
dnskey,
|
|
str(now),
|
|
"-r",
|
|
krrsig,
|
|
str(now),
|
|
"-z",
|
|
zrrsig,
|
|
str(now),
|
|
"-d",
|
|
ds,
|
|
str(now),
|
|
key.path,
|
|
]
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
# check that 'dnssec-settime -s' also unsets publish time metadata and
|
|
# states in key state file.
|
|
now = KeyTimingMetadata.now()
|
|
keyprops = ["csk 0 13 256"]
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
expected[0].timing = {
|
|
"Generated": created,
|
|
"Active": created,
|
|
}
|
|
|
|
settime = [
|
|
os.environ.get("SETTIME"),
|
|
"-s",
|
|
"-P",
|
|
"none",
|
|
"-g",
|
|
"none",
|
|
"-k",
|
|
"none",
|
|
str(now),
|
|
"-z",
|
|
"none",
|
|
str(now),
|
|
"-r",
|
|
"none",
|
|
str(now),
|
|
"-d",
|
|
"none",
|
|
str(now),
|
|
key.path,
|
|
]
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
# check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
|
|
soon = now + timedelta(hours=2)
|
|
goal = "hidden"
|
|
dnskey = "unretentive"
|
|
krrsig = "omnipresent"
|
|
zrrsig = "unretentive"
|
|
ds = "omnipresent"
|
|
keyprops = [
|
|
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
|
]
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
expected[0].timing = {
|
|
"Generated": created,
|
|
"Active": soon,
|
|
"DNSKEYChange": soon,
|
|
"KRRSIGChange": soon,
|
|
"ZRRSIGChange": soon,
|
|
"DSChange": soon,
|
|
}
|
|
|
|
settime = [
|
|
os.environ.get("SETTIME"),
|
|
"-s",
|
|
"-A",
|
|
str(soon),
|
|
"-g",
|
|
"HIDDEN",
|
|
"-k",
|
|
"UNRETENTIVE",
|
|
str(soon),
|
|
"-z",
|
|
"UNRETENTIVE",
|
|
str(soon),
|
|
"-r",
|
|
"OMNIPRESENT",
|
|
str(soon),
|
|
"-d",
|
|
"OMNIPRESENT",
|
|
str(soon),
|
|
key.path,
|
|
]
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
isctest.kasp.check_keytimes(keys, expected)
|