Files
NLnetLabs-krill/tests/functional_aspa.rs
Martin HoffmannandGitHub c277854eed Refactor the application call flow. (#1361)
This PR refactors how requests are processed in Krill. It creates a
clear distinction between the HTTP server running async on a Tokio
runtime and the core of Krill running as regular sync code on a thread
pool.

This means that those portions of the core that were previously async,
notable the HTTP requests to remote parents and publishers, end up
blocking a thread now. For most things this should be fine. For
potentially long-running tasks, we have a separate thread pool so they
won’t block all of Krill.
2026-04-02 11:59:18 +02:00

216 lines
7.4 KiB
Rust

//! Test manipulating ASPA definitions.
use std::slice;
use std::str::FromStr;
use reqwest::StatusCode;
use rpki::ca::idexchange::CaHandle;
use rpki::ca::provisioning::ResourceClassName;
use rpki::repository::resources::ResourceSet;
use krill::api::aspa::{
AspaDefinition, AspaDefinitionList, AspaProvidersUpdate, CustomerAsn,
ProviderAsn,
};
use krill::api::ca::ObjectName;
use krill::commons::httpclient;
mod common;
//------------ Test Function -------------------------------------------------
/// Tests sdding, updating, and deleting ASPA definitions.
///
/// Uses the following layout:
///
/// ```text
/// TA
/// |
/// testbed
/// |
/// CA
/// ```
#[tokio::test]
async fn functional_aspa() {
let server = common::KrillServer::start_with_testbed().await;
let testbed = common::ca_handle("testbed");
let ca = common::ca_handle("CA");
let ca_res = common::resources("AS65000", "10.0.0.0/16", "");
// 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' under 'testbed'.");
server.create_ca_with_repo(&ca).await;
server.register_ca_with_parent(&ca, &testbed, &ca_res).await;
eprintln!(">>>> Reject ASPA without providers.");
let aspa = aspa_definition("AS65000 => <none>");
assert!(!server.try_add_aspa(&ca, aspa.clone()).await);
assert!(server.wait_for_objects(&ca, &[]).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), &[]);
eprintln!(">>>> Reject ASPA using customer as provider.");
let aspa = aspa_definition("AS65000 => AS65000, AS65003, AS65005");
assert!(!server.try_add_aspa(&ca, aspa.clone()).await);
assert!(server.wait_for_objects(&ca, &[]).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), &[]);
eprintln!(">>>> Add an ASPA under CA.");
let aspa = aspa_definition("AS65000 => AS65002, AS65003, AS65005");
assert!(server.try_add_aspa(&ca, aspa.clone()).await);
let aspas = slice::from_ref(&aspa);
assert!(server.wait_for_objects(&ca, aspas).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), aspas);
eprintln!(">>>> Update an existing ASPA.");
assert!(server.try_update_aspa(
&ca, "AS65000", ["AS65006"], ["AS65002"]
).await);
let aspa = aspa_definition("AS65000 => AS65003, AS65005, AS65006");
let aspas = slice::from_ref(&aspa);
assert!(server.wait_for_objects(&ca, aspas).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), aspas);
eprintln!(">>>> Reject update that adds customer as provider.");
assert!(!server.try_update_aspa(&ca, "AS65000", ["AS65000"], []).await);
// Use `aspas` from before.
assert!(server.wait_for_objects(&ca, aspas).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), aspas);
eprintln!(">>>> Removing all providers should result in delete.");
assert!(server.try_update_aspa(
&ca, "AS65000", [], ["AS65003", "AS65005", "AS65006"]
).await);
assert!(server.wait_for_objects(&ca, &[]).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), &[]);
eprintln!(">>>> Adding provider to non-existing customer should add it.");
// This is useful for two reasons:
// 1) it allows for automation using just updates
// 2) because empty provider lists were accepted in Krill <0.13.0
// we need the code to deal with removing all providers, which
// will remove the AspaConfig when replayed, and then adding
// some provider again.
assert!(server.try_update_aspa(
&ca, "AS65000", ["AS65003", "AS65005", "AS65006"], []
).await);
let aspa = aspa_definition("AS65000 => AS65003, AS65005, AS65006");
let aspas = slice::from_ref(&aspa);
assert!(server.wait_for_objects(&ca, aspas).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), aspas);
eprintln!(">>>> Add existing and remove nonexisting provider.");
assert!(server.try_update_aspa(
&ca, "AS65000",
["AS65002", "AS65005"],
["AS65006", "AS65007"],
).await);
let aspa = aspa_definition("AS65000 => AS65002, AS65003, AS65005");
let aspas = slice::from_ref(&aspa);
assert!(server.wait_for_objects(&ca, aspas).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), aspas);
eprintln!(">>>> Delete an existing ASPA.");
server.client().aspas_delete_single(
&ca, customer("AS65000")
).await.unwrap();
assert!(server.wait_for_objects(&ca, &[]).await);
assert_eq!(server.aspa_definitions(&ca).await.as_slice(), &[]);
}
//------------ Extend KrillServer --------------------------------------------
impl common::KrillServer {
/// Adds a single ASPA definition.
///
/// Returns whether adding succeeded. Panics on error if anything other
/// than the server refusing to add the ASPA happened.
async fn try_add_aspa(
&self, ca: &CaHandle, aspa: AspaDefinition
) -> bool {
match self.client().aspas_add_single(ca, aspa).await {
Ok(_) => true,
Err(err) => {
assert!(matches!(
err,
httpclient::Error::ErrorResponseWithJson(
_, StatusCode::BAD_REQUEST, _
)
));
false
}
}
}
/// Updates a single ASPA definition.
///
/// Returns whether adding succeeded. Panics on error if anything other
/// than the server refusing to add the ASPA happened.
async fn try_update_aspa(
&self,
ca: &CaHandle,
customer_str: &str,
add: impl IntoIterator<Item=&str>,
remove: impl IntoIterator<Item=&str>,
) -> bool {
match self.client().aspas_update_single(
ca,
customer(customer_str),
AspaProvidersUpdate {
added: add.into_iter().map(provider).collect(),
removed: remove.into_iter().map(provider).collect(),
}
).await {
Ok(_) => true,
Err(err) => {
assert!(matches!(
err,
httpclient::Error::ErrorResponseWithJson(
_, StatusCode::BAD_REQUEST, _
)
));
false
}
}
}
/// Checks that the given CA has the given ASPA definitions.
async fn wait_for_objects<'s>(
&'s self, ca: &'s CaHandle, aspas: &'s [AspaDefinition]
) -> bool {
let mut files = self.expected_objects(ca);
files.push_mft_and_crl(&ResourceClassName::from(0)).await;
files.extend(aspas.iter().map(|aspa| {
ObjectName::aspa_from_customer(aspa.customer).to_string()
}));
files.wait_for_published().await
}
/// Returns the current ASPA definitions.
async fn aspa_definitions(&self, ca: &CaHandle) -> AspaDefinitionList {
self.client().aspas_list(ca).await.unwrap()
}
}
//------------ Misc Helpers --------------------------------------------------
pub fn aspa_definition(s: &str) -> AspaDefinition {
AspaDefinition::from_str(s).unwrap()
}
pub fn customer(s: &str) -> CustomerAsn {
CustomerAsn::from_str(s).unwrap()
}
pub fn provider(s: &str) -> ProviderAsn {
ProviderAsn::from_str(s).unwrap()
}