Isolate rollover-going-insecure test case

This commit is contained in:
Nicki Křížek 2025-06-05 17:51:40 +02:00
parent 8503a218c3
commit 7001056eab
13 changed files with 328 additions and 151 deletions

View file

@ -548,7 +548,7 @@ vulture:
<<: *precheck_job
needs: []
script:
- vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark" bin/tests/system/
- vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark,reconfigure_policy" bin/tests/system/
ci-variables:
<<: *precheck_job

View file

@ -0,0 +1 @@
../rollover/common.py

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
dnssec-policy "unsigning" {
dnskey-ttl 7200;
keys {
ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
};
};

View file

@ -0,0 +1 @@
../../rollover-dynamic2inline/ns6/named.common.conf.j2

View file

@ -0,0 +1,49 @@
/*
* 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.
*/
{% set policy = policy | default("unsigning") %}
include "kasp.conf";
include "named.common.conf";
zone "step1.going-insecure.kasp" {
type primary;
file "step1.going-insecure.kasp.db";
dnssec-policy @policy@;
};
{% if policy == "insecure" %}
zone "step2.going-insecure.kasp" {
type primary;
file "step2.going-insecure.kasp.db";
dnssec-policy insecure;
};
{% endif %}
zone "step1.going-insecure-dynamic.kasp" {
type primary;
file "step1.going-insecure-dynamic.kasp.db";
dnssec-policy @policy@;
inline-signing no;
allow-update { any; };
};
{% if policy == "insecure" %}
zone "step2.going-insecure-dynamic.kasp" {
type primary;
file "step2.going-insecure-dynamic.kasp.db";
dnssec-policy insecure;
inline-signing no;
allow-update { any; };
};
{% endif %}

View file

@ -0,0 +1 @@
../../rollover-dynamic2inline/ns6/template.db.in

View file

@ -0,0 +1,71 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
cd "ns6"
setup() {
zone="$1"
echo_i "setting up zone: $zone"
zonefile="${zone}.db"
infile="${zone}.db.infile"
}
# Make lines shorter by storing key states in environment variables.
H="HIDDEN"
R="RUMOURED"
O="OMNIPRESENT"
U="UNRETENTIVE"
# The child zones (step1, step2) beneath these zones represent the various
# steps of unsigning a zone.
for zn in going-insecure.kasp going-insecure-dynamic.kasp; do
# Step 1:
# Set up a zone with dnssec-policy that is going insecure.
setup step1.$zn
echo "$zone" >>zones
T="now-10d"
S="now-12955mi"
keytimes="-P $T -A $T"
cdstimes="-P sync $S"
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $keytimes $cdstimes $zone 2>keygen.out.$zone.1)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $keytimes $zone 2>keygen.out.$zone.2)
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 2:
# Set up a zone with dnssec-policy that is going insecure. Don't add
# this zone to the zones file, because this zone is no longer expected
# to be fully signed.
setup step2.$zn
# The DS was withdrawn from the parent zone 26 hours ago.
D="now-26h"
keytimes="-P $T -A $T -I $D -D now"
cdstimes="-P sync $S -D sync $D"
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $keytimes $cdstimes $zone 2>keygen.out.$zone.1)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $keytimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $O $T -r $O $T -d $U $D -D ds $D "$KSK" >settime.out.$zone.1 2>&1
$SETTIME -s -g $H -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${KSK}.state"
echo "Lifetime: 5184000" >>"${ZSK}.state"
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
done

View file

@ -0,0 +1,46 @@
# 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.
# pylint: disable=redefined-outer-name,unused-import
import pytest
import isctest
from common import (
pytestmark,
alg,
size,
CDSS,
DURATION,
UNSIGNING_CONFIG,
)
@pytest.mark.parametrize(
"zone",
[
"going-insecure.kasp",
"going-insecure-dynamic.kasp",
],
)
def test_going_insecure_initial(zone, servers, alg, size):
config = UNSIGNING_CONFIG
policy = "unsigning"
step = {
"zone": f"step1.{zone}",
"cdss": CDSS,
"keyprops": [
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}",
f"zsk {DURATION['P60D']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}",
],
"nextev": None,
}
isctest.kasp.check_rollover_step(servers["ns6"], config, policy, step)

View file

@ -0,0 +1,93 @@
# 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.
# pylint: disable=redefined-outer-name,unused-import
import pytest
import isctest
from common import (
pytestmark,
alg,
size,
CDSS,
DEFAULT_CONFIG,
DURATION,
UNSIGNING_CONFIG,
)
@pytest.fixture(scope="module", autouse=True)
def reconfigure_policy(servers, templates):
templates.render("ns6/named.conf", {"policy": "insecure"})
servers["ns6"].reconfigure()
@pytest.mark.parametrize(
"zone",
[
"going-insecure.kasp",
"going-insecure-dynamic.kasp",
],
)
def test_going_insecure_reconfig_step1(zone, alg, size, servers):
config = DEFAULT_CONFIG
policy = "insecure"
# Key goal states should be HIDDEN.
# The DS may be removed if we are going insecure.
step = {
"zone": f"step1.{zone}",
"cdss": CDSS,
"keyprops": [
f"ksk 0 {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{-DURATION['P10D']}",
f"zsk {DURATION['P60D']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}",
],
# Next key event is when the DS becomes HIDDEN. This
# happens after the# parent propagation delay plus DS TTL.
"nextev": DEFAULT_CONFIG["ds-ttl"] + DEFAULT_CONFIG["parent-propagation-delay"],
# Going insecure, check for CDS/CDNSKEY DELETE, and skip key timing checks.
"cds-delete": True,
"check-keytimes": False,
}
isctest.kasp.check_rollover_step(servers["ns6"], config, policy, step)
@pytest.mark.parametrize(
"zone",
[
"going-insecure.kasp",
"going-insecure-dynamic.kasp",
],
)
def test_going_insecure_reconfig_step2(zone, alg, size, servers):
config = DEFAULT_CONFIG
policy = "insecure"
# The DS is long enough removed from the zone to be considered
# HIDDEN. This means the DNSKEY and the KSK signatures can be
# removed.
step = {
"zone": f"step2.{zone}",
"cdss": CDSS,
"keyprops": [
f"ksk 0 {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{-DURATION['P10D']}",
f"zsk {DURATION['P60D']} {alg} {size} goal:hidden dnskey:unretentive zrrsig:unretentive offset:{-DURATION['P10D']}",
],
# Next key event is when the DNSKEY becomes HIDDEN.
# This happens after the propagation delay, plus DNSKEY TTL.
"nextev": UNSIGNING_CONFIG["dnskey-ttl"]
+ DEFAULT_CONFIG["zone-propagation-delay"],
# Zone is no longer signed.
"zone-signed": False,
"check-keytimes": False,
}
isctest.kasp.check_rollover_step(servers["ns6"], config, policy, step)

View file

@ -9,6 +9,9 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
from datetime import timedelta
import os
import pytest
pytestmark = pytest.mark.extra_artifacts(
@ -34,3 +37,44 @@ pytestmark = pytest.mark.extra_artifacts(
"ns*/zones",
]
)
TIMEDELTA = {
"PT5M": timedelta(minutes=5),
"PT1H": timedelta(hours=1),
"PT2H": timedelta(hours=2),
"P1D": timedelta(days=1),
"P5D": timedelta(days=5),
"P10D": timedelta(days=10),
"P14D": timedelta(days=14),
"P60D": timedelta(days=60),
"P90D": timedelta(days=90),
"P6M": timedelta(days=31 * 6),
"P1Y": timedelta(days=365),
}
DURATION = {isoname: int(delta.total_seconds()) for isoname, delta in TIMEDELTA.items()}
CDSS = ["CDNSKEY", "CDS (SHA-256)"]
DEFAULT_CONFIG = {
"dnskey-ttl": TIMEDELTA["PT1H"],
"ds-ttl": TIMEDELTA["P1D"],
"max-zone-ttl": TIMEDELTA["P1D"],
"parent-propagation-delay": TIMEDELTA["PT1H"],
"publish-safety": TIMEDELTA["PT1H"],
"purge-keys": TIMEDELTA["P90D"],
"retire-safety": TIMEDELTA["PT1H"],
"signatures-refresh": TIMEDELTA["P5D"],
"signatures-validity": TIMEDELTA["P14D"],
"zone-propagation-delay": TIMEDELTA["PT5M"],
}
UNSIGNING_CONFIG = DEFAULT_CONFIG.copy()
UNSIGNING_CONFIG["dnskey-ttl"] = TIMEDELTA["PT2H"]
@pytest.fixture
def alg():
return os.environ["DEFAULT_ALGORITHM_NUMBER"]
@pytest.fixture
def size():
return os.environ["DEFAULT_BITS"]

View file

@ -50,41 +50,6 @@ zone unlimit-lifetime {
dnssec-policy @_policy@;
};
/* Zones for testing going insecure. */
{% set _policy = "unsigning" if not csk_roll else "insecure" %}
zone "step1.going-insecure.kasp" {
type primary;
file "step1.going-insecure.kasp.db";
dnssec-policy @_policy@;
};
{% if csk_roll %} // TODO maybe omit?
zone "step2.going-insecure.kasp" {
type primary;
file "step2.going-insecure.kasp.db";
dnssec-policy "insecure";
};
{% endif %}
{% set _policy = "unsigning" if not csk_roll else "insecure" %}
zone "step1.going-insecure-dynamic.kasp" {
type primary;
file "step1.going-insecure-dynamic.kasp.db";
dnssec-policy @_policy@;
inline-signing no;
allow-update { any; };
};
{% if csk_roll %} // TODO maybe omit?
zone "step2.going-insecure-dynamic.kasp" {
type primary;
file "step2.going-insecure-dynamic.kasp.db";
dnssec-policy insecure;
inline-signing no;
allow-update { any; };
};
{% endif %}
{% set _policy = "default" if not csk_roll else "none" %}
zone "step1.going-straight-to-none.kasp" {
type primary;

View file

@ -35,48 +35,6 @@ for zn in shorter-lifetime longer-lifetime limit-lifetime \
cp template.db.in $zonefile
done
# The child zones (step1, step2) beneath these zones represent the various
# steps of unsigning a zone.
for zn in going-insecure.kasp going-insecure-dynamic.kasp; do
# Step 1:
# Set up a zone with dnssec-policy that is going insecure.
setup step1.$zn
echo "$zone" >>zones
T="now-10d"
S="now-12955mi"
keytimes="-P $T -A $T"
cdstimes="-P sync $S"
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $keytimes $cdstimes $zone 2>keygen.out.$zone.1)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $keytimes $zone 2>keygen.out.$zone.2)
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 2:
# Set up a zone with dnssec-policy that is going insecure. Don't add
# this zone to the zones file, because this zone is no longer expected
# to be fully signed.
setup step2.$zn
# The DS was withdrawn from the parent zone 26 hours ago.
D="now-26h"
keytimes="-P $T -A $T -I $D -D now"
cdstimes="-P sync $S -D sync $D"
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $keytimes $cdstimes $zone 2>keygen.out.$zone.1)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $keytimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $O $T -r $O $T -d $U $D -D ds $D "$KSK" >settime.out.$zone.1 2>&1
$SETTIME -s -g $H -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${KSK}.state"
echo "Lifetime: 5184000" >>"${ZSK}.state"
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
done
# These zones are going straight to "none" policy. This is undefined behavior.
T="now-10d"
S="now-12955mi"

View file

@ -1203,28 +1203,6 @@ def test_rollover_policy_changes(servers, templates):
}
steps.append(step)
# Test going insecure.
isctest.log.info("check going insecure")
offset = -timedelta(days=10)
offval = int(offset.total_seconds())
zones = [
"step1.going-insecure.kasp",
"step1.going-insecure-dynamic.kasp",
]
for zone in zones:
step = {
"zone": zone,
"cdss": cdss,
"config": unsigning_config,
"policy": "unsigning",
"keyprops": [
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offval}",
f"zsk {lifetime['P60D']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
],
"nextev": None,
}
steps.append(step)
# Test going straight to none.
isctest.log.info("check going straight to none")
zones = [
@ -1322,57 +1300,6 @@ def test_rollover_policy_changes(servers, templates):
}
steps.append(step)
# Test going insecure (after reconfig).
isctest.log.info("check going insecure (after reconfig)")
oldttl = unsigning_config["dnskey-ttl"]
offset = -timedelta(days=10)
offval = int(offset.total_seconds())
zones = ["going-insecure.kasp", "going-insecure-dynamic.kasp"]
for parent in zones:
# Step 1.
# Key goal states should be HIDDEN.
# The DS may be removed if we are going insecure.
step = {
"zone": f"step1.{parent}",
"cdss": cdss,
"config": default_config,
"policy": "insecure",
"keyprops": [
f"ksk 0 {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offval}",
f"zsk {lifetime['P60D']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
],
# Next key event is when the DS becomes HIDDEN. This
# happens after the# parent propagation delay plus DS TTL.
"nextev": default_config["ds-ttl"]
+ default_config["parent-propagation-delay"],
# Going insecure, check for CDS/CDNSKEY DELETE, and skip key timing checks.
"cds-delete": True,
"check-keytimes": False,
}
steps.append(step)
# Step 2.
# The DS is long enough removed from the zone to be considered
# HIDDEN. This means the DNSKEY and the KSK signatures can be
# removed.
step = {
"zone": f"step2.{parent}",
"cdss": cdss,
"config": default_config,
"policy": "insecure",
"keyprops": [
f"ksk 0 {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{offval}",
f"zsk {lifetime['P60D']} {alg} {size} goal:hidden dnskey:unretentive zrrsig:unretentive offset:{offval}",
],
# Next key event is when the DNSKEY becomes HIDDEN.
# This happens after the propagation delay, plus DNSKEY TTL.
"nextev": oldttl + default_config["zone-propagation-delay"],
# Zone is no longer signed.
"zone-signed": False,
"check-keytimes": False,
}
steps.append(step)
# Test going straight to none.
isctest.log.info("check going straight to none (after reconfig)")
zones = [