feat(smart-proxy): add hot-reloadable global ingress security policy across Rust and TypeScript proxy layers

This commit is contained in:
2026-04-26 15:11:10 +00:00
parent 8fa3a51b03
commit af4908b63f
53 changed files with 2350 additions and 1196 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
//! Route matching engine for RustProxy.
//! Provides domain/path/IP/header matchers and a port-indexed RouteManager.
pub mod route_manager;
pub mod matchers;
pub mod route_manager;
pub use route_manager::*;
@@ -20,7 +20,7 @@ pub fn domain_matches(pattern: &str, domain: &str) -> bool {
// Wildcard patterns
if pattern.starts_with("*.") || pattern.starts_with("*.") {
let suffix = &pattern[2..]; // e.g., "example.com"
// Match exact parent or any single-level subdomain
// Match exact parent or any single-level subdomain
if domain.eq_ignore_ascii_case(suffix) {
return true;
}
@@ -1,6 +1,6 @@
use ipnet::IpNet;
use std::net::IpAddr;
use std::str::FromStr;
use ipnet::IpNet;
/// Match an IP address against a pattern.
///
@@ -85,7 +85,10 @@ fn wildcard_to_cidr(pattern: &str) -> Option<String> {
}
}
Some(format!("{}.{}.{}.{}/{}", octets[0], octets[1], octets[2], octets[3], prefix_len))
Some(format!(
"{}.{}.{}.{}/{}",
octets[0], octets[1], octets[2], octets[3], prefix_len
))
}
#[cfg(test)]
@@ -1,9 +1,9 @@
pub mod domain;
pub mod path;
pub mod ip;
pub mod header;
pub mod ip;
pub mod path;
pub use domain::*;
pub use path::*;
pub use ip::*;
pub use header::*;
pub use ip::*;
pub use path::*;
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use rustproxy_config::{RouteConfig, RouteTarget, TransportProtocol, TlsMode};
use crate::matchers;
use rustproxy_config::{RouteConfig, RouteTarget, TlsMode, TransportProtocol};
/// Context for route matching (subset of connection info).
pub struct MatchContext<'a> {
@@ -42,19 +42,14 @@ impl RouteManager {
};
// Filter enabled routes and sort by priority
let mut enabled_routes: Vec<RouteConfig> = routes
.into_iter()
.filter(|r| r.is_enabled())
.collect();
let mut enabled_routes: Vec<RouteConfig> =
routes.into_iter().filter(|r| r.is_enabled()).collect();
enabled_routes.sort_by(|a, b| b.effective_priority().cmp(&a.effective_priority()));
// Build port index
for (idx, route) in enabled_routes.iter().enumerate() {
for port in route.listening_ports() {
manager.port_index
.entry(port)
.or_default()
.push(idx);
manager.port_index.entry(port).or_default().push(idx);
}
}
@@ -66,7 +61,9 @@ impl RouteManager {
/// Used to skip expensive header HashMap construction when no route needs it.
pub fn any_route_has_headers(&self, port: u16) -> bool {
if let Some(indices) = self.port_index.get(&port) {
indices.iter().any(|&idx| self.routes[idx].route_match.headers.is_some())
indices
.iter()
.any(|&idx| self.routes[idx].route_match.headers.is_some())
} else {
false
}
@@ -99,8 +96,8 @@ impl RouteManager {
let ctx_transport = ctx.transport.as_ref();
match (route_transport, ctx_transport) {
// Route requires UDP only — reject non-UDP contexts
(Some(TransportProtocol::Udp), None) |
(Some(TransportProtocol::Udp), Some(TransportProtocol::Tcp)) => return false,
(Some(TransportProtocol::Udp), None)
| (Some(TransportProtocol::Udp), Some(TransportProtocol::Tcp)) => return false,
// Route requires TCP only — reject UDP contexts
(Some(TransportProtocol::Tcp), Some(TransportProtocol::Udp)) => return false,
// Route has no transport (default = TCP) — reject UDP contexts
@@ -196,7 +193,11 @@ impl RouteManager {
}
/// Find the best matching target within a route.
fn find_target<'a>(&self, route: &'a RouteConfig, ctx: &MatchContext<'_>) -> Option<&'a RouteTarget> {
fn find_target<'a>(
&self,
route: &'a RouteConfig,
ctx: &MatchContext<'_>,
) -> Option<&'a RouteTarget> {
let targets = route.action.targets.as_ref()?;
if targets.len() == 1 && targets[0].target_match.is_none() {
@@ -223,17 +224,11 @@ impl RouteManager {
}
// Fall back to first target without match criteria
best.or_else(|| {
targets.iter().find(|t| t.target_match.is_none())
})
best.or_else(|| targets.iter().find(|t| t.target_match.is_none()))
}
/// Check if a target match criteria matches the context.
fn matches_target(
&self,
tm: &rustproxy_config::TargetMatch,
ctx: &MatchContext<'_>,
) -> bool {
fn matches_target(&self, tm: &rustproxy_config::TargetMatch, ctx: &MatchContext<'_>) -> bool {
// Port matching
if let Some(ref ports) = tm.ports {
if !ports.contains(&ctx.port) {
@@ -298,9 +293,7 @@ impl RouteManager {
// If multiple passthrough routes on same port, SNI is needed
let passthrough_routes: Vec<_> = routes
.iter()
.filter(|r| {
r.tls_mode() == Some(&TlsMode::Passthrough)
})
.filter(|r| r.tls_mode() == Some(&TlsMode::Passthrough))
.collect();
if passthrough_routes.len() > 1 {
@@ -419,7 +412,11 @@ mod tests {
let result = manager.find_route(&ctx).unwrap();
// Should match the higher-priority specific route
assert!(result.route.route_match.domains.as_ref()
assert!(result
.route
.route_match
.domains
.as_ref()
.map(|d| d.to_vec())
.unwrap()
.contains(&"api.example.com"));
@@ -619,8 +616,14 @@ mod tests {
let result = manager.find_route(&ctx);
assert!(result.is_some());
let matched_domains = result.unwrap().route.route_match.domains.as_ref()
.map(|d| d.to_vec()).unwrap();
let matched_domains = result
.unwrap()
.route
.route_match
.domains
.as_ref()
.map(|d| d.to_vec())
.unwrap();
assert!(matched_domains.contains(&"*"));
}
@@ -735,7 +738,11 @@ mod tests {
assert_eq!(result.target.unwrap().host.first(), "default-backend");
}
fn make_route_with_protocol(port: u16, domain: Option<&str>, protocol: Option<&str>) -> RouteConfig {
fn make_route_with_protocol(
port: u16,
domain: Option<&str>,
protocol: Option<&str>,
) -> RouteConfig {
let mut route = make_route(port, domain, 0);
route.route_match.protocol = protocol.map(|s| s.to_string());
route
@@ -1029,8 +1036,10 @@ mod tests {
transport: Some(TransportProtocol::Udp),
};
assert!(manager.find_route(&ctx).is_some(),
"QUIC (UDP) with is_tls=true and domain=None should match domain-restricted routes");
assert!(
manager.find_route(&ctx).is_some(),
"QUIC (UDP) with is_tls=true and domain=None should match domain-restricted routes"
);
}
#[test]
@@ -1051,7 +1060,9 @@ mod tests {
transport: None, // TCP (default)
};
assert!(manager.find_route(&ctx).is_none(),
"TCP TLS without SNI should NOT match domain-restricted routes");
assert!(
manager.find_route(&ctx).is_none(),
"TCP TLS without SNI should NOT match domain-restricted routes"
);
}
}