ipatests: dnssec: Add alternative approach for checking chain of trust

drill is currently broken on F34. Fortunately, there are another
tools for checking DNSSEC trust. One of them is `delv`:

> delv is a tool for sending DNS queries and validating the results,
using the same internal resolver and validator logic as named.

delv sends to a specified name server all queries needed to fetch and
validate the requested data; this includes the original requested query,
subsequent queries to follow CNAME or DNAME chains, queries for DNSKEY,
and DS records to establish a chain of trust for DNSSEC validation. It
does not perform iterative resolution, but simulates the behavior of a
name server configured for DNSSEC validating and forwarding.

Related: https://pagure.io/freeipa/issue/8793
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Stanislav Levin 2021-05-19 00:51:36 +03:00 committed by Alexander Bokovoy
parent 611b49e42b
commit 26ee44bcfd

View File

@ -4,17 +4,22 @@
from __future__ import absolute_import
import base64
import logging
import re
import subprocess
import time
import textwrap
import dns.dnssec
import dns.name
import pytest
import yaml
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks
from ipatests.pytest_ipa.integration.firewall import Firewall
from ipaplatform.tasks import tasks as platform_tasks
from ipaplatform.paths import paths
from ipapython.dnsutil import DNSResolver
@ -350,11 +355,7 @@ class TestInstallDNSSECFirst(IntegrationTest):
self.replicas[0].ip, root_zone, timeout=300
), "Zone %s is not signed (replica)" % root_zone
def test_chain_of_trust(self):
"""
Validate signed DNS records, using our own signed root zone
:return:
"""
def test_delegation(self):
dnszone_add_dnssec(self.master, example_test_zone)
# delegation
@ -419,6 +420,10 @@ class TestInstallDNSSECFirst(IntegrationTest):
rtype="DS"
), "No DS record of '%s' returned from replica" % example_test_zone
def test_chain_of_trust_drill(self):
"""
Validate signed DNS records, using our own signed root zone
"""
# extract DSKEY from root zone
ans = resolve_with_dnssec(self.master.ip, root_zone,
rtype="DNSKEY")
@ -462,6 +467,100 @@ class TestInstallDNSSECFirst(IntegrationTest):
self.master.run_command(args)
self.replicas[0].run_command(args)
def test_chain_of_trust_delv(self):
"""
Validate signed DNS records, using our own signed root zone
"""
INITIAL_KEY_FMT = '%s initial-key %d %d %d "%s";'
# delv reports its version on stderr
delv_version = self.master.run_command(
["delv", "-v"]
).stderr_text.rstrip().replace("delv ", "")
assert delv_version
delv_version_parsed = platform_tasks.parse_ipa_version(delv_version)
if delv_version_parsed < platform_tasks.parse_ipa_version("9.16"):
pytest.skip(
f"Requires delv >= 9.16(+yaml), installed: '{delv_version}'"
)
# extract DSKEY from root zone
ans = resolve_with_dnssec(
self.master.ip, root_zone, rtype="DNSKEY"
)
dnskey_rrset = ans.response.get_rrset(
ans.response.answer,
dns.name.from_text(root_zone),
dns.rdataclass.IN,
dns.rdatatype.DNSKEY,
)
assert dnskey_rrset, "No DNSKEY records received"
# export trust keys for root zone
initial_keys = []
for key_rdata in dnskey_rrset:
if key_rdata.flags != 257:
continue # it is not KSK
initial_keys.append(
INITIAL_KEY_FMT % (
root_zone,
key_rdata.flags,
key_rdata.protocol,
key_rdata.algorithm,
base64.b64encode(key_rdata.key).decode("utf-8"),
)
)
assert initial_keys, "No KSK returned from the root zone"
trust_anchors = textwrap.dedent(
"""\
trust-anchors {{
{initial_key}
}};
"""
).format(initial_key="\n".join(initial_keys))
logger.debug("Root zone trust-anchors: %s", trust_anchors)
# set trusted anchor for our root zone
for host in [self.master, self.replicas[0]]:
host.put_file_contents(paths.DNSSEC_TRUSTED_KEY, trust_anchors)
# verify signatures
args = [
"delv",
"+yaml",
"+nosplit",
"+vtrace",
"@127.0.0.1",
example_test_zone,
"-a",
paths.DNSSEC_TRUSTED_KEY,
"SOA",
]
# delv puts trace info on stderr
for host in [self.master, self.replicas[0]]:
result = host.run_command(args)
yaml_data = yaml.safe_load(result.stdout_text)
query_name_abs = dns.name.from_text(example_test_zone)
root_zone_name = dns.name.from_text(root_zone)
query_name_rel = query_name_abs.relativize(
root_zone_name
).to_text()
assert yaml_data["query_name"] == query_name_rel
assert yaml_data["status"] == "success"
assert len(yaml_data["records"]) == 1
fully_validated = yaml_data["records"][0]["fully_validated"]
fully_validated.sort()
assert len(fully_validated) == 2
assert f"{example_test_zone} 1 IN RRSIG SOA" in fully_validated[0]
assert f"{example_test_zone} 1 IN SOA" in fully_validated[1]
def test_servers_use_localhost_as_dns(self):
# check that localhost is set as DNS server
for host in [self.master, self.replicas[0]]: