0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

-24
View File
@@ -1,6 +1,3 @@
// Varnish-like HTTP BAN/PURGE on public vhost listeners. No new admin
// endpoint or session token: security rests on a strict, opt-in client IP/CIDR
// allowlist plus normal frontend/host ACLs and rate limits. Never forward.
fn cache_http_invalidation_method(method: &Method) -> bool {
method.as_str() == "BAN" || method.as_str() == "PURGE"
}
@@ -12,8 +9,6 @@ fn cache_http_invalidation_request(
scheme: &str,
request: &Request,
) -> Result<crate::cache::InvalidationRequest, &'static str> {
// Allow a single explicit Content-Length: 0 (used by some PURGE clients),
// but reject ambiguous lengths and all transfer-encoded bodies.
let mut lengths = request.headers().get_all(http::header::CONTENT_LENGTH).iter();
if let Some(length) = lengths.next() {
if lengths.next().is_some() || length.as_bytes() != b"0" {
@@ -26,8 +21,6 @@ fn cache_http_invalidation_request(
{
return Err("BAN/PURGE must not contain a request body or upgrade");
}
// Only this vhost and Host are permitted; no client-supplied cross-vhost
// selectors, regex, or arbitrary Cache-Tag from the public listener.
let target = request.uri().path_and_query().map(|pq| pq.as_str()).unwrap_or("/");
if target.len() > 4096 || !target.starts_with('/') {
return Err("invalid BAN/PURGE target");
@@ -42,8 +35,6 @@ fn cache_http_invalidation_request(
} else {
crate::cache::InvalidationSelector::Exact
};
// Never resolve ambiguous duplicate maintenance headers differently from
// intermediaries or audit logs. Reject them before selecting a target.
if request.headers().get_all("x-rustproxy-ban-ttl").iter().count() > 1
|| request.headers().get_all("x-rustproxy-confirm-wide").iter().count() > 1
{
@@ -71,9 +62,6 @@ fn cache_http_invalidation_request(
if method.as_str() != "BAN" && request.headers().contains_key("x-rustproxy-confirm-wide") {
return Err("wide confirmation is only supported for BAN");
}
// Explicitly scope maintenance to the externally visible scheme. The
// ordinary /path matcher would also invalidate the opposite scheme on
// a vhost shared between HTTP and HTTPS.
if !matches!(scheme, "http" | "https") {
return Err("invalid request scheme");
}
@@ -99,13 +87,9 @@ async fn cache_http_invalidation(
peer_ip: std::net::IpAddr,
scheme: &str,
) -> Response {
// Fail closed even when the vhost cache is disabled; never pass commands
// through to the backend, and never trust arbitrary X-Forwarded-For.
let Some(frontend) = frontend else {
return error(StatusCode::NOT_FOUND, "RustProxy: no matching vhost for cache invalidation\n");
};
// A trusted proxy missing a usable client chain must not inherit its own
// allowlist permission for every external visitor.
if snapshot.forwarding.trusted(peer_ip)
&& crate::vhost::canonical_ip(peer_ip) == client_ip
{
@@ -118,13 +102,8 @@ async fn cache_http_invalidation(
Ok(requested) => requested,
Err(reason) => return error(StatusCode::BAD_REQUEST, reason),
};
// Axum's Request<Body> is Send but its Body is not Sync. Never keep
// a shared `&Request` inside the handler future across an .await.
// Release the unused body before the asynchronous audit/cache work.
let method = request.method().clone();
drop(request);
// Allocate the independent maintenance slot before even writing an audit
// row, so a burst from an allowed IP cannot pile up DB writes and workers.
let Ok(permit) = state.cache_invalidation_slots.clone().try_acquire_owned() else {
return error(StatusCode::TOO_MANY_REQUESTS, "RustProxy: cache maintenance busy\n");
};
@@ -136,9 +115,6 @@ async fn cache_http_invalidation(
tracing::error!(error = %audit_error, "cache HTTP invalidation audit failed");
return error(StatusCode::SERVICE_UNAVAILABLE, "RustProxy: cache maintenance unavailable\n");
}
// Rate-limit the expensive maintenance path independently of the optional
// per-vhost traffic limiter. The permit lives inside the blocking worker
// even if a client disconnects while the cache scan is running.
let cache = state.cache.clone();
let result = tokio::task::spawn_blocking(move || {
let _permit = permit;