From 5c53ba0899ff451bd87608254ea238656e9e4fb8 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 8 Feb 2024 19:23:50 +0100 Subject: [PATCH] make `Client::delv` work & use it in dnssec tests --- .../src/resolver/dns/scenarios.rs | 4 +- .../src/resolver/dnssec/scenarios.rs | 27 ++++++---- packages/dns-test/src/client.rs | 17 +++++-- packages/dns-test/src/container.rs | 12 +++-- packages/dns-test/src/lib.rs | 2 + packages/dns-test/src/recursive_resolver.rs | 16 +++--- packages/dns-test/src/trust_anchor.rs | 51 +++++++++++++++++++ packages/dns-test/src/zone_file.rs | 14 +++++ 8 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 packages/dns-test/src/trust_anchor.rs diff --git a/packages/conformance-tests/src/resolver/dns/scenarios.rs b/packages/conformance-tests/src/resolver/dns/scenarios.rs index d76e9aee..e229d1b8 100644 --- a/packages/conformance-tests/src/resolver/dns/scenarios.rs +++ b/packages/conformance-tests/src/resolver/dns/scenarios.rs @@ -4,7 +4,7 @@ use dns_test::client::{Client, Dnssec, Recurse}; use dns_test::name_server::NameServer; use dns_test::record::RecordType; use dns_test::zone_file::Root; -use dns_test::{RecursiveResolver, Result, FQDN}; +use dns_test::{RecursiveResolver, Result, TrustAnchor, FQDN}; #[test] fn can_resolve() -> Result<()> { @@ -38,7 +38,7 @@ fn can_resolve() -> Result<()> { eprintln!("root.zone:\n{}", root_ns.zone_file()); let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; - let resolver = RecursiveResolver::start(roots, &[])?; + let resolver = RecursiveResolver::start(roots, &TrustAnchor::empty())?; let resolver_ip_addr = resolver.ipv4_addr(); let client = Client::new()?; diff --git a/packages/conformance-tests/src/resolver/dnssec/scenarios.rs b/packages/conformance-tests/src/resolver/dnssec/scenarios.rs index 62edb1f7..2a9d0846 100644 --- a/packages/conformance-tests/src/resolver/dnssec/scenarios.rs +++ b/packages/conformance-tests/src/resolver/dnssec/scenarios.rs @@ -4,7 +4,7 @@ use dns_test::client::{Client, Dnssec, Recurse}; use dns_test::name_server::NameServer; use dns_test::record::RecordType; use dns_test::zone_file::Root; -use dns_test::{RecursiveResolver, Result, FQDN}; +use dns_test::{RecursiveResolver, Result, TrustAnchor, FQDN}; // no DS records are involved; this is a single-link chain of trust #[test] @@ -24,7 +24,7 @@ fn can_validate_without_delegation() -> Result<()> { let roots = &[Root::new(ns.fqdn().clone(), ns.ipv4_addr())]; - let trust_anchor = [root_ksk.clone(), root_zsk.clone()]; + let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); let resolver = RecursiveResolver::start(roots, &trust_anchor)?; let resolver_addr = resolver.ipv4_addr(); @@ -40,13 +40,16 @@ fn can_validate_without_delegation() -> Result<()> { assert!(output.status.is_noerror()); assert!(output.flags.authenticated_data); + let output = client.delv(resolver_addr, RecordType::SOA, &FQDN::ROOT, &trust_anchor)?; + assert!(output.starts_with("; fully validated")); + Ok(()) } #[test] fn can_validate_with_delegation() -> Result<()> { let expected_ipv4_addr = Ipv4Addr::new(1, 2, 3, 4); - let needle = FQDN("example.nameservers.com.")?; + let needle_fqdn = FQDN("example.nameservers.com.")?; let mut root_ns = NameServer::new(FQDN::ROOT)?; let mut com_ns = NameServer::new(FQDN::COM)?; @@ -55,7 +58,7 @@ fn can_validate_with_delegation() -> Result<()> { nameservers_ns .a(root_ns.fqdn().clone(), root_ns.ipv4_addr()) .a(com_ns.fqdn().clone(), com_ns.ipv4_addr()) - .a(needle.clone(), expected_ipv4_addr); + .a(needle_fqdn.clone(), expected_ipv4_addr); let nameservers_ns = nameservers_ns.sign()?; let nameservers_ds = nameservers_ns.ds().clone(); let nameservers_ns = nameservers_ns.start()?; @@ -90,20 +93,19 @@ fn can_validate_with_delegation() -> Result<()> { let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; - let resolver = RecursiveResolver::start(roots, &[root_ksk.clone(), root_zsk.clone()])?; - let resolver_ip_addr = resolver.ipv4_addr(); + let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); + let resolver = RecursiveResolver::start(roots, &trust_anchor)?; + let resolver_addr = resolver.ipv4_addr(); let client = Client::new()?; let output = client.dig( Recurse::Yes, Dnssec::Yes, - resolver_ip_addr, + resolver_addr, RecordType::A, - &needle, + &needle_fqdn, )?; - drop(resolver); - assert!(output.status.is_noerror()); assert!(output.flags.authenticated_data); @@ -111,8 +113,11 @@ fn can_validate_with_delegation() -> Result<()> { let [a, _rrsig] = output.answer.try_into().unwrap(); let a = a.try_into_a().unwrap(); - assert_eq!(needle, a.fqdn); + assert_eq!(needle_fqdn, a.fqdn); assert_eq!(expected_ipv4_addr, a.ipv4_addr); + let output = client.delv(resolver_addr, RecordType::A, &needle_fqdn, &trust_anchor)?; + assert!(output.starts_with("; fully validated")); + Ok(()) } diff --git a/packages/dns-test/src/client.rs b/packages/dns-test/src/client.rs index 1902ac7d..d7b90144 100644 --- a/packages/dns-test/src/client.rs +++ b/packages/dns-test/src/client.rs @@ -3,6 +3,7 @@ use std::net::Ipv4Addr; use crate::container::Container; use crate::record::{Record, RecordType}; +use crate::trust_anchor::TrustAnchor; use crate::{Error, Result, FQDN}; pub struct Client { @@ -16,19 +17,29 @@ impl Client { }) } - // FIXME this needs to use the same trust anchor as `RecursiveResolver` or validation will fail pub fn delv( &self, server: Ipv4Addr, record_type: RecordType, fqdn: &FQDN<'_>, + trust_anchor: &TrustAnchor, ) -> Result { + const TRUST_ANCHOR_PATH: &str = "/etc/bind.keys"; + + assert!( + !trust_anchor.is_empty(), + "`delv` cannot be used with an empty trust anchor" + ); + + self.inner.cp(TRUST_ANCHOR_PATH, &trust_anchor.delv())?; + self.inner.stdout(&[ "delv", - "+mtrace", &format!("@{server}"), - record_type.as_str(), + "-a", + TRUST_ANCHOR_PATH, fqdn.as_str(), + record_type.as_str(), ]) } diff --git a/packages/dns-test/src/container.rs b/packages/dns-test/src/container.rs index 50bd12ff..545c7385 100644 --- a/packages/dns-test/src/container.rs +++ b/packages/dns-test/src/container.rs @@ -102,11 +102,17 @@ impl Container { /// Similar to `Self::output` but checks `command_and_args` ran successfully and only /// returns the stdout pub fn stdout(&self, command_and_args: &[&str]) -> Result { - let output = self.output(command_and_args)?; + let Output { + status, + stderr, + stdout, + } = self.output(command_and_args)?; - if output.status.success() { - Ok(output.stdout) + if status.success() { + Ok(stdout) } else { + eprintln!("STDOUT:\n{stdout}\nSTDERR:\n{stderr}"); + Err(format!("[{}] `{command_and_args:?}` failed", self.inner.name).into()) } } diff --git a/packages/dns-test/src/lib.rs b/packages/dns-test/src/lib.rs index 05fa9d41..f6232bfc 100644 --- a/packages/dns-test/src/lib.rs +++ b/packages/dns-test/src/lib.rs @@ -2,6 +2,7 @@ pub use crate::fqdn::FQDN; pub use crate::recursive_resolver::RecursiveResolver; +pub use crate::trust_anchor::TrustAnchor; pub type Error = Box; pub type Result = core::result::Result; @@ -12,4 +13,5 @@ mod fqdn; pub mod name_server; pub mod record; mod recursive_resolver; +mod trust_anchor; pub mod zone_file; diff --git a/packages/dns-test/src/recursive_resolver.rs b/packages/dns-test/src/recursive_resolver.rs index 8c318cd4..f88d8f8d 100644 --- a/packages/dns-test/src/recursive_resolver.rs +++ b/packages/dns-test/src/recursive_resolver.rs @@ -2,7 +2,8 @@ use core::fmt::Write; use std::net::Ipv4Addr; use crate::container::{Child, Container}; -use crate::zone_file::{Root, DNSKEY}; +use crate::trust_anchor::TrustAnchor; +use crate::zone_file::Root; use crate::Result; pub struct RecursiveResolver { @@ -11,7 +12,7 @@ pub struct RecursiveResolver { } impl RecursiveResolver { - pub fn start(roots: &[Root], trust_anchors: &[DNSKEY]) -> Result { + pub fn start(roots: &[Root], trust_anchor: &TrustAnchor) -> Result { const TRUST_ANCHOR_FILE: &str = "/etc/trusted-key.key"; let container = Container::run()?; @@ -23,16 +24,11 @@ impl RecursiveResolver { container.cp("/etc/unbound/root.hints", &hints)?; - let use_dnssec = !trust_anchors.is_empty(); + let use_dnssec = !trust_anchor.is_empty(); container.cp("/etc/unbound/unbound.conf", &unbound_conf(use_dnssec))?; if use_dnssec { - let trust_anchor = trust_anchors.iter().fold(String::new(), |mut buf, ds| { - writeln!(buf, "{ds}").expect("infallible"); - buf - }); - - container.cp(TRUST_ANCHOR_FILE, &trust_anchor)?; + container.cp(TRUST_ANCHOR_FILE, &trust_anchor.to_string())?; } let child = container.spawn(&["unbound", "-d"])?; @@ -76,7 +72,7 @@ mod tests { #[test] fn terminate_works() -> Result<()> { - let resolver = RecursiveResolver::start(&[], &[])?; + let resolver = RecursiveResolver::start(&[], &TrustAnchor::empty())?; let logs = resolver.terminate()?; eprintln!("{logs}"); diff --git a/packages/dns-test/src/trust_anchor.rs b/packages/dns-test/src/trust_anchor.rs new file mode 100644 index 00000000..b14173e2 --- /dev/null +++ b/packages/dns-test/src/trust_anchor.rs @@ -0,0 +1,51 @@ +use core::fmt; + +use crate::zone_file::DNSKEY; + +pub struct TrustAnchor { + keys: Vec, +} + +impl TrustAnchor { + pub fn empty() -> Self { + Self { keys: Vec::new() } + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() + } + + pub fn add(&mut self, key: DNSKEY) -> &mut Self { + self.keys.push(key); + self + } + + /// formats the `TrustAnchor` in the format `delv` expects + pub(super) fn delv(&self) -> String { + let mut buf = "trust-anchors {".to_string(); + + for key in &self.keys { + buf.push_str(&key.delv()); + } + + buf.push_str("};"); + buf + } +} + +impl fmt::Display for TrustAnchor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for key in &self.keys { + writeln!(f, "{key}")?; + } + Ok(()) + } +} + +impl FromIterator for TrustAnchor { + fn from_iter>(iter: T) -> Self { + Self { + keys: iter.into_iter().collect(), + } + } +} diff --git a/packages/dns-test/src/zone_file.rs b/packages/dns-test/src/zone_file.rs index 71a7b9c6..8dc153c9 100644 --- a/packages/dns-test/src/zone_file.rs +++ b/packages/dns-test/src/zone_file.rs @@ -165,6 +165,20 @@ impl DNSKEY { pub fn key_tag(&self) -> u16 { self.key_tag } + + /// formats the `DNSKEY` in the format `delv` expects + pub(super) fn delv(&self) -> String { + let Self { + zone, + flags, + protocol, + algorithm, + public_key, + .. + } = self; + + format!("{zone} static-key {flags} {protocol} {algorithm} \"{public_key}\";\n") + } } impl FromStr for DNSKEY {