mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-30 05:14:52 +02:00
317 lines
13 KiB
Rust
317 lines
13 KiB
Rust
//! Deal with asynchronous scheduled processes, either triggered by an
|
|
//! event that occurred, or planned (e.g. re-publishing).
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use clokwerk::{self, ScheduleHandle, TimeUnits};
|
|
use tokio::runtime::Runtime;
|
|
|
|
use rpki::x509::Time;
|
|
|
|
use crate::commons::bgp::BgpAnalyser;
|
|
use crate::commons::{actor::Actor, api::Handle};
|
|
use crate::constants::test_mode_enabled;
|
|
#[cfg(feature = "multi-user")]
|
|
use crate::daemon::auth::common::session::LoginSessionCache;
|
|
use crate::daemon::ca::CaServer;
|
|
use crate::daemon::config::Config;
|
|
use crate::daemon::mq::{EventQueueListener, QueueEvent};
|
|
use crate::pubd::PubServer;
|
|
use crate::publish::CaPublisher;
|
|
|
|
pub struct Scheduler {
|
|
/// Responsible for listening to events and executing triggered processes, such
|
|
/// as publication of newly generated RPKI objects.
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
cas_event_triggers: Option<ScheduleHandle>,
|
|
|
|
/// Responsible for periodically republishing so that MFTs and CRLs do not go stale.
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
cas_republish: Option<ScheduleHandle>,
|
|
|
|
/// Responsible for letting CA check with their parents whether their resource
|
|
/// entitlements have changed *and* for the shrinking of issued certificates, if
|
|
/// they are not renewed within the configured grace period.
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
cas_refresh: Option<ScheduleHandle>,
|
|
|
|
/// Responsible for refreshing announcement information
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
announcements_refresh: ScheduleHandle,
|
|
|
|
/// Responsible for archiving old commands
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
archive_old_commands: ScheduleHandle,
|
|
|
|
#[cfg(feature = "multi-user")]
|
|
/// Responsible for purging expired cached login tokens
|
|
#[allow(dead_code)] // just need to keep this in scope
|
|
login_cache_sweeper_sh: ScheduleHandle,
|
|
}
|
|
|
|
impl Scheduler {
|
|
pub fn build(
|
|
event_queue: Arc<EventQueueListener>,
|
|
caserver: Option<Arc<CaServer>>,
|
|
pubserver: Option<Arc<PubServer>>,
|
|
bgp_analyser: Arc<BgpAnalyser>,
|
|
#[cfg(feature = "multi-user")] login_session_cache: Arc<LoginSessionCache>,
|
|
config: &Config,
|
|
actor: &Actor,
|
|
) -> Self {
|
|
let mut cas_event_triggers = None;
|
|
let mut cas_republish = None;
|
|
let mut cas_refresh = None;
|
|
|
|
if let Some(caserver) = caserver.as_ref() {
|
|
cas_event_triggers = Some(make_cas_event_triggers(
|
|
event_queue,
|
|
caserver.clone(),
|
|
pubserver.clone(),
|
|
actor.clone(),
|
|
));
|
|
|
|
cas_republish = Some(make_cas_republish(caserver.clone(), actor.clone()));
|
|
cas_refresh = Some(make_cas_refresh(caserver.clone(), config.ca_refresh, actor.clone()));
|
|
}
|
|
|
|
let announcements_refresh = make_announcements_refresh(bgp_analyser);
|
|
let archive_old_commands =
|
|
make_archive_old_commands(caserver, pubserver, config.archive_threshold_days, actor.clone());
|
|
#[cfg(feature = "multi-user")]
|
|
let login_cache_sweeper_sh = make_login_cache_sweeper_sh(login_session_cache);
|
|
|
|
Scheduler {
|
|
cas_event_triggers,
|
|
cas_republish,
|
|
cas_refresh,
|
|
announcements_refresh,
|
|
archive_old_commands,
|
|
#[cfg(feature = "multi-user")]
|
|
login_cache_sweeper_sh,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::cognitive_complexity)]
|
|
fn make_cas_event_triggers(
|
|
event_queue: Arc<EventQueueListener>,
|
|
caserver: Arc<CaServer>,
|
|
pubserver: Option<Arc<PubServer>>,
|
|
actor: Actor,
|
|
) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(1.seconds()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
|
|
rt.block_on( async {
|
|
for evt in event_queue.pop_all() {
|
|
match evt {
|
|
QueueEvent::ServerStarted => {
|
|
info!("Will re-sync all CAs with their parents and repository after startup");
|
|
caserver.resync_all(&actor).await;
|
|
let publisher = CaPublisher::new(caserver.clone(), pubserver.clone());
|
|
match caserver.ca_list(&actor) {
|
|
Err(e) => error!("Unable to obtain CA list: {}", e),
|
|
Ok(list) => {
|
|
for ca in list.cas() {
|
|
if publisher.publish(ca.handle(), &actor).await.is_err() {
|
|
error!("Unable to synchronise CA '{}' with its repository after startup", ca.handle());
|
|
} else {
|
|
info!("CA '{}' is in sync with its repository", ca.handle());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QueueEvent::Delta(handle, _version) => {
|
|
try_publish(&event_queue, caserver.clone(), pubserver.clone(), handle, &actor).await
|
|
}
|
|
QueueEvent::ReschedulePublish(handle, last_try) => {
|
|
if Time::five_minutes_ago().timestamp() > last_try.timestamp() {
|
|
try_publish(&event_queue, caserver.clone(), pubserver.clone(), handle, &actor).await
|
|
} else {
|
|
event_queue.push_back(QueueEvent::ReschedulePublish(handle, last_try));
|
|
}
|
|
}
|
|
QueueEvent::ResourceClassRemoved(handle, _, parent, revocations) => {
|
|
info!("Trigger send revoke requests for removed RC for '{}' under '{}'",handle,parent);
|
|
|
|
if caserver.send_revoke_requests(&handle, &parent, revocations, &actor).await.is_err() {
|
|
warn!("Could not revoke key for removed resource class. This is not \
|
|
an issue, because typically the parent will revoke our keys pro-actively, \
|
|
just before removing the resource class entitlements.");
|
|
}
|
|
}
|
|
QueueEvent::UnexpectedKey(handle, _, rcn, revocation) => {
|
|
info!(
|
|
"Trigger sending revocation requests for unexpected key with id '{}' in RC '{}'",
|
|
revocation.key(),
|
|
rcn
|
|
);
|
|
if let Err(e) = caserver
|
|
.send_revoke_unexpected_key(&handle, rcn, revocation, &actor).await {
|
|
error!("Could not revoke unexpected surplus key at parent: {}", e);
|
|
}
|
|
}
|
|
QueueEvent::ParentAdded(handle, _, parent) => {
|
|
info!(
|
|
"Get updates for '{}' from added parent '{}'.",
|
|
handle,
|
|
parent
|
|
);
|
|
if let Err(e) = caserver.get_updates_from_parent(&handle, &parent, &actor).await {
|
|
error!(
|
|
"Error getting updates for '{}', from parent '{}', error: '{}'",
|
|
&handle, &parent, e
|
|
)
|
|
}
|
|
}
|
|
QueueEvent::RepositoryConfigured(ca, _) => {
|
|
info!("Repository configured for '{}'", ca);
|
|
if let Err(e) = caserver.get_delayed_updates(&ca, &actor).await {
|
|
error!(
|
|
"Error getting updates after configuring repository for '{}', error: '{}'",
|
|
&ca, e
|
|
)
|
|
}
|
|
}
|
|
|
|
QueueEvent::RequestsPending(handle, _) => {
|
|
info!("Get updates for pending requests for '{}'.", handle);
|
|
if let Err(e) = caserver.send_all_requests(&handle, &actor).await {
|
|
error!(
|
|
"Failed to send pending requests for '{}', error '{}'",
|
|
&handle, e
|
|
);
|
|
}
|
|
}
|
|
QueueEvent::CleanOldRepo(handle, _) => {
|
|
let publisher = CaPublisher::new(caserver.clone(), pubserver.clone());
|
|
if let Err(e) = publisher.clean_up(&handle, &actor).await {
|
|
info!(
|
|
"Could not clean up old repo for '{}', it may be that it's no longer available. Got error '{}'",
|
|
&handle, e
|
|
);
|
|
}
|
|
if let Err(e) = caserver.remove_old_repo(&handle, &actor).await {
|
|
error!(
|
|
"Failed to remove old repo from ca '{}', error '{}'",
|
|
&handle, e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|
|
|
|
async fn try_publish(
|
|
event_queue: &Arc<EventQueueListener>,
|
|
caserver: Arc<CaServer>,
|
|
pubserver: Option<Arc<PubServer>>,
|
|
ca: Handle,
|
|
actor: &Actor,
|
|
) {
|
|
info!("Try to publish for '{}'", ca);
|
|
let publisher = CaPublisher::new(caserver.clone(), pubserver);
|
|
|
|
if let Err(e) = publisher.publish(&ca, actor).await {
|
|
if test_mode_enabled() {
|
|
error!("Failed to publish for '{}', error: {}", ca, e);
|
|
} else {
|
|
error!("Failed to publish for '{}' will reschedule, error: {}", ca, e);
|
|
event_queue.push_back(QueueEvent::ReschedulePublish(ca, Time::now()));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn make_cas_republish(caserver: Arc<CaServer>, actor: Actor) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(2.minutes()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
info!("Triggering background republication for all CAs");
|
|
if let Err(e) = caserver.republish_all(&actor).await {
|
|
error!("Background republishing failed: {}", e);
|
|
}
|
|
})
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|
|
|
|
fn make_cas_refresh(caserver: Arc<CaServer>, refresh_rate: u32, actor: Actor) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(refresh_rate.seconds()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
info!("Triggering background refresh for all CAs");
|
|
caserver.resync_all(&actor).await
|
|
})
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|
|
|
|
fn make_announcements_refresh(bgp_analyser: Arc<BgpAnalyser>) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(1.seconds()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
if let Err(e) = bgp_analyser.update().await {
|
|
error!("Failed to update BGP announcements: {}", e)
|
|
}
|
|
})
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|
|
|
|
fn make_archive_old_commands(
|
|
caserver: Option<Arc<CaServer>>,
|
|
pubserver: Option<Arc<PubServer>>,
|
|
archive_threshold_days: Option<i64>,
|
|
actor: Actor,
|
|
) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(1.hours()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
if let Some(days) = archive_threshold_days {
|
|
if let Some(caserver) = caserver.as_ref() {
|
|
if let Err(e) = caserver.archive_old_commands(days, &actor).await {
|
|
error!("Failed to archive old CA commands: {}", e)
|
|
}
|
|
}
|
|
|
|
if let Some(pubserver) = pubserver.as_ref() {
|
|
if let Err(e) = pubserver.archive_old_commands(days) {
|
|
error!("Failed to archive old Publication Server commands: {}", e)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|
|
|
|
#[cfg(feature = "multi-user")]
|
|
fn make_login_cache_sweeper_sh(cache: Arc<LoginSessionCache>) -> ScheduleHandle {
|
|
let mut scheduler = clokwerk::Scheduler::new();
|
|
scheduler.every(1.minutes()).run(move || {
|
|
let mut rt = Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
debug!("Triggering background sweep of session decryption cache");
|
|
|
|
if let Err(e) = cache.sweep() {
|
|
error!("Background sweep of session decryption cache failed: {}", e);
|
|
}
|
|
})
|
|
});
|
|
scheduler.watch_thread(Duration::from_millis(100))
|
|
}
|