This commit is contained in:
Mateusz Gruszczyński committed 2026-10-08 22:22:46 +02:00
1 parent 76cb4acef0
commit 68678e98be
84 files changed
+1519 -325

No files matched your search

+56
View File
@@ -4,6 +4,7 @@ impl Default for PathRoute {
source: Source::default(),
backend_id: None,
id: "root".into(),
internal: false,
path_prefix: root(),
match_type: PathMatch::Prefix,
enabled: true,
@@ -16,6 +17,7 @@ impl Default for PathRoute {
rewrite: PathRewrite::default(),
response: None,
redirect: None,
error_fallback: None,
}
}
}
@@ -31,6 +33,7 @@ impl PathRoute {
&& self.enabled
&& self.source == Source::ReverseProxy
&& self.backend_id.is_none()
&& !self.internal
&& self.cache.is_none()
&& self.acl_profile_id.is_none()
&& self.acl.is_none()
@@ -42,6 +45,7 @@ impl PathRoute {
&& self.rewrite.value.is_empty()
&& self.response.is_none()
&& self.redirect.is_none()
&& self.error_fallback.is_none()
}
}
impl Vhost {
@@ -140,7 +144,15 @@ impl Vhost {
// Normalize the untouched pre-0.16 synthetic root route out of the custom
// location list so adding/removing Custom locations cannot delete fallback.
self.routes.retain(|route| !route.is_legacy_main_fallback());
if let Some(fallback) = &mut self.error_fallback {
fallback.validate()?;
}
let mut route_ids = std::collections::HashSet::new();
for route in &mut self.routes {
ensure!(
crate::config::valid_id(&route.id) && route_ids.insert(route.id.clone()),
"invalid/duplicate vhost path id"
);
route.source.validate()?;
ensure!(
route.response.is_none() || route.redirect.is_none(),
@@ -155,11 +167,55 @@ impl Vhost {
route.source.is_reverse_proxy() || route.backend_id.is_none(),
"Web Server location cannot pin a reverse-proxy backend"
);
if let Some(fallback) = &mut route.error_fallback {
fallback.validate()?;
}
}
ensure!(
self.routes.len() <= 64,
"at most 64 custom locations per vhost"
);
let resolve_target = |target: &str| {
self.routes
.iter()
.find(|route| route.id == target && route.enabled && route.internal)
};
if let Some(fallback) = &self.error_fallback {
ensure!(
resolve_target(&fallback.target).is_some(),
"vhost error fallback target must reference an enabled internal location"
);
}
for route in &self.routes {
if let Some(fallback) = &route.error_fallback {
ensure!(
resolve_target(&fallback.target).is_some(),
"location error fallback target must reference an enabled internal location"
);
}
}
// Reject cycles at configuration time; a runtime depth guard remains as a
// second line of defense for hot-reload/state inconsistencies.
let mut starts = vec![None];
starts.extend(self.routes.iter().filter(|route| route.enabled).map(|route| Some(route.id.as_str())));
for start in starts {
let mut seen = std::collections::HashSet::new();
let mut current = start;
loop {
let key = current.unwrap_or("<main>");
ensure!(seen.insert(key.to_owned()), "recursive error fallback loop detected");
let fallback = match current {
None => self.error_fallback.as_ref(),
Some(id) => self
.routes
.iter()
.find(|route| route.id == id)
.and_then(|route| route.error_fallback.as_ref()),
};
let Some(fallback) = fallback else { break };
current = Some(fallback.target.as_str());
}
}
self.load_balancing.validate()?;
self.proxy_timeouts.validate()?;
self.rate_limit.validate()?;