Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run nts key provider on dedicated thread #1208

Closed
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
4 changes: 3 additions & 1 deletion ntpd/src/daemon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ async fn run(options: NtpDaemonOptions) -> Result<(), Box<dyn Error>> {
config.check();

// we always generate the keyset (even if NTS is not used)
let keyset = nts_key_provider::spawn(config.keyset).await;
let keyset = tokio::task::spawn_blocking(move || nts_key_provider::spawn(config.keyset))
.await
.expect("nts_key_provider::spawn should not panic");

#[cfg(feature = "hardware-timestamping")]
let clock_config = config.clock;
Expand Down
139 changes: 83 additions & 56 deletions ntpd/src/daemon/nts_key_provider.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{
fs::{File, OpenOptions},
os::unix::prelude::{OpenOptionsExt, PermissionsExt},
path::Path,
sync::Arc,
time::Duration,
};

use ntp_proto::{KeySet, KeySetProvider};
Expand All @@ -10,71 +12,96 @@ use tracing::warn;

use super::config::KeysetConfig;

pub async fn spawn(config: KeysetConfig) -> watch::Receiver<Arc<KeySet>> {
let (mut provider, mut next_interval) = match &config.key_storage_path {
Some(path) => {
let path = path.to_owned();

if let Ok(meta) = std::fs::metadata(&path) {
let perm = meta.permissions();
/// Reads the file metadata and checks if the permissions are as expected. Logs a warning if they are not.
fn permission_check(path: &Path) {
let Ok(meta) = std::fs::metadata(path) else {
return;
};

if perm.mode() as libc::mode_t & (libc::S_IWOTH | libc::S_IROTH | libc::S_IXOTH)
!= 0
{
warn!("Keyset file permissions: Others can interact with it. This is a potential security issue.");
}
}
if meta.permissions().mode() as libc::mode_t & (libc::S_IWOTH | libc::S_IROTH | libc::S_IXOTH)
!= 0
{
warn!("Keyset file permissions: Others can interact with it. This is a potential security issue.");
}
}

let (provider, time) = tokio::task::spawn_blocking(
move || -> std::io::Result<(KeySetProvider, std::time::SystemTime)> {
let mut input = File::open(path)?;
KeySetProvider::load(&mut input, config.stale_key_count)
},
)
.await
.unwrap_or_else(|e| Err(std::io::Error::new(std::io::ErrorKind::Other, e)))
.unwrap_or_else(|e| {
warn!(error = ?e, "Could not load nts server keys, starting with new set");
(
KeySetProvider::new(config.stale_key_count),
std::time::SystemTime::now(),
)
});
(
provider,
std::time::Duration::from_secs(config.key_rotation_interval as _).saturating_sub(
std::time::SystemTime::now()
.duration_since(time)
.unwrap_or(std::time::Duration::from_secs(0)),
),
)
}
None => (
KeySetProvider::new(config.stale_key_count),
std::time::Duration::from_secs(config.key_rotation_interval as _),
),
};
let (tx, rx) = watch::channel(provider.get());
tokio::task::spawn_blocking(move || loop {
fn run(
mut provider: KeySetProvider,
config: KeysetConfig,
mut next_interval: Duration,
tx: watch::Sender<Arc<KeySet>>,
) {
loop {
std::thread::sleep(next_interval);
next_interval = std::time::Duration::from_secs(config.key_rotation_interval as _);
provider.rotate();
if let Some(path) = &config.key_storage_path {
if let Err(e) = (|| -> std::io::Result<()> {
let mut output = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.mode(0o600)
.open(path)?;
provider.store(&mut output)
})() {
warn!(error = ?e, "Could not store nts server keys");
}
OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.mode(0o600)
.open(path)
.and_then(|mut output| provider.store(&mut output))
.unwrap_or_else(|error| {
warn!(?error, "Could not store nts server keys");
});
}
if tx.send(provider.get()).is_err() {
break;
}
});
}
}

pub fn spawn(config: KeysetConfig) -> watch::Receiver<Arc<KeySet>> {
let (provider, next_interval) = config
.key_storage_path
.as_ref()
.and_then(|path| {
let path: &Path = path.as_ref();

permission_check(path);

File::open(path).map_or_else(
|error| {
warn!(
?error,
"Could not read nts server keys file, starting with new set"
);
None
},
Some,
)
})
.and_then(|mut input| {
KeySetProvider::load(&mut input, config.stale_key_count).map_or_else(
|error| {
warn!(
?error,
"Could not load nts server keys, starting with new set"
);
None
},
|(provider, time)| {
let next_interval =
std::time::Duration::from_secs(config.key_rotation_interval as _)
.saturating_sub(
std::time::SystemTime::now()
.duration_since(time)
.unwrap_or_default(),
);
Some((provider, next_interval))
},
)
})
.unwrap_or_else(|| {
(
KeySetProvider::new(config.stale_key_count),
std::time::Duration::from_secs(config.key_rotation_interval as _),
)
});

let (tx, rx) = watch::channel(provider.get());
std::thread::spawn(move || run(provider, config, next_interval, tx));
rx
}