Skip to content
This repository has been archived by the owner on Jun 7, 2024. It is now read-only.

shoehorn hickory's on-the-fly signing into the NameServer<Signed> API #46

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use dns_test::record::{Record, RecordType};
use dns_test::{Network, Result, FQDN};

#[test]
#[ignore]
fn rrsig_in_answer_section() -> Result<()> {
let network = Network::new()?;

Expand Down Expand Up @@ -33,7 +32,6 @@ fn rrsig_in_answer_section() -> Result<()> {
}

#[test]
#[ignore]
fn rrsig_in_authority_section() -> Result<()> {
let network = Network::new()?;

Expand Down
7 changes: 4 additions & 3 deletions packages/dns-test/src/docker/hickory.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
FROM rust:1-slim-bookworm

# ldns-utils = ldns-{key2ds,keygen,signzone}
# libssl-dev + pkg-config = required to build hickory-dns with feature dnssec-openssl
RUN apt-get update && \
apt-get install -y \
ldnsutils \
libssl-dev \
pkg-config \
tshark

# `dns-test` will invoke `docker build` from a temporary directory that contains
# a clone of the hickory repository. `./src` here refers to that clone; not to
# any directory inside the `dns-test` repository
COPY ./src /usr/src/hickory
RUN cargo install --path /usr/src/hickory/bin --features recursor --debug && \
RUN cargo install --path /usr/src/hickory/bin --features dnssec-openssl,recursor --debug && \
mkdir /etc/hickory
env RUST_LOG=debug
15 changes: 13 additions & 2 deletions packages/dns-test/src/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use crate::FQDN;
pub enum Config<'a> {
NameServer {
origin: &'a FQDN,
use_dnssec: bool,
},

Resolver {
use_dnssec: bool,
netmask: &'a str,
Expand Down Expand Up @@ -90,7 +92,7 @@ impl Implementation {
}
},

Config::NameServer { origin } => match self {
Config::NameServer { origin, use_dnssec } => match self {
Self::Bind => {
minijinja::render!(
include_str!("templates/named.name-server.conf.jinja"),
Expand All @@ -108,7 +110,8 @@ impl Implementation {
Self::Hickory(_) => {
minijinja::render!(
include_str!("templates/hickory.name-server.toml.jinja"),
fqdn => origin.as_str()
fqdn => origin.as_str(),
use_dnssec => use_dnssec
)
}
},
Expand Down Expand Up @@ -159,6 +162,14 @@ exec hickory-dns -d",
},
}
}

/// Returns `true` if the implementation is [`Hickory`].
///
/// [`Hickory`]: Implementation::Hickory
#[must_use]
pub fn is_hickory(&self) -> bool {
matches!(self, Self::Hickory(..))
}
}

impl fmt::Display for Implementation {
Expand Down
111 changes: 78 additions & 33 deletions packages/dns-test/src/name_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ impl NameServer<Stopped> {
const KSK_BITS: usize = 2048;
const ALGORITHM: &str = "RSASHA1-NSEC3-SHA1";

let is_hickory = self.implementation.is_hickory();
let Self {
container,
zone_file,
Expand All @@ -214,47 +215,89 @@ impl NameServer<Stopped> {

let zone = zone_file.origin();

let zsk_keygen =
format!("cd {ZONES_DIR} && ldns-keygen -a {ALGORITHM} -b {ZSK_BITS} {zone}");
let zsk_filename = container.stdout(&["sh", "-c", &zsk_keygen])?;
let zsk_path = format!("{ZONES_DIR}/{zsk_filename}.key");
let zsk: zone_file::DNSKEY = container.stdout(&["cat", &zsk_path])?.parse()?;

let ksk_keygen =
format!("cd {ZONES_DIR} && ldns-keygen -k -a {ALGORITHM} -b {KSK_BITS} {zone}");
let ksk_filename = container.stdout(&["sh", "-c", &ksk_keygen])?;
let ksk_path = format!("{ZONES_DIR}/{ksk_filename}.key");
let ksk: zone_file::DNSKEY = container.stdout(&["cat", &ksk_path])?.parse()?;

// -n = use NSEC3 instead of NSEC
// -p = set the opt-out flag on all nsec3 rrs
let signzone = format!(
"cd {ZONES_DIR} && ldns-signzone -n -p {ZONE_FILENAME} {zsk_filename} {ksk_filename}"
);
container.status_ok(&["sh", "-c", &signzone])?;

// TODO do we want to make the hashing algorithm configurable?
// -2 = use SHA256 for the DS hash
let key2ds = format!("cd {ZONES_DIR} && ldns-key2ds -n -2 {ZONE_FILENAME}.signed");
let ds: DS = container.stdout(&["sh", "-c", &key2ds])?.parse()?;
let state = if is_hickory {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imho this function is quite large, maybe to code in the conditional can be extracted, something like:

let state = if implementation.is_hickory {
    // automatic on-the-fly
    hickory_sign_zonefile(&container, &zone_file, zone.clone())?
} else {
    // manual ahead-of-time signing
    manual_sign_zonefile(&container, &zone_file, zone.clone())?
}

// automatic on-the-fly signing

// generate the ZSK
container.status_ok(&[
"openssl",
"genrsa",
"-out",
"/tmp/private.pem",
&ZSK_BITS.to_string(),
])?;

// FIXME produce DS and DNSKEY records
let ds = DS {
zone: zone.clone(),
ttl: 0,
key_tag: 0,
algorithm: 0,
digest_type: 0,
digest: String::new(),
};
let zsk = record::DNSKEY {
zone: zone.clone(),
ttl: 0,
flags: 0,
protocol: 0,
algorithm: 0,
public_key: String::new(),
};
// XXX no KSK in this case?
let ksk = zsk.clone();
// XXX there's no `signed` zone file in this branch
Signed {
ds,
signed: zone_file.clone(),
zsk,
ksk,
}
} else {
// manual ahead-of-time signing
let zsk_keygen =
format!("cd {ZONES_DIR} && ldns-keygen -a {ALGORITHM} -b {ZSK_BITS} {zone}");
let zsk_filename = container.stdout(&["sh", "-c", &zsk_keygen])?;
let zsk_path = format!("{ZONES_DIR}/{zsk_filename}.key");
let zsk: zone_file::DNSKEY = container.stdout(&["cat", &zsk_path])?.parse()?;

let ksk_keygen =
format!("cd {ZONES_DIR} && ldns-keygen -k -a {ALGORITHM} -b {KSK_BITS} {zone}");
let ksk_filename = container.stdout(&["sh", "-c", &ksk_keygen])?;
let ksk_path = format!("{ZONES_DIR}/{ksk_filename}.key");
let ksk: zone_file::DNSKEY = container.stdout(&["cat", &ksk_path])?.parse()?;

// -n = use NSEC3 instead of NSEC
// -p = set the opt-out flag on all nsec3 rrs
let signzone = format!(
"cd {ZONES_DIR} && ldns-signzone -n -p {ZONE_FILENAME} {zsk_filename} {ksk_filename}"
);
container.status_ok(&["sh", "-c", &signzone])?;

let signed: ZoneFile = container
.stdout(&["cat", &format!("{zone_file_path}.signed")])?
.parse()?;
// TODO do we want to make the hashing algorithm configurable?
// -2 = use SHA256 for the DS hash
let key2ds = format!("cd {ZONES_DIR} && ldns-key2ds -n -2 {ZONE_FILENAME}.signed");
let ds: DS = container.stdout(&["sh", "-c", &key2ds])?.parse()?;

let ttl = zone_file.soa.ttl;
let signed: ZoneFile = container
.stdout(&["cat", &format!("{zone_file_path}.signed")])?
.parse()?;

Ok(NameServer {
container,
implementation,
zone_file,
state: Signed {
let ttl = zone_file.soa.ttl;
Signed {
ds,
signed,
// inherit SOA's TTL value
ksk: ksk.with_ttl(ttl),
zsk: zsk.with_ttl(ttl),
},
}
};

Ok(NameServer {
container,
implementation,
zone_file,
state,
})
}

Expand All @@ -269,6 +312,7 @@ impl NameServer<Stopped> {

let config = Config::NameServer {
origin: zone_file.origin(),
use_dnssec: false,
};

container.cp(
Expand Down Expand Up @@ -314,6 +358,7 @@ impl NameServer<Signed> {

let config = Config::NameServer {
origin: zone_file.origin(),
use_dnssec: true,
};
container.cp(
implementation.conf_file_path(config.role()),
Expand Down
30 changes: 15 additions & 15 deletions packages/dns-test/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const CLASS: &str = "IN"; // "internet"
macro_rules! record_types {
($($variant:ident),*) => {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum RecordType {
$($variant),*
}
Expand Down Expand Up @@ -49,7 +49,7 @@ macro_rules! record_types {

record_types!(A, AAAA, DNSKEY, DS, MX, NS, NSEC3, NSEC3PARAM, RRSIG, SOA, TXT);

#[derive(Debug)]
#[derive(Clone, Debug)]
#[allow(clippy::upper_case_acronyms)]
pub enum Record {
A(A),
Expand Down Expand Up @@ -184,7 +184,7 @@ impl fmt::Display for Record {
}
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct A {
pub fqdn: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -327,12 +327,12 @@ impl fmt::Display for DNSKEY {

#[derive(Clone, Debug)]
pub struct DS {
zone: FQDN,
ttl: u32,
key_tag: u16,
algorithm: u8,
digest_type: u8,
digest: String,
pub zone: FQDN,
pub ttl: u32,
pub key_tag: u16,
pub algorithm: u8,
pub digest_type: u8,
pub digest: String,
}

impl FromStr for DS {
Expand Down Expand Up @@ -387,7 +387,7 @@ impl fmt::Display for DS {
}
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct NS {
pub zone: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -431,7 +431,7 @@ impl FromStr for NS {
}

// integer types chosen based on bit sizes in section 3.2 of RFC5155
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct NSEC3 {
pub fqdn: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -501,7 +501,7 @@ impl fmt::Display for NSEC3 {
}

// integer types chosen based on bit sizes in section 4.2 of RFC5155
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct NSEC3PARAM {
pub zone: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -559,7 +559,7 @@ impl fmt::Display for NSEC3PARAM {

// integer types chosen based on bit sizes in section 3.1 of RFC4034
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct RRSIG {
pub fqdn: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -638,7 +638,7 @@ impl fmt::Display for RRSIG {
}

#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SOA {
pub zone: FQDN,
pub ttl: u32,
Expand Down Expand Up @@ -696,7 +696,7 @@ impl fmt::Display for SOA {
}
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SoaSettings {
pub serial: u32,
pub refresh: u32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
zone = "{{ fqdn }}"
zone_type = "Primary"
file = "/etc/zones/main.zone"
enable_dnssec = {{ use_dnssec }}

{%if use_dnssec %}
[[zones.keys]]
key_path = "/tmp/private.pem"
algorithm = "RSASHA256"
is_zone_signing_key = true
{% endif %}
1 change: 1 addition & 0 deletions packages/dns-test/src/zone_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::str::FromStr;
use crate::record::{self, Record, SOA};
use crate::{Error, Result, DEFAULT_TTL, FQDN};

#[derive(Clone)]
pub struct ZoneFile {
origin: FQDN,
pub soa: SOA,
Expand Down