mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Scope network approvals by environment (#28899)
Stacked on #28766. ## Why Network approvals are environment-scoped: allowing a host in one execution environment should not allow the same host in another environment. #28766 adds the inert IDs and constructor plumbing. This PR applies the behavior on top. ## What changed - Route managed network traffic through per-environment HTTP and SOCKS proxy listeners. - Stamp HTTP, HTTPS CONNECT, SOCKS TCP, and SOCKS UDP policy requests with the source environment at the proxy boundary. - Carry the selected execution environment through shell, unified exec, zsh-fork, and sandbox transform paths. - Include the environment in pending, approved-for-session, and denied-for-session network approval cache keys. - Include the environment in approval IDs and approval prompts. - Preserve legacy fallback for unattributed requests, but deny when active-call attribution is ambiguous. - Fail closed if an environment-specific proxy endpoint cannot be prepared. ## Validation - just fmt - CI will run tests and clippy
This commit is contained in:
@@ -87,6 +87,7 @@ pub async fn run_http_proxy(
|
||||
state: Arc<NetworkProxyState>,
|
||||
addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
let listener = TcpListener::build()
|
||||
.bind(addr)
|
||||
@@ -99,23 +100,25 @@ pub async fn run_http_proxy(
|
||||
.map_err(anyhow::Error::from)
|
||||
.with_context(|| format!("bind HTTP proxy: {addr}"))?;
|
||||
|
||||
run_http_proxy_with_listener(state, listener, policy_decider).await
|
||||
run_http_proxy_with_listener(state, listener, policy_decider, environment_id).await
|
||||
}
|
||||
|
||||
pub async fn run_http_proxy_with_std_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: StdTcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
let listener =
|
||||
TcpListener::try_from(listener).context("convert std listener to HTTP proxy listener")?;
|
||||
run_http_proxy_with_listener(state, listener, policy_decider).await
|
||||
run_http_proxy_with_listener(state, listener, policy_decider, environment_id).await
|
||||
}
|
||||
|
||||
async fn run_http_proxy_with_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: TcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
ensure_rustls_crypto_provider();
|
||||
|
||||
@@ -133,7 +136,10 @@ async fn run_http_proxy_with_listener(
|
||||
MethodMatcher::CONNECT,
|
||||
service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
move |req| http_connect_accept(policy_decider.clone(), req)
|
||||
let environment_id = environment_id.clone();
|
||||
move |req| {
|
||||
http_connect_accept(policy_decider.clone(), environment_id.clone(), req)
|
||||
}
|
||||
}),
|
||||
service_fn(http_connect_proxy),
|
||||
),
|
||||
@@ -141,7 +147,8 @@ async fn run_http_proxy_with_listener(
|
||||
)
|
||||
.into_layer(service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
move |req| http_plain_proxy(policy_decider.clone(), req)
|
||||
let environment_id = environment_id.clone();
|
||||
move |req| http_plain_proxy(policy_decider.clone(), environment_id.clone(), req)
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -155,6 +162,7 @@ async fn run_http_proxy_with_listener(
|
||||
|
||||
async fn http_connect_accept(
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
mut req: Request,
|
||||
) -> Result<(Response, Request), Response> {
|
||||
let app_state = req
|
||||
@@ -200,7 +208,7 @@ async fn http_connect_accept(
|
||||
protocol: NetworkProtocol::HttpsConnect,
|
||||
host: host.clone(),
|
||||
port: authority.port,
|
||||
environment_id: None,
|
||||
environment_id,
|
||||
client_addr: client.clone(),
|
||||
method: Some("CONNECT".to_string()),
|
||||
command: None,
|
||||
@@ -479,6 +487,7 @@ async fn forward_connect_tunnel(
|
||||
|
||||
async fn http_plain_proxy(
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
mut req: Request,
|
||||
) -> Result<Response, Infallible> {
|
||||
let app_state = match req.extensions().get::<Arc<NetworkProxyState>>().cloned() {
|
||||
@@ -684,7 +693,7 @@ async fn http_plain_proxy(
|
||||
protocol: NetworkProtocol::Http,
|
||||
host: host.clone(),
|
||||
port,
|
||||
environment_id: None,
|
||||
environment_id,
|
||||
client_addr: client.clone(),
|
||||
method: Some(req.method().as_str().to_string()),
|
||||
command: None,
|
||||
@@ -1052,6 +1061,7 @@ mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener as TokioTcpListener;
|
||||
@@ -1076,9 +1086,11 @@ mod tests {
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_connect_accept(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let response = http_connect_accept(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
response.headers().get("x-proxy-error").unwrap(),
|
||||
@@ -1106,12 +1118,53 @@ mod tests {
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let (response, _request) = http_connect_accept(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap();
|
||||
let (response, _request) = http_connect_accept(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_passes_environment_id_to_decider() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let seen_environment_id = Arc::new(Mutex::new(None));
|
||||
let decider: Arc<dyn NetworkPolicyDecider> = Arc::new({
|
||||
let seen_environment_id = seen_environment_id.clone();
|
||||
move |request: NetworkPolicyRequest| {
|
||||
*seen_environment_id
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = request.environment_id;
|
||||
async { NetworkDecision::Allow }
|
||||
}
|
||||
});
|
||||
|
||||
let mut req = Request::builder()
|
||||
.method(Method::CONNECT)
|
||||
.uri("https://example.com:443")
|
||||
.header("host", "example.com:443")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let (response, _request) =
|
||||
http_connect_accept(Some(decider), Some("remote".to_string()), req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
seen_environment_id
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.as_deref(),
|
||||
Some("remote")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state() {
|
||||
let mut policy = NetworkProxySettings {
|
||||
@@ -1138,9 +1191,11 @@ mod tests {
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_connect_accept(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let response = http_connect_accept(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
response.headers().get("x-proxy-error").unwrap(),
|
||||
@@ -1177,7 +1232,7 @@ mod tests {
|
||||
.local_addr()
|
||||
.expect("proxy listener should expose local addr");
|
||||
let proxy_task = tokio::spawn(run_http_proxy_with_std_listener(
|
||||
state, listener, /*policy_decider*/ None,
|
||||
state, listener, /*policy_decider*/ None, /*environment_id*/ None,
|
||||
));
|
||||
|
||||
let mut stream = tokio::net::TcpStream::connect(proxy_addr)
|
||||
@@ -1228,9 +1283,11 @@ mod tests {
|
||||
.expect("request should build");
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_plain_proxy(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = http_plain_proxy(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
@@ -1253,9 +1310,11 @@ mod tests {
|
||||
.expect("request should build");
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_plain_proxy(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = http_plain_proxy(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
@@ -1285,9 +1344,11 @@ mod tests {
|
||||
.expect("request should build");
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_plain_proxy(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = http_plain_proxy(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
|
||||
}
|
||||
|
||||
@@ -1309,9 +1370,11 @@ mod tests {
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_connect_accept(/*policy_decider*/ None, req)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let response = http_connect_accept(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(response.status(), StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
response.headers().get("x-proxy-error").unwrap(),
|
||||
@@ -1332,7 +1395,10 @@ mod tests {
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(state);
|
||||
|
||||
let response = http_plain_proxy(/*policy_decider*/ None, req).await;
|
||||
let response = http_plain_proxy(
|
||||
/*policy_decider*/ None, /*environment_id*/ None, req,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.unwrap().status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
@@ -222,11 +222,13 @@ impl NetworkProxyBuilder {
|
||||
http_addr,
|
||||
socks_addr,
|
||||
socks_enabled: current_cfg.network.enable_socks5,
|
||||
socks5_udp_enabled: current_cfg.network.enable_socks5_udp,
|
||||
runtime_settings: Arc::new(RwLock::new(NetworkProxyRuntimeSettings::from_config(
|
||||
¤t_cfg,
|
||||
)?)),
|
||||
reserved_listeners,
|
||||
policy_decider: self.policy_decider,
|
||||
environment_proxies: Arc::new(Mutex::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -323,15 +325,29 @@ impl NetworkProxyRuntimeSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct EnvironmentProxyAddrs {
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
}
|
||||
|
||||
struct EnvironmentProxy {
|
||||
addrs: EnvironmentProxyAddrs,
|
||||
http_task: JoinHandle<Result<()>>,
|
||||
socks_task: Option<JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkProxy {
|
||||
state: Arc<NetworkProxyState>,
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
socks_enabled: bool,
|
||||
socks5_udp_enabled: bool,
|
||||
runtime_settings: Arc<RwLock<NetworkProxyRuntimeSettings>>,
|
||||
reserved_listeners: Option<Arc<ReservedListeners>>,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_proxies: Arc<Mutex<HashMap<String, EnvironmentProxy>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NetworkProxy {
|
||||
@@ -640,20 +656,135 @@ impl NetworkProxy {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
fn apply_to_env_for_addrs(
|
||||
&self,
|
||||
env: &mut HashMap<String, String>,
|
||||
addrs: EnvironmentProxyAddrs,
|
||||
) {
|
||||
let runtime_settings = self.runtime_settings();
|
||||
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
|
||||
// managed MITM CA vars preserve child-scoped overrides after proxy startup.
|
||||
apply_proxy_env_overrides(
|
||||
env,
|
||||
self.http_addr,
|
||||
self.socks_addr,
|
||||
addrs.http_addr,
|
||||
addrs.socks_addr,
|
||||
self.socks_enabled,
|
||||
runtime_settings.allow_local_binding,
|
||||
runtime_settings.mitm_ca_trust_bundle.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
self.apply_to_env_for_addrs(
|
||||
env,
|
||||
EnvironmentProxyAddrs {
|
||||
http_addr: self.http_addr,
|
||||
socks_addr: self.socks_addr,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn apply_to_env_for_environment(
|
||||
&self,
|
||||
env: &mut HashMap<String, String>,
|
||||
environment_id: &str,
|
||||
) -> Result<()> {
|
||||
let addrs = self.environment_proxy_addrs(environment_id)?;
|
||||
self.apply_to_env_for_addrs(env, addrs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_to_env_for_optional_environment(
|
||||
&self,
|
||||
env: &mut HashMap<String, String>,
|
||||
environment_id: Option<&str>,
|
||||
) -> Result<()> {
|
||||
match environment_id {
|
||||
Some(environment_id) => self.apply_to_env_for_environment(env, environment_id),
|
||||
None => {
|
||||
self.apply_to_env(env);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn environment_proxy_addrs(&self, environment_id: &str) -> Result<EnvironmentProxyAddrs> {
|
||||
let mut proxies = self
|
||||
.environment_proxies
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(proxy) = proxies.get(environment_id) {
|
||||
return Ok(proxy.addrs);
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Handle::try_current().with_context(|| {
|
||||
format!("failed to create network proxy for environment `{environment_id}`")
|
||||
})?;
|
||||
let listeners =
|
||||
reserve_loopback_ephemeral_listeners(self.socks_enabled).with_context(|| {
|
||||
format!("failed to reserve network proxy for environment `{environment_id}`")
|
||||
})?;
|
||||
let http_addr = listeners.http_addr().with_context(|| {
|
||||
format!("failed to read HTTP proxy address for environment `{environment_id}`")
|
||||
})?;
|
||||
let socks_addr = listeners.socks_addr(self.socks_addr).with_context(|| {
|
||||
format!("failed to read SOCKS proxy address for environment `{environment_id}`")
|
||||
})?;
|
||||
let addrs = EnvironmentProxyAddrs {
|
||||
http_addr,
|
||||
socks_addr,
|
||||
};
|
||||
let ReservedListenerSet {
|
||||
http_listener,
|
||||
socks_listener,
|
||||
} = listeners;
|
||||
|
||||
let environment_id = environment_id.to_string();
|
||||
let http_state = self.state.clone();
|
||||
let http_decider = self.policy_decider.clone();
|
||||
let http_environment_id = Some(environment_id.clone());
|
||||
let http_task = runtime.spawn(async move {
|
||||
http_proxy::run_http_proxy_with_std_listener(
|
||||
http_state,
|
||||
http_listener,
|
||||
http_decider,
|
||||
http_environment_id,
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
let socks_task = if self.socks_enabled {
|
||||
let socks_state = self.state.clone();
|
||||
let socks_decider = self.policy_decider.clone();
|
||||
let socks_environment_id = Some(environment_id.clone());
|
||||
let socks5_udp_enabled = self.socks5_udp_enabled;
|
||||
socks_listener.map(|listener| {
|
||||
runtime.spawn(async move {
|
||||
socks5::run_socks5_with_std_listener(
|
||||
socks_state,
|
||||
listener,
|
||||
socks_decider,
|
||||
socks_environment_id,
|
||||
socks5_udp_enabled,
|
||||
)
|
||||
.await
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
proxies.insert(
|
||||
environment_id,
|
||||
EnvironmentProxy {
|
||||
addrs,
|
||||
http_task,
|
||||
socks_task,
|
||||
},
|
||||
);
|
||||
Ok(addrs)
|
||||
}
|
||||
|
||||
pub async fn replace_config_state(&self, new_state: ConfigState) -> Result<()> {
|
||||
let current_cfg = self.state.current_cfg().await?;
|
||||
anyhow::ensure!(
|
||||
@@ -717,10 +848,23 @@ impl NetworkProxy {
|
||||
let http_task = tokio::spawn(async move {
|
||||
match http_listener {
|
||||
Some(listener) => {
|
||||
http_proxy::run_http_proxy_with_std_listener(http_state, listener, http_decider)
|
||||
.await
|
||||
http_proxy::run_http_proxy_with_std_listener(
|
||||
http_state,
|
||||
listener,
|
||||
http_decider,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
http_proxy::run_http_proxy(
|
||||
http_state,
|
||||
http_addr,
|
||||
http_decider,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
None => http_proxy::run_http_proxy(http_state, http_addr, http_decider).await,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -736,6 +880,7 @@ impl NetworkProxy {
|
||||
socks_state,
|
||||
listener,
|
||||
socks_decider,
|
||||
/*environment_id*/ None,
|
||||
enable_socks5_udp,
|
||||
)
|
||||
.await
|
||||
@@ -745,6 +890,7 @@ impl NetworkProxy {
|
||||
socks_state,
|
||||
socks_addr,
|
||||
socks_decider,
|
||||
/*environment_id*/ None,
|
||||
enable_socks5_udp,
|
||||
)
|
||||
.await
|
||||
@@ -758,6 +904,7 @@ impl NetworkProxy {
|
||||
Ok(NetworkProxyHandle {
|
||||
http_task: Some(http_task),
|
||||
socks_task,
|
||||
environment_proxies: self.environment_proxies.clone(),
|
||||
completed: false,
|
||||
})
|
||||
}
|
||||
@@ -766,6 +913,7 @@ impl NetworkProxy {
|
||||
pub struct NetworkProxyHandle {
|
||||
http_task: Option<JoinHandle<Result<()>>>,
|
||||
socks_task: Option<JoinHandle<Result<()>>>,
|
||||
environment_proxies: Arc<Mutex<HashMap<String, EnvironmentProxy>>>,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
@@ -774,6 +922,7 @@ impl NetworkProxyHandle {
|
||||
Self {
|
||||
http_task: Some(tokio::spawn(async { Ok(()) })),
|
||||
socks_task: None,
|
||||
environment_proxies: Arc::new(Mutex::new(HashMap::new())),
|
||||
completed: true,
|
||||
}
|
||||
}
|
||||
@@ -787,6 +936,7 @@ impl NetworkProxyHandle {
|
||||
None => None,
|
||||
};
|
||||
self.completed = true;
|
||||
abort_environment_proxies(self.environment_proxies.clone()).await;
|
||||
http_result??;
|
||||
if let Some(socks_result) = socks_result {
|
||||
socks_result??;
|
||||
@@ -796,6 +946,7 @@ impl NetworkProxyHandle {
|
||||
|
||||
pub async fn shutdown(mut self) -> Result<()> {
|
||||
abort_tasks(self.http_task.take(), self.socks_task.take()).await;
|
||||
abort_environment_proxies(self.environment_proxies.clone()).await;
|
||||
self.completed = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -816,6 +967,21 @@ async fn abort_tasks(
|
||||
abort_task(socks_task).await;
|
||||
}
|
||||
|
||||
async fn abort_environment_proxies(
|
||||
environment_proxies: Arc<Mutex<HashMap<String, EnvironmentProxy>>>,
|
||||
) {
|
||||
let proxies = {
|
||||
let mut guard = environment_proxies
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
guard.drain().map(|(_, proxy)| proxy).collect::<Vec<_>>()
|
||||
};
|
||||
for proxy in proxies {
|
||||
abort_task(Some(proxy.http_task)).await;
|
||||
abort_task(proxy.socks_task).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetworkProxyHandle {
|
||||
fn drop(&mut self) {
|
||||
if self.completed {
|
||||
@@ -823,8 +989,10 @@ impl Drop for NetworkProxyHandle {
|
||||
}
|
||||
let http_task = self.http_task.take();
|
||||
let socks_task = self.socks_task.take();
|
||||
let environment_proxies = self.environment_proxies.clone();
|
||||
tokio::spawn(async move {
|
||||
abort_tasks(http_task, socks_task).await;
|
||||
abort_environment_proxies(environment_proxies).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -905,6 +1073,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn apply_to_env_for_environment_uses_distinct_proxy_ports() -> Result<()> {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let proxy = NetworkProxy::builder().state(state).build().await?;
|
||||
let handle = proxy.run().await?;
|
||||
|
||||
let mut local_env = HashMap::new();
|
||||
proxy.apply_to_env_for_environment(&mut local_env, "local")?;
|
||||
let mut remote_env = HashMap::new();
|
||||
proxy.apply_to_env_for_environment(&mut remote_env, "remote")?;
|
||||
|
||||
assert_ne!(local_env.get("HTTP_PROXY"), remote_env.get("HTTP_PROXY"));
|
||||
assert_ne!(
|
||||
local_env.get("HTTP_PROXY"),
|
||||
Some(&format!("http://{}", proxy.http_addr()))
|
||||
);
|
||||
assert_ne!(
|
||||
remote_env.get("HTTP_PROXY"),
|
||||
Some(&format!("http://{}", proxy.http_addr()))
|
||||
);
|
||||
|
||||
handle.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_proxy_builder_does_not_reserve_socks_listener_when_disabled() {
|
||||
let settings = NetworkProxySettings {
|
||||
|
||||
@@ -64,6 +64,7 @@ pub async fn run_socks5(
|
||||
state: Arc<NetworkProxyState>,
|
||||
addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
enable_socks5_udp: bool,
|
||||
) -> Result<()> {
|
||||
let listener = TcpListener::build()
|
||||
@@ -74,24 +75,40 @@ pub async fn run_socks5(
|
||||
.map_err(anyhow::Error::from)
|
||||
.with_context(|| format!("bind SOCKS5 proxy: {addr}"))?;
|
||||
|
||||
run_socks5_with_listener(state, listener, policy_decider, enable_socks5_udp).await
|
||||
run_socks5_with_listener(
|
||||
state,
|
||||
listener,
|
||||
policy_decider,
|
||||
environment_id,
|
||||
enable_socks5_udp,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_socks5_with_std_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: StdTcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
enable_socks5_udp: bool,
|
||||
) -> Result<()> {
|
||||
let listener =
|
||||
TcpListener::try_from(listener).context("convert std listener to SOCKS5 proxy listener")?;
|
||||
run_socks5_with_listener(state, listener, policy_decider, enable_socks5_udp).await
|
||||
run_socks5_with_listener(
|
||||
state,
|
||||
listener,
|
||||
policy_decider,
|
||||
environment_id,
|
||||
enable_socks5_udp,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_socks5_with_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: TcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
enable_socks5_udp: bool,
|
||||
) -> Result<()> {
|
||||
let addr = listener
|
||||
@@ -115,10 +132,12 @@ async fn run_socks5_with_listener(
|
||||
let tcp_connector = TargetCheckedTcpConnector::new(state.clone());
|
||||
let policy_tcp_connector = service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
let environment_id = environment_id.clone();
|
||||
move |req: TcpRequest| {
|
||||
let tcp_connector = tcp_connector.clone();
|
||||
let policy_decider = policy_decider.clone();
|
||||
async move { handle_socks5_tcp(req, tcp_connector, policy_decider).await }
|
||||
let environment_id = environment_id.clone();
|
||||
async move { handle_socks5_tcp(req, tcp_connector, policy_decider, environment_id).await }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,13 +150,18 @@ async fn run_socks5_with_listener(
|
||||
if enable_socks5_udp {
|
||||
let udp_state = state.clone();
|
||||
let udp_decider = policy_decider.clone();
|
||||
let udp_relay = DefaultUdpRelay::default().with_async_inspector(service_fn({
|
||||
move |request: RelayRequest| {
|
||||
let udp_state = udp_state.clone();
|
||||
let udp_decider = udp_decider.clone();
|
||||
async move { inspect_socks5_udp(request, udp_state, udp_decider).await }
|
||||
}
|
||||
}));
|
||||
let udp_relay =
|
||||
DefaultUdpRelay::default().with_async_inspector(service_fn({
|
||||
let environment_id = environment_id.clone();
|
||||
move |request: RelayRequest| {
|
||||
let udp_state = udp_state.clone();
|
||||
let udp_decider = udp_decider.clone();
|
||||
let environment_id = environment_id.clone();
|
||||
async move {
|
||||
inspect_socks5_udp(request, udp_state, udp_decider, environment_id).await
|
||||
}
|
||||
}
|
||||
}));
|
||||
let socks_acceptor = base.with_udp_associator(udp_relay);
|
||||
listener
|
||||
.serve(AddInputExtensionLayer::new(state).into_layer(socks_acceptor))
|
||||
@@ -154,6 +178,7 @@ async fn handle_socks5_tcp(
|
||||
req: TcpRequest,
|
||||
tcp_connector: TargetCheckedTcpConnector,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
) -> Result<EstablishedClientConnection<Socks5TcpConnection, TcpRequest>, BoxError> {
|
||||
let app_state = req
|
||||
.extensions()
|
||||
@@ -268,7 +293,7 @@ async fn handle_socks5_tcp(
|
||||
protocol: NetworkProtocol::Socks5Tcp,
|
||||
host: host.clone(),
|
||||
port,
|
||||
environment_id: None,
|
||||
environment_id,
|
||||
client_addr: client.clone(),
|
||||
method: None,
|
||||
command: None,
|
||||
@@ -519,6 +544,7 @@ async fn inspect_socks5_udp(
|
||||
request: RelayRequest,
|
||||
state: Arc<NetworkProxyState>,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
environment_id: Option<String>,
|
||||
) -> io::Result<RelayResponse> {
|
||||
let RelayRequest {
|
||||
server_address,
|
||||
@@ -625,7 +651,7 @@ async fn inspect_socks5_udp(
|
||||
protocol: NetworkProtocol::Socks5Udp,
|
||||
host: host.clone(),
|
||||
port,
|
||||
environment_id: None,
|
||||
environment_id,
|
||||
client_addr: client.clone(),
|
||||
method: None,
|
||||
command: None,
|
||||
@@ -783,6 +809,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state.clone()),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
})
|
||||
@@ -826,6 +853,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("limited-mode HTTPS should use MITM");
|
||||
@@ -851,6 +879,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
})
|
||||
@@ -896,6 +925,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
.expect_err("limited-mode HTTPS requires MITM");
|
||||
@@ -933,6 +963,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("hooked HTTPS should use MITM");
|
||||
@@ -967,6 +998,7 @@ mod tests {
|
||||
request,
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
/*policy_decider*/ None,
|
||||
/*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
.expect_err("hooked non-HTTPS SOCKS should require MITM");
|
||||
@@ -992,7 +1024,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let (result, events) = capture_events(|| async {
|
||||
inspect_socks5_udp(request, state, /*policy_decider*/ None).await
|
||||
inspect_socks5_udp(
|
||||
request, state, /*policy_decider*/ None, /*environment_id*/ None,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await;
|
||||
assert!(result.is_err(), "limited-mode UDP request should be denied");
|
||||
|
||||
Reference in New Issue
Block a user