mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-08-17 21:25:53 +02:00
829 lines
26 KiB
Rust
829 lines
26 KiB
Rust
#![allow(dead_code)] // Different tests use different parts.
|
||
|
||
use std::env;
|
||
use std::str::FromStr;
|
||
use std::time::Duration;
|
||
use url::Url;
|
||
use log::LevelFilter;
|
||
use log::{debug, error};
|
||
use reqwest::StatusCode;
|
||
use rpki::uri;
|
||
use rpki::ca::idexchange::{
|
||
CaHandle, ChildHandle, ChildRequest, ParentResponse, ServiceUri
|
||
};
|
||
use rpki::ca::provisioning::ResourceClassName;
|
||
use rpki::repository::resources::{Asn, ResourceSet};
|
||
use tempfile::TempDir;
|
||
use tokio::sync::oneshot;
|
||
use tokio::task::JoinHandle;
|
||
use tokio::time::{sleep, timeout};
|
||
use krill::api;
|
||
use krill::api::admin::Token;
|
||
use krill::commons::httpclient;
|
||
use krill::commons::crypto::OpenSslSignerConfig;
|
||
use krill::cli::client::KrillClient;
|
||
use krill::constants::REPOSITORY_DIR;
|
||
use krill::config::{
|
||
AuthType, Config, ConfigDefaults, HttpsMode, IssuanceTimingConfig,
|
||
LogType, MetricsConfig, RrdpUpdatesConfig, SignerConfig,
|
||
SignerReference, SignerType, TestBed,
|
||
};
|
||
use krill::daemon::http::tls_keys::HTTPS_SUB_DIR;
|
||
use krill::daemon::start::start_krill_daemon;
|
||
use krill::tasigner::TaTimingConfig;
|
||
|
||
|
||
//------------ TestConfig ----------------------------------------------------
|
||
|
||
/// A test config builder.
|
||
pub struct TestConfig {
|
||
storage_uri: Url,
|
||
data_dir: TempDir,
|
||
port: u16,
|
||
enable_testbed: bool,
|
||
enable_ca_refresh: bool,
|
||
enable_suspend: bool,
|
||
second_signer: bool,
|
||
}
|
||
|
||
impl TestConfig {
|
||
pub fn mem_storage() -> Self {
|
||
let data_dir = TempDir::new().unwrap();
|
||
Self::new(
|
||
Url::parse(
|
||
&format!(
|
||
"memory://{}", hex::encode(rand::random::<[u8; 8]>())
|
||
)
|
||
).unwrap(),
|
||
data_dir,
|
||
)
|
||
}
|
||
|
||
pub fn file_storage() -> Self {
|
||
let data_dir = TempDir::new().unwrap();
|
||
Self::new(
|
||
Url::parse(
|
||
&format!("local://{}/data/", data_dir.path().display())
|
||
).unwrap() ,
|
||
data_dir,
|
||
)
|
||
}
|
||
|
||
fn new(storage_uri: Url, data_dir: TempDir) -> Self {
|
||
Self {
|
||
storage_uri,
|
||
data_dir,
|
||
port: ConfigDefaults::port(),
|
||
enable_testbed: false,
|
||
enable_ca_refresh: false,
|
||
enable_suspend: false,
|
||
second_signer: false,
|
||
}
|
||
}
|
||
|
||
pub fn alternative_port(mut self) -> Self {
|
||
self.port += 1;
|
||
self
|
||
}
|
||
|
||
pub fn enable_testbed(mut self) -> Self {
|
||
self.enable_testbed = true;
|
||
self
|
||
}
|
||
|
||
pub fn enable_ca_refresh(mut self) -> Self {
|
||
self.enable_ca_refresh = true;
|
||
self
|
||
}
|
||
|
||
pub fn enable_suspend (mut self) -> Self {
|
||
self.enable_suspend = true;
|
||
self
|
||
}
|
||
|
||
pub fn enable_second_signer(mut self) -> Self {
|
||
self.second_signer = true;
|
||
self
|
||
}
|
||
|
||
#[allow(unused_mut)] // for mut self if feature = "hsm"
|
||
pub fn finalize(mut self) -> (Config, TempDir) {
|
||
let ip = ConfigDefaults::ip();
|
||
let port = self.port;
|
||
|
||
let https_mode = HttpsMode::Generate;
|
||
|
||
let log_level = if env::var("KRILLTEST_DEBUG").is_ok() {
|
||
LevelFilter::Debug
|
||
}
|
||
else {
|
||
eprintln!(
|
||
"Limiting Krill log output. \
|
||
Set KRILLTEST_DEBUG=1 to enable debug log to stderr."
|
||
);
|
||
LevelFilter::Error
|
||
};
|
||
let log_type = LogType::Stderr;
|
||
let syslog_facility = ConfigDefaults::syslog_facility();
|
||
let auth_type = AuthType::AdminToken;
|
||
let admin_token = Token::from("secret");
|
||
#[cfg(feature = "multi-user")]
|
||
let auth_users = None;
|
||
#[cfg(feature = "multi-user")]
|
||
let auth_openidconnect = None;
|
||
let auth_roles = ConfigDefaults::auth_roles();
|
||
|
||
let default_signer = SignerReference::default();
|
||
let one_off_signer = SignerReference::default();
|
||
let signer_probe_retry_seconds =
|
||
ConfigDefaults::signer_probe_retry_seconds();
|
||
|
||
// Multiple signers are only needed and can only be configured when
|
||
// the "hsm" feature is enabled.
|
||
#[cfg(not(feature = "hsm"))]
|
||
{
|
||
self.second_signer = false;
|
||
}
|
||
|
||
let signers = match self.second_signer {
|
||
false => ConfigDefaults::signers(),
|
||
true => vec![SignerConfig::new(
|
||
"Second Test Signer".to_string(),
|
||
SignerType::OpenSsl(OpenSslSignerConfig::default()),
|
||
)],
|
||
};
|
||
|
||
let ca_refresh_seconds =
|
||
if self.enable_ca_refresh { 1 } else { 86400 };
|
||
let ca_refresh_jitter_seconds =
|
||
if self.enable_ca_refresh { 0 } else { 86400 };
|
||
let ca_refresh_parents_batch_size = 10;
|
||
let post_limit_api = ConfigDefaults::post_limit_api();
|
||
let post_limit_rfc8181 = ConfigDefaults::post_limit_rfc8181();
|
||
let post_limit_rfc6492 = ConfigDefaults::post_limit_rfc6492();
|
||
let post_protocol_msg_timeout_seconds =
|
||
ConfigDefaults::post_protocol_msg_timeout_seconds();
|
||
|
||
let bgp_api_enabled = false;
|
||
let bgp_api_uri = ConfigDefaults::bgp_api_uri();
|
||
let bgp_api_cache_duration = ConfigDefaults::bgp_api_cache_duration();
|
||
|
||
let roa_aggregate_threshold = 3;
|
||
let roa_deaggregate_threshold = 2;
|
||
|
||
let timing_publish_next_hours =
|
||
ConfigDefaults::timing_publish_next_hours();
|
||
let timing_publish_next_jitter_hours =
|
||
ConfigDefaults::timing_publish_next_jitter_hours();
|
||
let timing_publish_hours_before_next =
|
||
ConfigDefaults::timing_publish_hours_before_next();
|
||
let timing_child_certificate_valid_weeks =
|
||
ConfigDefaults::timing_child_certificate_valid_weeks();
|
||
let timing_child_certificate_reissue_weeks_before =
|
||
ConfigDefaults::timing_child_certificate_reissue_weeks_before();
|
||
let timing_roa_valid_weeks = ConfigDefaults::timing_roa_valid_weeks();
|
||
let timing_roa_reissue_weeks_before =
|
||
ConfigDefaults::timing_roa_reissue_weeks_before();
|
||
let timing_aspa_valid_weeks =
|
||
ConfigDefaults::timing_aspa_valid_weeks();
|
||
let timing_aspa_reissue_weeks_before =
|
||
ConfigDefaults::timing_aspa_reissue_weeks_before();
|
||
let timing_bgpsec_valid_weeks =
|
||
ConfigDefaults::timing_bgpsec_valid_weeks();
|
||
let timing_bgpsec_reissue_weeks_before =
|
||
ConfigDefaults::timing_bgpsec_reissue_weeks_before();
|
||
|
||
let issuance_timing = IssuanceTimingConfig {
|
||
timing_publish_next_hours,
|
||
timing_publish_next_jitter_hours,
|
||
timing_publish_hours_before_next,
|
||
timing_child_certificate_valid_weeks,
|
||
timing_child_certificate_reissue_weeks_before,
|
||
timing_roa_valid_weeks,
|
||
timing_roa_reissue_weeks_before,
|
||
timing_aspa_valid_weeks,
|
||
timing_aspa_reissue_weeks_before,
|
||
timing_bgpsec_valid_weeks,
|
||
timing_bgpsec_reissue_weeks_before,
|
||
};
|
||
|
||
let rrdp_updates_config = RrdpUpdatesConfig {
|
||
rrdp_delta_files_min_seconds: 0,
|
||
rrdp_delta_files_min_nr: 5,
|
||
rrdp_delta_files_max_seconds: 1,
|
||
rrdp_delta_files_max_nr: 50,
|
||
rrdp_delta_interval_min_seconds: 0,
|
||
rrdp_files_archive: false,
|
||
};
|
||
|
||
let metrics = MetricsConfig {
|
||
metrics_hide_ca_details: false,
|
||
metrics_hide_child_details: false,
|
||
metrics_hide_publisher_details: false,
|
||
metrics_hide_roa_details: false,
|
||
};
|
||
|
||
let testbed = if self.enable_testbed {
|
||
krill::constants::enable_test_mode();
|
||
krill::constants::enable_test_announcements();
|
||
Some(TestBed::new(
|
||
uri::Rsync::from_str("rsync://localhost/ta/ta.cer").unwrap(),
|
||
uri::Https::from_string(
|
||
format!("https://localhost:{port}/ta/ta.cer")
|
||
).unwrap(),
|
||
uri::Https::from_string(
|
||
format!("https://localhost:{port}/rrdp/")
|
||
).unwrap(),
|
||
uri::Rsync::from_str("rsync://localhost/repo/").unwrap(),
|
||
))
|
||
} else {
|
||
None
|
||
};
|
||
|
||
let suspend_child_after_inactive_seconds =
|
||
if self.enable_suspend { Some(3) } else { None };
|
||
|
||
let mut res = Config {
|
||
ip,
|
||
port,
|
||
https_mode,
|
||
storage_uri: self.storage_uri,
|
||
use_history_cache: false,
|
||
tls_keys_dir: Some(self.data_dir.path().join(HTTPS_SUB_DIR)),
|
||
repo_dir: Some(self.data_dir.path().join(REPOSITORY_DIR)),
|
||
ta_support_enabled: false, /* but, enabled by testbed where
|
||
* applicable */
|
||
ta_signer_enabled: false, // same as above
|
||
pid_file: Some(self.data_dir.path().join("krill.pid")),
|
||
service_uri: None,
|
||
log_level,
|
||
log_type,
|
||
log_file: None,
|
||
syslog_facility,
|
||
admin_token,
|
||
auth_type,
|
||
#[cfg(feature = "multi-user")]
|
||
auth_users,
|
||
#[cfg(feature = "multi-user")]
|
||
auth_openidconnect,
|
||
auth_roles,
|
||
default_signer,
|
||
one_off_signer,
|
||
signers,
|
||
signer_probe_retry_seconds,
|
||
ca_refresh_seconds,
|
||
ca_refresh_jitter_seconds,
|
||
ca_refresh_parents_batch_size,
|
||
suspend_child_after_inactive_seconds,
|
||
suspend_child_after_inactive_hours: None,
|
||
post_limit_api,
|
||
post_limit_rfc8181,
|
||
rfc8181_log_dir: None,
|
||
post_limit_rfc6492,
|
||
rfc6492_log_dir: None,
|
||
post_protocol_msg_timeout_seconds,
|
||
bgp_api_enabled,
|
||
bgp_api_uri,
|
||
bgp_api_cache_duration,
|
||
roa_aggregate_threshold,
|
||
roa_deaggregate_threshold,
|
||
issuance_timing,
|
||
rrdp_updates_config,
|
||
metrics,
|
||
testbed,
|
||
benchmark: None,
|
||
ta_timing: TaTimingConfig::default(),
|
||
};
|
||
let _ = res.init_logging(); // Allow failing on repeast attempts.
|
||
res.process().unwrap();
|
||
(res, self.data_dir)
|
||
}
|
||
}
|
||
|
||
|
||
//------------ KrillServer ---------------------------------------------------
|
||
|
||
/// A test Krill server.
|
||
pub struct KrillServer {
|
||
join: JoinHandle<()>,
|
||
running: Option<oneshot::Receiver<()>>,
|
||
client: KrillClient,
|
||
}
|
||
|
||
impl KrillServer {
|
||
/// Starts a default test server.
|
||
///
|
||
/// The server will use memory storage. The function will start the
|
||
/// server and wait for it to become ready.
|
||
pub async fn start() -> (Self, TempDir) {
|
||
let (config, data_dir) = TestConfig::mem_storage().finalize();
|
||
(Self::start_with_config(config).await, data_dir)
|
||
}
|
||
|
||
/// Starts a test server with testbed enabled.
|
||
///
|
||
/// The server will use memory storage. The function will start the
|
||
/// server and wait for it to become ready.
|
||
pub async fn start_with_testbed() -> (Self, TempDir) {
|
||
let (config, data_dir)
|
||
= TestConfig::mem_storage().enable_testbed().finalize();
|
||
(Self::start_with_config(config).await, data_dir)
|
||
}
|
||
|
||
/// Starts a test server with testbed enabled and a modified config.
|
||
pub async fn start_with_config_testbed(
|
||
op: impl FnOnce(&mut Config)
|
||
) -> (Self, TempDir) {
|
||
let (mut config, data_dir)
|
||
= TestConfig::mem_storage().enable_testbed().finalize();
|
||
op(&mut config);
|
||
(Self::start_with_config(config).await, data_dir)
|
||
}
|
||
|
||
/// Starts a test server with file storage with testbed enabled.
|
||
///
|
||
/// The server will use memory storage. The function will start the
|
||
/// server and wait for it to become ready.
|
||
pub async fn start_with_file_storage_and_testbed() -> (Self, TempDir) {
|
||
let (config, data_dir)
|
||
= TestConfig::file_storage().enable_testbed().finalize();
|
||
(Self::start_with_config(config).await, data_dir)
|
||
}
|
||
|
||
/// Starts a second test server with testbed enabled.
|
||
///
|
||
/// The server will use memory storage. The function will start the
|
||
/// server and wait for it to become ready.
|
||
pub async fn start_second_with_testbed() -> (Self, TempDir) {
|
||
let (config, data_dir) = TestConfig::mem_storage()
|
||
.alternative_port().enable_testbed().enable_second_signer()
|
||
.finalize();
|
||
(Self::start_with_config(config).await, data_dir)
|
||
}
|
||
|
||
/// Starts a publication daemon.
|
||
///
|
||
/// The server will use the given interval in seconds for its RRDP
|
||
/// udpates.
|
||
pub async fn start_pubd(
|
||
rrdp_delta_min_interval_seconds: u32
|
||
) -> (Self, TempDir) {
|
||
let (mut config, data_dir) = TestConfig::mem_storage()
|
||
.alternative_port()
|
||
.enable_second_signer() // XXX Not sure why?
|
||
.finalize();
|
||
config.rrdp_updates_config.rrdp_delta_interval_min_seconds =
|
||
rrdp_delta_min_interval_seconds;
|
||
let port = config.port;
|
||
let server = Self::start_with_config(config).await;
|
||
server.pubserver_init(port).await;
|
||
(server, data_dir)
|
||
}
|
||
|
||
/// Starts a test server with the given config.
|
||
///
|
||
/// This will start the server and wait for it to become ready.
|
||
pub async fn start_with_config(config: Config) -> Self {
|
||
let uri = ServiceUri::from_str(
|
||
&format!(
|
||
"https://{}:{}/",
|
||
config.ip.first().unwrap(), config.port
|
||
)
|
||
).unwrap();
|
||
let client = KrillClient::new(uri, config.admin_token.clone());
|
||
let (tx, running) = oneshot::channel();
|
||
let mut res = Self {
|
||
join: tokio::spawn(async {
|
||
if let Err(err) = start_krill_daemon(
|
||
config.into(), Some(tx)
|
||
).await {
|
||
error!("Krill failed to start: {err}");
|
||
}
|
||
}),
|
||
running: Some(running),
|
||
client,
|
||
};
|
||
res.ready().await;
|
||
res
|
||
}
|
||
|
||
async fn ready(&mut self) {
|
||
let running = match self.running.take() {
|
||
Some(running) => running,
|
||
None => return
|
||
};
|
||
assert!(running.await.is_ok());
|
||
match timeout(
|
||
Duration::from_secs(1),
|
||
self.client.authorized(),
|
||
).await {
|
||
Ok(Ok(_)) => { debug!("health check succeded") },
|
||
err => panic!("health check failed: {err:?}"),
|
||
}
|
||
}
|
||
|
||
pub async fn pubserver_init(&self, port: u16) {
|
||
self.client().pubserver_init(
|
||
uri::Https::from_str(
|
||
&format!("https://localhost:{port}/test-rrdp/")
|
||
).unwrap(),
|
||
uri::Rsync::from_str(
|
||
"rsync://localhost/dedicated-repo/"
|
||
).unwrap(),
|
||
).await.unwrap();
|
||
}
|
||
|
||
/// Aborts the server and waits for it to conclude cleanup.
|
||
pub async fn abort(self) {
|
||
self.join.abort();
|
||
let _ = self.join.await;
|
||
}
|
||
|
||
/// Returns a Krill client for this server.
|
||
pub fn client(&self) -> &KrillClient {
|
||
&self.client
|
||
}
|
||
|
||
/// Returns an expected files check object.
|
||
pub fn expected_objects<'s>(
|
||
&'s self, ca: &'s CaHandle
|
||
) -> ExpectedObjects<'s> {
|
||
ExpectedObjects::new(self, ca)
|
||
}
|
||
}
|
||
|
||
|
||
impl KrillServer {
|
||
/// Creates a CA publishing in the built-in publisher.
|
||
pub async fn create_ca_with_repo(&self, ca: &CaHandle) {
|
||
// Create the CA
|
||
self.client.ca_add(ca.clone()).await.unwrap();
|
||
|
||
// Add the CA as a publisher
|
||
let request = self.client().repo_request(ca).await.unwrap();
|
||
self.client().publishers_add(request).await.unwrap();
|
||
|
||
// Get a Repository Response for the CA.
|
||
let response = self.client().publisher_response(
|
||
&ca.convert()
|
||
).await.unwrap();
|
||
|
||
// Update the repo for the CA.
|
||
self.client().repo_update(ca, response).await.unwrap();
|
||
}
|
||
|
||
/// Registers a CA with a parent managed by the same server.
|
||
pub async fn register_ca_with_parent(
|
||
&self, ca: &CaHandle, parent: &CaHandle, resources: &ResourceSet
|
||
) {
|
||
let request = self.client().child_request(ca).await.unwrap();
|
||
let response = self.add_child(
|
||
parent, ca.convert(), request, resources.clone()
|
||
).await;
|
||
self.client.parent_add(
|
||
ca, api::admin::ParentCaReq { handle: parent.convert(), response }
|
||
).await.unwrap();
|
||
assert!(self.wait_for_ca_resources(ca, resources).await);
|
||
}
|
||
|
||
/// Add a child to the CA.
|
||
pub async fn add_child(
|
||
&self,
|
||
ca: &CaHandle,
|
||
child: ChildHandle,
|
||
child_request: ChildRequest,
|
||
resources: ResourceSet,
|
||
) -> ParentResponse {
|
||
let id_cert = child_request.validate().unwrap();
|
||
self.client.child_add(ca, child, resources, id_cert).await.unwrap()
|
||
}
|
||
|
||
pub async fn ca_key_for_rcn(
|
||
&self, ca: &CaHandle, rcn: &ResourceClassName
|
||
) -> api::ca::CertifiedKeyInfo {
|
||
self.client.ca_details(ca).await.unwrap()
|
||
.resource_classes.get(rcn).unwrap()
|
||
.keys.current_key().unwrap().clone()
|
||
}
|
||
|
||
pub async fn ca_new_key_for_rcn(
|
||
&self, ca: &CaHandle, rcn: &ResourceClassName,
|
||
) -> api::ca::CertifiedKeyInfo {
|
||
self.client.ca_details(ca).await.unwrap()
|
||
.resource_classes.get(rcn).unwrap()
|
||
.keys.new_key().unwrap().clone()
|
||
}
|
||
|
||
pub async fn current_ca_resources(&self, ca: &CaHandle) -> ResourceSet {
|
||
let details = self.client().ca_details(ca).await.unwrap();
|
||
|
||
let mut res = ResourceSet::default();
|
||
for rc in details.resource_classes.values() {
|
||
if let Some(resources) = rc.keys.current_resources() {
|
||
res = res.union(resources)
|
||
}
|
||
}
|
||
res
|
||
}
|
||
|
||
pub async fn check_configured_roas(
|
||
&self, ca: &CaHandle, expected: &[api::roa::RoaConfiguration]
|
||
) -> bool {
|
||
let roas = self.client().roas_list(ca).await.unwrap().into_vec();
|
||
assert_eq!(roas.len(), expected.len());
|
||
|
||
// Copy the expected configs, but convert them to an explicit max-len
|
||
// because Krill always stores configs that way to avoid duplicate
|
||
// equivalent entries.
|
||
let expected = expected.iter().map(|entry| {
|
||
let mut entry = entry.clone();
|
||
entry.set_explicit_max_length();
|
||
entry
|
||
}).collect::<Vec<_>>();
|
||
|
||
for roa in roas.iter().map(|item| &item.roa_configuration)
|
||
{
|
||
if !expected.contains(roa) {
|
||
let expected_strs: Vec<_> = expected.into_iter().map(|e| {
|
||
e.to_string()
|
||
}).collect();
|
||
eprintln!(
|
||
"Actual configuration: '{}' not in expected: {}",
|
||
roa, expected_strs.join(", ")
|
||
);
|
||
return false
|
||
}
|
||
}
|
||
true
|
||
}
|
||
|
||
pub async fn wait_for_ca_resources(
|
||
&self, ca: &CaHandle, resources: &ResourceSet
|
||
) -> bool {
|
||
for _ in 0..300 {
|
||
sleep_millis(100).await;
|
||
if self.current_ca_resources(ca).await.contains(resources) {
|
||
return true
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
pub async fn wait_for_state_new_key(&self, ca: &CaHandle) -> bool {
|
||
for _ in 0..300 {
|
||
let ca = self.client().ca_details(ca).await.unwrap();
|
||
|
||
// wait for ALL RCs to become state new key
|
||
let rc_map = &ca.resource_classes;
|
||
|
||
let expected = rc_map.len();
|
||
let mut found = 0;
|
||
|
||
for rc in rc_map.values() {
|
||
if let api::ca::ResourceClassKeysInfo::RollNew(_) = rc.keys {
|
||
found += 1;
|
||
}
|
||
}
|
||
|
||
if found == expected {
|
||
return true;
|
||
}
|
||
|
||
sleep_millis(100).await
|
||
}
|
||
false
|
||
}
|
||
|
||
pub async fn wait_for_state_active(&self, ca: &CaHandle) -> bool {
|
||
for _ in 0..300 {
|
||
let ca = self.client().ca_details(ca).await.unwrap();
|
||
|
||
// wait for ALL RCs to become state active key
|
||
let rc_map = &ca.resource_classes;
|
||
|
||
let expected = rc_map.len();
|
||
let mut found = 0;
|
||
|
||
for rc in rc_map.values() {
|
||
if let api::ca::ResourceClassKeysInfo::Active(_) = rc.keys {
|
||
found += 1;
|
||
}
|
||
}
|
||
|
||
if found == expected {
|
||
return true;
|
||
}
|
||
|
||
sleep_millis(100).await
|
||
}
|
||
false
|
||
}
|
||
|
||
pub async fn http_get(
|
||
&self, rel_url: &str
|
||
) -> Result<String, httpclient::Error> {
|
||
httpclient::get_text(
|
||
&format!("{}{}", self.client().base_uri(), rel_url),
|
||
None
|
||
).await
|
||
}
|
||
|
||
pub async fn http_get_404(
|
||
&self, rel_url: &str
|
||
) -> bool {
|
||
match httpclient::get_text(
|
||
&format!("{}{}", self.client().base_uri(), rel_url),
|
||
None
|
||
).await {
|
||
Ok(_) => false,
|
||
Err(err) if err.status_code() == Some(StatusCode::NOT_FOUND) => {
|
||
true
|
||
}
|
||
Err(err) => panic!("{}", err),
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//------------ ExpectedObjects -----------------------------------------------
|
||
|
||
pub struct ExpectedObjects<'a> {
|
||
pub server: &'a KrillServer,
|
||
pub ca: &'a CaHandle,
|
||
pub files: Vec<String>,
|
||
}
|
||
|
||
impl<'a> ExpectedObjects<'a> {
|
||
pub fn new(
|
||
server: &'a KrillServer,
|
||
ca: &'a CaHandle,
|
||
) -> Self {
|
||
Self { server, ca, files: Vec::new() }
|
||
}
|
||
|
||
pub fn push(&mut self, file: String) {
|
||
self.files.push(file);
|
||
}
|
||
|
||
pub async fn push_mft_and_crl(&mut self, rcn: &ResourceClassName) {
|
||
let rc_key = self.server.ca_key_for_rcn(self.ca, rcn).await;
|
||
self.push(rc_key.incoming_cert.mft_name().to_string());
|
||
self.push(rc_key.incoming_cert.crl_name().to_string());
|
||
}
|
||
|
||
pub async fn push_new_key_mft_and_crl(&mut self, rcn: &ResourceClassName) {
|
||
let rc_key = self.server.ca_new_key_for_rcn(self.ca, rcn).await;
|
||
self.push(rc_key.incoming_cert.mft_name().to_string());
|
||
self.push(rc_key.incoming_cert.crl_name().to_string());
|
||
}
|
||
|
||
pub async fn push_cer(&mut self, ca: &CaHandle, rcn: &ResourceClassName) {
|
||
let rc_key = self.server.ca_key_for_rcn(ca, rcn).await;
|
||
self.push(
|
||
api::ca::ObjectName::from_key(&rc_key.key_id, "cer").to_string()
|
||
)
|
||
}
|
||
|
||
pub fn push_roas<'b>(
|
||
&mut self,
|
||
roas: impl IntoIterator<Item = &'b api::roa::RoaConfiguration>,
|
||
) {
|
||
self.extend(roas.into_iter().map(|roa| {
|
||
api::ca::ObjectName::from(
|
||
roa.payload.into_explicit_max_length()
|
||
).to_string()
|
||
}))
|
||
}
|
||
|
||
pub async fn wait_for_published(&self) -> bool {
|
||
self.wait_for_published_at(self.server).await
|
||
}
|
||
|
||
pub async fn wait_for_published_at(&self, server: &KrillServer) -> bool {
|
||
let publisher = self.ca.convert();
|
||
|
||
for _ in 0..600 {
|
||
sleep_millis(100).await;
|
||
let details = server.client.publisher_details(
|
||
&publisher
|
||
).await.unwrap();
|
||
if details.current_files.len() == self.files.len() {
|
||
let current_files: Vec<_> =
|
||
details.current_files.iter().map(|p| &p.uri).collect();
|
||
let mut all_matched = true;
|
||
for o in &self.files {
|
||
if !current_files.iter().any(|uri| uri.ends_with(o)) {
|
||
all_matched = false;
|
||
}
|
||
}
|
||
if all_matched {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
let details = server.client.publisher_details(
|
||
&publisher
|
||
).await.unwrap();
|
||
|
||
eprintln!("Published files didn’t match for {}", self.ca);
|
||
eprintln!("Found:");
|
||
for file in &details.current_files {
|
||
eprintln!(" {}", file.uri);
|
||
}
|
||
eprintln!("Expected:");
|
||
for file in &self.files {
|
||
eprintln!(" {file}");
|
||
}
|
||
|
||
false
|
||
}
|
||
}
|
||
|
||
impl std::iter::Extend<String> for ExpectedObjects<'_> {
|
||
fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
|
||
self.files.extend(iter)
|
||
}
|
||
}
|
||
|
||
|
||
//------------ Misc Helpers --------------------------------------------------
|
||
|
||
pub fn ca_handle(s: &str) -> CaHandle {
|
||
CaHandle::from_str(s).unwrap()
|
||
}
|
||
|
||
pub fn rcn(nr: u32) -> ResourceClassName {
|
||
ResourceClassName::from(nr)
|
||
}
|
||
|
||
pub fn resources(asn: &str, v4: &str, v6: &str) -> ResourceSet {
|
||
ResourceSet::from_strs(asn, v4, v6).unwrap()
|
||
}
|
||
|
||
pub fn asn(asn: &str) -> Asn {
|
||
Asn::from_str(asn).unwrap()
|
||
}
|
||
|
||
pub fn ipv4_resources(v4: &str) -> ResourceSet {
|
||
resources("", v4, "")
|
||
}
|
||
|
||
pub fn roa_conf(s: &str) -> api::roa::RoaConfiguration {
|
||
api::roa::RoaConfiguration::from_str(s).unwrap()
|
||
}
|
||
|
||
pub fn roa_payload(s: &str) -> api::roa::RoaPayload {
|
||
api::roa::RoaPayload::from_str(s).unwrap()
|
||
}
|
||
|
||
pub fn aspa_def(s: &str) -> api::aspa::AspaDefinition {
|
||
api::aspa::AspaDefinition::from_str(s).unwrap()
|
||
}
|
||
|
||
pub async fn sleep_seconds(secs: u64) {
|
||
sleep(Duration::from_secs(secs)).await
|
||
}
|
||
|
||
pub async fn sleep_millis(secs: u64) {
|
||
sleep(Duration::from_millis(secs)).await
|
||
}
|
||
|
||
/// Checks if a result has certain non-200 HTTP status code.
|
||
///
|
||
/// Returns `true` if it does, returns `false` if the result is `Ok(_)`
|
||
/// and panics in all other cases.
|
||
pub fn check_status<T>(
|
||
res: Result<T, httpclient::Error>,
|
||
code: StatusCode,
|
||
) -> bool {
|
||
match res {
|
||
Ok(_) => false,
|
||
Err(err) => {
|
||
match err.status_code() {
|
||
Some(some) => some == code,
|
||
None => {
|
||
panic!("{}", err);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Checks if a result has bad request status.
|
||
///
|
||
/// Returns `true` if it does, returns `false` if the result is `Ok(_)`
|
||
/// and panics in all other cases.
|
||
pub fn check_bad_request<T>(res: Result<T, httpclient::Error>) -> bool {
|
||
check_status(res, StatusCode::BAD_REQUEST)
|
||
}
|
||
|
||
/// Checks if a result has bad request status.
|
||
///
|
||
/// Returns `true` if it does, returns `false` if the result is `Ok(_)`
|
||
/// and panics in all other cases.
|
||
pub fn check_not_found<T>(res: Result<T, httpclient::Error>) -> bool {
|
||
check_status(res, StatusCode::NOT_FOUND)
|
||
}
|
||
|