- Add hostname-based request routing for multiple capsules per server - Parse virtual host configs from TOML sections ([hostname]) - Implement per-host certificate and content isolation - Add comprehensive virtual host testing and validation - Update docs and examples for multi-host deployments This enables Pollux to serve multiple Gemini domains from one instance, providing the foundation for multi-tenant Gemini hosting.
131 lines
No EOL
4.3 KiB
Rust
131 lines
No EOL
4.3 KiB
Rust
mod common;
|
|
|
|
|
|
|
|
#[test]
|
|
fn test_per_host_content_isolation() {
|
|
let temp_dir = common::setup_test_environment();
|
|
|
|
// Create different content for each host
|
|
let site1_root = temp_dir.path().join("site1");
|
|
let site2_root = temp_dir.path().join("site2");
|
|
std::fs::create_dir(&site1_root).unwrap();
|
|
std::fs::create_dir(&site2_root).unwrap();
|
|
|
|
// Create different index.gmi files for each site
|
|
std::fs::write(site1_root.join("index.gmi"), "Welcome to Site 1").unwrap();
|
|
std::fs::write(site2_root.join("index.gmi"), "Welcome to Site 2").unwrap();
|
|
|
|
// Create config with two hosts
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
let port = 1965 + (std::process::id() % 1000) as u16; // Use dynamic port
|
|
let config_content = format!(r#"
|
|
bind_host = "127.0.0.1"
|
|
port = {}
|
|
|
|
["site1.com"]
|
|
root = "{}"
|
|
cert = "{}"
|
|
key = "{}"
|
|
|
|
["site2.org"]
|
|
root = "{}"
|
|
cert = "{}"
|
|
key = "{}"
|
|
"#,
|
|
port,
|
|
site1_root.display(),
|
|
temp_dir.path().join("cert.pem").display(),
|
|
temp_dir.path().join("key.pem").display(),
|
|
site2_root.display(),
|
|
temp_dir.path().join("cert.pem").display(),
|
|
temp_dir.path().join("key.pem").display());
|
|
std::fs::write(&config_path, config_content).unwrap();
|
|
|
|
// Start server with TLS
|
|
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
|
|
.arg("--config")
|
|
.arg(&config_path)
|
|
.spawn()
|
|
.unwrap();
|
|
|
|
// Wait for server to start
|
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
|
|
|
// Test site1.com serves its content
|
|
let response1 = make_gemini_request("127.0.0.1", port, "gemini://site1.com/");
|
|
assert!(response1.starts_with("20"), "Expected success for site1.com, got: {}", response1);
|
|
assert!(response1.contains("Welcome to Site 1"), "Should serve site1 content, got: {}", response1);
|
|
|
|
// Test site2.org serves its content
|
|
let response2 = make_gemini_request("127.0.0.1", port, "gemini://site2.org/");
|
|
assert!(response2.starts_with("20"), "Expected success for site2.org, got: {}", response2);
|
|
assert!(response2.contains("Welcome to Site 2"), "Should serve site2 content, got: {}", response2);
|
|
|
|
server_process.kill().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_per_host_path_security() {
|
|
let temp_dir = common::setup_test_environment();
|
|
|
|
// Create directory structure for site1
|
|
let site1_root = temp_dir.path().join("site1");
|
|
std::fs::create_dir(&site1_root).unwrap();
|
|
std::fs::create_dir(site1_root.join("subdir")).unwrap();
|
|
std::fs::write(site1_root.join("subdir").join("secret.gmi"), "Secret content").unwrap();
|
|
|
|
// Create config
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
let port = 1968 + (std::process::id() % 1000) as u16;
|
|
let cert_path = temp_dir.path().join("cert.pem");
|
|
let key_path = temp_dir.path().join("key.pem");
|
|
|
|
let config_content = format!(r#"
|
|
bind_host = "127.0.0.1"
|
|
port = {}
|
|
|
|
["site1.com"]
|
|
root = "{}"
|
|
cert = "{}"
|
|
key = "{}"
|
|
"#,
|
|
port,
|
|
site1_root.display(),
|
|
cert_path.display(),
|
|
key_path.display());
|
|
std::fs::write(&config_path, config_content).unwrap();
|
|
|
|
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
|
|
.arg("--config")
|
|
.arg(&config_path)
|
|
.spawn()
|
|
.unwrap();
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
|
|
|
// Test path traversal attempt should be blocked
|
|
let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/../../../etc/passwd");
|
|
assert!(response.starts_with("51"), "Path traversal should be blocked, got: {}", response);
|
|
|
|
// Test valid subdirectory access should work
|
|
let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/subdir/secret.gmi");
|
|
assert!(response.starts_with("20"), "Valid subdirectory access should work, got: {}", response);
|
|
assert!(response.contains("Secret content"), "Should serve content from subdirectory, got: {}", response);
|
|
|
|
server_process.kill().unwrap();
|
|
}
|
|
|
|
fn make_gemini_request(host: &str, port: u16, url: &str) -> String {
|
|
// Use the Python client for TLS requests
|
|
use std::process::Command;
|
|
|
|
let output = Command::new("python3")
|
|
.arg("tests/gemini_test_client.py")
|
|
.arg(url)
|
|
.env("GEMINI_PORT", &port.to_string())
|
|
.env("GEMINI_CONNECT_HOST", host)
|
|
.output()
|
|
.unwrap();
|
|
String::from_utf8(output.stdout).unwrap()
|
|
} |