use std::{
fs::{File, OpenOptions},
os::unix::prelude::{OpenOptionsExt, PermissionsExt},
sync::Arc,
};
use ntp_proto::{KeySet, KeySetProvider};
use tokio::sync::watch;
use tracing::{Span, instrument, warn};
use super::config::KeysetConfig;
#[instrument(level = tracing::Level::ERROR, name = "KeySet Provider", skip_all, fields(path = debug(config.key_storage_path.clone())))]
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();
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."
);
}
}
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::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());
let span = Span::current();
tokio::task::spawn_blocking(move || {
let _enter = span.enter();
loop {
if let Some(path) = &config.key_storage_path
&& 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)
})()
{
if e.kind() == std::io::ErrorKind::NotFound
|| e.kind() == std::io::ErrorKind::PermissionDenied
{
warn!(error = ?e, "Could not store nts server keys, parent directory does not exist or has insufficient permissions");
} else {
warn!(error = ?e, "Could not store nts server keys");
}
}
if tx.send(provider.get()).is_err() {
break;
}
std::thread::sleep(next_interval);
next_interval = std::time::Duration::from_secs(config.key_rotation_interval as _);
provider.rotate();
}
});
rx
}