mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Allow explicitly permitted loopback proxy targets (#34603)
## Why With `allow_local_binding = false`, proxy-aware clients bypassed the managed proxy for loopback and private IP targets. This prevented explicit local IP and `localhost` allowlist entries from taking effect. ## What changed - Route local targets through the managed proxy when local binding is disabled, while preserving direct access when it is enabled. - Allow a non-public connection only when its requested IP literal or `localhost` target is explicitly permitted. Continue blocking private addresses reached through unrelated hostnames. - Bypass inherited upstream proxies for non-public targets so Codex applies the local-target policy directly. ## Testing Added connector, upstream proxy, environment override, and Linux sandbox coverage for explicitly allowlisted loopback access. GitOrigin-RevId: 35e0d73633d5051816e096bc5aa8d586aec5eea4
This commit is contained in:
@@ -41,6 +41,8 @@ mode = "full" # default when unset; use "limited" for read-only mode
|
||||
# If false, local/private networking is rejected. Explicit allowlisting of local IP literals
|
||||
# (or `localhost`) is required to permit them.
|
||||
# Hostnames that resolve to local/private IPs are still blocked even if allowlisted.
|
||||
# Clients that always bypass proxies for loopback, such as Go's `net/http`, remain blocked by
|
||||
# the operating-system sandbox when local binding is disabled.
|
||||
allow_local_binding = false
|
||||
|
||||
# DANGEROUS (macOS-only): bypasses unix socket allowlisting and permits any
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::policy::is_non_public_ip;
|
||||
use crate::runtime::HostBlockDecision;
|
||||
use crate::state::NetworkProxyState;
|
||||
use rama_core::Service;
|
||||
use rama_core::error::BoxError;
|
||||
use rama_core::error::ErrorExt as _;
|
||||
use rama_core::error::OpaqueError;
|
||||
use rama_core::extensions::ExtensionsMut;
|
||||
use rama_net::address::Host;
|
||||
use rama_net::address::HostWithPort;
|
||||
use rama_net::address::ProxyAddress;
|
||||
use rama_net::client::EstablishedClientConnection;
|
||||
use rama_net::transport::TryRefIntoTransportContext;
|
||||
@@ -17,22 +20,12 @@ use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TargetCheckedTcpConnector {
|
||||
policy: TargetPolicy,
|
||||
state: Arc<NetworkProxyState>,
|
||||
}
|
||||
|
||||
impl TargetCheckedTcpConnector {
|
||||
pub(crate) fn new(state: Arc<NetworkProxyState>) -> Self {
|
||||
Self {
|
||||
policy: TargetPolicy::State(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_allow_local_binding(allow_local_binding: bool) -> Self {
|
||||
Self {
|
||||
policy: TargetPolicy::Config {
|
||||
allow_local_binding,
|
||||
},
|
||||
}
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +42,16 @@ where
|
||||
return TcpConnector::new().serve(input).await;
|
||||
}
|
||||
|
||||
let target = input
|
||||
.try_ref_into_transport_ctx()
|
||||
.map_err(|err| OpaqueError::from_boxed(err.into()).context("read network target"))?
|
||||
.host_with_port()
|
||||
.ok_or_else(|| OpaqueError::from_display("network target is missing a port"))?;
|
||||
|
||||
TcpConnector::new()
|
||||
.with_connector(TargetCheckedStreamConnector {
|
||||
policy: self.policy.clone(),
|
||||
state: self.state.clone(),
|
||||
target,
|
||||
})
|
||||
.serve(input)
|
||||
.await
|
||||
@@ -60,14 +60,15 @@ where
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TargetCheckedStreamConnector {
|
||||
policy: TargetPolicy,
|
||||
state: Arc<NetworkProxyState>,
|
||||
target: HostWithPort,
|
||||
}
|
||||
|
||||
impl TcpStreamConnector for TargetCheckedStreamConnector {
|
||||
type Error = BoxError;
|
||||
|
||||
async fn connect(&self, addr: SocketAddr) -> Result<TcpStream, Self::Error> {
|
||||
if !self.policy.allow_local_binding().await? && is_non_public_ip(addr.ip()) {
|
||||
if is_non_public_ip(addr.ip()) && !self.allows_non_public_target(addr).await? {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
"network target rejected by policy",
|
||||
@@ -82,24 +83,52 @@ impl TcpStreamConnector for TargetCheckedStreamConnector {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum TargetPolicy {
|
||||
Config { allow_local_binding: bool },
|
||||
State(Arc<NetworkProxyState>),
|
||||
}
|
||||
impl TargetCheckedStreamConnector {
|
||||
async fn allows_non_public_target(&self, addr: SocketAddr) -> Result<bool, BoxError> {
|
||||
if self.state.allow_local_binding().await.map_err(|err| {
|
||||
let err: BoxError = err.into();
|
||||
OpaqueError::from_boxed(err)
|
||||
.context("read network proxy config")
|
||||
.into_boxed()
|
||||
})? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
impl TargetPolicy {
|
||||
async fn allow_local_binding(&self) -> Result<bool, BoxError> {
|
||||
match self {
|
||||
Self::Config {
|
||||
allow_local_binding,
|
||||
} => Ok(*allow_local_binding),
|
||||
Self::State(state) => state.allow_local_binding().await.map_err(|err| {
|
||||
if !target_matches_non_public_addr(&self.target.host, addr.ip()) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.state
|
||||
.host_blocked(&self.target.host.to_string(), self.target.port)
|
||||
.await
|
||||
.map(|decision| decision == HostBlockDecision::Allowed)
|
||||
.map_err(|err| {
|
||||
let err: BoxError = err.into();
|
||||
OpaqueError::from_boxed(err)
|
||||
.context("read network proxy config")
|
||||
.context("evaluate network proxy target")
|
||||
.into_boxed()
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_non_public_target(host: &Host) -> bool {
|
||||
match host {
|
||||
Host::Address(ip) => is_non_public_ip(*ip),
|
||||
Host::Name(name) => name
|
||||
.as_str()
|
||||
.trim_end_matches('.')
|
||||
.eq_ignore_ascii_case("localhost"),
|
||||
}
|
||||
}
|
||||
|
||||
fn target_matches_non_public_addr(host: &Host, addr: std::net::IpAddr) -> bool {
|
||||
match host {
|
||||
Host::Address(ip) => *ip == addr,
|
||||
Host::Name(name) => {
|
||||
name.as_str()
|
||||
.trim_end_matches('.')
|
||||
.eq_ignore_ascii_case("localhost")
|
||||
&& addr.is_loopback()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,4 +183,56 @@ mod tests {
|
||||
|
||||
assert!(result.is_ok(), "local target should be allowed: {result:?}");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn direct_connector_allows_explicitly_allowlisted_non_public_target() {
|
||||
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))
|
||||
.await
|
||||
.expect("bind local listener");
|
||||
let target = listener.local_addr().expect("local addr");
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.set_allowed_domains(vec![target.ip().to_string()]);
|
||||
let connector =
|
||||
TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(config)));
|
||||
|
||||
let request: rama_tcp::client::Request =
|
||||
rama_tcp::client::Request::new(HostWithPort::from(target));
|
||||
let result = Service::serve(&connector, request).await;
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"explicitly allowlisted local target should be allowed: {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn direct_connector_allows_explicitly_allowlisted_localhost_target() {
|
||||
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))
|
||||
.await
|
||||
.expect("bind local listener");
|
||||
let target = listener.local_addr().expect("local addr");
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.set_allowed_domains(vec!["localhost".to_string()]);
|
||||
let connector =
|
||||
TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(config)));
|
||||
|
||||
let request: rama_tcp::client::Request =
|
||||
rama_tcp::client::Request::new(HostWithPort::new(Host::LOCALHOST_NAME, target.port()));
|
||||
let result = Service::serve(&connector, request).await;
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"explicitly allowlisted localhost target should be allowed: {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolved_private_address_does_not_match_allowlisted_hostname() {
|
||||
let host = Host::Name("example.com".parse().expect("valid domain"));
|
||||
|
||||
assert!(!target_matches_non_public_addr(
|
||||
&host,
|
||||
Ipv4Addr::LOCALHOST.into()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ where
|
||||
}
|
||||
};
|
||||
let proxy = if allow_upstream_proxy {
|
||||
proxy_for_connect()
|
||||
proxy_for_connect(&authority)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@ use rama_http_backend::server::HttpServer;
|
||||
use rama_net::proxy::ProxyTarget;
|
||||
use rama_net::stream::SocketInfo;
|
||||
use rama_net::tls::server::TlsPeekStream;
|
||||
use rama_tls_rustls::dep::rustls;
|
||||
use rama_tls_rustls::server::TlsAcceptorData;
|
||||
use rama_tls_rustls::server::TlsAcceptorLayer;
|
||||
use std::pin::Pin;
|
||||
@@ -59,14 +60,14 @@ use tracing::warn;
|
||||
/// State needed to terminate a CONNECT tunnel and enforce policy on inner HTTPS requests.
|
||||
pub struct MitmState {
|
||||
ca: Arc<ManagedMitmCa>,
|
||||
upstream: UpstreamClient,
|
||||
allow_upstream_proxy: bool,
|
||||
upstream_tls_root_store: Arc<rustls::RootCertStore>,
|
||||
inspect: bool,
|
||||
max_body_bytes: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct MitmUpstreamConfig {
|
||||
pub(crate) allow_upstream_proxy: bool,
|
||||
pub(crate) allow_local_binding: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -81,6 +82,7 @@ struct MitmPolicyContext {
|
||||
struct MitmRequestContext {
|
||||
policy: MitmPolicyContext,
|
||||
mitm: Arc<MitmState>,
|
||||
upstream: UpstreamClient,
|
||||
}
|
||||
|
||||
enum MitmPolicyDecision {
|
||||
@@ -160,21 +162,10 @@ impl MitmState {
|
||||
let upstream_tls_root_store =
|
||||
crate::certs::upstream_tls_root_store(&crate::certs::ca_env_from_process())?;
|
||||
|
||||
let upstream = if config.allow_upstream_proxy {
|
||||
UpstreamClient::from_env_proxy_with_allow_local_binding(
|
||||
config.allow_local_binding,
|
||||
upstream_tls_root_store,
|
||||
)
|
||||
} else {
|
||||
UpstreamClient::direct_with_allow_local_binding(
|
||||
config.allow_local_binding,
|
||||
upstream_tls_root_store,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
ca,
|
||||
upstream,
|
||||
allow_upstream_proxy: config.allow_upstream_proxy,
|
||||
upstream_tls_root_store,
|
||||
inspect: MITM_INSPECT_BODIES,
|
||||
max_body_bytes: MITM_MAX_BODY_BYTES,
|
||||
})
|
||||
@@ -222,6 +213,17 @@ where
|
||||
.get::<NetworkMode>()
|
||||
.copied()
|
||||
.unwrap_or(NetworkMode::Full);
|
||||
let upstream = if mitm.allow_upstream_proxy {
|
||||
UpstreamClient::from_env_proxy_with_tls_root_store(
|
||||
app_state.clone(),
|
||||
mitm.upstream_tls_root_store.clone(),
|
||||
)
|
||||
} else {
|
||||
UpstreamClient::direct_with_tls_root_store(
|
||||
app_state.clone(),
|
||||
mitm.upstream_tls_root_store.clone(),
|
||||
)
|
||||
};
|
||||
let request_ctx = Arc::new(MitmRequestContext {
|
||||
policy: MitmPolicyContext {
|
||||
target_host,
|
||||
@@ -230,6 +232,7 @@ where
|
||||
app_state,
|
||||
},
|
||||
mitm,
|
||||
upstream,
|
||||
});
|
||||
|
||||
let executor = stream
|
||||
@@ -320,7 +323,7 @@ async fn forward_request(req: Request, request_ctx: &MitmRequestContext) -> Resu
|
||||
};
|
||||
|
||||
let upstream_req = Request::from_parts(parts, body);
|
||||
let upstream_resp = mitm.upstream.serve(upstream_req).await?;
|
||||
let upstream_resp = request_ctx.upstream.serve(upstream_req).await?;
|
||||
respond_with_inspection(
|
||||
upstream_resp,
|
||||
inspect,
|
||||
|
||||
@@ -572,10 +572,14 @@ fn apply_proxy_env_overrides(
|
||||
// HTTP(S)_PROXY. Keep them aligned with the managed HTTP proxy endpoint.
|
||||
set_env_keys(env, WEBSOCKET_PROXY_ENV_KEYS, &http_proxy_url);
|
||||
|
||||
// Keep loopback and IP-literal private targets direct so local IPC/LAN access avoids the proxy.
|
||||
// Do not include hostname suffixes here: those can force clients to resolve internal names
|
||||
// locally instead of letting the proxy resolve them.
|
||||
set_env_keys(env, NO_PROXY_ENV_KEYS, DEFAULT_NO_PROXY_VALUE);
|
||||
// Keep local targets direct only when local binding is enabled. Otherwise route them through
|
||||
// the proxy so explicit literal allowlists and local-network restrictions can be enforced.
|
||||
let no_proxy = if allow_local_binding {
|
||||
DEFAULT_NO_PROXY_VALUE
|
||||
} else {
|
||||
""
|
||||
};
|
||||
set_env_keys(env, NO_PROXY_ENV_KEYS, no_proxy);
|
||||
|
||||
env.insert(
|
||||
ELECTRON_GET_USE_PROXY_ENV_KEY.to_string(),
|
||||
@@ -1413,15 +1417,7 @@ mod tests {
|
||||
env.get("FTP_PROXY"),
|
||||
Some(&"socks5h://127.0.0.1:8081".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env.get("NO_PROXY"),
|
||||
Some(&DEFAULT_NO_PROXY_VALUE.to_string())
|
||||
);
|
||||
let no_proxy = env.get("NO_PROXY").expect("NO_PROXY should be set");
|
||||
assert!(no_proxy.contains("10.0.0.0/8"));
|
||||
assert!(no_proxy.contains("172.16.0.0/12"));
|
||||
assert!(no_proxy.contains("192.168.0.0/16"));
|
||||
assert!(!no_proxy.contains("169.254.0.0/16"));
|
||||
assert_eq!(env.get("NO_PROXY"), Some(&String::new()));
|
||||
assert_eq!(env.get(PROXY_ACTIVE_ENV_KEY), Some(&"1".to_string()));
|
||||
assert_eq!(env.get(ALLOW_LOCAL_BINDING_ENV_KEY), Some(&"0".to_string()));
|
||||
assert_eq!(
|
||||
@@ -1441,6 +1437,24 @@ mod tests {
|
||||
assert_eq!(env.get(GIT_SSH_COMMAND_ENV_KEY), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_keeps_local_targets_direct_when_local_binding_enabled() {
|
||||
let mut env = HashMap::new();
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3128),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ true,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
env.get("NO_PROXY"),
|
||||
Some(&DEFAULT_NO_PROXY_VALUE.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_sets_only_expected_env_keys() {
|
||||
let mut env = HashMap::new();
|
||||
|
||||
@@ -76,7 +76,6 @@ pub fn build_config_state(
|
||||
let mitm = if config.mitm {
|
||||
Some(Arc::new(MitmState::new(MitmUpstreamConfig {
|
||||
allow_upstream_proxy: config.allow_upstream_proxy,
|
||||
allow_local_binding: config.allow_local_binding,
|
||||
})?))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::connect_policy::TargetCheckedTcpConnector;
|
||||
use crate::connect_policy::is_non_public_target;
|
||||
use crate::state::NetworkProxyState;
|
||||
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
|
||||
use rama_core::Layer;
|
||||
@@ -16,6 +17,7 @@ use rama_http::layer::version_adapter::RequestVersionAdapter;
|
||||
use rama_http_backend::client::HttpClientService;
|
||||
use rama_http_backend::client::HttpConnector;
|
||||
use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
|
||||
use rama_net::address::HostWithPort;
|
||||
use rama_net::address::ProxyAddress;
|
||||
use rama_net::client::EstablishedClientConnection;
|
||||
use rama_net::http::RequestContext;
|
||||
@@ -56,6 +58,13 @@ impl ProxyConfig {
|
||||
self.http.clone().or_else(|| self.all.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn proxy_for_target(&self, target: &HostWithPort, is_secure: bool) -> Option<ProxyAddress> {
|
||||
if is_non_public_target(&target.host) {
|
||||
return None;
|
||||
}
|
||||
self.proxy_for_protocol(is_secure)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_proxy_env(keys: &[&str]) -> Option<ProxyAddress> {
|
||||
@@ -87,8 +96,8 @@ fn read_proxy_env(keys: &[&str]) -> Option<ProxyAddress> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn proxy_for_connect() -> Option<ProxyAddress> {
|
||||
ProxyConfig::from_env().proxy_for_protocol(/*is_secure*/ true)
|
||||
pub(crate) fn proxy_for_connect(target: &HostWithPort) -> Option<ProxyAddress> {
|
||||
ProxyConfig::from_env().proxy_for_target(target, /*is_secure*/ true)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -118,24 +127,24 @@ impl UpstreamClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn direct_with_allow_local_binding(
|
||||
allow_local_binding: bool,
|
||||
pub(crate) fn direct_with_tls_root_store(
|
||||
state: Arc<NetworkProxyState>,
|
||||
tls_root_store: Arc<rustls::RootCertStore>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
ProxyConfig::default(),
|
||||
TargetCheckedTcpConnector::from_allow_local_binding(allow_local_binding),
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
tls_root_store,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_env_proxy_with_allow_local_binding(
|
||||
allow_local_binding: bool,
|
||||
pub(crate) fn from_env_proxy_with_tls_root_store(
|
||||
state: Arc<NetworkProxyState>,
|
||||
tls_root_store: Arc<rustls::RootCertStore>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
ProxyConfig::from_env(),
|
||||
TargetCheckedTcpConnector::from_allow_local_binding(allow_local_binding),
|
||||
TargetCheckedTcpConnector::new(state),
|
||||
tls_root_store,
|
||||
)
|
||||
}
|
||||
@@ -172,11 +181,12 @@ impl Service<Request<Body>> for UpstreamClient {
|
||||
.as_ref()
|
||||
.map(|ctx| ctx.host_with_port().to_string())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let proxy = self.proxy_config.proxy_for_protocol(
|
||||
request_context
|
||||
.as_ref()
|
||||
.map(|ctx| ctx.protocol.is_secure())
|
||||
.unwrap_or(false),
|
||||
let proxy = request_context.as_ref().map_or_else(
|
||||
|| self.proxy_config.proxy_for_protocol(/*is_secure*/ false),
|
||||
|ctx| {
|
||||
self.proxy_config
|
||||
.proxy_for_target(&ctx.host_with_port(), ctx.protocol.is_secure())
|
||||
},
|
||||
);
|
||||
match proxy.as_ref() {
|
||||
Some(proxy) => info!(
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use super::*;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::state::network_proxy_state_for_policy;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rama_http::StatusCode;
|
||||
use rama_http::Version;
|
||||
use rama_net::address::Host;
|
||||
use rama_net::address::HostWithPort;
|
||||
use rama_tls_rustls::dep::pki_types::CertificateDer;
|
||||
use rama_tls_rustls::dep::pki_types::PrivateKeyDer;
|
||||
use rama_tls_rustls::dep::pki_types::pem::PemObject;
|
||||
@@ -94,11 +99,14 @@ async fn mitm_upstream_client_trusts_startup_custom_ca() {
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let client =
|
||||
UpstreamClient::direct_with_allow_local_binding(/*allow_local_binding*/ true, roots);
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.set_allowed_domains(vec!["localhost".to_string()]);
|
||||
let state = Arc::new(network_proxy_state_for_policy(config));
|
||||
let client = UpstreamClient::direct_with_tls_root_store(state, roots);
|
||||
let response = client
|
||||
.serve(
|
||||
Request::builder()
|
||||
.version(Version::HTTP_2)
|
||||
.uri(format!("https://localhost:{}/", address.port()))
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
@@ -109,3 +117,29 @@ async fn mitm_upstream_client_trusts_startup_custom_ca() {
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inherited_upstream_proxy_is_bypassed_for_non_public_targets() {
|
||||
let proxy = ProxyAddress::try_from("http://127.0.0.1:43128").unwrap();
|
||||
let config = ProxyConfig {
|
||||
http: Some(proxy.clone()),
|
||||
https: Some(proxy),
|
||||
all: None,
|
||||
};
|
||||
|
||||
for target in [
|
||||
HostWithPort::new(Host::LOCALHOST_NAME, 8080),
|
||||
HostWithPort::new(Host::LOCALHOST_IPV4, 8080),
|
||||
HostWithPort::new(Host::Address("10.0.0.1".parse().unwrap()), 8080),
|
||||
] {
|
||||
assert_eq!(config.proxy_for_target(&target, /*is_secure*/ false), None);
|
||||
assert_eq!(config.proxy_for_target(&target, /*is_secure*/ true), None);
|
||||
}
|
||||
|
||||
let public = HostWithPort::new(Host::EXAMPLE_NAME, 443);
|
||||
assert!(
|
||||
config
|
||||
.proxy_for_target(&public, /*is_secure*/ true)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user