use std::fs;
use std::io;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use rcgen::generate_simple_self_signed;
use runewarp::{
PreparedClient, PreparedServer, generate_client_identity, initialize_manual_server_certificate,
load_client_settings, load_server_settings,
};
use rustls::RootCertStore;
use rustls::pki_types::{CertificateDer, ServerName};
use rustls_acme::CertCache;
use rustls_acme::acme::LETS_ENCRYPT_PRODUCTION_DIRECTORY;
use rustls_acme::caches::DirCache;
use tempfile::tempdir;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::time::timeout;
use tokio_rustls::{TlsAcceptor, TlsConnector};
#[tokio::test]
async fn prepared_server_binds_the_existing_runtime_from_validated_settings() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
)
.unwrap();
let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();
let server = PreparedServer::bind(&settings, localhost(0), localhost(0))
.await
.unwrap();
assert_ne!(server.public_addr().unwrap().port(), 0);
assert_ne!(server.tunnel_addr().unwrap().port(), 0);
}
#[tokio::test]
async fn prepared_server_binds_listener_addresses_loaded_from_settings() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
public-bind-address = "127.0.0.1:0"
tunnel-bind-address = "127.0.0.1:0"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
)
.unwrap();
let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();
let server = PreparedServer::bind(
&settings,
settings.public_bind_address,
settings.tunnel_connection_bind_address,
)
.await
.unwrap();
assert!(server.public_addr().unwrap().ip().is_loopback());
assert!(server.tunnel_addr().unwrap().ip().is_loopback());
}
fn localhost(port: u16) -> SocketAddr {
SocketAddr::from((Ipv4Addr::LOCALHOST, port))
}
#[tokio::test]
async fn prepared_server_drops_public_tls_addressed_to_the_server_hostname() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
client_identity.client_identity.to_string(),
)
.unwrap();
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
"#,
client_identity.client_identity
),
)
.unwrap();
let backend = spawn_tls_backend(
vec![
"app.example.test".to_owned(),
"tunnel.example.test".to_owned(),
],
*b"pong",
)
.await;
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "."
[[client.services]]
backend-address = "__BACKEND_ADDRESS__"
"#
.replace("__BACKEND_ADDRESS__", &backend.0.to_string()),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let client_task = tokio::spawn(client.run());
let app_response = request_tls_response(public_addr, &backend.1, "app.example.test")
.await
.unwrap();
assert_eq!(app_response, *b"pong");
let server_hostname_result = timeout(
Duration::from_secs(1),
request_tls_response(public_addr, &backend.1, "tunnel.example.test"),
)
.await;
assert!(matches!(server_hostname_result, Ok(Err(_))));
backend.2.abort();
server_task.abort();
client_task.abort();
let _ = backend.2.await;
let _ = server_task.await;
let _ = client_task.await;
}
#[tokio::test]
async fn prepared_server_drops_public_tls_for_unconfigured_public_hostnames() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
client_identity.client_identity.to_string(),
)
.unwrap();
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
"#,
client_identity.client_identity
),
)
.unwrap();
let backend = spawn_tls_backend(
vec![
"app.example.test".to_owned(),
"other.example.test".to_owned(),
],
*b"pong",
)
.await;
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "."
[[client.services]]
backend-address = "__BACKEND_ADDRESS__"
"#
.replace("__BACKEND_ADDRESS__", &backend.0.to_string()),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let client_task = tokio::spawn(client.run());
let app_response = request_tls_response(public_addr, &backend.1, "app.example.test")
.await
.unwrap();
assert_eq!(app_response, *b"pong");
let unknown_hostname_result = timeout(
Duration::from_secs(1),
request_tls_response(public_addr, &backend.1, "other.example.test"),
)
.await;
assert!(matches!(unknown_hostname_result, Ok(Err(_))));
backend.2.abort();
server_task.abort();
client_task.abort();
let _ = backend.2.await;
let _ = server_task.await;
let _ = client_task.await;
}
#[tokio::test]
async fn prepared_client_routes_mirrored_public_hostnames_to_matching_services() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
client_identity.client_identity.to_string(),
)
.unwrap();
let app_backend = spawn_tls_backend(vec!["app.example.test".to_owned()], *b"app!").await;
let api_backend = spawn_tls_backend(vec!["api.example.test".to_owned()], *b"api!").await;
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test", "api.example.test"]
client-identity = "{}"
"#,
client_identity.client_identity
),
)
.unwrap();
fs::write(
tempdir.path().join("client.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "."
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "{}"
[[client.services]]
public-hostnames = ["api.example.test"]
backend-address = "{}"
"#,
app_backend.0, api_backend.0
),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let client_task = tokio::spawn(client.run());
let app_response = request_tls_response(public_addr, &app_backend.1, "app.example.test")
.await
.unwrap();
assert_eq!(app_response, *b"app!");
let api_response = request_tls_response(public_addr, &api_backend.1, "api.example.test")
.await
.unwrap();
assert_eq!(api_response, *b"api!");
app_backend.2.abort();
api_backend.2.abort();
server_task.abort();
client_task.abort();
let _ = app_backend.2.await;
let _ = api_backend.2.await;
let _ = server_task.await;
let _ = client_task.await;
}
#[tokio::test]
async fn prepared_client_rejects_streams_without_a_matching_service() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
client_identity.client_identity.to_string(),
)
.unwrap();
let backend = spawn_tls_backend(
vec!["app.example.test".to_owned(), "api.example.test".to_owned()],
*b"pong",
)
.await;
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test", "api.example.test"]
client-identity = "{}"
"#,
client_identity.client_identity
),
)
.unwrap();
fs::write(
tempdir.path().join("client.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "."
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "{}"
"#,
backend.0
),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let client_task = tokio::spawn(client.run());
let app_response = request_tls_response(public_addr, &backend.1, "app.example.test")
.await
.unwrap();
assert_eq!(app_response, *b"pong");
let api_result = timeout(
Duration::from_secs(1),
request_tls_response(public_addr, &backend.1, "api.example.test"),
)
.await;
assert!(matches!(api_result, Ok(Err(_))));
backend.2.abort();
server_task.abort();
client_task.abort();
let _ = backend.2.await;
let _ = server_task.await;
let _ = client_task.await;
}
#[tokio::test]
async fn prepared_server_routes_different_public_hostnames_to_different_tunnels() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let app_client_identity = generate_client_identity().unwrap();
fs::create_dir(tempdir.path().join("client-app")).unwrap();
fs::write(
tempdir.path().join("client-app/client.crt"),
app_client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-app/client.key"),
app_client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-app/client-identity.txt"),
app_client_identity.client_identity.to_string(),
)
.unwrap();
let api_client_identity = generate_client_identity().unwrap();
fs::create_dir(tempdir.path().join("client-api")).unwrap();
fs::write(
tempdir.path().join("client-api/client.crt"),
api_client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-api/client.key"),
api_client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-api/client-identity.txt"),
api_client_identity.client_identity.to_string(),
)
.unwrap();
let app_backend = spawn_tls_backend(vec!["app.example.test".to_owned()], *b"app!").await;
let api_backend = spawn_tls_backend(vec!["api.example.test".to_owned()], *b"api!").await;
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
[[server.tunnels]]
public-hostnames = ["api.example.test"]
client-identity = "{}"
"#,
app_client_identity.client_identity, api_client_identity.client_identity
),
)
.unwrap();
fs::write(
tempdir.path().join("client-app.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "client-app"
[[client.services]]
backend-address = "{}"
"#,
app_backend.0
),
)
.unwrap();
fs::write(
tempdir.path().join("client-api.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "client-api"
[[client.services]]
backend-address = "{}"
"#,
api_backend.0
),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let app_client_settings =
load_client_settings(&tempdir.path().join("client-app.toml")).unwrap();
let app_client = PreparedClient::connect_to(&app_client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let app_client_task = tokio::spawn(app_client.run());
let api_client_settings =
load_client_settings(&tempdir.path().join("client-api.toml")).unwrap();
let api_client = PreparedClient::connect_to(&api_client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let api_client_task = tokio::spawn(api_client.run());
let app_response = request_tls_response(public_addr, &app_backend.1, "app.example.test")
.await
.unwrap();
assert_eq!(app_response, *b"app!");
let api_response = request_tls_response(public_addr, &api_backend.1, "api.example.test")
.await
.unwrap();
assert_eq!(api_response, *b"api!");
app_backend.2.abort();
api_backend.2.abort();
server_task.abort();
app_client_task.abort();
api_client_task.abort();
let _ = app_backend.2.await;
let _ = api_backend.2.await;
let _ = server_task.await;
let _ = app_client_task.await;
let _ = api_client_task.await;
}
#[tokio::test]
async fn replacing_one_tunnel_connection_does_not_disrupt_other_tunnels() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let app_client_identity = generate_client_identity().unwrap();
fs::create_dir(tempdir.path().join("client-app")).unwrap();
fs::write(
tempdir.path().join("client-app/client.crt"),
app_client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-app/client.key"),
app_client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-app/client-identity.txt"),
app_client_identity.client_identity.to_string(),
)
.unwrap();
let api_client_identity = generate_client_identity().unwrap();
fs::create_dir(tempdir.path().join("client-api")).unwrap();
fs::write(
tempdir.path().join("client-api/client.crt"),
api_client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-api/client.key"),
api_client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-api/client-identity.txt"),
api_client_identity.client_identity.to_string(),
)
.unwrap();
let app_backend_one = spawn_tls_backend(vec!["app.example.test".to_owned()], *b"one!").await;
let app_backend_two = spawn_tls_backend(vec!["app.example.test".to_owned()], *b"two!").await;
let api_backend = spawn_tls_backend(vec!["api.example.test".to_owned()], *b"api!").await;
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
[[server.tunnels]]
public-hostnames = ["api.example.test"]
client-identity = "{}"
"#,
app_client_identity.client_identity, api_client_identity.client_identity
),
)
.unwrap();
fs::write(
tempdir.path().join("client-app-one.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "client-app"
[[client.services]]
backend-address = "{}"
"#,
app_backend_one.0
),
)
.unwrap();
fs::write(
tempdir.path().join("client-app-two.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "client-app"
[[client.services]]
backend-address = "{}"
"#,
app_backend_two.0
),
)
.unwrap();
fs::write(
tempdir.path().join("client-api.toml"),
format!(
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "client-api"
[[client.services]]
backend-address = "{}"
"#,
api_backend.0
),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let app_client_one_settings =
load_client_settings(&tempdir.path().join("client-app-one.toml")).unwrap();
let app_client_one =
PreparedClient::connect_to(&app_client_one_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let app_client_one_task = tokio::spawn(app_client_one.run());
let api_client_settings =
load_client_settings(&tempdir.path().join("client-api.toml")).unwrap();
let api_client = PreparedClient::connect_to(&api_client_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let api_client_task = tokio::spawn(api_client.run());
let first_app_response =
request_tls_response(public_addr, &app_backend_one.1, "app.example.test")
.await
.unwrap();
assert_eq!(first_app_response, *b"one!");
let first_api_response = request_tls_response(public_addr, &api_backend.1, "api.example.test")
.await
.unwrap();
assert_eq!(first_api_response, *b"api!");
let app_client_two_settings =
load_client_settings(&tempdir.path().join("client-app-two.toml")).unwrap();
let app_client_two =
PreparedClient::connect_to(&app_client_two_settings, localhost(0), tunnel_addr)
.await
.unwrap();
let app_client_two_task = tokio::spawn(app_client_two.run());
let replaced_app_response = timeout(Duration::from_secs(1), async {
loop {
match request_tls_response(public_addr, &app_backend_two.1, "app.example.test").await {
Ok(response) => return response,
Err(_) => tokio::time::sleep(Duration::from_millis(10)).await,
}
}
})
.await
.expect("timed out waiting for the replacement app tunnel connection");
assert_eq!(replaced_app_response, *b"two!");
let second_api_response = request_tls_response(public_addr, &api_backend.1, "api.example.test")
.await
.unwrap();
assert_eq!(second_api_response, *b"api!");
app_backend_one.2.abort();
app_backend_two.2.abort();
api_backend.2.abort();
server_task.abort();
app_client_one_task.abort();
app_client_two_task.abort();
api_client_task.abort();
let _ = app_backend_one.2.await;
let _ = app_backend_two.2.await;
let _ = api_backend.2.await;
let _ = server_task.await;
let _ = app_client_one_task.await;
let _ = app_client_two_task.await;
let _ = api_client_task.await;
}
#[tokio::test]
async fn prepared_server_rejects_an_untrusted_client_identity_before_serving_public_tls() {
let tempdir = tempdir().unwrap();
initialize_manual_server_certificate(
tempdir.path().join("server-cert").as_path(),
"tunnel.example.test",
)
.unwrap();
let trusted_client_identity = generate_client_identity().unwrap();
let untrusted_client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
untrusted_client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
untrusted_client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
untrusted_client_identity.client_identity.to_string(),
)
.unwrap();
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
"#,
trusted_client_identity.client_identity
),
)
.unwrap();
let backend = spawn_tls_backend(vec!["app.example.test".to_owned()], *b"pong").await;
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-cert/server-ca.crt"
identity-dir = "."
[[client.services]]
backend-address = "__BACKEND_ADDRESS__"
"#
.replace("__BACKEND_ADDRESS__", &backend.0.to_string()),
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let public_addr = server.public_addr().unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr).await;
let client_task = client.ok().map(|client| tokio::spawn(client.run()));
let visitor_result = timeout(
Duration::from_secs(1),
request_tls_response(public_addr, &backend.1, "app.example.test"),
)
.await;
assert!(
matches!(visitor_result, Ok(Err(_))),
"an untrusted client identity must never become the active tunnel"
);
backend.2.abort();
server_task.abort();
if let Some(client_task) = client_task {
client_task.abort();
let _ = client_task.await;
}
let _ = backend.2.await;
let _ = server_task.await;
}
#[tokio::test]
async fn prepared_server_binds_acme_settings_without_cached_tls_material() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[server]
hostname = "tunnel.example.test"
[server.acme]
email = "admin@example.test"
state-dir = "acme-state"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
)
.unwrap();
let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();
let server = PreparedServer::bind(&settings, localhost(0), localhost(0))
.await
.unwrap();
assert_ne!(server.public_addr().unwrap().port(), 0);
assert_ne!(server.tunnel_addr().unwrap().port(), 0);
}
#[tokio::test]
async fn prepared_server_loads_cached_acme_certificates_from_state_directory() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
let cached_server_cert =
generate_simple_self_signed(vec!["tunnel.example.test".to_owned()]).unwrap();
let cached_server_pem = format!(
"{}\n{}",
cached_server_cert.signing_key.serialize_pem(),
cached_server_cert.cert.pem()
);
DirCache::new(tempdir.path().join("acme-state"))
.store_cert(
&["tunnel.example.test".to_owned()],
LETS_ENCRYPT_PRODUCTION_DIRECTORY,
cached_server_pem.as_bytes(),
)
.await
.unwrap();
let client_identity = generate_client_identity().unwrap();
fs::write(
tempdir.path().join("client.crt"),
client_identity.certificate_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client.key"),
client_identity.private_key_pem,
)
.unwrap();
fs::write(
tempdir.path().join("client-identity.txt"),
client_identity.client_identity.to_string(),
)
.unwrap();
fs::write(
tempdir.path().join("server-ca.pem"),
cached_server_cert.cert.pem(),
)
.unwrap();
fs::write(
tempdir.path().join("server.toml"),
format!(
r#"
[server]
hostname = "tunnel.example.test"
[server.acme]
email = "admin@example.test"
state-dir = "acme-state"
[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "{}"
"#,
client_identity.client_identity
),
)
.unwrap();
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-ca.pem"
identity-dir = "."
[[client.services]]
backend-address = "127.0.0.1:1"
"#,
)
.unwrap();
let server_settings = load_server_settings(&tempdir.path().join("server.toml")).unwrap();
let server = PreparedServer::bind(&server_settings, localhost(0), localhost(0))
.await
.unwrap();
let tunnel_addr = server.tunnel_addr().unwrap();
let server_task = tokio::spawn(server.run());
let client_settings = load_client_settings(&tempdir.path().join("client.toml")).unwrap();
let client = timeout(Duration::from_secs(1), async {
loop {
match PreparedClient::connect_to(&client_settings, localhost(0), tunnel_addr).await {
Ok(client) => return client,
Err(_) => tokio::time::sleep(Duration::from_millis(10)).await,
}
}
})
.await
.expect("timed out waiting for the cached ACME certificate to load");
assert_ne!(client.local_addr().unwrap().port(), 0);
server_task.abort();
let _ = server_task.await;
}
async fn spawn_tls_backend(
server_names: Vec<String>,
response: [u8; 4],
) -> (
SocketAddr,
CertificateDer<'static>,
tokio::task::JoinHandle<()>,
) {
let certified_key = generate_simple_self_signed(server_names).unwrap();
let certificate = CertificateDer::from(certified_key.cert);
let private_key =
rustls::pki_types::PrivatePkcs8KeyDer::from(certified_key.signing_key.serialize_der())
.into();
let listener = TcpListener::bind(localhost(0)).await.unwrap();
let addr = listener.local_addr().unwrap();
let acceptor = TlsAcceptor::from(Arc::new(
rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![certificate.clone()], private_key)
.unwrap(),
));
let task = tokio::spawn(async move {
loop {
let (tcp_stream, _) = listener.accept().await.unwrap();
let acceptor = acceptor.clone();
tokio::spawn(async move {
let mut tls_stream = acceptor.accept(tcp_stream).await.unwrap();
let mut request = [0_u8; 4];
tls_stream.read_exact(&mut request).await.unwrap();
assert_eq!(&request, b"ping");
tls_stream.write_all(&response).await.unwrap();
tls_stream.shutdown().await.unwrap();
});
}
});
(addr, certificate, task)
}
async fn request_tls_response(
public_addr: SocketAddr,
backend_cert: &CertificateDer<'static>,
server_name: &str,
) -> io::Result<[u8; 4]> {
let connector = TlsConnector::from(Arc::new(
rustls::ClientConfig::builder()
.with_root_certificates(root_store_with(backend_cert))
.with_no_client_auth(),
));
let tcp_stream = TcpStream::connect(public_addr).await?;
let mut tls_stream = connector
.connect(
ServerName::try_from(server_name.to_owned()).map_err(io::Error::other)?,
tcp_stream,
)
.await
.map_err(io::Error::other)?;
tls_stream.write_all(b"ping").await?;
let mut response = [0_u8; 4];
tls_stream.read_exact(&mut response).await?;
Ok(response)
}
fn root_store_with(certificate: &CertificateDer<'static>) -> RootCertStore {
let mut roots = RootCertStore::empty();
roots.add(certificate.clone()).unwrap();
roots
}