335 lines
10 KiB
Rust
335 lines
10 KiB
Rust
use crate::route_types::*;
|
|
use crate::tls_types::*;
|
|
|
|
/// Create a simple HTTP forwarding route.
|
|
/// Equivalent to SmartProxy's `createHttpRoute()`.
|
|
pub fn create_http_route(
|
|
domains: impl Into<DomainSpec>,
|
|
target_host: impl Into<String>,
|
|
target_port: u16,
|
|
) -> RouteConfig {
|
|
RouteConfig {
|
|
id: None,
|
|
route_match: RouteMatch {
|
|
ports: PortRange::Single(80),
|
|
domains: Some(domains.into()),
|
|
path: None,
|
|
client_ip: None,
|
|
tls_version: None,
|
|
headers: None,
|
|
},
|
|
action: RouteAction {
|
|
action_type: RouteActionType::Forward,
|
|
targets: Some(vec![RouteTarget {
|
|
target_match: None,
|
|
host: HostSpec::Single(target_host.into()),
|
|
port: PortSpec::Fixed(target_port),
|
|
tls: None,
|
|
websocket: None,
|
|
load_balancing: None,
|
|
send_proxy_protocol: None,
|
|
headers: None,
|
|
advanced: None,
|
|
priority: None,
|
|
}]),
|
|
tls: None,
|
|
websocket: None,
|
|
load_balancing: None,
|
|
advanced: None,
|
|
options: None,
|
|
forwarding_engine: None,
|
|
nftables: None,
|
|
send_proxy_protocol: None,
|
|
},
|
|
headers: None,
|
|
security: None,
|
|
name: None,
|
|
description: None,
|
|
priority: None,
|
|
tags: None,
|
|
enabled: None,
|
|
}
|
|
}
|
|
|
|
/// Create an HTTPS termination route.
|
|
/// Equivalent to SmartProxy's `createHttpsTerminateRoute()`.
|
|
pub fn create_https_terminate_route(
|
|
domains: impl Into<DomainSpec>,
|
|
target_host: impl Into<String>,
|
|
target_port: u16,
|
|
) -> RouteConfig {
|
|
let mut route = create_http_route(domains, target_host, target_port);
|
|
route.route_match.ports = PortRange::Single(443);
|
|
route.action.tls = Some(RouteTls {
|
|
mode: TlsMode::Terminate,
|
|
certificate: Some(CertificateSpec::Auto("auto".to_string())),
|
|
acme: None,
|
|
versions: None,
|
|
ciphers: None,
|
|
honor_cipher_order: None,
|
|
session_timeout: None,
|
|
});
|
|
route
|
|
}
|
|
|
|
/// Create a TLS passthrough route.
|
|
/// Equivalent to SmartProxy's `createHttpsPassthroughRoute()`.
|
|
pub fn create_https_passthrough_route(
|
|
domains: impl Into<DomainSpec>,
|
|
target_host: impl Into<String>,
|
|
target_port: u16,
|
|
) -> RouteConfig {
|
|
let mut route = create_http_route(domains, target_host, target_port);
|
|
route.route_match.ports = PortRange::Single(443);
|
|
route.action.tls = Some(RouteTls {
|
|
mode: TlsMode::Passthrough,
|
|
certificate: None,
|
|
acme: None,
|
|
versions: None,
|
|
ciphers: None,
|
|
honor_cipher_order: None,
|
|
session_timeout: None,
|
|
});
|
|
route
|
|
}
|
|
|
|
/// Create an HTTP-to-HTTPS redirect route.
|
|
/// Equivalent to SmartProxy's `createHttpToHttpsRedirect()`.
|
|
pub fn create_http_to_https_redirect(
|
|
domains: impl Into<DomainSpec>,
|
|
) -> RouteConfig {
|
|
let domains = domains.into();
|
|
RouteConfig {
|
|
id: None,
|
|
route_match: RouteMatch {
|
|
ports: PortRange::Single(80),
|
|
domains: Some(domains),
|
|
path: None,
|
|
client_ip: None,
|
|
tls_version: None,
|
|
headers: None,
|
|
},
|
|
action: RouteAction {
|
|
action_type: RouteActionType::Forward,
|
|
targets: None,
|
|
tls: None,
|
|
websocket: None,
|
|
load_balancing: None,
|
|
advanced: Some(RouteAdvanced {
|
|
timeout: None,
|
|
headers: None,
|
|
keep_alive: None,
|
|
static_files: None,
|
|
test_response: Some(RouteTestResponse {
|
|
status: 301,
|
|
headers: {
|
|
let mut h = std::collections::HashMap::new();
|
|
h.insert("Location".to_string(), "https://{domain}{path}".to_string());
|
|
h
|
|
},
|
|
body: String::new(),
|
|
}),
|
|
url_rewrite: None,
|
|
}),
|
|
options: None,
|
|
forwarding_engine: None,
|
|
nftables: None,
|
|
send_proxy_protocol: None,
|
|
},
|
|
headers: None,
|
|
security: None,
|
|
name: Some("HTTP to HTTPS Redirect".to_string()),
|
|
description: None,
|
|
priority: None,
|
|
tags: None,
|
|
enabled: None,
|
|
}
|
|
}
|
|
|
|
/// Create a complete HTTPS server with HTTP redirect.
|
|
/// Equivalent to SmartProxy's `createCompleteHttpsServer()`.
|
|
pub fn create_complete_https_server(
|
|
domain: impl Into<String>,
|
|
target_host: impl Into<String>,
|
|
target_port: u16,
|
|
) -> Vec<RouteConfig> {
|
|
let domain = domain.into();
|
|
let target_host = target_host.into();
|
|
|
|
vec![
|
|
create_http_to_https_redirect(DomainSpec::Single(domain.clone())),
|
|
create_https_terminate_route(
|
|
DomainSpec::Single(domain),
|
|
target_host,
|
|
target_port,
|
|
),
|
|
]
|
|
}
|
|
|
|
/// Create a load balancer route.
|
|
/// Equivalent to SmartProxy's `createLoadBalancerRoute()`.
|
|
pub fn create_load_balancer_route(
|
|
domains: impl Into<DomainSpec>,
|
|
targets: Vec<(String, u16)>,
|
|
tls: Option<RouteTls>,
|
|
) -> RouteConfig {
|
|
let route_targets: Vec<RouteTarget> = targets
|
|
.into_iter()
|
|
.map(|(host, port)| RouteTarget {
|
|
target_match: None,
|
|
host: HostSpec::Single(host),
|
|
port: PortSpec::Fixed(port),
|
|
tls: None,
|
|
websocket: None,
|
|
load_balancing: None,
|
|
send_proxy_protocol: None,
|
|
headers: None,
|
|
advanced: None,
|
|
priority: None,
|
|
})
|
|
.collect();
|
|
|
|
let port = if tls.is_some() { 443 } else { 80 };
|
|
|
|
RouteConfig {
|
|
id: None,
|
|
route_match: RouteMatch {
|
|
ports: PortRange::Single(port),
|
|
domains: Some(domains.into()),
|
|
path: None,
|
|
client_ip: None,
|
|
tls_version: None,
|
|
headers: None,
|
|
},
|
|
action: RouteAction {
|
|
action_type: RouteActionType::Forward,
|
|
targets: Some(route_targets),
|
|
tls,
|
|
websocket: None,
|
|
load_balancing: Some(RouteLoadBalancing {
|
|
algorithm: LoadBalancingAlgorithm::RoundRobin,
|
|
health_check: None,
|
|
}),
|
|
advanced: None,
|
|
options: None,
|
|
forwarding_engine: None,
|
|
nftables: None,
|
|
send_proxy_protocol: None,
|
|
},
|
|
headers: None,
|
|
security: None,
|
|
name: Some("Load Balancer".to_string()),
|
|
description: None,
|
|
priority: None,
|
|
tags: None,
|
|
enabled: None,
|
|
}
|
|
}
|
|
|
|
// Convenience conversions for DomainSpec
|
|
impl From<&str> for DomainSpec {
|
|
fn from(s: &str) -> Self {
|
|
DomainSpec::Single(s.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<String> for DomainSpec {
|
|
fn from(s: String) -> Self {
|
|
DomainSpec::Single(s)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<String>> for DomainSpec {
|
|
fn from(v: Vec<String>) -> Self {
|
|
DomainSpec::List(v)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<&str>> for DomainSpec {
|
|
fn from(v: Vec<&str>) -> Self {
|
|
DomainSpec::List(v.into_iter().map(|s| s.to_string()).collect())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::tls_types::TlsMode;
|
|
|
|
#[test]
|
|
fn test_create_http_route() {
|
|
let route = create_http_route("example.com", "localhost", 8080);
|
|
assert_eq!(route.route_match.ports.to_ports(), vec![80]);
|
|
let domains = route.route_match.domains.as_ref().unwrap().to_vec();
|
|
assert_eq!(domains, vec!["example.com"]);
|
|
let target = &route.action.targets.as_ref().unwrap()[0];
|
|
assert_eq!(target.host.first(), "localhost");
|
|
assert_eq!(target.port.resolve(80), 8080);
|
|
assert!(route.action.tls.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_https_terminate_route() {
|
|
let route = create_https_terminate_route("api.example.com", "backend", 3000);
|
|
assert_eq!(route.route_match.ports.to_ports(), vec![443]);
|
|
let tls = route.action.tls.as_ref().unwrap();
|
|
assert_eq!(tls.mode, TlsMode::Terminate);
|
|
assert!(tls.certificate.as_ref().unwrap().is_auto());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_https_passthrough_route() {
|
|
let route = create_https_passthrough_route("secure.example.com", "backend", 443);
|
|
assert_eq!(route.route_match.ports.to_ports(), vec![443]);
|
|
let tls = route.action.tls.as_ref().unwrap();
|
|
assert_eq!(tls.mode, TlsMode::Passthrough);
|
|
assert!(tls.certificate.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_http_to_https_redirect() {
|
|
let route = create_http_to_https_redirect("example.com");
|
|
assert_eq!(route.route_match.ports.to_ports(), vec![80]);
|
|
assert!(route.action.targets.is_none());
|
|
let test_response = route.action.advanced.as_ref().unwrap().test_response.as_ref().unwrap();
|
|
assert_eq!(test_response.status, 301);
|
|
assert!(test_response.headers.contains_key("Location"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_complete_https_server() {
|
|
let routes = create_complete_https_server("example.com", "backend", 8080);
|
|
assert_eq!(routes.len(), 2);
|
|
// First route is HTTP redirect
|
|
assert_eq!(routes[0].route_match.ports.to_ports(), vec![80]);
|
|
// Second route is HTTPS terminate
|
|
assert_eq!(routes[1].route_match.ports.to_ports(), vec![443]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_load_balancer_route() {
|
|
let targets = vec![
|
|
("backend1".to_string(), 8080),
|
|
("backend2".to_string(), 8080),
|
|
("backend3".to_string(), 8080),
|
|
];
|
|
let route = create_load_balancer_route("*.example.com", targets, None);
|
|
assert_eq!(route.route_match.ports.to_ports(), vec![80]);
|
|
assert_eq!(route.action.targets.as_ref().unwrap().len(), 3);
|
|
let lb = route.action.load_balancing.as_ref().unwrap();
|
|
assert_eq!(lb.algorithm, LoadBalancingAlgorithm::RoundRobin);
|
|
}
|
|
|
|
#[test]
|
|
fn test_domain_spec_from_str() {
|
|
let spec: DomainSpec = "example.com".into();
|
|
assert_eq!(spec.to_vec(), vec!["example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_domain_spec_from_vec() {
|
|
let spec: DomainSpec = vec!["a.com", "b.com"].into();
|
|
assert_eq!(spec.to_vec(), vec!["a.com", "b.com"]);
|
|
}
|
|
}
|