make Client::delv work & use it in dnssec tests

This commit is contained in:
Jorge Aparicio 2024-02-08 19:23:50 +01:00
parent edd6eebe1a
commit 5c53ba0899
8 changed files with 114 additions and 29 deletions

View File

@ -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()?;

View File

@ -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(())
}

View File

@ -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<String> {
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(),
])
}

View File

@ -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<String> {
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())
}
}

View File

@ -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<dyn std::error::Error>;
pub type Result<T> = core::result::Result<T, Error>;
@ -12,4 +13,5 @@ mod fqdn;
pub mod name_server;
pub mod record;
mod recursive_resolver;
mod trust_anchor;
pub mod zone_file;

View File

@ -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<Self> {
pub fn start(roots: &[Root], trust_anchor: &TrustAnchor) -> Result<Self> {
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}");

View File

@ -0,0 +1,51 @@
use core::fmt;
use crate::zone_file::DNSKEY;
pub struct TrustAnchor {
keys: Vec<DNSKEY>,
}
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<DNSKEY> for TrustAnchor {
fn from_iter<T: IntoIterator<Item = DNSKEY>>(iter: T) -> Self {
Self {
keys: iter.into_iter().collect(),
}
}
}

View File

@ -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 {