Compare commits
18 Commits
each-remov
...
matthijs-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec79078beb | ||
|
|
d46274014a | ||
|
|
486130652c | ||
|
|
bbba4a9fd1 | ||
|
|
b46d937db5 | ||
|
|
a528db84dd | ||
|
|
5aa1d9edbf | ||
|
|
6fc904d8d9 | ||
|
|
bbaa9ac808 | ||
|
|
2b11debbae | ||
|
|
05c9d4218f | ||
|
|
e5eb84f3ce | ||
|
|
e09516a5e7 | ||
|
|
092beb55b1 | ||
|
|
d8aa8db8bd | ||
|
|
ffb78c8f85 | ||
|
|
42c1eae444 | ||
|
|
aecf92dcf0 |
@@ -9,6 +9,7 @@
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import difflib
|
||||
import shutil
|
||||
from typing import Optional
|
||||
|
||||
@@ -97,6 +98,28 @@ def zones_equal(
|
||||
assert found_rdataset.ttl == rdataset.ttl
|
||||
|
||||
|
||||
def zone_contains(
|
||||
zone: dns.zone.Zone, rrset: dns.rrset.RRset, compare_ttl=False
|
||||
) -> bool:
|
||||
"""Check if a zone contains RRset"""
|
||||
|
||||
def compare_rrs(rr1, rrset):
|
||||
rr2 = next((other_rr for other_rr in rrset if rr1 == other_rr), None)
|
||||
if rr2 is None:
|
||||
return False
|
||||
if compare_ttl:
|
||||
return rr1.ttl == rr2.ttl
|
||||
return True
|
||||
|
||||
for _, node in zone.nodes.items():
|
||||
for rdataset in node:
|
||||
for rr in rdataset:
|
||||
if compare_rrs(rr, rrset):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_executable(cmd: str, errmsg: str) -> None:
|
||||
executable = shutil.which(cmd)
|
||||
assert executable is not None, errmsg
|
||||
@@ -128,3 +151,32 @@ def is_response_to(response: dns.message.Message, query: dns.message.Message) ->
|
||||
single_question(response)
|
||||
single_question(query)
|
||||
assert query.is_response(response), str(response)
|
||||
|
||||
|
||||
def file_contents_contain(file, substr):
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
if f"{substr}" in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
@@ -10,24 +10,34 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from functools import total_ordering
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Optional, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import dns
|
||||
import dns.tsig
|
||||
import isctest.log
|
||||
import isctest.query
|
||||
|
||||
DEFAULT_TTL = 300
|
||||
|
||||
NEXT_KEY_EVENT_THRESHOLD = 100
|
||||
|
||||
def _query(server, qname, qtype):
|
||||
|
||||
def _query(server, qname, qtype, tsig=None):
|
||||
query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
|
||||
|
||||
if tsig is not None:
|
||||
tsigkey = tsig.split(":")
|
||||
keyring = dns.tsig.Key(tsigkey[1], tsigkey[2], tsigkey[0])
|
||||
query.use_tsig(keyring)
|
||||
|
||||
try:
|
||||
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
|
||||
except dns.exception.Timeout:
|
||||
@@ -37,6 +47,158 @@ def _query(server, qname, qtype):
|
||||
return response
|
||||
|
||||
|
||||
class KeyProperties:
|
||||
"""
|
||||
Represent the (expected) properties a key should have.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, properties: dict, metadata: dict, timing: dict):
|
||||
self.name = name
|
||||
self.key = None
|
||||
self.properties = properties
|
||||
self.metadata = metadata
|
||||
self.timing = timing
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def default(with_state=True) -> "KeyProperties":
|
||||
result = KeyProperties.__new__(KeyProperties)
|
||||
result.name = "DEFAULT"
|
||||
result.key = None
|
||||
result.timing = {}
|
||||
result.properties = {
|
||||
"expect": True,
|
||||
"private": True,
|
||||
"legacy": False,
|
||||
"role": "csk",
|
||||
"role_full": "key-signing",
|
||||
"dnskey_ttl": 3600,
|
||||
"flags": 257,
|
||||
}
|
||||
result.metadata = {
|
||||
"Algorithm": 13, # ECDSAP256SHA256
|
||||
"Length": 256,
|
||||
"Lifetime": 0,
|
||||
"KSK": "yes",
|
||||
"ZSK": "yes",
|
||||
}
|
||||
if with_state:
|
||||
result.metadata["GoalState"] = "omnipresent"
|
||||
result.metadata["DNSKEYState"] = "rumoured"
|
||||
result.metadata["KRRSIGState"] = "rumoured"
|
||||
result.metadata["ZRRSIGState"] = "rumoured"
|
||||
result.metadata["DSState"] = "hidden"
|
||||
|
||||
return result
|
||||
|
||||
def Ipub(self, config):
|
||||
ipub = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) != "undefined":
|
||||
# Ipub = Dprp + TTLkey
|
||||
ipub = (
|
||||
config["dnskey-ttl"]
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["publish-safety"]
|
||||
)
|
||||
|
||||
self.timing["Active"] = self.timing["Published"] + ipub
|
||||
|
||||
def IpubC(self, config):
|
||||
if not self.key.is_ksk():
|
||||
return
|
||||
|
||||
ttl1 = config["dnskey-ttl"] + config["publish-safety"]
|
||||
ttl2 = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) == "undefined":
|
||||
# If this is the first key, we also need to wait until the zone
|
||||
# signatures are omnipresent. Use max-zone-ttl instead of
|
||||
# dnskey-ttl, and no publish-safety (because we are looking at
|
||||
# signatures here, not the public key).
|
||||
ttl2 = config["max-zone-ttl"]
|
||||
|
||||
# IpubC = DprpC + TTLkey
|
||||
ipubc = config["zone-propagation-delay"] + max(ttl1, ttl2)
|
||||
|
||||
self.timing["PublishCDS"] = self.timing["Published"] + ipubc
|
||||
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["DeleteCDS"] = self.timing["PublishCDS"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
def Iret(self, config):
|
||||
if self.metadata["Lifetime"] == 0:
|
||||
return
|
||||
|
||||
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
|
||||
safety_interval = config["retire-safety"]
|
||||
|
||||
iretKSK = timedelta(0)
|
||||
iretZSK = timedelta(0)
|
||||
if self.key.is_ksk():
|
||||
# Iret = DprpP + TTLds
|
||||
iretKSK = (
|
||||
config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval
|
||||
)
|
||||
if self.key.is_zsk():
|
||||
# Iret = Dsgn + Dprp + TTLsig
|
||||
iretZSK = (
|
||||
sign_delay
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["max-zone-ttl"]
|
||||
+ safety_interval
|
||||
)
|
||||
|
||||
self.timing["Removed"] = self.timing["Retired"] + max(iretKSK, iretZSK)
|
||||
|
||||
def set_expected_keytimes(self, config, offset=None, pregenerated=False):
|
||||
if self.key is None:
|
||||
raise ValueError("KeyProperties must be attached to a Key")
|
||||
|
||||
if self.properties["legacy"]:
|
||||
return
|
||||
|
||||
if offset is None:
|
||||
offset = self.properties["offset"]
|
||||
|
||||
self.timing["Generated"] = self.key.get_timing("Created")
|
||||
|
||||
self.timing["Published"] = self.timing["Generated"]
|
||||
if pregenerated:
|
||||
self.timing["Published"] = self.key.get_timing("Publish")
|
||||
self.timing["Published"] = self.timing["Published"] + offset
|
||||
self.Ipub(config)
|
||||
|
||||
# Set Retired timing metadata if key has lifetime.
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["Retired"] = self.timing["Active"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
self.IpubC(config)
|
||||
self.Iret(config)
|
||||
|
||||
# Key state change times must exist, but since we cannot reliably tell
|
||||
# when named made the actual state change, we don't care what the
|
||||
# value is. Set it to None will verify that the metadata exists, but
|
||||
# without actual checking the value.
|
||||
self.timing["DNSKEYChange"] = None
|
||||
|
||||
if self.key.is_ksk():
|
||||
self.timing["DSChange"] = None
|
||||
self.timing["KRRSIGChange"] = None
|
||||
|
||||
if self.key.is_zsk():
|
||||
self.timing["ZRRSIGChange"] = None
|
||||
|
||||
|
||||
@total_ordering
|
||||
class KeyTimingMetadata:
|
||||
"""
|
||||
@@ -117,6 +279,7 @@ class Key:
|
||||
else:
|
||||
self.keydir = Path(keydir)
|
||||
self.path = str(self.keydir / name)
|
||||
self.privatefile = f"{self.path}.private"
|
||||
self.keyfile = f"{self.path}.key"
|
||||
self.statefile = f"{self.path}.state"
|
||||
self.tag = int(self.name[-5:])
|
||||
@@ -139,21 +302,43 @@ class Key:
|
||||
)
|
||||
return None
|
||||
|
||||
def get_metadata(self, metadata: str, must_exist=True) -> str:
|
||||
def get_metadata(
|
||||
self, metadata: str, file=None, comment=False, must_exist=True
|
||||
) -> str:
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
value = "undefined"
|
||||
regex = rf"{metadata}:\s+(.*)"
|
||||
with open(self.statefile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
regex = rf"{metadata}:\s+(\S+).*"
|
||||
if comment:
|
||||
# The expected metadata is prefixed with a ';'.
|
||||
regex = rf";\s+{metadata}:\s+(\S+).*"
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
match = re.match(regex, line)
|
||||
if match is not None:
|
||||
value = match.group(1)
|
||||
break
|
||||
if must_exist and value == "undefined":
|
||||
raise ValueError(
|
||||
'state metadata "{metadata}" for key "{self.name}" undefined'
|
||||
f'metadata "{metadata}" for key "{self.name}" in file "{file}" undefined'
|
||||
)
|
||||
return value
|
||||
|
||||
def ttl(self) -> int:
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
return int(line.split()[1])
|
||||
return 0
|
||||
|
||||
def dnskey(self):
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if "DNSKEY" in line:
|
||||
return line.strip()
|
||||
return "undefined"
|
||||
|
||||
def is_ksk(self) -> bool:
|
||||
return self.get_metadata("KSK") == "yes"
|
||||
|
||||
@@ -187,7 +372,7 @@ class Key:
|
||||
dsfromkey_command = [
|
||||
os.environ.get("DSFROMKEY"),
|
||||
"-T",
|
||||
"3600",
|
||||
str(self.ttl()),
|
||||
"-a",
|
||||
alg,
|
||||
"-C",
|
||||
@@ -216,6 +401,152 @@ class Key:
|
||||
|
||||
return digest_fromfile == digest_fromwire
|
||||
|
||||
def has_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must also exist in the state
|
||||
# meta data. Otherwise, it must not exist in the state meta data.
|
||||
if key in metadata:
|
||||
return self.get_metadata(key) != "undefined"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must match the value in the
|
||||
# state meta data. Otherwise, it must also not exist in the state meta
|
||||
# data.
|
||||
if key in metadata:
|
||||
value = self.get_metadata(key)
|
||||
if value != f"{metadata[key]}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} METADATA MISMATCH: {value} - {metadata[key]}"
|
||||
)
|
||||
return value == f"{metadata[key]}"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_timing(self, key, timing, file, comment=False):
|
||||
# If 'key' exists in 'timing' then it must match the value in the
|
||||
# state timing data. Otherwise, it must also not exist in the state timing
|
||||
# data.
|
||||
if key in timing:
|
||||
value = self.get_metadata(key, file=file, comment=comment)
|
||||
if value != str(timing[key]):
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} TIMING MISMATCH: {value} - {timing[key]}"
|
||||
)
|
||||
return value == str(timing[key])
|
||||
|
||||
value = self.get_metadata(key, file=file, comment=comment, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} TIMING UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_properties(self, zone, properties):
|
||||
# Check the key with given properties.
|
||||
if not properties.properties["expect"]:
|
||||
return False
|
||||
|
||||
# Check file existence.
|
||||
# Noop. If file is missing then the get_metadata calls will fail.
|
||||
|
||||
# Check the public key file.
|
||||
role = properties.properties["role_full"]
|
||||
comment = f"This is a {role} key, keyid {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.keyfile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
ttl = properties.properties["dnskey_ttl"]
|
||||
flags = properties.properties["flags"]
|
||||
alg = properties.metadata["Algorithm"]
|
||||
dnskey = f"{zone}. {ttl} IN DNSKEY {flags} 3 {alg}"
|
||||
if not isctest.check.file_contents_contain(self.keyfile, dnskey):
|
||||
isctest.log.debug(f"{self.name} DNSKEY MISMATCH: expected '{dnskey}'")
|
||||
return False
|
||||
|
||||
# Now check the private key file.
|
||||
if properties.properties["private"]:
|
||||
# Retrieve creation date.
|
||||
created = self.get_metadata("Generated")
|
||||
|
||||
pval = self.get_metadata("Created", file=self.privatefile)
|
||||
if pval != created:
|
||||
isctest.log.debug(
|
||||
f"{self.name} Created METADATA MISMATCH: {pval} - {created}"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Private-key-format", file=self.privatefile)
|
||||
if pval != "v1.3":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Algorithm", file=self.privatefile)
|
||||
if pval != f"{alg}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Now check the key state file.
|
||||
if properties.properties["legacy"]:
|
||||
return True
|
||||
|
||||
comment = f"This is the state of key {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.statefile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
attributes = [
|
||||
"Lifetime",
|
||||
"Algorithm",
|
||||
"Length",
|
||||
"KSK",
|
||||
"ZSK",
|
||||
"GoalState",
|
||||
"DNSKEYState",
|
||||
"KRRSIGState",
|
||||
"ZRRSIGState",
|
||||
"DSState",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_metadata(key, properties.metadata):
|
||||
return False
|
||||
|
||||
# A match is found.
|
||||
return True
|
||||
|
||||
def match_timingmetadata(self, timings, file=None, comment=False):
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
|
||||
attributes = [
|
||||
"Generated",
|
||||
"Created",
|
||||
"Published",
|
||||
"Publish",
|
||||
"PublishCDS",
|
||||
"SyncPublish",
|
||||
"Active",
|
||||
"Activate",
|
||||
"Retired",
|
||||
"Inactive",
|
||||
"Revoked",
|
||||
"Removed",
|
||||
"Delete",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_timing(key, timings, file, comment=comment):
|
||||
isctest.log.debug(f"{self.name} TIMING METADATA MISMATCH: {key}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __lt__(self, other: "Key"):
|
||||
return self.name < other.name
|
||||
|
||||
@@ -226,14 +557,14 @@ class Key:
|
||||
return self.path
|
||||
|
||||
|
||||
def check_zone_is_signed(server, zone):
|
||||
def check_zone_is_signed(server, zone, tsig=None):
|
||||
addr = server.ip
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# wait until zone is fully signed
|
||||
signed = False
|
||||
for _ in range(10):
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC)
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC, tsig=tsig)
|
||||
if not isinstance(response, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} NSEC from {addr}")
|
||||
elif response.rcode() != dns.rcode.NOERROR:
|
||||
@@ -277,13 +608,111 @@ def check_zone_is_signed(server, zone):
|
||||
assert signed
|
||||
|
||||
|
||||
def check_dnssec_verify(server, zone):
|
||||
def check_keys(zone, keys, expected):
|
||||
# Checks keys for a configured zone. This verifies:
|
||||
# 1. The expected number of keys exist in 'keys'.
|
||||
# 2. The keys match the expected properties.
|
||||
|
||||
def _check_keys():
|
||||
# check number of keys matches expected.
|
||||
if len(keys) != len(expected):
|
||||
return False
|
||||
|
||||
if len(keys) == 0:
|
||||
return True
|
||||
|
||||
for expect in expected:
|
||||
expect.key = None
|
||||
|
||||
for key in keys:
|
||||
found = False
|
||||
i = 0
|
||||
while not found and i < len(expected):
|
||||
if expected[i].key is None:
|
||||
found = key.match_properties(zone, expected[i])
|
||||
if found:
|
||||
key.external = expected[i].properties["legacy"]
|
||||
expected[i].key = key
|
||||
i += 1
|
||||
if not found:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
isctest.run.retry_with_timeout(_check_keys, timeout=10)
|
||||
|
||||
|
||||
def check_keytimes(keys, expected):
|
||||
# Check the key timing metadata for all keys in 'keys'.
|
||||
assert len(keys) == len(expected)
|
||||
|
||||
if len(keys) == 0:
|
||||
return
|
||||
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
synonyms = {}
|
||||
if "Generated" in expect.timing:
|
||||
synonyms["Created"] = expect.timing["Generated"]
|
||||
if "Published" in expect.timing:
|
||||
synonyms["Publish"] = expect.timing["Published"]
|
||||
if "PublishCDS" in expect.timing:
|
||||
synonyms["SyncPublish"] = expect.timing["PublishCDS"]
|
||||
if "Active" in expect.timing:
|
||||
synonyms["Activate"] = expect.timing["Active"]
|
||||
if "Retired" in expect.timing:
|
||||
synonyms["Inactive"] = expect.timing["Retired"]
|
||||
if "DeleteCDS" in expect.timing:
|
||||
synonyms["SyncDelete"] = expect.timing["DeleteCDS"]
|
||||
if "Revoked" in expect.timing:
|
||||
synonyms["Revoked"] = expect.timing["Revoked"]
|
||||
if "Removed" in expect.timing:
|
||||
synonyms["Delete"] = expect.timing["Removed"]
|
||||
|
||||
assert key.match_timingmetadata(synonyms, file=key.keyfile, comment=True)
|
||||
if expect.properties["private"]:
|
||||
assert key.match_timingmetadata(synonyms, file=key.privatefile)
|
||||
if not expect.properties["legacy"]:
|
||||
assert key.match_timingmetadata(expect.timing)
|
||||
|
||||
state_changes = [
|
||||
"DNSKEYChange",
|
||||
"KRRSIGChange",
|
||||
"ZRRSIGChange",
|
||||
"DSChange",
|
||||
]
|
||||
for change in state_changes:
|
||||
assert key.has_metadata(change, expect.timing)
|
||||
|
||||
|
||||
def check_keyrelationships(keys, expected):
|
||||
# Check the key relationships (Successor and Predecessor metadata).
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
relationship_status = ["Predecessor", "Successor"]
|
||||
for status in relationship_status:
|
||||
assert key.match_metadata(status, expect.metadata)
|
||||
|
||||
|
||||
def check_dnssec_verify(server, zone, tsig=None):
|
||||
# Check if zone if DNSSEC valid with dnssec-verify.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
verified = False
|
||||
for _ in range(10):
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR)
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR, tsig=tsig)
|
||||
if not isinstance(transfer, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} AXFR from {server.ip}")
|
||||
elif transfer.rcode() != dns.rcode.NOERROR:
|
||||
@@ -415,9 +844,9 @@ def _check_dnskeys(dnskeys, keys, cdnskey=False):
|
||||
delete_md = f"Sync{delete_md}"
|
||||
|
||||
for key in keys:
|
||||
publish = key.get_timing(publish_md)
|
||||
publish = key.get_timing(publish_md, must_exist=False)
|
||||
delete = key.get_timing(delete_md, must_exist=False)
|
||||
published = now >= publish
|
||||
published = publish is not None and now >= publish
|
||||
removed = delete is not None and delete <= now
|
||||
|
||||
if not published or removed:
|
||||
@@ -502,8 +931,8 @@ def check_cds(rrset, keys):
|
||||
assert numcds == len(cdss)
|
||||
|
||||
|
||||
def _query_rrset(server, fqdn, qtype):
|
||||
response = _query(server, fqdn, qtype)
|
||||
def _query_rrset(server, fqdn, qtype, tsig=None):
|
||||
response = _query(server, fqdn, qtype, tsig=tsig)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
rrs = []
|
||||
@@ -523,46 +952,43 @@ def _query_rrset(server, fqdn, qtype):
|
||||
return rrs, rrsigs
|
||||
|
||||
|
||||
def check_apex(server, zone, ksks, zsks):
|
||||
def check_apex(server, zone, ksks, zsks, tsig=None):
|
||||
# Test the apex of a zone. This checks that the SOA and DNSKEY RRsets
|
||||
# are signed correctly and with the appropriate keys.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# test dnskey query
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY)
|
||||
assert len(dnskeys) > 0
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY, tsig=tsig)
|
||||
check_dnskeys(dnskeys, ksks, zsks)
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks)
|
||||
|
||||
# test soa query
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA)
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA, tsig=tsig)
|
||||
assert len(soa) == 1
|
||||
assert f"{zone}. {DEFAULT_TTL} IN SOA" in soa[0].to_text()
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.SOA, fqdn, ksks, zsks)
|
||||
|
||||
# test cdnskey query
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY)
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY, tsig=tsig)
|
||||
check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True)
|
||||
if len(cdnskeys) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks)
|
||||
|
||||
# test cds query
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS)
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS, tsig=tsig)
|
||||
check_cds(cds, ksks)
|
||||
if len(cds) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks)
|
||||
|
||||
|
||||
def check_subdomain(server, zone, ksks, zsks):
|
||||
def check_subdomain(server, zone, ksks, zsks, tsig=None):
|
||||
# Test an RRset below the apex and verify it is signed correctly.
|
||||
fqdn = f"{zone}."
|
||||
qname = f"a.{zone}."
|
||||
qtype = dns.rdatatype.A
|
||||
response = _query(server, qname, qtype)
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
match = f"{qname} {DEFAULT_TTL} IN A 10.0.0.1"
|
||||
@@ -575,5 +1001,180 @@ def check_subdomain(server, zone, ksks, zsks):
|
||||
else:
|
||||
assert match in rrset.to_text()
|
||||
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
|
||||
def check_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 check_next_key_event(server, zone, next_event):
|
||||
if next_event is None:
|
||||
# No next key event check.
|
||||
return True
|
||||
|
||||
val = int(next_event.total_seconds())
|
||||
if val == 3600:
|
||||
waitfor = rf".*zone {zone}.*: next key event in (.*) seconds"
|
||||
else:
|
||||
# Don't want default loadkeys interval.
|
||||
waitfor = rf".*zone {zone}.*: next key event in (?!3600$)(.*) seconds"
|
||||
|
||||
with server.watch_log_from_start() as watcher:
|
||||
watcher.wait_for_line(re.compile(waitfor))
|
||||
|
||||
next_found = False
|
||||
minval = val - NEXT_KEY_EVENT_THRESHOLD
|
||||
maxval = val + NEXT_KEY_EVENT_THRESHOLD
|
||||
with open(f"{server.identifier}/named.run", "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
match = re.match(waitfor, line)
|
||||
if match is not None:
|
||||
nextval = int(match.group(1))
|
||||
if minval <= nextval <= maxval:
|
||||
next_found = True
|
||||
break
|
||||
|
||||
isctest.log.debug(
|
||||
f"check next key event: expected {val} in: {line.strip()}"
|
||||
)
|
||||
|
||||
return next_found
|
||||
|
||||
|
||||
def keydir_to_keylist(
|
||||
zone: str, keydir: Optional[str] = None, in_use: Optional[bool] = False
|
||||
) -> List[Key]:
|
||||
# Retrieve all keys from the key files in a directory. If 'zone' is None,
|
||||
# retrieve all keys in the directory, otherwise only those matching the
|
||||
# zone name. If 'keydir' is None, search the current directory.
|
||||
if zone is None:
|
||||
zone = ""
|
||||
|
||||
all_keys = []
|
||||
if keydir is None:
|
||||
regex = rf"(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1)))
|
||||
else:
|
||||
regex = rf"{keydir}/(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"{keydir}/K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1), keydir))
|
||||
|
||||
states = ["GoalState", "DNSKEYState", "KRRSIGState", "ZRRSIGState", "DSState"]
|
||||
|
||||
def used(kk):
|
||||
if not in_use:
|
||||
return True
|
||||
|
||||
for state in states:
|
||||
val = kk.get_metadata(state, must_exist=False)
|
||||
if val not in ["undefined", "hidden"]:
|
||||
isctest.log.debug(f"key {kk} in use")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return [k for k in all_keys if used(k)]
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]:
|
||||
# Get the policies from a list of specially formatted strings.
|
||||
# The splitted line should result in the following items:
|
||||
# line[0]: Role
|
||||
# line[1]: Lifetime
|
||||
# line[2]: Algorithm
|
||||
# line[3]: Length
|
||||
# Then, optional data for specific tests may follow:
|
||||
# - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value,
|
||||
# sets the given state to the specific value
|
||||
# - "offset", an offset for testing key rollover timings
|
||||
proplist = []
|
||||
count = 0
|
||||
for key in keys:
|
||||
count += 1
|
||||
line = key.split()
|
||||
keyprop = KeyProperties(f"KEY{count}", {}, {}, {})
|
||||
keyprop.properties["expect"] = True
|
||||
keyprop.properties["private"] = True
|
||||
keyprop.properties["legacy"] = False
|
||||
keyprop.properties["offset"] = timedelta(0)
|
||||
keyprop.properties["role"] = line[0]
|
||||
if line[0] == "zsk":
|
||||
keyprop.properties["role_full"] = "zone-signing"
|
||||
keyprop.properties["flags"] = 256
|
||||
keyprop.metadata["ZSK"] = "yes"
|
||||
keyprop.metadata["KSK"] = "no"
|
||||
else:
|
||||
keyprop.properties["role_full"] = "key-signing"
|
||||
keyprop.properties["flags"] = 257
|
||||
keyprop.metadata["ZSK"] = "yes" if line[0] == "csk" else "no"
|
||||
keyprop.metadata["KSK"] = "yes"
|
||||
|
||||
keyprop.properties["dnskey_ttl"] = ttl
|
||||
keyprop.metadata["Algorithm"] = line[2]
|
||||
keyprop.metadata["Length"] = line[3]
|
||||
keyprop.metadata["Lifetime"] = 0
|
||||
if line[1] != "unlimited":
|
||||
keyprop.metadata["Lifetime"] = int(line[1])
|
||||
|
||||
if len(line) > 4:
|
||||
i = 4
|
||||
while i < len(line):
|
||||
if line[i].startswith("goal:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["GoalState"] = keyval[1]
|
||||
elif line[i].startswith("dnskey:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DNSKEYState"] = keyval[1]
|
||||
elif line[i].startswith("krrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["KRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("zrrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["ZRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("ds:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DSState"] = keyval[1]
|
||||
elif line[i].startswith("offset:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.properties["offset"] = timedelta(seconds=int(keyval[1]))
|
||||
else:
|
||||
assert False, f"undefined optional data {line[i]}"
|
||||
|
||||
i += 1
|
||||
|
||||
proplist.append(keyprop)
|
||||
|
||||
return proplist
|
||||
|
||||
@@ -130,7 +130,7 @@ $KEYGEN -G -k rsasha256 -l policies/kasp.conf $zone >keygen.out.$zone.2 2>&1
|
||||
zone="multisigner-model2.kasp"
|
||||
echo_i "setting up zone: $zone"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 -M 32768:65535 $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zone -M 32768:65535 2>keygen.out.$zone.2)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -M 32768:65535 $zone 2>keygen.out.$zone.2)
|
||||
cat "${KSK}.key" | grep -v ";.*" >>"${zone}.db"
|
||||
cat "${ZSK}.key" | grep -v ";.*" >>"${zone}.db"
|
||||
# Import the ZSK sets of the other providers into their DNSKEY RRset.
|
||||
@@ -350,10 +350,9 @@ setup step2.enable-dnssec.autosign
|
||||
TpubN="now-900s"
|
||||
# RRSIG TTL: 12 hour (43200 seconds)
|
||||
# zone-propagation-delay: 5 minutes (300 seconds)
|
||||
# retire-safety: 20 minutes (1200 seconds)
|
||||
# Already passed time: -900 seconds
|
||||
# Total: 43800 seconds
|
||||
TsbmN="now+43800s"
|
||||
# Total: 42600 seconds
|
||||
TsbmN="now+42600s"
|
||||
keytimes="-P ${TpubN} -P sync ${TsbmN} -A ${TpubN}"
|
||||
CSK=$($KEYGEN -k enable-dnssec -l policies/autosign.conf $keytimes $zone 2>keygen.out.$zone.1)
|
||||
$SETTIME -s -g $O -k $R $TpubN -r $R $TpubN -d $H $TpubN -z $R $TpubN "$CSK" >settime.out.$zone.1 2>&1
|
||||
@@ -365,10 +364,10 @@ $SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O raw -f "${zonefile}.signed" $i
|
||||
# Step 3:
|
||||
# The zone signatures have been published long enough to become OMNIPRESENT.
|
||||
setup step3.enable-dnssec.autosign
|
||||
# Passed time since publications: 43800 + 900 = 44700 seconds.
|
||||
TpubN="now-44700s"
|
||||
# Passed time since publications: 42600 + 900 = 43500 seconds.
|
||||
TpubN="now-43500s"
|
||||
# The key is secure for using in chain of trust when the DNSKEY is OMNIPRESENT.
|
||||
TcotN="now-43800s"
|
||||
TcotN="now-42600s"
|
||||
# We can submit the DS now.
|
||||
TsbmN="now"
|
||||
keytimes="-P ${TpubN} -P sync ${TsbmN} -A ${TpubN}"
|
||||
|
||||
@@ -127,9 +127,9 @@ setup step2.algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 3 hours.
|
||||
TactN="now-3h"
|
||||
TpubN1="now-3h"
|
||||
# Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp + publish-safety =
|
||||
# now - 3h + 6h + 1h + 1h = now + 5h
|
||||
TsbmN1="now+5h"
|
||||
# Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp =
|
||||
# now - 3h + 6h + 1h = now + 4h
|
||||
TsbmN1="now+4h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I now"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I now"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -156,11 +156,11 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# Step 3:
|
||||
# The zone signatures are also OMNIPRESENT.
|
||||
setup step3.algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 9 hours.
|
||||
TactN="now-9h"
|
||||
TretN="now-6h"
|
||||
TpubN1="now-9h"
|
||||
TsbmN1="now-1h"
|
||||
# The time passed since the new algorithm keys have been introduced is 7 hours.
|
||||
TactN="now-7h"
|
||||
TretN="now-3h"
|
||||
TpubN1="now-7h"
|
||||
TsbmN1="now"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -188,11 +188,11 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The DS is swapped and can become OMNIPRESENT.
|
||||
setup step4.algorithm-roll.kasp
|
||||
# The time passed since the DS has been swapped is 29 hours.
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TpubN1="now-38h"
|
||||
TsbmN1="now-30h"
|
||||
TactN1="now-29h"
|
||||
TactN="now-36h"
|
||||
TretN="now-33h"
|
||||
TpubN1="now-36h"
|
||||
TsbmN1="now-29h"
|
||||
TactN1="now-27h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -220,12 +220,12 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The DNSKEY is removed long enough to be HIDDEN.
|
||||
setup step5.algorithm-roll.kasp
|
||||
# The time passed since the DNSKEY has been removed is 2 hours.
|
||||
TactN="now-40h"
|
||||
TretN="now-37h"
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TremN="now-2h"
|
||||
TpubN1="now-40h"
|
||||
TsbmN1="now-32h"
|
||||
TactN1="now-31h"
|
||||
TpubN1="now-38h"
|
||||
TsbmN1="now-31h"
|
||||
TactN1="now-29h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -253,13 +253,13 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The RRSIGs have been removed long enough to be HIDDEN.
|
||||
setup step6.algorithm-roll.kasp
|
||||
# Additional time passed: 7h.
|
||||
TactN="now-47h"
|
||||
TretN="now-44h"
|
||||
TactN="now-45h"
|
||||
TretN="now-42h"
|
||||
TremN="now-7h"
|
||||
TpubN1="now-47h"
|
||||
TsbmN1="now-39h"
|
||||
TactN1="now-38h"
|
||||
TdeaN="now-9h"
|
||||
TpubN1="now-45h"
|
||||
TsbmN1="now-38h"
|
||||
TactN1="now-36h"
|
||||
TdeaN="now-7h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -324,11 +324,11 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# Step 3:
|
||||
# The zone signatures are also OMNIPRESENT.
|
||||
setup step3.csk-algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 9 hours.
|
||||
TactN="now-9h"
|
||||
TretN="now-6h"
|
||||
TpubN1="now-9h"
|
||||
TactN1="now-6h"
|
||||
# The time passed since the new algorithm keys have been introduced is 7 hours.
|
||||
TactN="now-7h"
|
||||
TretN="now-3h"
|
||||
TpubN1="now-7h"
|
||||
TactN1="now-3h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
|
||||
@@ -347,10 +347,10 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The DS is swapped and can become OMNIPRESENT.
|
||||
setup step4.csk-algorithm-roll.kasp
|
||||
# The time passed since the DS has been swapped is 29 hours.
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TpubN1="now-38h"
|
||||
TactN1="now-35h"
|
||||
TactN="now-36h"
|
||||
TretN="now-33h"
|
||||
TpubN1="now-36h"
|
||||
TactN1="now-33h"
|
||||
TsubN1="now-29h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
@@ -370,11 +370,11 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The DNSKEY is removed long enough to be HIDDEN.
|
||||
setup step5.csk-algorithm-roll.kasp
|
||||
# The time passed since the DNSKEY has been removed is 2 hours.
|
||||
TactN="now-40h"
|
||||
TretN="now-37h"
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TremN="now-2h"
|
||||
TpubN1="now-40h"
|
||||
TactN1="now-37h"
|
||||
TpubN1="now-38h"
|
||||
TactN1="now-35h"
|
||||
TsubN1="now-31h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
@@ -394,12 +394,12 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The RRSIGs have been removed long enough to be HIDDEN.
|
||||
setup step6.csk-algorithm-roll.kasp
|
||||
# Additional time passed: 7h.
|
||||
TactN="now-47h"
|
||||
TretN="now-44h"
|
||||
TactN="now-45h"
|
||||
TretN="now-42h"
|
||||
TdeaN="now-9h"
|
||||
TremN="now-7h"
|
||||
TpubN1="now-47h"
|
||||
TactN1="now-44h"
|
||||
TpubN1="now-45h"
|
||||
TactN1="now-42h"
|
||||
TsubN1="now-38h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
|
||||
28
bin/tests/system/kasp/prereq.sh
Normal file
28
bin/tests/system/kasp/prereq.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
if test -n "$PYTHON"; then
|
||||
if $PYTHON -c "from dns.update import UpdateMessage" 2>/dev/null; then
|
||||
:
|
||||
else
|
||||
echo_i "This test requires the dnspython >= 2.0.0 module." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo_i "This test requires Python and the dnspython module." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -54,178 +54,6 @@ next_key_event_threshold=100
|
||||
# Tests #
|
||||
###############################################################################
|
||||
|
||||
#
|
||||
# dnssec-keygen
|
||||
#
|
||||
set_zone "kasp"
|
||||
set_policy "kasp" "4" "200"
|
||||
set_server "keys" "10.53.0.1"
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)"
|
||||
ret=0
|
||||
$KEYGEN -K keys -k "$POLICY" -l kasp.conf "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
|
||||
lines=$(wc -l <"keygen.out.$POLICY.test$n")
|
||||
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy kasp: $lines"
|
||||
# Temporarily don't log errors because we are searching multiple files.
|
||||
disable_logerror
|
||||
|
||||
# Key properties.
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "31536000"
|
||||
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
|
||||
set_keysigning "KEY1" "yes"
|
||||
set_zonesigning "KEY1" "yes"
|
||||
|
||||
set_keyrole "KEY2" "ksk"
|
||||
set_keylifetime "KEY2" "31536000"
|
||||
set_keyalgorithm "KEY2" "8" "RSASHA256" "2048"
|
||||
set_keysigning "KEY2" "yes"
|
||||
set_zonesigning "KEY2" "no"
|
||||
|
||||
set_keyrole "KEY3" "zsk"
|
||||
set_keylifetime "KEY3" "2592000"
|
||||
set_keyalgorithm "KEY3" "8" "RSASHA256" "2048"
|
||||
set_keysigning "KEY3" "no"
|
||||
set_zonesigning "KEY3" "yes"
|
||||
|
||||
set_keyrole "KEY4" "zsk"
|
||||
set_keylifetime "KEY4" "16070400"
|
||||
set_keyalgorithm "KEY4" "8" "RSASHA256" "3072"
|
||||
set_keysigning "KEY4" "no"
|
||||
set_zonesigning "KEY4" "yes"
|
||||
|
||||
lines=$(get_keyids "$DIR" "$ZONE" | wc -l)
|
||||
test "$lines" -eq $NUM_KEYS || log_error "bad number of key ids"
|
||||
status=$((status + ret))
|
||||
|
||||
ids=$(get_keyids "$DIR" "$ZONE")
|
||||
for id in $ids; do
|
||||
# There are four key files with the same algorithm.
|
||||
# Check them until a match is found.
|
||||
ret=0 && check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY2" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY3" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY4" "$id"
|
||||
|
||||
# If ret is still non-zero, non of the files matched.
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
done
|
||||
# Turn error logs on again.
|
||||
enable_logerror
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
|
||||
ret=0
|
||||
set_zone "kasp"
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "." "10.53.0.1"
|
||||
# Key properties.
|
||||
key_clear "KEY1"
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "0"
|
||||
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
|
||||
set_keysigning "KEY1" "yes"
|
||||
set_zonesigning "KEY1" "yes"
|
||||
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
$KEYGEN -G -k "$POLICY" "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
|
||||
lines=$(wc -l <"keygen.out.$POLICY.test$n")
|
||||
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy default: $lines"
|
||||
# Temporarily adjust max search depth for this test
|
||||
MAXDEPTH=1
|
||||
ids=$(get_keyids "$DIR" "$ZONE")
|
||||
MAXDEPTH=3
|
||||
echo_i "found in dir $DIR for zone $ZONE the following keytags: $ids"
|
||||
for id in $ids; do
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
check_keytimes
|
||||
done
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# dnssec-settime
|
||||
#
|
||||
|
||||
# These test builds upon the latest created key with dnssec-keygen and uses the
|
||||
# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE.
|
||||
CMP_FILE="${BASE_FILE}.cmp"
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
$SETTIME -P +3600 "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
grep "; Publish: " "$KEY_FILE" >/dev/null || log_error "mismatch published in $KEY_FILE"
|
||||
grep "Publish: " "$PRIVATE_FILE" >/dev/null || log_error "mismatch published in $PRIVATE_FILE"
|
||||
diff "$CMP_FILE" "$STATE_FILE" || log_error "unexpected file change in $STATE_FILE"
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
now=$(date +%Y%m%d%H%M%S)
|
||||
$SETTIME -s -P "$now" -g "omnipresent" -k "rumoured" "$now" -z "omnipresent" "$now" -r "rumoured" "$now" -d "hidden" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_DS" "hidden"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "PUBLISHED" "${now}"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
$SETTIME -s -P "none" -g "none" -k "none" "$now" -z "none" "$now" -r "none" "$now" -d "none" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "none"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "none"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "none"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "none"
|
||||
set_keystate "KEY1" "STATE_DS" "none"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "PUBLISHED" "none"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
now=$(date +%Y%m%d%H%M%S)
|
||||
$SETTIME -s -A "$now" -g "HIDDEN" -k "UNRETENTIVE" "$now" -z "UNRETENTIVE" "$now" -r "OMNIPRESENT" "$now" -d "OMNIPRESENT" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "hidden"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "unretentive"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "unretentive"
|
||||
set_keystate "KEY1" "STATE_DS" "omnipresent"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "ACTIVE" "${now}"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# named
|
||||
#
|
||||
@@ -236,6 +64,7 @@ status=$((status + ret))
|
||||
# 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
|
||||
@@ -256,18 +85,6 @@ retry_quiet 30 _wait_for_done_apexnsec || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Test max-zone-ttl rejects zones with too high TTL.
|
||||
n=$((n + 1))
|
||||
echo_i "check that max-zone-ttl rejects zones with too high TTL ($n)"
|
||||
ret=0
|
||||
set_zone "max-zone-ttl.kasp"
|
||||
grep "loading from master file ${ZONE}.db failed: out of range" "ns3/named.run" >/dev/null || ret=1
|
||||
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)
|
||||
@@ -275,16 +92,11 @@ set_keytimes_csk_policy() {
|
||||
set_keytime "KEY1" "ACTIVE" "${created}"
|
||||
# The DS can be published if the DNSKEY and RRSIG records are
|
||||
# OMNIPRESENT. This happens after max-zone-ttl (1d) plus
|
||||
# publish-safety (1h) plus zone-propagation-delay (300s) =
|
||||
# 86400 + 3600 + 300 = 90300.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 90300
|
||||
# zone-propagation-delay (300s) = 86400 + 300 = 86700.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 86700
|
||||
# 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"
|
||||
@@ -298,240 +110,6 @@ set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
|
||||
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
|
||||
|
||||
if [ "$ip_a" != "-" ]; then
|
||||
dig_with_opts "a.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.a" || return 1
|
||||
grep "status: NOERROR" "dig.out.$DIR.test$n.a" >/dev/null || return 1
|
||||
grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_a}" "dig.out.$DIR.test$n.a" >/dev/null || return 1
|
||||
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | wc -l)
|
||||
test "$lines" -eq 1 || return 1
|
||||
get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | grep "^${KEY_ID}$" >/dev/null || return 1
|
||||
fi
|
||||
|
||||
if [ "$ip_d" != "-" ]; then
|
||||
dig_with_opts "d.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n".d || return 1
|
||||
grep "status: NOERROR" "dig.out.$DIR.test$n".d >/dev/null || return 1
|
||||
grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_d}" "dig.out.$DIR.test$n".d >/dev/null || return 1
|
||||
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | wc -l)
|
||||
test "$lines" -eq 1 || return 1
|
||||
get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | grep "^${KEY_ID}$" >/dev/null || return 1
|
||||
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.
|
||||
#
|
||||
set_zone "i-am.\":\;?&[]\@!\$*+,|=\.\(\)special.kasp."
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# It is non-trivial to adapt the tests to deal with all possible different
|
||||
# escaping characters, so we will just try to verify the zone.
|
||||
dnssec_verify
|
||||
|
||||
#
|
||||
# Zone: dynamic.kasp
|
||||
#
|
||||
set_zone "dynamic.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Update zone with nsupdate.
|
||||
n=$((n + 1))
|
||||
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
(
|
||||
echo zone ${ZONE}
|
||||
echo server 10.53.0.3 "$PORT"
|
||||
echo update del "a.${ZONE}" 300 A 10.0.0.1
|
||||
echo update add "a.${ZONE}" 300 A 10.0.0.101
|
||||
echo update add "d.${ZONE}" 300 A 10.0.0.4
|
||||
echo send
|
||||
) | $NSUPDATE
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.101" "10.0.0.4" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Update zone with nsupdate (reverting the above change).
|
||||
n=$((n + 1))
|
||||
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
(
|
||||
echo zone ${ZONE}
|
||||
echo server 10.53.0.3 "$PORT"
|
||||
echo update add "a.${ZONE}" 300 A 10.0.0.1
|
||||
echo update del "a.${ZONE}" 300 A 10.0.0.101
|
||||
echo update del "d.${ZONE}" 300 A 10.0.0.4
|
||||
echo send
|
||||
) | $NSUPDATE
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.1" "-" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
n=$((n + 1))
|
||||
echo_i "modify zone file and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
|
||||
sleep 1
|
||||
echo "d.${ZONE}. 300 A 10.0.0.44" >>"${DIR}/${ZONE}.db"
|
||||
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.1" "10.0.0.44" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: dynamic-inline-signing.kasp
|
||||
#
|
||||
set_zone "dynamic-inline-signing.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
n=$((n + 1))
|
||||
echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
|
||||
sleep 1
|
||||
cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
|
||||
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
|
||||
|
||||
retry_quiet 10 update_is_signed || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: dynamic-signed-inline-signing.kasp
|
||||
#
|
||||
set_zone "dynamic-signed-inline-signing.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
dnssec_verify
|
||||
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
|
||||
n=$((n + 1))
|
||||
echo_i "check if resigning the raw version of the zone is prevented for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
grep "zone_resigninc: zone $ZONE/IN (unsigned): enter" $DIR/named.run && ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: inline-signing.kasp
|
||||
#
|
||||
set_zone "inline-signing.kasp"
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
#
|
||||
# Zone: checkds-ksk.kasp.
|
||||
#
|
||||
@@ -769,9 +347,8 @@ set_keytimes_algorithm_policy() {
|
||||
|
||||
# The DS can be published if the DNSKEY and RRSIG records are
|
||||
# OMNIPRESENT. This happens after max-zone-ttl (1d) plus
|
||||
# publish-safety (1h) plus zone-propagation-delay (300s) =
|
||||
# 86400 + 3600 + 300 = 90300.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${published}" 90300
|
||||
# zone-propagation-delay (300s) = 86400 + 300 = 86700.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${published}" 86700
|
||||
# Key lifetime is 10 years, 315360000 seconds.
|
||||
set_addkeytime "KEY1" "RETIRED" "${published}" 315360000
|
||||
# The key is removed after the retire time plus DS TTL (1d),
|
||||
@@ -878,53 +455,16 @@ if [ $RSASHA1_SUPPORTED = 1 ]; then
|
||||
dnssec_verify
|
||||
fi
|
||||
|
||||
#
|
||||
# Zone: unsigned.kasp.
|
||||
#
|
||||
set_zone "unsigned.kasp"
|
||||
set_policy "none" "0" "0"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_apex
|
||||
check_subdomain
|
||||
# Make sure the zone file is untouched.
|
||||
n=$((n + 1))
|
||||
echo_i "Make sure the zonefile for zone ${ZONE} is not edited ($n)"
|
||||
ret=0
|
||||
diff "${DIR}/${ZONE}.db.infile" "${DIR}/${ZONE}.db" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: insecure.kasp.
|
||||
#
|
||||
set_zone "insecure.kasp"
|
||||
set_policy "insecure" "0" "0"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_apex
|
||||
check_subdomain
|
||||
|
||||
#
|
||||
# Zone: unlimited.kasp.
|
||||
#
|
||||
set_zone "unlimited.kasp"
|
||||
set_policy "unlimited" "1" "1234"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
# Key properties.
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "0"
|
||||
@@ -1720,10 +1260,10 @@ published=$(awk '{print $3}' <published.test${n}.key1)
|
||||
set_keytime "KEY1" "PUBLISHED" "${published}"
|
||||
set_keytime "KEY1" "ACTIVE" "${published}"
|
||||
published=$(key_get KEY1 PUBLISHED)
|
||||
# The DS can be published if the DNSKEY and RRSIG records are OMNIPRESENT.
|
||||
# This happens after max-zone-ttl (1d) plus publish-safety (1h) plus
|
||||
# zone-propagation-delay (300s) = 86400 + 3600 + 300 = 90300.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${published}" 90300
|
||||
# The DS can be published if the zone is fully signed.
|
||||
# This happens after max-zone-ttl (1d) plus
|
||||
# zone-propagation-delay (300s) = 86400 + 300 = 86700.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${published}" 86700
|
||||
# Key lifetime is 6 months, 315360000 seconds.
|
||||
set_addkeytime "KEY1" "RETIRED" "${published}" 16070400
|
||||
# The key is removed after the retire time plus DS TTL (1d), parent
|
||||
@@ -2486,9 +2026,9 @@ set_keytime "KEY1" "PUBLISHED" "${created}"
|
||||
set_keytime "KEY1" "ACTIVE" "${created}"
|
||||
# - The DS can be published if the DNSKEY and RRSIG records are
|
||||
# OMNIPRESENT. This happens after max-zone-ttl (12h) plus
|
||||
# publish-safety (5m) plus zone-propagation-delay (5m) =
|
||||
# 43200 + 300 + 300 = 43800.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 43800
|
||||
# plus zone-propagation-delay (5m) =
|
||||
# 43200 + 300 = 43500.
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 43500
|
||||
# - Key lifetime is unlimited, so not setting RETIRED and REMOVED.
|
||||
|
||||
# Various signing policy checks.
|
||||
@@ -2556,7 +2096,7 @@ check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "PUBLISHED" "${created}" -900
|
||||
set_addkeytime "KEY1" "ACTIVE" "${created}" -900
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 43800
|
||||
set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" 42600
|
||||
|
||||
# Continue signing policy checks.
|
||||
check_keytimes
|
||||
@@ -2566,8 +2106,8 @@ dnssec_verify
|
||||
|
||||
# Next key event is when the zone signatures become OMNIPRESENT: max-zone-ttl
|
||||
# plus zone propagation delay plus retire safety minus the already elapsed
|
||||
# 900 seconds: 12h + 300s + 20m - 900 = 44700 - 900 = 43800 seconds
|
||||
check_next_key_event 43800
|
||||
# 900 seconds: 12h + 300s + 20m - 900 = 43500 - 900 = 42600 seconds
|
||||
check_next_key_event 42600
|
||||
|
||||
#
|
||||
# Zone: step3.enable-dnssec.autosign.
|
||||
@@ -2584,10 +2124,10 @@ check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The key was published and activated 44700 seconds ago (with settime).
|
||||
# - The key was published and activated 43500 seconds ago (with settime).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "PUBLISHED" "${created}" -44700
|
||||
set_addkeytime "KEY1" "ACTIVE" "${created}" -44700
|
||||
set_addkeytime "KEY1" "PUBLISHED" "${created}" -43500
|
||||
set_addkeytime "KEY1" "ACTIVE" "${created}" -43500
|
||||
set_keytime "KEY1" "SYNCPUBLISH" "${created}"
|
||||
|
||||
# Continue signing policy checks.
|
||||
@@ -2603,8 +2143,8 @@ check_cdslog "$DIR" "$ZONE" KEY1
|
||||
rndc_checkds "$SERVER" "$DIR" KEY1 "now" "published" "$ZONE"
|
||||
# Next key event is when the DS can move to the OMNIPRESENT state. This occurs
|
||||
# when the parent propagation delay have passed, plus the DS TTL and retire
|
||||
# safety delay: 1h + 2h + 20m = 3h20m = 12000 seconds
|
||||
check_next_key_event 12000
|
||||
# safety delay: 1h + 2h = 3h = 10800 seconds
|
||||
check_next_key_event 10800
|
||||
|
||||
#
|
||||
# Zone: step4.enable-dnssec.autosign.
|
||||
@@ -4388,9 +3928,9 @@ check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Next key event is when the DS becomes HIDDEN. This happens after the
|
||||
# parent propagation delay, retire safety delay, and DS TTL:
|
||||
# 1h + 1h + 1d = 26h = 93600 seconds.
|
||||
check_next_key_event 93600
|
||||
# parent propagation delay, and DS TTL:
|
||||
# 1h + 1d = 25h = 90000 seconds.
|
||||
check_next_key_event 90000
|
||||
|
||||
#
|
||||
# Zone: step2.going-insecure.kasp
|
||||
@@ -4456,8 +3996,8 @@ dnssec_verify
|
||||
|
||||
# Next key event is when the DS becomes HIDDEN. This happens after the
|
||||
# parent propagation delay, retire safety delay, and DS TTL:
|
||||
# 1h + 1h + 1d = 26h = 93600 seconds.
|
||||
check_next_key_event 93600
|
||||
# 1h + 1d = 25h = 90000 seconds.
|
||||
check_next_key_event 90000
|
||||
|
||||
#
|
||||
# Zone: step2.going-insecure-dynamic.kasp
|
||||
@@ -4651,12 +4191,11 @@ set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
|
||||
created=$(key_get KEY3 CREATED)
|
||||
set_keytime "KEY3" "PUBLISHED" "${created}"
|
||||
set_keytime "KEY3" "ACTIVE" "${created}"
|
||||
# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
|
||||
# - It takes TTLsig + Dprp to propagate the zone.
|
||||
# TTLsig: 6h (39600 seconds)
|
||||
# Dprp: 1h (3600 seconds)
|
||||
# publish-safety: 1h (3600 seconds)
|
||||
# Ipub: 8h (28800 seconds)
|
||||
Ipub=28800
|
||||
# Ipub: 7h (25200 seconds)
|
||||
Ipub=25200
|
||||
set_addkeytime "KEY3" "SYNCPUBLISH" "${created}" "${Ipub}"
|
||||
# - The new ZSK is published and activated.
|
||||
created=$(key_get KEY4 CREATED)
|
||||
@@ -4725,12 +4264,12 @@ dnssec_verify
|
||||
|
||||
# Next key event is when all zone signatures are signed with the new
|
||||
# algorithm. This is the max-zone-ttl plus zone propagation delay
|
||||
# plus retire safety: 6h + 1h + 2h. But three hours have already passed
|
||||
# (the time it took to make the DNSKEY omnipresent), so the next event
|
||||
# should be scheduled in 6 hour: 21600 seconds. Prevent intermittent
|
||||
# 6h + 1h. But three hours have already passed (the time it took to
|
||||
# make the DNSKEY omnipresent), so the next event should be scheduled
|
||||
# in 4 hour: 14400 seconds. Prevent intermittent
|
||||
# false positives on slow platforms by subtracting the number of seconds
|
||||
# which passed between key creation and invoking 'rndc reconfig'.
|
||||
next_time=$((21600 - time_passed))
|
||||
next_time=$((14400 - time_passed))
|
||||
check_next_key_event $next_time
|
||||
|
||||
#
|
||||
@@ -4753,28 +4292,28 @@ check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_cdslog "$DIR" "$ZONE" KEY3
|
||||
|
||||
# Set expected key times:
|
||||
# - The old keys were activated 9 hours ago (32400 seconds).
|
||||
rollover_predecessor_keytimes -32400
|
||||
# - And retired 6 hours ago (21600 seconds).
|
||||
# - The old keys were activated 7 hours ago (25200 seconds).
|
||||
rollover_predecessor_keytimes -25200
|
||||
# - And retired 3 hours ago (10800 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -21600
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -10800
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
|
||||
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -21600
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -10800
|
||||
retired=$(key_get KEY2 RETIRED)
|
||||
set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
|
||||
# - The new keys are published 9 hours ago.
|
||||
# - The new keys are published 7 hours ago.
|
||||
created=$(key_get KEY3 CREATED)
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -32400
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -32400
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -25200
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -25200
|
||||
published=$(key_get KEY3 PUBLISHED)
|
||||
set_addkeytime "KEY3" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
created=$(key_get KEY4 CREATED)
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -32400
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -32400
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -25200
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -25200
|
||||
|
||||
# Continue signing policy checks.
|
||||
check_keytimes
|
||||
@@ -4787,9 +4326,9 @@ dnssec_verify
|
||||
rndc_checkds "$SERVER" "$DIR" KEY1 "now" "withdrawn" "$ZONE"
|
||||
rndc_checkds "$SERVER" "$DIR" KEY3 "now" "published" "$ZONE"
|
||||
# Next key event is when the DS becomes OMNIPRESENT. This happens after the
|
||||
# parent propagation delay, retire safety delay, and DS TTL:
|
||||
# 1h + 2h + 2h = 5h = 18000 seconds.
|
||||
check_next_key_event 18000
|
||||
# parent propagation delay, and DS TTL:
|
||||
# 1h + 2h = 3h = 10800 seconds.
|
||||
check_next_key_event 10800
|
||||
|
||||
#
|
||||
# Zone: step4.algorithm-roll.kasp
|
||||
@@ -4816,29 +4355,29 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old keys were activated 38 hours ago (136800 seconds).
|
||||
rollover_predecessor_keytimes -136800
|
||||
# - And retired 35 hours ago (126000 seconds).
|
||||
# - The old keys were activated 36 hours ago (129600 seconds).
|
||||
rollover_predecessor_keytimes -129600
|
||||
# - And retired 33 hours ago (118800 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -126000
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -118800
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
|
||||
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -126000
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -118800
|
||||
retired=$(key_get KEY2 RETIRED)
|
||||
set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
|
||||
|
||||
# - The new keys are published 38 hours ago.
|
||||
# - The new keys are published 36 hours ago.
|
||||
created=$(key_get KEY3 CREATED)
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -136800
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -129600
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -129600
|
||||
published=$(key_get KEY3 PUBLISHED)
|
||||
set_addkeytime "KEY3" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
created=$(key_get KEY4 CREATED)
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -136800
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -129600
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -129600
|
||||
|
||||
# Continue signing policy checks.
|
||||
check_keytimes
|
||||
@@ -4867,29 +4406,29 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old keys were activated 40 hours ago (144000 seconds)
|
||||
rollover_predecessor_keytimes -144000
|
||||
# - And retired 37 hours ago (133200 seconds).
|
||||
# - The old keys were activated 38 hours ago (136800 seconds)
|
||||
rollover_predecessor_keytimes -136800
|
||||
# - And retired 35 hours ago (126000 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -133200
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -126000
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
|
||||
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -133200
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -126000
|
||||
retired=$(key_get KEY2 RETIRED)
|
||||
set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
|
||||
|
||||
# The new keys are published 40 hours ago.
|
||||
created=$(key_get KEY3 CREATED)
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -144000
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -144000
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -136800
|
||||
published=$(key_get KEY3 PUBLISHED)
|
||||
set_addkeytime "KEY3" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
created=$(key_get KEY4 CREATED)
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -144000
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -144000
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -136800
|
||||
|
||||
# Continue signing policy checks.
|
||||
check_keytimes
|
||||
@@ -4898,12 +4437,12 @@ check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Next key event is when the RSASHA1 signatures become HIDDEN. This happens
|
||||
# after the max-zone-ttl plus zone propagation delay plus retire safety
|
||||
# (6h + 1h + 2h) minus the time already passed since the UNRETENTIVE state has
|
||||
# been reached (2h): 9h - 2h = 7h = 25200 seconds. Prevent intermittent
|
||||
# after the max-zone-ttl plus zone propagation delay (6h + 1h)
|
||||
# minus the time already passed since the UNRETENTIVE state has
|
||||
# been reached (2h): 7h - 2h = 5h = 18000 seconds. Prevent intermittent
|
||||
# false positives on slow platforms by subtracting the number of seconds
|
||||
# which passed between key creation and invoking 'rndc reconfig'.
|
||||
next_time=$((25200 - time_passed))
|
||||
next_time=$((18000 - time_passed))
|
||||
check_next_key_event $next_time
|
||||
|
||||
#
|
||||
@@ -4921,29 +4460,29 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old keys were activated 47 hours ago (169200 seconds)
|
||||
rollover_predecessor_keytimes -169200
|
||||
# - And retired 44 hours ago (158400 seconds).
|
||||
# - The old keys were activated 45 hours ago (162000 seconds)
|
||||
rollover_predecessor_keytimes -162000
|
||||
# - And retired 42 hours ago (151200 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -158400
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -151200
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
|
||||
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -158400
|
||||
set_addkeytime "KEY2" "RETIRED" "${created}" -151200
|
||||
retired=$(key_get KEY2 RETIRED)
|
||||
set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
|
||||
|
||||
# The new keys are published 47 hours ago.
|
||||
created=$(key_get KEY3 CREATED)
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -169200
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -169200
|
||||
set_addkeytime "KEY3" "PUBLISHED" "${created}" -162000
|
||||
set_addkeytime "KEY3" "ACTIVE" "${created}" -162000
|
||||
published=$(key_get KEY3 PUBLISHED)
|
||||
set_addkeytime "KEY3" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
created=$(key_get KEY4 CREATED)
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -169200
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -169200
|
||||
set_addkeytime "KEY4" "PUBLISHED" "${created}" -162000
|
||||
set_addkeytime "KEY4" "ACTIVE" "${created}" -162000
|
||||
|
||||
# Continue signing policy checks.
|
||||
check_keytimes
|
||||
@@ -5026,9 +4565,8 @@ set_keytime "KEY2" "ACTIVE" "${created}"
|
||||
# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
|
||||
# TTLsig: 6h (39600 seconds)
|
||||
# Dprp: 1h (3600 seconds)
|
||||
# publish-safety: 1h (3600 seconds)
|
||||
# Ipub: 8h (28800 seconds)
|
||||
Ipub=28800
|
||||
# Ipub: 7h (25200 seconds)
|
||||
Ipub=25200
|
||||
set_addkeytime "KEY2" "SYNCPUBLISH" "${created}" "${Ipub}"
|
||||
|
||||
# Continue signing policy checks.
|
||||
@@ -5082,14 +4620,13 @@ check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Next key event is when all zone signatures are signed with the new
|
||||
# algorithm. This is the max-zone-ttl plus zone propagation delay
|
||||
# plus retire safety: 6h + 1h + 2h. But three hours have already passed
|
||||
# (the time it took to make the DNSKEY omnipresent), so the next event
|
||||
# should be scheduled in 6 hour: 21600 seconds. Prevent intermittent
|
||||
# false positives on slow platforms by subtracting the number of seconds
|
||||
# which passed between key creation and invoking 'rndc reconfig'.
|
||||
next_time=$((21600 - time_passed))
|
||||
# Next key event is when all zone signatures are signed with the new algorithm.
|
||||
# This is the max-zone-ttl plus zone propagation delay: 6h + 1h. But three
|
||||
# hours have already passed (the time it took to make the DNSKEY omnipresent),
|
||||
# so the next event should be scheduled in 4 hour: 14400 seconds. Prevent
|
||||
# intermittent false positives on slow platforms by subtracting the number of
|
||||
# seconds which passed between key creation and invoking 'rndc reconfig'.
|
||||
next_time=$((14400 - time_passed))
|
||||
check_next_key_event $next_time
|
||||
|
||||
#
|
||||
@@ -5114,17 +4651,17 @@ check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_cdslog "$DIR" "$ZONE" KEY2
|
||||
|
||||
# Set expected key times:
|
||||
# - The old key was activated 9 hours ago (32400 seconds).
|
||||
csk_rollover_predecessor_keytimes -32400
|
||||
# - And was retired 6 hours ago (21600 seconds).
|
||||
# - The old key was activated 7 hours ago (25200 seconds).
|
||||
csk_rollover_predecessor_keytimes -25200
|
||||
# - And was retired 3 hours ago (10800 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -21600
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -10800
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretCSK}"
|
||||
# - The new key was published 9 hours ago.
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -32400
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -32400
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -25200
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -25200
|
||||
published=$(key_get KEY2 PUBLISHED)
|
||||
set_addkeytime "KEY2" "SYNCPUBLISH" "${published}" "${Ipub}"
|
||||
|
||||
@@ -5138,9 +4675,9 @@ dnssec_verify
|
||||
rndc_checkds "$SERVER" "$DIR" KEY1 "now" "withdrawn" "$ZONE"
|
||||
rndc_checkds "$SERVER" "$DIR" KEY2 "now" "published" "$ZONE"
|
||||
# Next key event is when the DS becomes OMNIPRESENT. This happens after the
|
||||
# parent propagation delay, retire safety delay, and DS TTL:
|
||||
# 1h + 2h + 2h = 5h = 18000 seconds.
|
||||
check_next_key_event 18000
|
||||
# parent propagation delay, and DS TTL:
|
||||
# 1h + 2h = 3h = 10800 seconds.
|
||||
check_next_key_event 10800
|
||||
|
||||
#
|
||||
# Zone: step4.csk-algorithm-roll.kasp
|
||||
@@ -5164,17 +4701,17 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old key was activated 38 hours ago (136800 seconds)
|
||||
csk_rollover_predecessor_keytimes -136800
|
||||
# - And retired 35 hours ago (126000 seconds).
|
||||
# - The old keys were activated 36 hours ago (129600 seconds).
|
||||
csk_rollover_predecessor_keytimes -129600
|
||||
# - And retired 33 hours ago (118800 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -126000
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -118800
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretCSK}"
|
||||
# - The new key was published 38 hours ago.
|
||||
# - The new key was published 36 hours ago.
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -136800
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -129600
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -129600
|
||||
published=$(key_get KEY2 PUBLISHED)
|
||||
set_addkeytime "KEY2" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
@@ -5204,17 +4741,17 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old key was activated 40 hours ago (144000 seconds)
|
||||
csk_rollover_predecessor_keytimes -144000
|
||||
# - And retired 37 hours ago (133200 seconds).
|
||||
# - The old key was activated 38 hours ago (136800 seconds)
|
||||
csk_rollover_predecessor_keytimes -136800
|
||||
# - And retired 35 hours ago (126000 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -133200
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -126000
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretCSK}"
|
||||
# - The new key was published 40 hours ago.
|
||||
# - The new key was published 38 hours ago.
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -144000
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -144000
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -136800
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -136800
|
||||
published=$(key_get KEY2 PUBLISHED)
|
||||
set_addkeytime "KEY2" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
@@ -5225,12 +4762,12 @@ check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Next key event is when the RSASHA1 signatures become HIDDEN. This happens
|
||||
# after the max-zone-ttl plus zone propagation delay plus retire safety
|
||||
# (6h + 1h + 2h) minus the time already passed since the UNRETENTIVE state has
|
||||
# been reached (2h): 9h - 2h = 7h = 25200 seconds. Prevent intermittent
|
||||
# false positives on slow platforms by subtracting the number of seconds
|
||||
# which passed between key creation and invoking 'rndc reconfig'.
|
||||
next_time=$((25200 - time_passed))
|
||||
# after the max-zone-ttl plus zone propagation delay (6h + 1h) minus the
|
||||
# time already passed since the UNRETENTIVE state has been reached (2h):
|
||||
# 7h - 2h = 5h = 18000 seconds. Prevent intermittent false positives on slow
|
||||
# platforms by subtracting the number of seconds which passed between key
|
||||
# creation and invoking 'rndc reconfig'.
|
||||
next_time=$((18000 - time_passed))
|
||||
check_next_key_event $next_time
|
||||
|
||||
#
|
||||
@@ -5248,17 +4785,17 @@ wait_for_done_signing
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
|
||||
# Set expected key times:
|
||||
# - The old keys were activated 47 hours ago (169200 seconds)
|
||||
csk_rollover_predecessor_keytimes -169200
|
||||
# - And retired 44 hours ago (158400 seconds).
|
||||
# - The old keys were activated 45 hours ago (162000 seconds)
|
||||
csk_rollover_predecessor_keytimes -162000
|
||||
# - And retired 42 hours ago (151200 seconds).
|
||||
created=$(key_get KEY1 CREATED)
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -158400
|
||||
set_addkeytime "KEY1" "RETIRED" "${created}" -151200
|
||||
retired=$(key_get KEY1 RETIRED)
|
||||
set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretCSK}"
|
||||
# - The new key was published 47 hours ago.
|
||||
created=$(key_get KEY2 CREATED)
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -169200
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -169200
|
||||
set_addkeytime "KEY2" "PUBLISHED" "${created}" -162000
|
||||
set_addkeytime "KEY2" "ACTIVE" "${created}" -162000
|
||||
published=$(key_get KEY2 PUBLISHED)
|
||||
set_addkeytime "KEY2" "SYNCPUBLISH" "${published}" ${Ipub}
|
||||
|
||||
|
||||
569
bin/tests/system/kasp/tests_kasp.py
Normal file
569
bin/tests/system/kasp/tests_kasp.py
Normal file
@@ -0,0 +1,569 @@
|
||||
# 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
|
||||
import time
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import dns
|
||||
import dns.update
|
||||
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.check_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, [])
|
||||
|
||||
# A zone that uses inline-signing.
|
||||
isctest.log.info("check an inline-signed zone with the default policy is signed")
|
||||
zone = "inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
expected = [key1]
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
set_keytimes_default_policy(key1)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
|
||||
def test_kasp_dynamic(servers):
|
||||
# Dynamic update test cases.
|
||||
server = servers["ns3"]
|
||||
|
||||
# Standard dynamic zone.
|
||||
isctest.log.info("check dynamic zone is updated and signed after update")
|
||||
zone = "dynamic.kasp"
|
||||
policy = "default"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
expected = [key1]
|
||||
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(key1)
|
||||
expected = [key1]
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# Update zone with nsupdate.
|
||||
def nsupdate():
|
||||
message = dns.update.UpdateMessage(zone)
|
||||
for update in updates:
|
||||
if update[0] == 0:
|
||||
message.delete(update[1], update[2], update[3])
|
||||
else:
|
||||
message.add(update[1], update[2], update[3], update[4])
|
||||
|
||||
try:
|
||||
response = isctest.query.udp(
|
||||
message, server.ip, server.ports.dns, timeout=3
|
||||
)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
except dns.exception.Timeout:
|
||||
isctest.log.info(f"error: update timeout for {zone}")
|
||||
|
||||
isctest.log.debug(f"update of zone {zone} to server {server.ip} successful")
|
||||
|
||||
def update_is_signed():
|
||||
parts = update.split()
|
||||
qname = parts[0]
|
||||
qtype = dns.rdatatype.from_text(parts[1])
|
||||
rdata = parts[2]
|
||||
return isctest.kasp.check_update_is_signed(
|
||||
server, zone, qname, qtype, rdata, keys, []
|
||||
)
|
||||
|
||||
updates = [
|
||||
[0, f"a.{zone}.", "A", "10.0.0.1"],
|
||||
[1, f"a.{zone}.", 300, "A", "10.0.0.101"],
|
||||
[1, f"d.{zone}.", 300, "A", "10.0.0.4"],
|
||||
]
|
||||
nsupdate()
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.101", f"d.{zone}. A 10.0.0.4"]
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Update zone with nsupdate (reverting the above change).
|
||||
updates = [
|
||||
[1, f"a.{zone}.", 300, "A", "10.0.0.1"],
|
||||
[0, f"a.{zone}.", "A", "10.0.0.101"],
|
||||
[0, f"d.{zone}.", "A", "10.0.0.4"],
|
||||
]
|
||||
nsupdate()
|
||||
|
||||
update = f"a.{zone}. A 10.0.0.1"
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
isctest.log.info("check dynamic zone is updated and signed after freeze and thaw")
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"freeze {zone}", log=False)
|
||||
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
||||
|
||||
time.sleep(1)
|
||||
with open(f"ns3/{zone}.db", "a", encoding="utf-8") as zonefile:
|
||||
zonefile.write(f"d.{zone}. 300 A 10.0.0.44\n")
|
||||
time.sleep(1)
|
||||
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"thaw {zone}", log=False)
|
||||
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.1", f"d.{zone}. A 10.0.0.44"]
|
||||
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Dynamic, and inline-signing.
|
||||
zone = "dynamic-inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
expected = [key1]
|
||||
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(key1)
|
||||
expected = [key1]
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
isctest.log.info(
|
||||
"check dynamic inline-signed zone is updated and signed after freeze and thaw"
|
||||
)
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"freeze {zone}", log=False)
|
||||
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
||||
|
||||
time.sleep(1)
|
||||
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
|
||||
time.sleep(1)
|
||||
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"thaw {zone}", log=False)
|
||||
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
||||
|
||||
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)
|
||||
|
||||
# Dynamic, signed, and inline-signing.
|
||||
isctest.log.info("check dynamic signed, and inline-signed zone")
|
||||
zone = "dynamic-signed-inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
# The ns3/setup.sh script sets all states to omnipresent.
|
||||
key1.metadata["DNSKEYState"] = "omnipresent"
|
||||
key1.metadata["KRRSIGState"] = "omnipresent"
|
||||
key1.metadata["ZRRSIGState"] = "omnipresent"
|
||||
key1.metadata["DSState"] = "omnipresent"
|
||||
expected = [key1]
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3/keys")
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
|
||||
assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run"
|
||||
|
||||
|
||||
def test_kasp_special_cases(servers):
|
||||
server = servers["ns3"]
|
||||
|
||||
# Insecure zones.
|
||||
isctest.log.info("check insecure zones")
|
||||
|
||||
zone = "insecure.kasp"
|
||||
expected = []
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
isctest.kasp.check_dnssecstatus(server, zone, keys, policy="insecure")
|
||||
isctest.kasp.check_apex(server, zone, keys, [])
|
||||
isctest.kasp.check_subdomain(server, zone, keys, [])
|
||||
|
||||
zone = "unsigned.kasp"
|
||||
expected = []
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=None)
|
||||
isctest.kasp.check_apex(server, zone, keys, [])
|
||||
isctest.kasp.check_subdomain(server, zone, keys, [])
|
||||
# Make sure the zone file is untouched.
|
||||
isctest.check.file_contents_equal(f"ns3/{zone}.db.infile", f"ns3/{zone}.db")
|
||||
|
||||
# A zone with special characters.
|
||||
isctest.log.info("check special characters")
|
||||
|
||||
zone = r'i-am.":\;?&[]\@!\$*+,|=\.\(\)special.kasp'
|
||||
# It is non-trivial to adapt the tests to deal with all possible different
|
||||
# escaping characters, so we will just try to verify the zone.
|
||||
isctest.kasp.check_dnssec_verify(server, zone)
|
||||
|
||||
# check that max-zone-ttl rejects zones with too high TTL.
|
||||
isctest.log.info("check max-zone-ttl rejects zones with too high TTL")
|
||||
zone = "max-zone-ttl.kasp"
|
||||
assert f"loading from master file {zone}.db failed: out of range" in server.log
|
||||
|
||||
|
||||
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]
|
||||
privatefile = f"{key.path}.private"
|
||||
keyfile = f"{key.path}.key"
|
||||
statefile = f"{key.path}.state"
|
||||
shutil.copyfile(privatefile, f"{privatefile}.backup")
|
||||
shutil.copyfile(keyfile, f"{keyfile}.backup")
|
||||
shutil.copyfile(statefile, f"{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.path}.state", f"{key.path}.state.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)
|
||||
@@ -10,19 +10,14 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from datetime import timedelta
|
||||
import difflib
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
from isctest.kasp import (
|
||||
Key,
|
||||
KeyTimingMetadata,
|
||||
)
|
||||
from isctest.kasp import KeyTimingMetadata
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
@@ -89,31 +84,6 @@ def between(value, start, end):
|
||||
return start < value < end
|
||||
|
||||
|
||||
def check_file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def ksr(zone, policy, action, options="", raise_on_exception=True):
|
||||
ksr_command = [
|
||||
os.environ.get("KSR"),
|
||||
@@ -515,14 +485,14 @@ def test_ksr_common(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
|
||||
zsks = keystr_to_keylist(out)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -532,7 +502,7 @@ def test_ksr_common(servers):
|
||||
# in the given key directory
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -575,18 +545,22 @@ def test_ksr_common(servers):
|
||||
# check that 'dnssec-ksr keygen' selects pregenerated keys for
|
||||
# the same time bundle
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +1y")
|
||||
selected_zsks = keystr_to_keylist(out, zskdir)
|
||||
selected_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(selected_zsks) == 2
|
||||
for index, key in enumerate(selected_zsks):
|
||||
assert zsks[index] == key
|
||||
check_file_contents_equal(f"{key.path}.private", f"{key.path}.private.backup")
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
|
||||
# check that 'dnssec-ksr keygen' generates only necessary keys for
|
||||
# overlapping time bundle
|
||||
out, err = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y -v 1")
|
||||
overlapping_zsks = keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks) == 4
|
||||
|
||||
verbose = err.split()
|
||||
@@ -602,15 +576,19 @@ def test_ksr_common(servers):
|
||||
for index, key in enumerate(overlapping_zsks):
|
||||
if index < 2:
|
||||
assert zsks[index] == key
|
||||
check_file_contents_equal(
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.key", f"{key.path}.key.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
|
||||
# run 'dnssec-ksr keygen' again with verbosity 0
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y")
|
||||
overlapping_zsks2 = keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks2 = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks2) == 4
|
||||
check_keys(overlapping_zsks2, lifetime)
|
||||
for index, key in enumerate(overlapping_zsks2):
|
||||
@@ -705,7 +683,7 @@ def test_ksr_lastbundle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1d -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -713,7 +691,7 @@ def test_ksr_lastbundle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1d")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -784,7 +762,7 @@ def test_ksr_inthemiddle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -792,7 +770,7 @@ def test_ksr_inthemiddle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 4
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -864,13 +842,13 @@ def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
|
||||
then = now + offset
|
||||
until = now + end
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i {then} -e {until} -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
# key generation
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {then} -e {until}")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
# create request
|
||||
@@ -937,7 +915,7 @@ def test_ksr_unlimited(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +2y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
@@ -945,7 +923,7 @@ def test_ksr_unlimited(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +2y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
lifetime = None
|
||||
@@ -1054,7 +1032,7 @@ def test_ksr_twotone(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
ksks_defalg = []
|
||||
@@ -1078,7 +1056,7 @@ def test_ksr_twotone(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
# First algorithm keys have a lifetime of 3 months, so there should
|
||||
# be 4 created keys. Second algorithm keys have a lifetime of 5
|
||||
# months, so there should be 3 created keys. While only two time
|
||||
@@ -1172,7 +1150,7 @@ def test_ksr_kskroll(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -1181,7 +1159,7 @@ def test_ksr_kskroll(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
check_keys(zsks, None)
|
||||
|
||||
109
lib/dns/keymgr.c
109
lib/dns/keymgr.c
@@ -189,13 +189,19 @@ dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first) {
|
||||
isc_stdtime_t zrrsig_present;
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
||||
zrrsig_present = published + ttlsig +
|
||||
dns_kasp_zonepropagationdelay(kasp) +
|
||||
dns_kasp_publishsafety(kasp);
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
if (zrrsig_present > syncpublish) {
|
||||
syncpublish = zrrsig_present;
|
||||
}
|
||||
}
|
||||
dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncpublish);
|
||||
|
||||
uint32_t lifetime = 0;
|
||||
ret = dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime);
|
||||
if (ret == ISC_R_SUCCESS && lifetime > 0) {
|
||||
dst_key_settime(key, DST_TIME_SYNCDELETE,
|
||||
(syncpublish + lifetime));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -243,6 +249,17 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
pub = now;
|
||||
}
|
||||
|
||||
/*
|
||||
* To calculate phase out times ("Retired", "Removed", ...),
|
||||
* the key lifetime is required.
|
||||
*/
|
||||
uint32_t klifetime = 0;
|
||||
ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
klifetime = lifetime;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate prepublication time.
|
||||
*/
|
||||
@@ -272,13 +289,16 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp,
|
||||
true);
|
||||
syncpub2 = pub + ttlsig +
|
||||
dns_kasp_publishsafety(kasp) +
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
}
|
||||
|
||||
syncpub = ISC_MAX(syncpub1, syncpub2);
|
||||
dst_key_settime(key->key, DST_TIME_SYNCPUBLISH,
|
||||
syncpub);
|
||||
if (klifetime > 0) {
|
||||
dst_key_settime(key->key, DST_TIME_SYNCDELETE,
|
||||
(syncpub + klifetime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,13 +311,6 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
|
||||
ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
uint32_t klifetime = 0;
|
||||
|
||||
ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
klifetime = lifetime;
|
||||
}
|
||||
if (klifetime == 0) {
|
||||
/*
|
||||
* No inactive time and no lifetime,
|
||||
@@ -398,7 +411,7 @@ keymgr_key_update_lifetime(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
/* Initialize lifetime. */
|
||||
if (r != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
return;
|
||||
l = lifetime - 1;
|
||||
}
|
||||
/* Skip keys that are still hidden or already retiring. */
|
||||
if (g != OMNIPRESENT) {
|
||||
@@ -420,6 +433,7 @@ keymgr_key_update_lifetime(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
} else {
|
||||
dst_key_unsettime(key->key, DST_TIME_INACTIVE);
|
||||
dst_key_unsettime(key->key, DST_TIME_DELETE);
|
||||
dst_key_unsettime(key->key, DST_TIME_SYNCDELETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1286,6 +1300,7 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
isc_result_t ret;
|
||||
isc_stdtime_t lastchange, dstime, nexttime = now;
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
||||
uint32_t dsstate;
|
||||
|
||||
/*
|
||||
* No need to wait if we move things into an uncertain state.
|
||||
@@ -1355,15 +1370,12 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
* records. This translates to:
|
||||
*
|
||||
* Dsgn + zone-propagation-delay + max-zone-ttl.
|
||||
*
|
||||
* We will also add the retire-safety interval.
|
||||
*/
|
||||
nexttime = lastchange + ttlsig +
|
||||
dns_kasp_zonepropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
/*
|
||||
* Only add the sign delay Dsgn if there is an actual
|
||||
* predecessor or successor key.
|
||||
* Only add the sign delay Dsgn and retire-safety if
|
||||
* there is an actual predecessor or successor key.
|
||||
*/
|
||||
uint32_t tag;
|
||||
ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
|
||||
@@ -1373,7 +1385,8 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
DST_NUM_SUCCESSOR, &tag);
|
||||
}
|
||||
if (ret == ISC_R_SUCCESS) {
|
||||
nexttime += dns_kasp_signdelay(kasp);
|
||||
nexttime += dns_kasp_signdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1399,35 +1412,36 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
* This translates to:
|
||||
*
|
||||
* parent-propagation-delay + parent-ds-ttl.
|
||||
*
|
||||
* We will also add the retire-safety interval.
|
||||
*/
|
||||
case OMNIPRESENT:
|
||||
/* Make sure DS has been seen in the parent. */
|
||||
ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
|
||||
&dstime);
|
||||
if (ret != ISC_R_SUCCESS || dstime > now) {
|
||||
/* Not yet, try again in an hour. */
|
||||
nexttime = now + 3600;
|
||||
} else {
|
||||
nexttime =
|
||||
dstime + dns_kasp_dsttl(kasp) +
|
||||
dns_kasp_parentpropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
break;
|
||||
case HIDDEN:
|
||||
/* Make sure DS has been withdrawn from the parent. */
|
||||
ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
|
||||
&dstime);
|
||||
/* Make sure DS has been seen in/withdrawn from the
|
||||
* parent. */
|
||||
dsstate = next_state == HIDDEN ? DST_TIME_DSDELETE
|
||||
: DST_TIME_DSPUBLISH;
|
||||
ret = dst_key_gettime(key->key, dsstate, &dstime);
|
||||
if (ret != ISC_R_SUCCESS || dstime > now) {
|
||||
/* Not yet, try again in an hour. */
|
||||
nexttime = now + 3600;
|
||||
} else {
|
||||
nexttime =
|
||||
dstime + dns_kasp_dsttl(kasp) +
|
||||
dns_kasp_parentpropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
dns_kasp_parentpropagationdelay(kasp);
|
||||
/*
|
||||
* Only add the retire-safety if there is an
|
||||
* actual predecessor or successor key.
|
||||
*/
|
||||
uint32_t tag;
|
||||
ret = dst_key_getnum(key->key,
|
||||
DST_NUM_PREDECESSOR, &tag);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
ret = dst_key_getnum(key->key,
|
||||
DST_NUM_SUCCESSOR,
|
||||
&tag);
|
||||
}
|
||||
if (ret == ISC_R_SUCCESS) {
|
||||
nexttime += dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1763,7 +1777,9 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
|
||||
if (prepub == 0 || prepub > now) {
|
||||
/* No need to start rollover now. */
|
||||
if (*nexttime == 0 || prepub < *nexttime) {
|
||||
*nexttime = prepub;
|
||||
if (prepub > 0) {
|
||||
*nexttime = prepub;
|
||||
}
|
||||
}
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
@@ -2022,6 +2038,20 @@ keymgr_purge_keyfile(dst_key_t *key, int type) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
dst_key_doublematch(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
|
||||
int matches = 0;
|
||||
|
||||
for (dns_kasp_key_t *kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp));
|
||||
kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link))
|
||||
{
|
||||
if (dns_kasp_key_match(kkey, key)) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
return matches > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine 'keys' and match 'kasp' policy.
|
||||
*
|
||||
@@ -2161,6 +2191,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
|
||||
* matches the kasp policy.
|
||||
*/
|
||||
if (!dst_key_is_unused(dkey->key) &&
|
||||
!dst_key_doublematch(dkey, kasp) &&
|
||||
(dst_key_goal(dkey->key) ==
|
||||
OMNIPRESENT) &&
|
||||
!keymgr_dep(dkey->key, keyring,
|
||||
|
||||
Reference in New Issue
Block a user