support BIND in the NameServer role
This commit is contained in:
parent
d25cc923ec
commit
7aa9d543b4
@ -9,8 +9,9 @@ use crate::{Implementation, Result, DEFAULT_TTL, FQDN};
|
|||||||
|
|
||||||
pub struct NameServer<State> {
|
pub struct NameServer<State> {
|
||||||
container: Container,
|
container: Container,
|
||||||
zone_file: ZoneFile,
|
implementation: Implementation,
|
||||||
state: State,
|
state: State,
|
||||||
|
zone_file: ZoneFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NameServer<Stopped> {
|
impl NameServer<Stopped> {
|
||||||
@ -28,12 +29,17 @@ impl NameServer<Stopped> {
|
|||||||
/// the zone
|
/// the zone
|
||||||
pub fn new(implementation: &Implementation, zone: FQDN, network: &Network) -> Result<Self> {
|
pub fn new(implementation: &Implementation, zone: FQDN, network: &Network) -> Result<Self> {
|
||||||
assert!(
|
assert!(
|
||||||
matches!(implementation, Implementation::Unbound),
|
matches!(
|
||||||
"currently only `unbound` (`nsd`) can be used as a `NameServer`"
|
implementation,
|
||||||
|
Implementation::Unbound | Implementation::Bind
|
||||||
|
),
|
||||||
|
"currently only `unbound` (`nsd`) and BIND can be used as a `NameServer`"
|
||||||
);
|
);
|
||||||
|
|
||||||
let ns_count = ns_count();
|
let ns_count = ns_count();
|
||||||
let nameserver = primary_ns(ns_count);
|
let nameserver = primary_ns(ns_count);
|
||||||
|
let image = implementation.clone().into();
|
||||||
|
let container = Container::run(&image, network)?;
|
||||||
|
|
||||||
let soa = SOA {
|
let soa = SOA {
|
||||||
zone: zone.clone(),
|
zone: zone.clone(),
|
||||||
@ -45,10 +51,12 @@ impl NameServer<Stopped> {
|
|||||||
let mut zone_file = ZoneFile::new(soa);
|
let mut zone_file = ZoneFile::new(soa);
|
||||||
|
|
||||||
zone_file.add(Record::ns(zone, nameserver.clone()));
|
zone_file.add(Record::ns(zone, nameserver.clone()));
|
||||||
|
// BIND requires that `nameserver` has an A record
|
||||||
|
zone_file.add(Record::a(nameserver.clone(), container.ipv4_addr()));
|
||||||
|
|
||||||
let image = implementation.clone().into();
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
container: Container::run(&image, network)?,
|
container,
|
||||||
|
implementation: implementation.clone(),
|
||||||
zone_file,
|
zone_file,
|
||||||
state: Stopped,
|
state: Stopped,
|
||||||
})
|
})
|
||||||
@ -76,11 +84,13 @@ impl NameServer<Stopped> {
|
|||||||
let Self {
|
let Self {
|
||||||
container,
|
container,
|
||||||
zone_file,
|
zone_file,
|
||||||
|
implementation,
|
||||||
state: _,
|
state: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
container.status_ok(&["mkdir", "-p", ZONES_DIR])?;
|
container.status_ok(&["mkdir", "-p", ZONES_DIR])?;
|
||||||
container.cp("/etc/nsd/zones/main.zone", &zone_file.to_string())?;
|
let zone_file_path = zone_file_path();
|
||||||
|
container.cp(&zone_file_path, &zone_file.to_string())?;
|
||||||
|
|
||||||
let zone = zone_file.origin();
|
let zone = zone_file.origin();
|
||||||
|
|
||||||
@ -108,7 +118,6 @@ impl NameServer<Stopped> {
|
|||||||
let key2ds = format!("cd {ZONES_DIR} && ldns-key2ds -n -2 {ZONE_FILENAME}.signed");
|
let key2ds = format!("cd {ZONES_DIR} && ldns-key2ds -n -2 {ZONE_FILENAME}.signed");
|
||||||
let ds: DS = container.stdout(&["sh", "-c", &key2ds])?.parse()?;
|
let ds: DS = container.stdout(&["sh", "-c", &key2ds])?.parse()?;
|
||||||
|
|
||||||
let zone_file_path = zone_file_path();
|
|
||||||
let signed: ZoneFile = container
|
let signed: ZoneFile = container
|
||||||
.stdout(&["cat", &format!("{zone_file_path}.signed")])?
|
.stdout(&["cat", &format!("{zone_file_path}.signed")])?
|
||||||
.parse()?;
|
.parse()?;
|
||||||
@ -117,6 +126,7 @@ impl NameServer<Stopped> {
|
|||||||
|
|
||||||
Ok(NameServer {
|
Ok(NameServer {
|
||||||
container,
|
container,
|
||||||
|
implementation,
|
||||||
zone_file,
|
zone_file,
|
||||||
state: Signed {
|
state: Signed {
|
||||||
ds,
|
ds,
|
||||||
@ -133,28 +143,45 @@ impl NameServer<Stopped> {
|
|||||||
let Self {
|
let Self {
|
||||||
container,
|
container,
|
||||||
zone_file,
|
zone_file,
|
||||||
|
implementation,
|
||||||
state: _,
|
state: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// for PID file
|
let origin = zone_file.origin();
|
||||||
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
|
let (path, contents, cmd_args) = match &implementation {
|
||||||
|
Implementation::Bind => (
|
||||||
|
"/etc/bind/named.conf",
|
||||||
|
named_conf(origin),
|
||||||
|
&["named", "-g", "-d5"][..],
|
||||||
|
),
|
||||||
|
|
||||||
container.cp("/etc/nsd/nsd.conf", &nsd_conf(zone_file.origin()))?;
|
Implementation::Unbound => {
|
||||||
|
// for PID file
|
||||||
|
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
|
||||||
|
|
||||||
|
("/etc/nsd/nsd.conf", nsd_conf(origin), &["nsd", "-d"][..])
|
||||||
|
}
|
||||||
|
|
||||||
|
Implementation::Hickory(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
container.cp(path, &contents)?;
|
||||||
|
|
||||||
container.status_ok(&["mkdir", "-p", ZONES_DIR])?;
|
container.status_ok(&["mkdir", "-p", ZONES_DIR])?;
|
||||||
container.cp(&zone_file_path(), &zone_file.to_string())?;
|
container.cp(&zone_file_path(), &zone_file.to_string())?;
|
||||||
|
|
||||||
let child = container.spawn(&["nsd", "-d"])?;
|
let child = container.spawn(cmd_args)?;
|
||||||
|
|
||||||
Ok(NameServer {
|
Ok(NameServer {
|
||||||
container,
|
container,
|
||||||
|
implementation,
|
||||||
zone_file,
|
zone_file,
|
||||||
state: Running { child },
|
state: Running { child },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZONES_DIR: &str = "/etc/nsd/zones";
|
const ZONES_DIR: &str = "/etc/zones";
|
||||||
const ZONE_FILENAME: &str = "main.zone";
|
const ZONE_FILENAME: &str = "main.zone";
|
||||||
|
|
||||||
fn zone_file_path() -> String {
|
fn zone_file_path() -> String {
|
||||||
@ -172,20 +199,40 @@ impl NameServer<Signed> {
|
|||||||
let Self {
|
let Self {
|
||||||
container,
|
container,
|
||||||
zone_file,
|
zone_file,
|
||||||
|
implementation,
|
||||||
state,
|
state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// for PID file
|
let (conf_path, conf_contents, cmd_args) = match implementation {
|
||||||
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
|
Implementation::Bind => (
|
||||||
|
"/etc/bind/named.conf",
|
||||||
|
named_conf(zone_file.origin()),
|
||||||
|
&["named", "-g", "-d5"][..],
|
||||||
|
),
|
||||||
|
|
||||||
container.cp("/etc/nsd/nsd.conf", &nsd_conf(zone_file.origin()))?;
|
Implementation::Unbound => {
|
||||||
|
// for PID file
|
||||||
|
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
|
||||||
|
|
||||||
|
(
|
||||||
|
"/etc/nsd/nsd.conf",
|
||||||
|
nsd_conf(zone_file.origin()),
|
||||||
|
&["nsd", "-d"][..],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Implementation::Hickory(..) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
container.cp(conf_path, &conf_contents)?;
|
||||||
|
|
||||||
container.cp(&zone_file_path(), &state.signed.to_string())?;
|
container.cp(&zone_file_path(), &state.signed.to_string())?;
|
||||||
|
|
||||||
let child = container.spawn(&["nsd", "-d"])?;
|
let child = container.spawn(cmd_args)?;
|
||||||
|
|
||||||
Ok(NameServer {
|
Ok(NameServer {
|
||||||
container,
|
container,
|
||||||
|
implementation,
|
||||||
zone_file,
|
zone_file,
|
||||||
state: Running { child },
|
state: Running { child },
|
||||||
})
|
})
|
||||||
@ -220,7 +267,13 @@ impl NameServer<Running> {
|
|||||||
|
|
||||||
/// gracefully terminates the name server collecting all logs
|
/// gracefully terminates the name server collecting all logs
|
||||||
pub fn terminate(self) -> Result<String> {
|
pub fn terminate(self) -> Result<String> {
|
||||||
let pidfile = "/run/nsd/nsd.pid";
|
let pidfile = match &self.implementation {
|
||||||
|
Implementation::Bind => "/tmp/named.pid",
|
||||||
|
|
||||||
|
Implementation::Unbound => "/run/nsd/nsd.pid",
|
||||||
|
|
||||||
|
Implementation::Hickory(_) => unreachable!(),
|
||||||
|
};
|
||||||
// if `terminate` is called right after `start` NSD may not have had the chance to create
|
// if `terminate` is called right after `start` NSD may not have had the chance to create
|
||||||
// the PID file so if it doesn't exist wait for a bit before invoking `kill`
|
// the PID file so if it doesn't exist wait for a bit before invoking `kill`
|
||||||
let kill = format!(
|
let kill = format!(
|
||||||
@ -286,6 +339,13 @@ fn admin_ns(ns_count: usize) -> FQDN {
|
|||||||
FQDN(format!("admin{ns_count}.nameservers.com.")).unwrap()
|
FQDN(format!("admin{ns_count}.nameservers.com.")).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn named_conf(fqdn: &FQDN) -> String {
|
||||||
|
minijinja::render!(
|
||||||
|
include_str!("templates/named.name-server.conf.jinja"),
|
||||||
|
fqdn => fqdn.as_str()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn nsd_conf(fqdn: &FQDN) -> String {
|
fn nsd_conf(fqdn: &FQDN) -> String {
|
||||||
minijinja::render!(
|
minijinja::render!(
|
||||||
include_str!("templates/nsd.conf.jinja"),
|
include_str!("templates/nsd.conf.jinja"),
|
||||||
@ -375,7 +435,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn terminate_works() -> Result<()> {
|
fn terminate_nsd_works() -> Result<()> {
|
||||||
let network = Network::new()?;
|
let network = Network::new()?;
|
||||||
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
|
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
|
||||||
let logs = ns.terminate()?;
|
let logs = ns.terminate()?;
|
||||||
@ -384,4 +444,16 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn terminate_named_works() -> Result<()> {
|
||||||
|
let network = Network::new()?;
|
||||||
|
let ns = NameServer::new(&Implementation::Bind, FQDN::ROOT, &network)?.start()?;
|
||||||
|
let logs = ns.terminate()?;
|
||||||
|
|
||||||
|
eprintln!("{logs}");
|
||||||
|
assert!(logs.contains("starting BIND"));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
14
packages/dns-test/src/templates/named.name-server.conf.jinja
Normal file
14
packages/dns-test/src/templates/named.name-server.conf.jinja
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
options {
|
||||||
|
directory "/var/cache/bind";
|
||||||
|
pid-file "/tmp/named.pid";
|
||||||
|
recursion no;
|
||||||
|
dnssec-validation no;
|
||||||
|
allow-transfer { none; };
|
||||||
|
# significantly reduces noise in logs
|
||||||
|
empty-zones-enable no;
|
||||||
|
};
|
||||||
|
|
||||||
|
zone "{{ fqdn }}" IN {
|
||||||
|
type primary;
|
||||||
|
file "/etc/zones/main.zone";
|
||||||
|
};
|
@ -3,4 +3,4 @@ remote-control:
|
|||||||
|
|
||||||
zone:
|
zone:
|
||||||
name: {{ fqdn }}
|
name: {{ fqdn }}
|
||||||
zonefile: /etc/nsd/zones/main.zone
|
zonefile: /etc/zones/main.zone
|
||||||
|
Loading…
Reference in New Issue
Block a user