use super::*;
use crate::sqlite::status::OrderStatus;
#[derive(Default)]
struct StubUpdater {
published: std::sync::Mutex<Vec<(String, String)>>,
deleted: std::sync::Mutex<Vec<(String, String)>>,
fail: bool,
}
#[async_trait]
impl dns01::DnsUpdater for StubUpdater {
async fn upsert_txt(&self, name: &str, value: &str) -> Result<(), String> {
if self.fail {
return Err("no DNS for you".to_string());
}
self.published
.lock()
.unwrap()
.push((name.to_string(), value.to_string()));
Ok(())
}
async fn delete_txt(&self, name: &str, value: &str) -> Result<(), String> {
self.deleted
.lock()
.unwrap()
.push((name.to_string(), value.to_string()));
Ok(())
}
}
fn with_updater(signer: RelaySigner, updater: Arc<StubUpdater>) -> RelaySigner {
let inner = Arc::try_unwrap(signer.0).unwrap_or_else(|_| panic!("sole owner"));
RelaySigner(Arc::new(Inner {
strategy: ChallengeStrategy::Dns01(updater),
..inner
}))
}
#[tokio::test(flavor = "multi_thread")]
async fn bypass_triggers_the_offered_challenge() {
let upstream = testsrv::start(Script {
chain: real_chain().await,
pose_challenge: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let queue = test_queue(db.clone());
let signer = RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap();
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Valid).await;
assert_eq!(
upstream.challenge_triggered(),
1,
"bypass still has to trigger the challenge the upstream posed"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn bypass_triggers_a_challenge_of_any_type() {
let upstream = testsrv::start(Script {
chain: real_chain().await,
pose_challenge: true,
offer_http01: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let queue = test_queue(db.clone());
let signer = RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap();
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Valid).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn bypass_fails_the_order_when_the_upstream_rejects() {
let upstream = testsrv::start(Script {
pose_challenge: true,
fail_challenge: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let queue = test_queue(db.clone());
let signer = RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap();
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Invalid).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn dns01_publishes_triggers_and_cleans_up() {
let upstream = testsrv::start(Script {
chain: real_chain().await,
pose_challenge: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let updater = Arc::new(StubUpdater::default());
let queue = test_queue(db.clone());
let signer = with_updater(
RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap(),
updater.clone(),
);
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Valid).await;
assert_eq!(
upstream.challenge_triggered(),
1,
"the challenge must be triggered"
);
let published = updater.published.lock().unwrap().clone();
assert_eq!(published.len(), 1);
let (name, value) = &published[0];
assert_eq!(name, "_acme-challenge.example.com.");
let thumbprint = crate::extractors::acme::jwk_thumbprint(signer.0.account.spki_der()).unwrap();
let expected =
crate::challenge::dns_01::expected_value(&format!("upstream-token-value.{thumbprint}"));
assert_eq!(value, &expected);
assert_eq!(updater.deleted.lock().unwrap().clone(), published);
}
#[tokio::test(flavor = "multi_thread")]
async fn dns01_cleans_up_after_a_rejected_challenge() {
let upstream = testsrv::start(Script {
pose_challenge: true,
fail_challenge: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let updater = Arc::new(StubUpdater::default());
let queue = test_queue(db.clone());
let signer = with_updater(
RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap(),
updater.clone(),
);
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Invalid).await;
assert_eq!(
updater.deleted.lock().unwrap().len(),
1,
"a failed attempt must still retract its record"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn dns01_refuses_an_upstream_offering_only_http01() {
let upstream = testsrv::start(Script {
pose_challenge: true,
offer_http01: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let queue = test_queue(db.clone());
let signer = with_updater(
RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap(),
Arc::new(StubUpdater::default()),
);
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db.clone(), &order.id, OrderStatus::Invalid).await;
let mapping = UpstreamOrder::find_by_order_id(&order.id, &db)
.await
.unwrap()
.unwrap();
assert!(
mapping.error.unwrap().contains("no dns-01"),
"the reason must name what was missing"
);
assert_eq!(upstream.challenge_triggered(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn dns01_fails_when_the_record_cannot_be_published() {
let upstream = testsrv::start(Script {
pose_challenge: true,
..Script::default()
})
.await;
let dir = TempDir::new("upstream");
let db = database().await;
let queue = test_queue(db.clone());
let signer = with_updater(
RelaySigner::from_config(
&config(&upstream, &dir),
vec!["default".to_string()],
&relay_parts(db.clone(), no_notifiers(), queue.clone()),
&crate::signer::CarriedState::new(),
)
.unwrap(),
Arc::new(StubUpdater {
fail: true,
..StubUpdater::default()
}),
);
let _runner = TestRunner::start(queue, &signer);
let order = ready_order(db.clone()).await;
signer
.issue(
&order.id,
&csr_der(),
&identifiers(),
RequestedValidity::default(),
)
.await
.unwrap();
await_status(db, &order.id, OrderStatus::Invalid).await;
assert_eq!(
upstream.challenge_triggered(),
0,
"nothing should be triggered when the record was never published"
);
}