Skip to content

Commit 1775511

Browse files
committed
Persist and restore TSIG keys for zone sources
1 parent 5b912f6 commit 1775511

4 files changed

Lines changed: 134 additions & 48 deletions

File tree

src/main.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,31 @@ fn main() -> ExitCode {
7676
// Load the global state file or build one from scratch.
7777
let mut zones = Default::default();
7878
let mut policies = Default::default();
79-
let mut state = match center::State::init_from_file(&config, &mut zones, &mut policies) {
79+
let state = match center::State::init_from_file(&config, &mut zones, &mut policies) {
8080
Ok(mut state) => {
81-
// TODO: Restore the TSIG key store here, so that keys are available
82-
// to the zones and policies being restored.
81+
// Load the TSIG store file.
82+
match state.tsig_store.init_from_file(&config) {
83+
Ok(()) => debug!("Loaded the TSIG store"),
84+
Err(err) if err.kind() == io::ErrorKind::NotFound => {
85+
debug!("No TSIG store found; will create one");
86+
}
87+
Err(err) => {
88+
error!("Failed to load the TSIG store: {err}");
89+
return ExitCode::FAILURE;
90+
}
91+
}
8392

8493
// Restore pending zones.
8594
for name in zones {
8695
assert!(
8796
!state.zones.contains(&name),
8897
"Zone '{name}' was encountered twice"
8998
);
90-
let zone = match Zone::restore(&config, name, &mut state.policies) {
91-
Ok(zone) => zone,
92-
Err(_) => return ExitCode::FAILURE,
93-
};
99+
let zone =
100+
match Zone::restore(&config, name, &mut state.policies, &state.tsig_store) {
101+
Ok(zone) => zone,
102+
Err(_) => return ExitCode::FAILURE,
103+
};
94104
state.zones.insert(ZoneByName(Arc::new(zone)));
95105
}
96106

@@ -137,6 +147,18 @@ fn main() -> ExitCode {
137147

138148
let mut state = center::State::default();
139149

150+
// Load the TSIG store file.
151+
match state.tsig_store.init_from_file(&config) {
152+
Ok(()) => debug!("Loaded the TSIG store"),
153+
Err(err) if err.kind() == io::ErrorKind::NotFound => {
154+
debug!("No TSIG store found; will create one");
155+
}
156+
Err(err) => {
157+
error!("Failed to load the TSIG store: {err}");
158+
return ExitCode::FAILURE;
159+
}
160+
}
161+
140162
// Load all policies.
141163
let mut updates = Vec::new();
142164
let res = policy::reload_all(
@@ -187,20 +209,6 @@ fn main() -> ExitCode {
187209
);
188210
}
189211

190-
// Load the TSIG store file.
191-
//
192-
// TODO: Track which TSIG keys are in use by zones.
193-
match state.tsig_store.init_from_file(&config) {
194-
Ok(()) => debug!("Loaded the TSIG store"),
195-
Err(err) if err.kind() == io::ErrorKind::NotFound => {
196-
debug!("No TSIG store found; will create one");
197-
}
198-
Err(err) => {
199-
error!("Failed to load the TSIG store: {err}");
200-
return ExitCode::FAILURE;
201-
}
202-
}
203-
204212
// Bind to listen addresses before daemonizing.
205213
let Ok(socket_provider) = bind_to_listen_sockets_as_needed(&config) else {
206214
return ExitCode::FAILURE;

src/zone/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::{
66
cmp::Ordering,
77
fmt,
88
hash::{Hash, Hasher},
9-
io,
109
sync::{Arc, Mutex},
1110
time::{Duration, SystemTime},
1211
};
@@ -26,6 +25,7 @@ use crate::{
2625
persistence::zone::{PersistenceState, ZonePersistenceHandle},
2726
policy::{Policy, PolicyVersion},
2827
signer::zone::{SignerState, SignerZoneHandle},
28+
tsig::TsigStore,
2929
util::{deserialize_duration_from_secs, serialize_duration_as_secs},
3030
zone::machine::ZoneStateMachine,
3131
};
@@ -96,15 +96,19 @@ impl Zone {
9696
config: &Config,
9797
name: Name<Bytes>,
9898
policies: &mut foldhash::HashMap<Box<str>, Policy>,
99-
) -> io::Result<Self> {
99+
tsig_store: &TsigStore,
100+
) -> Result<Self, state::LoadError> {
100101
let path = config.zone_state_dir.join(format!("{name}.db"));
101102

102103
// Load the underlying state file.
103104
let state = match state::Spec::load(&path) {
104-
Ok(spec) => spec.parse(&name, policies),
105-
Err(err) => {
106-
error!("Failed to load the state of zone '{name}' from '{path}': {err}");
107-
return Err(err);
105+
Ok(spec) => spec.parse(&name, policies, tsig_store)?,
106+
Err(error) => {
107+
error!("Failed to load the state of zone '{name}' from '{path}': {error}");
108+
return Err(state::LoadError::Read {
109+
path: path.into(),
110+
error,
111+
});
108112
}
109113
};
110114

src/zone/state/mod.rs

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
33
use std::{
44
collections::hash_map,
5-
fs,
5+
error::Error,
6+
fmt, fs,
67
io::{self, BufReader},
78
sync::Arc,
89
};
910

1011
use bytes::Bytes;
1112
use camino::Utf8Path;
12-
use domain::base::Name;
13+
use domain::{base::Name, dep::octseq::Array};
1314
use serde::{Deserialize, Serialize};
1415
use tracing::warn;
1516

1617
use crate::{
1718
loader::zone::LoaderState,
1819
policy::{Policy, PolicyVersion},
20+
tsig::TsigStore,
1921
zone::ZoneState,
2022
};
2123

@@ -39,7 +41,8 @@ impl Spec {
3941
self,
4042
zone_name: &Name<Bytes>,
4143
policies: &mut foldhash::HashMap<Box<str>, Policy>,
42-
) -> ZoneState {
44+
tsig_store: &TsigStore,
45+
) -> Result<ZoneState, LoadError> {
4346
/// Synchronize a loaded policy with global state.
4447
fn sync_policy(
4548
known_version: PolicyVersion,
@@ -93,7 +96,9 @@ impl Spec {
9396
history,
9497
}) => {
9598
let loader = LoaderState {
96-
source: source.parse(),
99+
source: source
100+
.parse(tsig_store)
101+
.map_err(LoadError::MissingSourceTsigKey)?,
97102
..Default::default()
98103
};
99104

@@ -106,7 +111,7 @@ impl Spec {
106111
}
107112
let policy = policy.map(|p| p.latest.clone());
108113

109-
ZoneState {
114+
Ok(ZoneState {
110115
policy,
111116
min_expiration,
112117
next_min_expiration,
@@ -119,7 +124,7 @@ impl Spec {
119124
loader,
120125
history,
121126
..Default::default()
122-
}
127+
})
123128
}
124129
}
125130
}
@@ -146,3 +151,65 @@ impl Spec {
146151
crate::util::write_file(path, text.as_bytes())
147152
}
148153
}
154+
155+
//============ Errors ==========================================================
156+
157+
//----------- LoadError --------------------------------------------------------
158+
159+
/// An error loading a zone state file.
160+
#[derive(Debug)]
161+
pub enum LoadError {
162+
/// The file could not be read.
163+
Read {
164+
/// The path being read from.
165+
path: Box<Utf8Path>,
166+
167+
/// The I/O error.
168+
error: io::Error,
169+
},
170+
171+
/// The TSIG key for the zone source could not be found.
172+
MissingSourceTsigKey(MissingTsigKeyError),
173+
}
174+
175+
impl Error for LoadError {
176+
fn source(&self) -> Option<&(dyn Error + 'static)> {
177+
match self {
178+
Self::Read { error, .. } => Some(error),
179+
Self::MissingSourceTsigKey(error) => Some(error),
180+
}
181+
}
182+
}
183+
184+
impl fmt::Display for LoadError {
185+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186+
match self {
187+
Self::Read { path, error } => {
188+
write!(f, "could not read the zone state file '{path}': {error}")
189+
}
190+
Self::MissingSourceTsigKey(error) => {
191+
write!(f, "could not load the zone source setting: {error}")
192+
}
193+
}
194+
}
195+
}
196+
197+
//----------- MissingTsigKeyError ----------------------------------------------
198+
199+
/// A TSIG key could not be found.
200+
///
201+
/// A zone's state file indicated that it was using a TSIG key, but the key
202+
/// could not be found in the configured TSIG key store.
203+
#[derive(Clone, Debug)]
204+
pub struct MissingTsigKeyError {
205+
/// The name of the TSIG key.
206+
pub name: Box<Name<Array<255>>>,
207+
}
208+
209+
impl Error for MissingTsigKeyError {}
210+
211+
impl fmt::Display for MissingTsigKeyError {
212+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213+
write!(f, "TSIG key '{}' could not be found", self.name)
214+
}
215+
}

src/zone/state/v1.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
use std::collections::HashSet;
44
use std::net::SocketAddr;
55

6-
use bytes::Bytes;
76
use camino::Utf8Path;
87
use domain::base::{Rtype, Serial, Ttl};
8+
use domain::dep::octseq::Array;
99
use domain::dnssec::sign::keys::keyset::UnixTime;
1010
use domain::{base::Name, rdata::dnssec::Timestamp};
1111
use serde::{Deserialize, Serialize};
1212

1313
use crate::loader::Source;
1414
use crate::policy::file::v1::{NameserverCommsSpec, OutboundSpec};
1515
use crate::policy::{AutoConfig, DsAlgorithm, KeyParameters};
16+
use crate::tsig::TsigStore;
1617
use crate::zone::HistoryItem;
1718
use crate::{
1819
policy::{
@@ -22,6 +23,8 @@ use crate::{
2223
zone::ZoneState,
2324
};
2425

26+
use super::MissingTsigKeyError;
27+
2528
//----------- Spec -------------------------------------------------------------
2629

2730
/// A zone state file.
@@ -547,23 +550,31 @@ pub enum ZoneLoadSourceSpec {
547550
addr: SocketAddr,
548551

549552
/// The TSIG key to use, if any.
550-
tsig_key: Option<Name<Bytes>>,
553+
tsig_key: Option<Box<Name<Array<255>>>>,
551554
},
552555
}
553556

554557
//--- Conversion
555558

556559
impl ZoneLoadSourceSpec {
557560
/// Parse from this specification.
558-
pub fn parse(self) -> Source {
561+
pub fn parse(self, tsig_store: &TsigStore) -> Result<Source, MissingTsigKeyError> {
559562
match self {
560-
Self::None => Source::None,
561-
Self::Zonefile { path } => Source::Zonefile { path },
562-
// TODO: Look up the TSIG key in the key store.
563-
Self::Server { addr, tsig_key: _ } => Source::Server {
564-
addr,
565-
tsig_key: None,
566-
},
563+
Self::None => Ok(Source::None),
564+
Self::Zonefile { path } => Ok(Source::Zonefile { path }),
565+
Self::Server { addr, tsig_key } => {
566+
// Look up the TSIG key from the key store.
567+
let tsig_key = tsig_key
568+
.map(|name| {
569+
tsig_store
570+
.get(&*name)
571+
.map(|key| key.inner.clone())
572+
.ok_or(MissingTsigKeyError { name })
573+
})
574+
.transpose()?;
575+
576+
Ok(Source::Server { addr, tsig_key })
577+
}
567578
}
568579
}
569580

@@ -574,11 +585,7 @@ impl ZoneLoadSourceSpec {
574585
Source::Zonefile { path } => Self::Zonefile { path },
575586
Source::Server { addr, tsig_key } => Self::Server {
576587
addr,
577-
tsig_key: tsig_key.map(|key| {
578-
let bytes = key.name().as_slice();
579-
let bytes = Bytes::copy_from_slice(bytes);
580-
Name::from_octets(bytes).unwrap()
581-
}),
588+
tsig_key: tsig_key.map(|key| key.name().clone().into()),
582589
},
583590
}
584591
}

0 commit comments

Comments
 (0)