Add destination domain ACL rules

This commit is contained in:
2026-10-28 14:13:53 +00:00
parent a33549e81a
commit 467624b747
5 changed files with 450 additions and 24 deletions
+69 -5
View File
@@ -28,7 +28,7 @@ use tracing_opentelemetry::OpenTelemetrySpanExt;
use crate::{
BoxError,
acl::{Acl, Decision},
acl::{Acl, Decision, DestinationHost},
config::Config,
metrics::Metrics,
telemetry::{HeaderExtractor, HeaderInjector, remove_hop_by_hop_headers},
@@ -177,7 +177,16 @@ fn authorize_request(
peer: IpAddr,
state: &ProxyState,
) -> Result<Option<ProxyResponse>, ProxyError> {
let outcome = state.acl.authorize(peer, request.headers());
let destination = request.uri().authority().and_then(|authority| {
if authority.as_str().contains('@') {
None
} else {
DestinationHost::parse(authority.host())
}
});
let outcome = state
.acl
.authorize(peer, request.headers(), destination.as_ref());
state
.metrics
@@ -671,7 +680,10 @@ mod tests {
let (mut sender, connection_task) = connect_client(proxy_addr).await;
let request = Request::builder()
.method(Method::GET)
.uri(format!("http://{upstream_addr}/hello?q=1"))
.uri(format!(
"http://localhost:{}/hello?q=1",
upstream_addr.port()
))
.header(HOST, "wrong.invalid")
.header(PROXY_AUTHORIZATION, basic_authorization())
.body(Full::new(Bytes::new()))
@@ -685,7 +697,7 @@ mod tests {
upstream_task.abort();
let observed = observed.lock().unwrap();
assert_eq!(observed.target, "/hello?q=1");
assert_eq!(observed.host, upstream_addr.to_string());
assert_eq!(observed.host, format!("localhost:{}", upstream_addr.port()));
assert!(!observed.forwarded_proxy_authorization);
drop(observed);
@@ -710,7 +722,7 @@ mod tests {
let (mut sender, connection_task) = connect_client(proxy_addr).await;
let request = Request::builder()
.method(Method::CONNECT)
.uri(echo_addr.to_string())
.uri(format!("localhost:{}", echo_addr.port()))
.body(Full::new(Bytes::new()))
.unwrap();
@@ -759,6 +771,56 @@ mod tests {
connection_task.abort();
}
#[tokio::test]
async fn denies_blacklisted_http_domain_before_upstream_connection() {
let acl = Acl::from_config(AclConfig {
default_action: Action::Allow,
users: BTreeMap::new(),
rules: vec![RuleConfig {
name: "blocked-domain".to_owned(),
action: Action::Deny,
users: vec![],
networks: vec![],
domains: vec!["blocked.example".to_owned()],
}],
})
.unwrap();
let (proxy_addr, proxy_task) = spawn_proxy_with_acl(acl).await;
let (mut sender, connection_task) = connect_client(proxy_addr).await;
let request = Request::builder()
.method(Method::GET)
.uri("http://blocked.example/")
.body(Full::new(Bytes::new()))
.unwrap();
let response = sender.send_request(request).await.unwrap();
assert_eq!(response.status(), StatusCode::FORBIDDEN);
response.into_body().collect().await.unwrap();
proxy_task.abort();
connection_task.abort();
}
#[tokio::test]
async fn domain_whitelist_rejects_ip_literal_connect() {
let echo_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let echo_addr = echo_listener.local_addr().unwrap();
let (proxy_addr, proxy_task) = spawn_proxy_with_acl(localhost_acl()).await;
let (mut sender, connection_task) = connect_client(proxy_addr).await;
let request = Request::builder()
.method(Method::CONNECT)
.uri(echo_addr.to_string())
.body(Full::new(Bytes::new()))
.unwrap();
let response = sender.send_request(request).await.unwrap();
assert_eq!(response.status(), StatusCode::FORBIDDEN);
response.into_body().collect().await.unwrap();
proxy_task.abort();
connection_task.abort();
}
fn basic_authorization() -> String {
format!(
"Basic {}",
@@ -781,6 +843,7 @@ mod tests {
action: Action::Allow,
users: vec!["alice".to_owned()],
networks: vec![],
domains: vec!["localhost".to_owned()],
}],
})
.unwrap()
@@ -795,6 +858,7 @@ mod tests {
action: Action::Allow,
users: vec![],
networks: vec!["127.0.0.1/32".to_owned(), "::1/128".to_owned()],
domains: vec!["localhost".to_owned()],
}],
})
.unwrap()