mod challenge_tests { use super::*; use axum::{body::Body, Extension}; use http::Request; use http_body_util::BodyExt; use tower::ServiceExt; async fn fixture() -> (Arc, tempfile::TempDir) { let directory = tempfile::tempdir().unwrap(); let certs = directory.path().join("certs"); crate::tls::initialize_default(&certs).unwrap(); let root = certs.join("acme-http01/.well-known/acme-challenge"); std::fs::create_dir_all(&root).unwrap(); std::fs::write(root.join("sample_token"), b"sample_token.thumbprint").unwrap(); let config = crate::config::Config::default(); let policy = crate::config::ValidationPolicy { proxy_addr: "127.0.0.1:8080".parse().unwrap(), admin_addr: "127.0.0.1:9090".parse().unwrap(), tls_addr: None, cert_dir: Some(certs), data_dir: None, }; let store = crate::db::Store::open(&directory.path().join("db"), &config) .await .unwrap(); let auth = crate::auth::Auth::new(store.clone(), directory.path().to_owned()) .await .unwrap(); let snapshot = crate::config::compile(config, 0, &policy).unwrap(); let app = App::new( store, snapshot, policy, auth, 8, Duration::from_secs(2), Duration::from_secs(2), Duration::from_secs(30), ); *app.acme.job.lock().unwrap() = Some(Job { id: "test".into(), certificate_id: "test".into(), status: "running", kind: "issue", provider: "http01".into(), domains: vec!["example.org".into()], environment: "staging".into(), message: "test".into(), log_directory: "test/logs".into(), certificate: None, }); (app, directory) } #[tokio::test] async fn http01_serves_only_current_job_tokens_and_correct_head() { let (app, _directory) = fixture().await; let router = crate::proxy::router(app.clone()); let req = |method, host, path| { Request::builder() .method(method) .uri(path) .header("host", host) .body(Body::empty()) .unwrap() }; let response = router .clone() .oneshot(req( "GET", "example.org", "/.well-known/acme-challenge/sample_token", )) .await .unwrap(); assert_eq!(response.status(), 200); assert_eq!(response.headers()["cache-control"], "no-store"); assert_eq!( response .into_body() .collect() .await .unwrap() .to_bytes() .as_ref(), b"sample_token.thumbprint" ); let response = router .clone() .oneshot(req( "HEAD", "example.org", "/.well-known/acme-challenge/sample_token", )) .await .unwrap(); assert_eq!(response.status(), 200); assert_eq!(response.headers()["content-length"], "23"); assert!(response .into_body() .collect() .await .unwrap() .to_bytes() .is_empty()); for path in [ "/.well-known/acme-challenge/../sample_token", "/.well-known/acme-challenge/sample_token?x=1", "/.well-known/acme-challenge/%2e%2e", "/.well-known/acme-challenge/missing", ] { assert_eq!( router .clone() .oneshot(req("GET", "example.org", path)) .await .unwrap() .status(), 404 ); } assert_eq!( router .clone() .oneshot(req( "GET", "unrelated.example.org", "/.well-known/acme-challenge/sample_token" )) .await .unwrap() .status(), 404 ); assert_eq!( router .clone() .oneshot(req( "POST", "example.org", "/.well-known/acme-challenge/sample_token" )) .await .unwrap() .status(), 405 ); let tls = router .clone() .layer(Extension(crate::server::Transport { tls: true })); assert_eq!( tls.oneshot(req( "GET", "example.org", "/.well-known/acme-challenge/sample_token" )) .await .unwrap() .status(), 404 ); app.acme.job.lock().unwrap().as_mut().unwrap().status = "ready"; assert_eq!( router .oneshot(req( "GET", "example.org", "/.well-known/acme-challenge/sample_token" )) .await .unwrap() .status(), 404 ); app.store.close().await; } }