This commit is contained in:
Mateusz Gruszczyński committed 2026-10-25 17:45:57 +01:00
1 parent bd077754a9
commit ed7e3377c1
40 files changed
+1764 -365

No files matched your search

+72 -15
View File
@@ -106,13 +106,19 @@ async fn forward_request(
} else {
snapshot.frontend(&host, local.0, tls)
};
if snapshot.global_security.content_type_policy.blocks(request.headers()) {
return error(
StatusCode::FORBIDDEN,
"RustProxy: request Content-Type denied by global policy\n",
);
}
if frontend.is_some_and(|item| item.content_type_policy.blocks(request.headers())) {
return error(
StatusCode::FORBIDDEN,
"RustProxy: request Content-Type denied by vhost policy\n",
);
}
if snapshot.user_agent_blocking.blocks(request.headers())
if snapshot.global_security.user_agent_blocking.blocks(request.headers())
|| frontend.is_some_and(|frontend| frontend.user_agent_blocking.blocks(request.headers()))
{
return error(
@@ -164,24 +170,16 @@ async fn forward_request(
"RustProxy: RFC 9220 WebSocket transport is not enabled for this vhost\n",
);
}
if let Some(response) = method_policy_denial(&snapshot.global_security.method_policy, request.method()) {
return response;
}
if !cache_http_invalidation_method(request.method()) {
if let Some(response) = frontend
.and_then(|frontend| method_policy_denial(&frontend.method_policy, request.method()))
{
return response;
}
if let Some(response) = frontend.and_then(|frontend| {
canonical_host_redirect(frontend, &host, tls, local.0.port(), request.uri())
}) {
return response;
}
if !tls {
if let Some(response) =
frontend.and_then(|frontend| force_ssl_redirect(frontend, &host, request.uri()))
{
return response;
}
}
// Redirects are evaluated after global and per-vhost country/IP/rate checks below.
}
let mut context = match snapshot.forwarding.context_with_peer(
request.headers(),
@@ -226,13 +224,53 @@ async fn forward_request(
let capture_country = state.logs.capture_country();
let mut country_resolved = false;
let mut country: Option<String> = None;
if capture_country
|| snapshot.global_security.country_access.needs_geoip()
|| snapshot.global_security.rate_limit.needs_geoip()
{
country = cloudflare_country
.clone()
.or_else(|| state.geoip.load().country(context.client_ip));
country_resolved = true;
if capture_country {
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
*exchange.country.lock().unwrap_or_else(|e| e.into_inner()) = country.clone();
}
}
}
// Global rules are cumulative: an explicit IP allow cannot bypass a country deny/allowlist.
if !snapshot.global_security.country_access.allows(country.as_deref())
|| !snapshot.global_security.rate_limit.country_allowed(country.as_deref())
|| snapshot.global_security.acl.as_ref().is_some_and(|acl| !acl.allows(context.client_ip))
{
return error(StatusCode::FORBIDDEN, "RustProxy: denied by global access policy\n");
}
if internal_dispatch.is_none() {
if let Err(limited) = state.rate_limiter.check_with_user_agent(
snapshot.envelope.revision,
"\u{1f}rustproxy-global-security",
context.client_ip,
country.as_deref(),
request.headers().get(http::header::USER_AGENT).and_then(|value| value.to_str().ok()),
&snapshot.global_security.rate_limit,
) {
let mut response = error(
StatusCode::TOO_MANY_REQUESTS,
"RustProxy: global request rate limit exceeded\n",
);
if let Ok(value) = HeaderValue::from_str(&limited.retry_after_seconds.to_string()) {
response.headers_mut().insert("retry-after", value);
}
return response;
}
}
if let Some(frontend) = frontend {
if capture_country
if !country_resolved && (capture_country
|| frontend
.profile_policies
.iter()
.any(|profile| profile.countries.needs_geoip())
|| frontend.rate_limit.needs_geoip()
|| frontend.rate_limit.needs_geoip())
{
country = cloudflare_country
.clone()
@@ -288,6 +326,23 @@ async fn forward_request(
"RustProxy: denied by access policy\n",
);
}
// Canonical / HTTPS redirects must not bypass global or per-vhost access policy.
if !cache_http_invalidation_method(request.method()) {
if let Some(mut response) = frontend.and_then(|frontend| {
canonical_host_redirect(frontend, &host, tls, local.0.port(), request.uri())
}) {
snapshot.global_security.headers.apply(response.headers_mut(), tls);
return response;
}
if !tls {
if let Some(mut response) =
frontend.and_then(|frontend| force_ssl_redirect(frontend, &host, request.uri()))
{
snapshot.global_security.headers.apply(response.headers_mut(), tls);
return response;
}
}
}
if cache_http_invalidation_method(request.method()) {
return cache_http_invalidation(
&state,
@@ -309,6 +364,7 @@ async fn forward_request(
"RustProxy: redirect target could not be rendered\n",
);
};
snapshot.global_security.headers.apply(response.headers_mut(), tls);
response.extensions_mut().insert(RedirectResponse {
http3_enabled: _redirect.http3_enabled,
});
@@ -316,6 +372,7 @@ async fn forward_request(
}
if let Some(item) = local_host {
let mut response = item.page.serve(request.method(), StatusCode::NOT_FOUND);
snapshot.global_security.headers.apply(response.headers_mut(), tls);
response.extensions_mut().insert(LocalHostResponse {
http3_enabled: item.http3_enabled,
});