mod dns01_strategy;
mod eab;
mod http01_strategy;
mod lifecycle;
mod renewal;
fn test_jobs_config() -> crate::config::JobsConfig {
crate::config::JobsConfig {
poll_interval_ms: 5,
max_attempts: 1,
retry_base_seconds: 0,
retry_max_seconds: 0,
lease_seconds: 5,
retention_days: 0,
..crate::config::JobsConfig::default()
}
}
fn test_queue(database: Arc<Database>) -> crate::jobs::JobQueue {
crate::jobs::JobQueue::new(database, &test_jobs_config())
}
fn test_queue_with(
database: Arc<Database>,
config: &crate::config::JobsConfig,
) -> crate::jobs::JobQueue {
crate::jobs::JobQueue::new(database, config)
}
struct TestRunner {
shutdown: tokio::sync::watch::Sender<bool>,
}
impl TestRunner {
fn start(queue: crate::jobs::JobQueue, signer: &RelaySigner) -> Self {
Self::start_with(queue, signer, test_jobs_config())
}
fn start_notifying(
queue: crate::jobs::JobQueue,
signer: &RelaySigner,
notifiers: crate::notify::Notifiers,
) -> Self {
Self::start_inner(queue, signer, test_jobs_config(), Some(notifiers))
}
fn start_with(
queue: crate::jobs::JobQueue,
signer: &RelaySigner,
config: crate::config::JobsConfig,
) -> Self {
Self::start_inner(queue, signer, config, None)
}
fn start_inner(
queue: crate::jobs::JobQueue,
signer: &RelaySigner,
config: crate::config::JobsConfig,
notifiers: Option<crate::notify::Notifiers>,
) -> Self {
let mut registry = crate::jobs::JobRegistry::new();
for handler in signer.jobs() {
registry.register(handler).unwrap();
}
if let Some(notifiers) = notifiers {
registry
.register(Arc::new(crate::notify::NotifyJob::new(notifiers)))
.unwrap();
}
let (shutdown, receiver) = tokio::sync::watch::channel(false);
crate::jobs::spawn_runner(queue, Arc::new(registry), &config, receiver);
Self { shutdown }
}
}
impl Drop for TestRunner {
fn drop(&mut self) {
let _ = self.shutdown.send(true);
}
}
fn test_resolver() -> Arc<dyn crate::dns::Resolver> {
Arc::new(crate::dns::HickoryResolver::from_system_uncached().unwrap())
}
use super::account::kid_path;
use super::client::UpstreamError;
use super::flow::settle;
use super::http01::TokenStore;
use super::*;
use crate::audit::ClientContext;
use crate::notify::{NotifyDispatcher, NotifyEvent};
use crate::signer::local_ca::LocalCa;
use crate::sqlite::account::Account;
use crate::sqlite::nonce::now_secs;
use crate::sqlite::order::Order;
use crate::sqlite::status::OrderStatus;
use crate::testutil::TempDir;
use std::collections::HashMap;
use std::path::PathBuf;
use testsrv::{Script, Upstream};
fn key_path(dir: &TempDir) -> String {
dir.join("upstream.key").to_string_lossy().into_owned()
}
fn config(upstream: &Upstream, dir: &TempDir) -> RelayConfig {
RelayConfig {
directory_url: upstream.directory_url(),
account_key_path: key_path(dir),
poll_interval_ms: 5,
poll_timeout_secs: 5,
..RelayConfig::default()
}
}
async fn database() -> Arc<Database> {
Arc::new(Database::connect_in_memory().await.unwrap())
}
fn no_notifiers() -> crate::notify::Notifiers {
HashMap::new().into()
}
fn relay_parts(
database: Arc<Database>,
notifiers: crate::notify::Notifiers,
jobs: crate::jobs::JobQueue,
) -> crate::signer::SignerParts {
crate::signer::SignerParts {
database: database.clone(),
notifiers,
metrics: crate::testutil::test_metrics(database),
egress: crate::testutil::egress_with(test_resolver()),
jobs,
}
}
struct RecordingNotifyBackend {
events: std::sync::Mutex<Vec<NotifyEvent>>,
}
impl RecordingNotifyBackend {
fn new() -> Self {
Self {
events: std::sync::Mutex::new(Vec::new()),
}
}
}
#[async_trait]
impl crate::notify::NotifyBackend for RecordingNotifyBackend {
fn name(&self) -> &'static str {
"recording"
}
async fn send(&self, event: &NotifyEvent) -> Result<(), crate::notify::NotifyError> {
self.events.lock().unwrap().push(event.clone());
Ok(())
}
}
async fn await_recorded(recorder: &Arc<RecordingNotifyBackend>) {
for _ in 0..200 {
if !recorder.events.lock().unwrap().is_empty() {
return;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
panic!("no notification was delivered within the budget");
}
fn recording_slot(recorder: Arc<RecordingNotifyBackend>) -> crate::notify::BackendSlot {
let every: Vec<String> = crate::config::ALL_NOTIFY_EVENTS
.iter()
.map(|kind| (*kind).to_string())
.collect();
crate::notify::BackendSlot::new("recording", recorder, &every)
}
async fn ready_order_for(profile: &str, database: Arc<Database>) -> Order {
let (account, _) = Account::find_or_create(
profile,
uuid::Uuid::new_v4().as_bytes(),
Vec::new(),
&ClientContext::default(),
&database,
)
.await
.unwrap();
let mut order = Order::create(
profile,
&account.id,
vec![Identifier::dns("example.com")],
now_secs() + 3600,
None,
None,
&database,
)
.await
.unwrap();
order.mark_ready(&database).await.unwrap();
order
}
async fn real_chain() -> String {
let ca = LocalCa::generate_in_memory("ecdsa-p256", 90).unwrap();
let key_pair = rcgen::KeyPair::generate().unwrap();
let params = rcgen::CertificateParams::new(vec!["example.com".to_string()]).unwrap();
let csr = params.serialize_request(&key_pair).unwrap();
match ca
.issue(
"ord-x",
csr.der(),
&identifiers(),
RequestedValidity::default(),
)
.await
{
Ok(IssueOutcome::Issued(chain)) => chain,
_ => panic!("the in-memory CA must issue"),
}
}
async fn ready_order(database: Arc<Database>) -> Order {
let (account, _) = Account::find_or_create(
"default",
uuid::Uuid::new_v4().as_bytes(),
Vec::new(),
&ClientContext::default(),
&database,
)
.await
.unwrap();
let mut order = Order::create(
"default",
&account.id,
vec![Identifier::dns("example.com")],
now_secs() + 3600,
None,
None,
&database,
)
.await
.unwrap();
order.mark_ready(&database).await.unwrap();
order
}
fn ca_signed_leaf_with_aki() -> Vec<u8> {
let ca_key = rcgen::KeyPair::generate().unwrap();
let mut ca_params = rcgen::CertificateParams::new(vec!["ca.example".to_string()]).unwrap();
ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
let ca_pem = ca_params.self_signed(&ca_key).unwrap().pem();
let issuer = rcgen::Issuer::from_ca_cert_pem(&ca_pem, ca_key).unwrap();
let leaf_key = rcgen::KeyPair::generate().unwrap();
let mut leaf_params = rcgen::CertificateParams::new(vec!["example.com".to_string()]).unwrap();
leaf_params.use_authority_key_identifier_extension = true;
leaf_params
.signed_by(&leaf_key, &issuer)
.unwrap()
.der()
.to_vec()
}
fn csr_der() -> Vec<u8> {
let key_pair = rcgen::KeyPair::generate().unwrap();
let params = rcgen::CertificateParams::new(vec!["example.com".to_string()]).unwrap();
params.serialize_request(&key_pair).unwrap().der().to_vec()
}
fn startup_error(result: anyhow::Result<RelaySigner>) -> String {
match result {
Err(error) => error.to_string(),
Ok(_) => panic!("this configuration must not build"),
}
}
fn identifiers() -> Vec<Identifier> {
vec![Identifier::dns("example.com")]
}
async fn await_status(database: Arc<Database>, order_id: &str, wanted: OrderStatus) -> Order {
for _ in 0..200 {
let order = Order::find_by_id(order_id, &database)
.await
.unwrap()
.unwrap();
if order.status == wanted {
return order;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
let order = Order::find_by_id(order_id, &database)
.await
.unwrap()
.unwrap();
panic!("order stayed {}, expected {wanted}", order.status);
}