use std::io::Write;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tempfile::NamedTempFile;
fn toml_path(path: &std::path::Path) -> String {
path.display().to_string().replace('\\', "/")
}
fn write_config(content: &str) -> NamedTempFile {
let mut f = NamedTempFile::new().unwrap();
f.write_all(content.as_bytes()).unwrap();
f.flush().unwrap();
f
}
fn install_crypto() {
eggress_transport_tls::install_default_crypto_provider();
}
fn self_signed_cert() -> (String, String) {
let cert_params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
let key_pair = rcgen::KeyPair::generate().unwrap();
let cert_der = cert_params.self_signed(&key_pair).unwrap();
(cert_der.pem(), key_pair.serialize_pem())
}
async fn wait_ready(state: &eggress_runtime::RuntimeState) {
for _ in 0..100 {
if state.readiness.load(Ordering::Relaxed) {
return;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
panic!("timeout waiting for readiness");
}
fn get_addrs(state: &eggress_runtime::RuntimeState) -> Vec<std::net::SocketAddr> {
state
.listener_addrs
.lock()
.unwrap()
.iter()
.filter_map(|a| *a)
.collect()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn tls_listener_accepts_https_connection() {
install_crypto();
let (cert_pem, key_pem) = self_signed_cert();
let cert_file = NamedTempFile::new().unwrap();
let key_file = NamedTempFile::new().unwrap();
std::fs::write(cert_file.path(), &cert_pem).unwrap();
std::fs::write(key_file.path(), &key_pem).unwrap();
let config = format!(
r#"
version = 1
[[listeners]]
name = "https-in"
bind = "127.0.0.1:0"
protocols = ["http"]
[listeners.tls]
cert = "{}"
key = "{}"
"#,
toml_path(cert_file.path()),
toml_path(key_file.path())
);
let f = write_config(&config);
let path = f.path().to_str().unwrap();
let mut sup = eggress_runtime::ServiceSupervisor::start(path).unwrap();
let state = sup.state().clone();
let token = sup.shutdown_token();
let jh = tokio::task::spawn_blocking(move || sup.run());
wait_ready(&state).await;
let addrs = get_addrs(&state);
let addr = addrs[0];
let client_config = eggress_transport_tls::TlsClientConfigBuilder::new()
.with_insecure()
.build()
.unwrap();
let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
let boxed: eggress_core::BoxStream = Box::new(tcp);
let tls_result = eggress_transport_tls::tls_connect(boxed, client_config, "localhost").await;
assert!(
tls_result.is_ok(),
"TLS handshake should succeed: {:?}",
tls_result.err()
);
let mut tls_stream = tls_result.unwrap();
use tokio::io::AsyncWriteExt;
tls_stream
.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.await
.unwrap();
tls_stream.flush().await.unwrap();
use tokio::io::AsyncReadExt;
let mut buf = [0u8; 1024];
let _ = tokio::time::timeout(Duration::from_secs(3), async {
let _ = tls_stream.read(&mut buf).await;
})
.await;
token.cancel();
jh.await.ok();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn plaintext_to_tls_listener_fails() {
install_crypto();
let (cert_pem, key_pem) = self_signed_cert();
let cert_file = NamedTempFile::new().unwrap();
let key_file = NamedTempFile::new().unwrap();
std::fs::write(cert_file.path(), &cert_pem).unwrap();
std::fs::write(key_file.path(), &key_pem).unwrap();
let config = format!(
r#"
version = 1
[[listeners]]
name = "https-in"
bind = "127.0.0.1:0"
protocols = ["http"]
[listeners.tls]
cert = "{}"
key = "{}"
"#,
toml_path(cert_file.path()),
toml_path(key_file.path())
);
let f = write_config(&config);
let path = f.path().to_str().unwrap();
let mut sup = eggress_runtime::ServiceSupervisor::start(path).unwrap();
let state = sup.state().clone();
let token = sup.shutdown_token();
let jh = tokio::task::spawn_blocking(move || sup.run());
wait_ready(&state).await;
let addrs = get_addrs(&state);
let addr = addrs[0];
let mut tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
use tokio::io::AsyncWriteExt;
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.await
.unwrap();
use tokio::io::AsyncReadExt;
let mut buf = [0u8; 1];
let result =
tokio::time::timeout(Duration::from_secs(3), async { tcp.read(&mut buf).await }).await;
match result {
Ok(Ok(0)) => {}
Ok(Ok(_)) => {}
Ok(Err(_)) => {}
Err(_) => {}
}
token.cancel();
jh.await.ok();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn mixed_tls_and_plaintext_listeners() {
install_crypto();
let (cert_pem, key_pem) = self_signed_cert();
let cert_file = NamedTempFile::new().unwrap();
let key_file = NamedTempFile::new().unwrap();
std::fs::write(cert_file.path(), &cert_pem).unwrap();
std::fs::write(key_file.path(), &key_pem).unwrap();
let config = format!(
r#"
version = 1
[[listeners]]
name = "plaintext"
bind = "127.0.0.1:0"
protocols = ["http"]
[[listeners]]
name = "tls"
bind = "127.0.0.1:0"
protocols = ["http"]
[listeners.tls]
cert = "{}"
key = "{}"
"#,
toml_path(cert_file.path()),
toml_path(key_file.path())
);
let f = write_config(&config);
let path = f.path().to_str().unwrap();
let mut sup = eggress_runtime::ServiceSupervisor::start(path).unwrap();
let state = sup.state().clone();
let token = sup.shutdown_token();
let jh = tokio::task::spawn_blocking(move || sup.run());
wait_ready(&state).await;
let addrs = get_addrs(&state);
let plaintext_addr = addrs[0];
let tls_addr = addrs[1];
let mut tcp = tokio::net::TcpStream::connect(plaintext_addr)
.await
.unwrap();
use tokio::io::AsyncWriteExt;
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.await
.unwrap();
use tokio::io::AsyncReadExt;
let mut buf = [0u8; 1024];
let _ = tokio::time::timeout(Duration::from_secs(3), async {
let _ = tcp.read(&mut buf).await;
})
.await;
let client_config = eggress_transport_tls::TlsClientConfigBuilder::new()
.with_insecure()
.build()
.unwrap();
let tcp = tokio::net::TcpStream::connect(tls_addr).await.unwrap();
let boxed: eggress_core::BoxStream = Box::new(tcp);
let mut tls_stream = eggress_transport_tls::tls_connect(boxed, client_config, "localhost")
.await
.unwrap();
tls_stream
.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.await
.unwrap();
tls_stream.flush().await.unwrap();
let mut buf = [0u8; 1024];
let _ = tokio::time::timeout(Duration::from_secs(3), async {
let _ = tls_stream.read(&mut buf).await;
})
.await;
token.cancel();
jh.await.ok();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn tls_listener_with_wrong_server_name_fails() {
install_crypto();
let (cert_pem, key_pem) = self_signed_cert();
let cert_file = NamedTempFile::new().unwrap();
let key_file = NamedTempFile::new().unwrap();
std::fs::write(cert_file.path(), &cert_pem).unwrap();
std::fs::write(key_file.path(), &key_pem).unwrap();
let config = format!(
r#"
version = 1
[[listeners]]
name = "https-in"
bind = "127.0.0.1:0"
protocols = ["http"]
[listeners.tls]
cert = "{}"
key = "{}"
"#,
toml_path(cert_file.path()),
toml_path(key_file.path())
);
let f = write_config(&config);
let path = f.path().to_str().unwrap();
let mut sup = eggress_runtime::ServiceSupervisor::start(path).unwrap();
let state = sup.state().clone();
let token = sup.shutdown_token();
let jh = tokio::task::spawn_blocking(move || sup.run());
wait_ready(&state).await;
let addrs = get_addrs(&state);
let addr = addrs[0];
let client_config = eggress_transport_tls::TlsClientConfigBuilder::new()
.with_system_roots()
.unwrap()
.build()
.unwrap();
let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
let boxed: eggress_core::BoxStream = Box::new(tcp);
let result =
eggress_transport_tls::tls_connect(boxed, client_config, "wrong.example.com").await;
assert!(result.is_err(), "TLS with wrong server name should fail");
token.cancel();
jh.await.ok();
}