]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Convert kasp dnssectools tests to pytest
authorMatthijs Mekking <matthijs@isc.org>
Fri, 14 Mar 2025 11:19:36 +0000 (12:19 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Thu, 17 Apr 2025 11:50:49 +0000 (13:50 +0200)
Convert the first couple of tests from 'kasp/tests.sh' to
'kasp/tests_kasp.py', those are test cases related to 'dnssec-keygen'
and 'dnssec-settime'.

For this, we also add a new KeyProperties method,
'policy_to_properties', that takes a list of strings which represent
the keys according to the dnssec-policy and the expected key states.

bin/tests/system/isctest/kasp.py
bin/tests/system/kasp/tests.sh
bin/tests/system/kasp/tests_kasp.py [new file with mode: 0644]

index 1b82baf4169f42e68260b421ac22687571f8c973..e4b3ead5cc811aa69261656c923136812df5ad1d 100644 (file)
@@ -1101,3 +1101,72 @@ def keydir_to_keylist(
 
 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])
+
+        for i in range(4, 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]}"
+
+        proplist.append(keyprop)
+
+    return proplist
index fa64c69f589a49c693c8af0bc55f48343449cd82..86b0bfbcd71d02afd2d1137f20b445d97ab154ef 100644 (file)
@@ -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
 #
diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py
new file mode 100644 (file)
index 0000000..55dbf12
--- /dev/null
@@ -0,0 +1,264 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import os
+import shutil
+
+from datetime import timedelta
+
+import pytest
+
+import isctest
+from isctest.kasp import (
+    KeyProperties,
+    KeyTimingMetadata,
+)
+
+pytestmark = pytest.mark.extra_artifacts(
+    [
+        "K*.private",
+        "K*.backup",
+        "K*.cmp",
+        "K*.key",
+        "K*.state",
+        "*.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*.private",
+        "ns*/K*.key",
+        "ns*/K*.state",
+        "ns*/*.db",
+        "ns*/*.db.infile",
+        "ns*/*.db.signed",
+        "ns*/*.jbk",
+        "ns*/*.jnl",
+        "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",
+        "ns*/*.zsk1",
+        "ns*/*.zsk2",
+        "ns3/legacy-keys.*",
+        "ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
+    ]
+)
+
+
+def test_kasp_dnssec_keygen():
+    def keygen(zone, policy, keydir=None):
+        if keydir is None:
+            keydir = "."
+
+        keygen_command = [
+            os.environ.get("KEYGEN"),
+            "-K",
+            keydir,
+            "-k",
+            policy,
+            "-l",
+            "kasp.conf",
+            zone,
+        ]
+
+        return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
+
+    # check that 'dnssec-keygen -k' (configured policy) creates valid files.
+    lifetime = {
+        "P1Y": int(timedelta(days=365).total_seconds()),
+        "P30D": int(timedelta(days=30).total_seconds()),
+        "P6M": int(timedelta(days=31*6).total_seconds()),
+    }
+    keyprops = [
+        f"csk {lifetime['P1Y']} 13 256",
+        f"ksk {lifetime['P1Y']} 8 2048",
+        f"zsk {lifetime['P30D']} 8 2048",
+        f"zsk {lifetime['P6M']} 8 3072",
+    ]
+    keydir="keys"
+    out = keygen("kasp", "kasp", keydir)
+    keys = isctest.kasp.keystr_to_keylist(out, keydir)
+    expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)
+    isctest.kasp.check_keys("kasp", keys, expected)
+
+    # check that 'dnssec-keygen -k' (default policy) creates valid files.
+    keyprops = ["csk 0 13 256"]
+    out = keygen("kasp", "default")
+    keys = isctest.kasp.keystr_to_keylist(out)
+    expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+    isctest.kasp.check_keys("kasp", keys, expected)
+
+    # check that 'dnssec-settime' by default does not edit key state file.
+    key = keys[0]
+    shutil.copyfile(key.privatefile, f"{key.privatefile}.backup")
+    shutil.copyfile(key.keyfile, f"{key.keyfile}.backup")
+    shutil.copyfile(key.statefile, f"{key.statefile}.backup")
+
+    created = key.get_timing("Created")
+    publish = key.get_timing("Publish") + timedelta(hours=1)
+    settime = [
+        os.environ.get("SETTIME"),
+        "-P",
+        str(publish),
+        key.path,
+    ]
+    out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+
+    isctest.check.file_contents_equal(f"{key.statefile}", f"{key.statefile}.backup")
+    assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
+    assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
+
+    # check that 'dnssec-settime -s' also sets publish time metadata and
+    # states in key state file.
+    now = KeyTimingMetadata.now()
+    goal = "omnipresent"
+    dnskey = "rumoured"
+    krrsig = "rumoured"
+    zrrsig = "omnipresent"
+    ds = "hidden"
+    keyprops = [
+        f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
+    ]
+    expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+    expected[0].timing = {
+        "Generated": created,
+        "Published": now,
+        "Active": created,
+        "DNSKEYChange": now,
+        "KRRSIGChange": now,
+        "ZRRSIGChange": now,
+        "DSChange": now,
+    }
+
+    settime = [
+        os.environ.get("SETTIME"),
+        "-s",
+        "-P",
+        str(now),
+        "-g",
+        goal,
+        "-k",
+        dnskey,
+        str(now),
+        "-r",
+        krrsig,
+        str(now),
+        "-z",
+        zrrsig,
+        str(now),
+        "-d",
+        ds,
+        str(now),
+        key.path,
+    ]
+    out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+    isctest.kasp.check_keys("kasp", keys, expected)
+    isctest.kasp.check_keytimes(keys, expected)
+
+    # check that 'dnssec-settime -s' also unsets publish time metadata and
+    # states in key state file.
+    now = KeyTimingMetadata.now()
+    keyprops = ["csk 0 13 256"]
+    expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+    expected[0].timing = {
+        "Generated": created,
+        "Active": created,
+    }
+
+    settime = [
+        os.environ.get("SETTIME"),
+        "-s",
+        "-P",
+        "none",
+        "-g",
+        "none",
+        "-k",
+        "none",
+        str(now),
+        "-z",
+        "none",
+        str(now),
+        "-r",
+        "none",
+        str(now),
+        "-d",
+        "none",
+        str(now),
+        key.path,
+    ]
+    out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+    isctest.kasp.check_keys("kasp", keys, expected)
+    isctest.kasp.check_keytimes(keys, expected)
+
+    # check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
+    soon = now + timedelta(hours=2)
+    goal = "hidden"
+    dnskey = "unretentive"
+    krrsig = "omnipresent"
+    zrrsig = "unretentive"
+    ds = "omnipresent"
+    keyprops = [
+        f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
+    ]
+    expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+    expected[0].timing = {
+        "Generated": created,
+        "Active": soon,
+        "DNSKEYChange": soon,
+        "KRRSIGChange": soon,
+        "ZRRSIGChange": soon,
+        "DSChange": soon,
+    }
+
+    settime = [
+        os.environ.get("SETTIME"),
+        "-s",
+        "-A",
+        str(soon),
+        "-g",
+        "HIDDEN",
+        "-k",
+        "UNRETENTIVE",
+        str(soon),
+        "-z",
+        "UNRETENTIVE",
+        str(soon),
+        "-r",
+        "OMNIPRESENT",
+        str(soon),
+        "-d",
+        "OMNIPRESENT",
+        str(soon),
+        key.path,
+    ]
+    out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+    isctest.kasp.check_keys("kasp", keys, expected)
+    isctest.kasp.check_keytimes(keys, expected)