diff --git a/src/server/mq.rs b/src/server/mq.rs index 22dcab4fb..e1e81a4e3 100644 --- a/src/server/mq.rs +++ b/src/server/mq.rs @@ -56,6 +56,11 @@ pub enum Task { parent: ParentHandle, }, + SyncChildren { + ca_handle: CaHandle, + ca_version: u64 + }, + ResourceClassRemoved { ca_handle: CaHandle, ca_version: u64, @@ -120,6 +125,20 @@ impl Task { ).finish() ) } + Task::SyncChildren { + ca_handle: ca, + .. + } => { + Cow::Owned( + Ident::builder( + const { Ident::make("sync_") } + ).push_handle( + ca + ).push_ident( + const { Ident::make("_children") } + ).finish() + ) + }, Task::SuspendChildrenIfNeeded { ca_handle: ca } => { Cow::Owned( Ident::builder( @@ -234,6 +253,12 @@ impl fmt::Display for Task { } => { write!(f, "synchronize CA '{ca}' with parent '{parent}'") } + Task::SyncChildren { + ca_handle: ca, + .. + } => { + write!(f, "synchronize CA '{ca}' with children") + } Task::RenewTestbedTa => write!(f, "renew testbed TA"), Task::SyncTrustAnchorProxySignerIfPossible => { write!(f, "sync TA Proxy and Signer if both in this server.") @@ -624,7 +649,21 @@ impl eventsourcing::PostSaveEventListener for TaskQueue { ); } } - + CertAuthEvent::ChildCertificateIssued { child, .. } => { + if let Err(e) = self.schedule( + Task::SyncChildren { + ca_handle: child.convert(), + ca_version: 0 + }, + now() + ) { + error!( + "Could not schedule sync from {}. Restart Krill or run 'krillc bulk refresh'. Error was: {}", + child, + e + ); + } + } _ => { // nothing to do } diff --git a/src/server/scheduler.rs b/src/server/scheduler.rs index 564aa59a0..96c717d7a 100644 --- a/src/server/scheduler.rs +++ b/src/server/scheduler.rs @@ -147,6 +147,11 @@ impl Scheduler { parent, } => self.sync_parent(ca, ca_version, parent).await, + Task::SyncChildren { + ca_handle: ca, + ca_version, + } => self.sync_children(ca, ca_version).await, + Task::RenewTestbedTa => self.renew_testbed_ta().await, Task::SyncTrustAnchorProxySignerIfPossible => { @@ -419,6 +424,30 @@ impl Scheduler { } } + async fn sync_children( + &self, + ca: CaHandle, + ca_version: u64, + ) -> Result { + if let Ok(parent) = self.ca_manager.get_ca(&ca) { + for child in parent.children() { + if let Ok(child) = self.ca_manager.get_ca(&child.convert()) { + debug!( + "Syncing child '{}' for parent {}", + child.handle(), + parent.handle() + ); + self.tasks.schedule(Task::SyncParent { + ca_handle: child.handle().convert(), + ca_version, + parent: parent.handle().convert() + }, now()).map_err(FatalError)?; + } + } + } + Ok(TaskResult::Done) + } + /// Resync the testbed TA signer and proxy async fn renew_testbed_ta(&self) -> Result { if let Err(e) = self.ca_manager.ta_renew_testbed_ta() { diff --git a/tests/functional_grandchild_resources.rs b/tests/functional_grandchild_resources.rs new file mode 100644 index 000000000..baec7869a --- /dev/null +++ b/tests/functional_grandchild_resources.rs @@ -0,0 +1,182 @@ +//! Tests grandchild resources updated. + +use krill::api; +use rpki::repository::resources::ResourceSet; + +use crate::common::sleep_seconds; + +mod common; + + +//------------ Test Function ------------------------------------------------- + +/// Test Krill parent/child/grandchild interactions. +/// +/// The setup is: +/// +/// ```text +/// TA +/// | +/// parent +/// | +/// child +/// | +/// grandchild +/// | +/// grandgrandchild +/// ``` +/// +/// The test verifies that: +/// * Grandchild gets the resources from child, +/// * Parent can restrict the resources on child, +/// * Grandchild will learn about those resource changes and adjust the +/// effective resources accordingly + +#[tokio::test] +async fn functional_resource_updates() { + let (server, _tmpdir) + = common::KrillServer::start_with_file_storage_and_testbed().await; + + let testbed = common::ca_handle("testbed"); + + let ca_parent = common::ca_handle("parent"); + let ca_parent_res = common::resources("AS65000", "10.0.0.0/16", ""); + + let ca_child = common::ca_handle("child"); + let ca_child_res = common::resources("AS65000", "10.0.0.0/16", ""); + + let ca_grandchild = common::ca_handle("grandchild"); + let ca_grandchild_res = common::resources("AS65000", "10.0.0.0/23", ""); + + let ca_grandgrandchild = common::ca_handle("grandgrandchild"); + let ca_grandgrandchild_res = common::resources("AS65000", "10.0.0.0/23", ""); + + let rcn0 = common::rcn(0); + + // Wait for the *testbed* CA to get its certificate, this means + // that all CAs which are set up as part of krill_start under the + // testbed config have been set up. + assert!( + server.wait_for_ca_resources(&testbed, &ResourceSet::all()).await + ); + + eprintln!(">>>> Set up CA parent under testbed."); + server.create_ca_with_repo(&ca_parent).await; + server.register_ca_with_parent(&ca_parent, &testbed, &ca_parent_res).await; + + eprintln!(">>>> Set up CA child under parent."); + server.create_ca_with_repo(&ca_child).await; + server.register_ca_with_parent(&ca_child, &ca_parent, &ca_child_res).await; + + eprintln!(">>>> Set up CA grandchild under child."); + server.create_ca_with_repo(&ca_grandchild).await; + server.register_ca_with_parent(&ca_grandchild, &ca_child, &ca_grandchild_res).await; + + eprintln!(">>>> Set up CA grandgrandchild under grandchild."); + server.create_ca_with_repo(&ca_grandgrandchild).await; + server.register_ca_with_parent(&ca_grandgrandchild, &ca_grandchild, &ca_grandgrandchild_res).await; + + eprintln!(">>>> Expect that CA child publishes the certificate for CA grandchild."); + let mut files = server.expected_objects(&ca_child); + files.push_mft_and_crl(&rcn0).await; + files.push_cer(&ca_grandchild, &rcn0).await; + assert!(files.wait_for_published().await); + + sleep_seconds(3).await; + + eprintln!(">>>> Update resources given to the child."); + let ca_child_new_res = common::resources("65000", "10.0.0.0/24", ""); + let ca_grandchild_new_res = + ca_grandchild_res.clone().intersection(&ca_child_new_res); + let ca_grandgrandchild_new_res = + ca_grandgrandchild_res.clone().intersection(&ca_grandchild_new_res); + eprintln!("New resources: {}", &ca_grandchild_new_res); + server.client().child_update( + &ca_parent, + &ca_child.convert(), + api::admin::UpdateChildRequest::resources(ca_child_new_res.clone()) + ).await.unwrap(); + assert!(server.wait_for_ca_resources(&ca_child, &ca_child_new_res).await); + + // Wait before the child gets aware of the new resources + sleep_seconds(3).await; + + eprintln!(">>>> Check new resources given to the child."); + assert_eq!( + server.client().ca_details(&ca_child).await.unwrap().resources, + ca_child_new_res.clone() + ); + // We want the 'parent' and 'child' to agree on the new resources. + // i.e. that the 'child' learnt that the new resource set it smaller. + assert_eq!( + server.client().ca_details(&ca_child).await.unwrap().resources, + server.client().child_details( + &ca_parent, &ca_child.convert() + ).await.unwrap().entitled_resources, + ); + + sleep_seconds(3).await; + + eprintln!(">>>> Check resources updated on grandchild."); + // We do not want the 'child' to update the entitled resources of the + // 'grandchild'. The resources of the grandchild will shrink automatically + // and grow again when the child gets more resources again. + // This checks the entitled resources have not changed. + assert_eq!( + server.client().child_details( + &ca_child, &ca_grandchild.convert() + ).await.unwrap().entitled_resources, + ca_grandchild_res.clone() + ); + // We do want 'grandchild' to learn of the effective resources after + // shrinking. + assert_ne!( + server.client().ca_details(&ca_grandchild).await.unwrap().resources, + ca_grandchild_res.clone() + ); + assert_eq!( + server.client().ca_details(&ca_grandchild).await.unwrap().resources, + ca_grandchild_new_res.clone() + ); + // We also want the grandgrandchild to learn of the new effective resources + assert_eq!( + server.client().child_details( + &ca_grandchild, &ca_grandgrandchild.convert() + ).await.unwrap().entitled_resources, + ca_grandgrandchild_res.clone() + ); + assert_eq!( + server.client().ca_details(&ca_grandgrandchild).await.unwrap().resources, + ca_grandgrandchild_new_res.clone() + ); + + eprintln!(">>>> Revert the resources given to the child."); + server.client().child_update( + &ca_parent, + &ca_child.convert(), + api::admin::UpdateChildRequest::resources(ca_child_res.clone()) + ).await.unwrap(); + assert!(server.wait_for_ca_resources(&ca_child, &ca_child_new_res).await); + + sleep_seconds(3).await; + + // Everything should be back to the original state again + assert_eq!( + server.client().ca_details(&ca_child).await.unwrap().resources, + ca_child_res.clone() + ); + assert_eq!( + server.client().child_details( + &ca_child, &ca_grandchild.convert() + ).await.unwrap().entitled_resources, + ca_grandchild_res.clone() + ); + assert_eq!( + server.client().ca_details(&ca_grandchild).await.unwrap().resources, + ca_grandchild_res.clone() + ); + assert_eq!( + server.client().ca_details(&ca_grandgrandchild).await.unwrap().resources, + ca_grandgrandchild_res.clone() + ); +}