mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Remove the network proxy config wrapper (#31767)
## Why `NetworkProxyConfig` only wrapped `NetworkProxySettings` in a single `network` field. That extra level made runtime callers repeat `.network` everywhere without representing a real boundary. ## What changed - move the managed-network fields directly onto `NetworkProxyConfig` - collapse the matching partial-config wrapper - update runtime callers and tests to use the direct fields - keep the user-facing permissions/profile TOML layout unchanged The internal serialized shape now matches the runtime type itself. This does not change managed-network behavior or the `config.toml` shape.
This commit is contained in:
@@ -501,40 +501,39 @@ impl NetworkMitmActionToml {
|
||||
impl NetworkToml {
|
||||
pub fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig) {
|
||||
if let Some(enabled) = self.enabled {
|
||||
config.network.enabled = enabled;
|
||||
config.enabled = enabled;
|
||||
}
|
||||
if let Some(proxy_url) = self.proxy_url.as_ref() {
|
||||
config.network.proxy_url = proxy_url.clone();
|
||||
config.proxy_url = proxy_url.clone();
|
||||
}
|
||||
if let Some(enable_socks5) = self.enable_socks5 {
|
||||
config.network.enable_socks5 = enable_socks5;
|
||||
config.enable_socks5 = enable_socks5;
|
||||
}
|
||||
if let Some(socks_url) = self.socks_url.as_ref() {
|
||||
config.network.socks_url = socks_url.clone();
|
||||
config.socks_url = socks_url.clone();
|
||||
}
|
||||
if let Some(enable_socks5_udp) = self.enable_socks5_udp {
|
||||
config.network.enable_socks5_udp = enable_socks5_udp;
|
||||
config.enable_socks5_udp = enable_socks5_udp;
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = self.allow_upstream_proxy {
|
||||
config.network.allow_upstream_proxy = allow_upstream_proxy;
|
||||
config.allow_upstream_proxy = allow_upstream_proxy;
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_proxy) =
|
||||
self.dangerously_allow_non_loopback_proxy
|
||||
{
|
||||
config.network.dangerously_allow_non_loopback_proxy =
|
||||
dangerously_allow_non_loopback_proxy;
|
||||
config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy;
|
||||
}
|
||||
if let Some(dangerously_allow_all_unix_sockets) = self.dangerously_allow_all_unix_sockets {
|
||||
config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
|
||||
config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
|
||||
}
|
||||
if let Some(mode) = self.mode {
|
||||
config.network.mode = mode;
|
||||
config.mode = mode;
|
||||
}
|
||||
if let Some(domains) = self.domains.as_ref() {
|
||||
overlay_network_domain_permissions(config, domains);
|
||||
}
|
||||
if let Some(unix_sockets) = self.unix_sockets.as_ref() {
|
||||
let mut proxy_unix_sockets = config.network.unix_sockets.take().unwrap_or_default();
|
||||
let mut proxy_unix_sockets = config.unix_sockets.take().unwrap_or_default();
|
||||
for (path, permission) in &unix_sockets.entries {
|
||||
let permission = match permission {
|
||||
NetworkUnixSocketPermissionToml::Allow => {
|
||||
@@ -544,17 +543,16 @@ impl NetworkToml {
|
||||
};
|
||||
proxy_unix_sockets.entries.insert(path.clone(), permission);
|
||||
}
|
||||
config.network.unix_sockets =
|
||||
config.unix_sockets =
|
||||
(!proxy_unix_sockets.entries.is_empty()).then_some(proxy_unix_sockets);
|
||||
}
|
||||
if let Some(allow_local_binding) = self.allow_local_binding {
|
||||
config.network.allow_local_binding = allow_local_binding;
|
||||
config.allow_local_binding = allow_local_binding;
|
||||
}
|
||||
if let Some(mitm) = self.mitm.as_ref() {
|
||||
config.network.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref());
|
||||
config.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref());
|
||||
}
|
||||
config.network.mitm =
|
||||
config.network.mode == NetworkMode::Limited || !config.network.mitm_hooks.is_empty();
|
||||
config.mitm = config.mode == NetworkMode::Limited || !config.mitm_hooks.is_empty();
|
||||
}
|
||||
|
||||
pub fn to_network_proxy_config(&self) -> NetworkProxyConfig {
|
||||
@@ -628,8 +626,6 @@ pub fn overlay_network_domain_permissions(
|
||||
NetworkDomainPermissionToml::Allow => ProxyNetworkDomainPermission::Allow,
|
||||
NetworkDomainPermissionToml::Deny => ProxyNetworkDomainPermission::Deny,
|
||||
};
|
||||
config
|
||||
.network
|
||||
.upsert_domain_permission(pattern.clone(), permission, normalize_host);
|
||||
config.upsert_domain_permission(pattern.clone(), permission, normalize_host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1182,16 +1182,16 @@ fn permissions_profile_network_to_proxy_config_preserves_mitm_hooks() {
|
||||
|
||||
let config = network.to_network_proxy_config();
|
||||
|
||||
assert_eq!(config.network.mode, NetworkMode::Full);
|
||||
assert!(config.network.mitm);
|
||||
assert_eq!(config.network.mitm_hooks.len(), 1);
|
||||
assert_eq!(config.network.mitm_hooks[0].host, "api.github.com");
|
||||
assert_eq!(config.mode, NetworkMode::Full);
|
||||
assert!(config.mitm);
|
||||
assert_eq!(config.mitm_hooks.len(), 1);
|
||||
assert_eq!(config.mitm_hooks[0].host, "api.github.com");
|
||||
assert_eq!(
|
||||
config.network.mitm_hooks[0].matcher.methods,
|
||||
config.mitm_hooks[0].matcher.methods,
|
||||
vec!["POST".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.network.mitm_hooks[0].actions.strip_request_headers,
|
||||
config.mitm_hooks[0].actions.strip_request_headers,
|
||||
vec!["authorization".to_string()]
|
||||
);
|
||||
}
|
||||
@@ -1228,13 +1228,13 @@ action = ["noop"]
|
||||
|
||||
let config = network.to_network_proxy_config();
|
||||
|
||||
assert_eq!(config.network.mitm_hooks.len(), 2);
|
||||
assert_eq!(config.mitm_hooks.len(), 2);
|
||||
assert_eq!(
|
||||
config.network.mitm_hooks[0].matcher.path_prefixes,
|
||||
config.mitm_hooks[0].matcher.path_prefixes,
|
||||
vec!["/repos/openai/".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.network.mitm_hooks[1].matcher.path_prefixes,
|
||||
config.mitm_hooks[1].matcher.path_prefixes,
|
||||
vec!["/repos/".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3368,7 +3368,7 @@ impl Config {
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config.network.enabled = true;
|
||||
configured_network_proxy_config.enabled = true;
|
||||
}
|
||||
let approval_policy_was_explicit =
|
||||
approval_policy_override.is_some() || cfg.approval_policy.is_some();
|
||||
@@ -4128,7 +4128,7 @@ impl Config {
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config.network.enabled = true;
|
||||
configured_network_proxy_config.enabled = true;
|
||||
}
|
||||
configured_network_proxy_config
|
||||
} else {
|
||||
|
||||
@@ -74,15 +74,15 @@ impl ConfigReloader for StaticNetworkProxyReloader {
|
||||
|
||||
impl NetworkProxySpec {
|
||||
pub(crate) fn enabled(&self) -> bool {
|
||||
self.config.network.enabled
|
||||
self.config.enabled
|
||||
}
|
||||
|
||||
pub fn proxy_host_and_port(&self) -> String {
|
||||
host_and_port_from_network_addr(&self.config.network.proxy_url, /*default_port*/ 3128)
|
||||
host_and_port_from_network_addr(&self.config.proxy_url, /*default_port*/ 3128)
|
||||
}
|
||||
|
||||
pub fn socks_enabled(&self) -> bool {
|
||||
self.config.network.enable_socks5
|
||||
self.config.enable_socks5
|
||||
}
|
||||
|
||||
pub(crate) fn from_config_and_constraints(
|
||||
@@ -221,31 +221,30 @@ impl NetworkProxySpec {
|
||||
let denylist_expansion_enabled = Self::denylist_expansion_enabled(permission_profile);
|
||||
|
||||
if let Some(enabled) = requirements.enabled {
|
||||
config.network.enabled = enabled;
|
||||
config.enabled = enabled;
|
||||
constraints.enabled = Some(enabled);
|
||||
}
|
||||
if let Some(http_port) = requirements.http_port {
|
||||
config.network.proxy_url = format!("http://127.0.0.1:{http_port}");
|
||||
config.proxy_url = format!("http://127.0.0.1:{http_port}");
|
||||
}
|
||||
if let Some(socks_port) = requirements.socks_port {
|
||||
config.network.socks_url = format!("http://127.0.0.1:{socks_port}");
|
||||
config.socks_url = format!("http://127.0.0.1:{socks_port}");
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = requirements.allow_upstream_proxy {
|
||||
config.network.allow_upstream_proxy = allow_upstream_proxy;
|
||||
config.allow_upstream_proxy = allow_upstream_proxy;
|
||||
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_proxy) =
|
||||
requirements.dangerously_allow_non_loopback_proxy
|
||||
{
|
||||
config.network.dangerously_allow_non_loopback_proxy =
|
||||
dangerously_allow_non_loopback_proxy;
|
||||
config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy;
|
||||
constraints.dangerously_allow_non_loopback_proxy =
|
||||
Some(dangerously_allow_non_loopback_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_all_unix_sockets) =
|
||||
requirements.dangerously_allow_all_unix_sockets
|
||||
{
|
||||
config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
|
||||
config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
|
||||
constraints.dangerously_allow_all_unix_sockets =
|
||||
Some(dangerously_allow_all_unix_sockets);
|
||||
}
|
||||
@@ -270,14 +269,12 @@ impl NetworkProxySpec {
|
||||
let effective_allowed_domains = if allowlist_expansion_enabled {
|
||||
Self::merge_domain_lists(
|
||||
managed_allowed_domains.clone(),
|
||||
config.network.allowed_domains().as_deref().unwrap_or(&[]),
|
||||
config.allowed_domains().as_deref().unwrap_or(&[]),
|
||||
)
|
||||
} else {
|
||||
managed_allowed_domains.clone()
|
||||
};
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(effective_allowed_domains);
|
||||
config.set_allowed_domains(effective_allowed_domains);
|
||||
constraints.allowed_domains = Some(managed_allowed_domains);
|
||||
constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled);
|
||||
}
|
||||
@@ -289,12 +286,12 @@ impl NetworkProxySpec {
|
||||
let effective_denied_domains = if denylist_expansion_enabled {
|
||||
Self::merge_domain_lists(
|
||||
managed_denied_domains.clone(),
|
||||
config.network.denied_domains().as_deref().unwrap_or(&[]),
|
||||
config.denied_domains().as_deref().unwrap_or(&[]),
|
||||
)
|
||||
} else {
|
||||
managed_denied_domains.clone()
|
||||
};
|
||||
config.network.set_denied_domains(effective_denied_domains);
|
||||
config.set_denied_domains(effective_denied_domains);
|
||||
constraints.denied_domains = Some(managed_denied_domains);
|
||||
constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled);
|
||||
}
|
||||
@@ -304,13 +301,11 @@ impl NetworkProxySpec {
|
||||
.as_ref()
|
||||
.map(codex_config::NetworkUnixSocketPermissionsToml::allow_unix_sockets)
|
||||
.unwrap_or_default();
|
||||
config
|
||||
.network
|
||||
.set_allow_unix_sockets(allow_unix_sockets.clone());
|
||||
config.set_allow_unix_sockets(allow_unix_sockets.clone());
|
||||
constraints.allow_unix_sockets = Some(allow_unix_sockets);
|
||||
}
|
||||
if let Some(allow_local_binding) = requirements.allow_local_binding {
|
||||
config.network.allow_local_binding = allow_local_binding;
|
||||
config.allow_local_binding = allow_local_binding;
|
||||
constraints.allow_local_binding = Some(allow_local_binding);
|
||||
}
|
||||
|
||||
@@ -359,7 +354,7 @@ fn upsert_network_domains(config: &mut NetworkProxyConfig, hosts: Vec<String>, a
|
||||
let mut incoming = HashSet::new();
|
||||
for host in hosts {
|
||||
if incoming.insert(host.clone()) {
|
||||
config.network.upsert_domain_permission(
|
||||
config.upsert_domain_permission(
|
||||
host,
|
||||
if allow {
|
||||
codex_network_proxy::NetworkDomainPermission::Allow
|
||||
|
||||
@@ -43,9 +43,7 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() {
|
||||
#[test]
|
||||
fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"*.example.com",
|
||||
@@ -62,7 +60,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
.expect("config should stay within the managed allowlist");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec![
|
||||
"*.example.com".to_string(),
|
||||
"api.example.com".to_string()
|
||||
@@ -78,9 +76,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
#[test]
|
||||
fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_denied_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"api.example.com",
|
||||
@@ -96,9 +92,9 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
|
||||
)
|
||||
.expect("managed allowlist should not erase a user deny");
|
||||
|
||||
assert_eq!(spec.config.network.allowed_domains(), None);
|
||||
assert_eq!(spec.config.allowed_domains(), None);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
spec.config.denied_domains(),
|
||||
Some(vec!["api.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -110,9 +106,7 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() {
|
||||
#[test]
|
||||
fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"*.example.com",
|
||||
@@ -129,18 +123,18 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
|
||||
.expect("managed baseline should still allow user edits");
|
||||
|
||||
let mut candidate = spec.config.clone();
|
||||
candidate.network.upsert_domain_permission(
|
||||
candidate.upsert_domain_permission(
|
||||
"api.example.com".to_string(),
|
||||
NetworkDomainPermission::Deny,
|
||||
normalize_host,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
candidate.network.allowed_domains(),
|
||||
candidate.allowed_domains(),
|
||||
Some(vec!["*.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
candidate.network.denied_domains(),
|
||||
candidate.denied_domains(),
|
||||
Some(vec!["api.example.com".to_string()])
|
||||
);
|
||||
validate_policy_against_constraints(&candidate, &spec.constraints)
|
||||
@@ -150,9 +144,7 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() {
|
||||
#[test]
|
||||
fn managed_unrestricted_profile_allows_domain_expansion() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"*.example.com",
|
||||
@@ -173,7 +165,7 @@ fn managed_unrestricted_profile_allows_domain_expansion() {
|
||||
.expect("managed unrestricted filesystem should still use managed network constraints");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec![
|
||||
"*.example.com".to_string(),
|
||||
"api.example.com".to_string()
|
||||
@@ -185,12 +177,8 @@ fn managed_unrestricted_profile_allows_domain_expansion() {
|
||||
#[test]
|
||||
fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["evil.com".to_string()]);
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["more-blocked.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["evil.com".to_string()]);
|
||||
config.set_denied_domains(vec!["more-blocked.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([
|
||||
("*.example.com", NetworkDomainPermissionToml::Allow),
|
||||
@@ -207,11 +195,11 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
.expect("yolo mode should pin the effective policy to the managed baseline");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec!["*.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
spec.config.denied_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
|
||||
@@ -221,9 +209,7 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {
|
||||
#[test]
|
||||
fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"*.example.com",
|
||||
@@ -241,7 +227,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
.expect("managed baseline should still load");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec!["*.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
|
||||
@@ -250,9 +236,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() {
|
||||
#[test]
|
||||
fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"managed.example.com",
|
||||
@@ -270,7 +254,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
|
||||
.expect("managed-only allowlist should still load");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec!["managed.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -284,9 +268,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses()
|
||||
#[test]
|
||||
fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
managed_allowed_domains_only: Some(true),
|
||||
..Default::default()
|
||||
@@ -299,7 +281,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
|
||||
)
|
||||
.expect("managed-only mode should treat missing managed allowlist as empty");
|
||||
|
||||
assert_eq!(spec.config.network.allowed_domains(), None);
|
||||
assert_eq!(spec.config.allowed_domains(), None);
|
||||
assert_eq!(spec.constraints.allowed_domains, Some(Vec::new()));
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
|
||||
assert!(spec.hard_deny_allowlist_misses);
|
||||
@@ -308,9 +290,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain
|
||||
#[test]
|
||||
fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_managed_list() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
managed_allowed_domains_only: Some(true),
|
||||
..Default::default()
|
||||
@@ -323,7 +303,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
|
||||
)
|
||||
.expect("managed-only mode should treat missing managed allowlist as empty");
|
||||
|
||||
assert_eq!(spec.config.network.allowed_domains(), None);
|
||||
assert_eq!(spec.config.allowed_domains(), None);
|
||||
assert_eq!(spec.constraints.allowed_domains, Some(Vec::new()));
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false));
|
||||
assert!(spec.hard_deny_allowlist_misses);
|
||||
@@ -332,9 +312,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
|
||||
#[test]
|
||||
fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["api.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"managed-blocked.example.com",
|
||||
@@ -351,13 +329,13 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
|
||||
.expect("deny-only requirements should not constrain the allowlist");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec!["api.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.allowed_domains, None);
|
||||
assert_eq!(spec.constraints.allowlist_expansion_enabled, None);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
spec.config.denied_domains(),
|
||||
Some(vec!["managed-blocked.example.com".to_string()])
|
||||
);
|
||||
}
|
||||
@@ -365,9 +343,7 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() {
|
||||
#[test]
|
||||
fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"managed.example.com",
|
||||
@@ -384,11 +360,11 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
|
||||
.expect("allow-only requirements should not constrain the denylist");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.allowed_domains(),
|
||||
spec.config.allowed_domains(),
|
||||
Some(vec!["managed.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
spec.config.denied_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(spec.constraints.denied_domains, None);
|
||||
@@ -398,9 +374,7 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() {
|
||||
#[test]
|
||||
fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"managed-blocked.example.com",
|
||||
@@ -417,7 +391,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
.expect("default mode should merge managed and user deny entries");
|
||||
|
||||
assert_eq!(
|
||||
spec.config.network.denied_domains(),
|
||||
spec.config.denied_domains(),
|
||||
Some(vec![
|
||||
"managed-blocked.example.com".to_string(),
|
||||
"blocked.example.com".to_string()
|
||||
@@ -433,9 +407,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
#[test]
|
||||
fn requirements_denylist_expansion_keeps_user_entries_mutable() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(domain_permissions([(
|
||||
"managed-blocked.example.com",
|
||||
@@ -452,18 +424,18 @@ fn requirements_denylist_expansion_keeps_user_entries_mutable() {
|
||||
.expect("managed baseline should still allow user edits");
|
||||
|
||||
let mut candidate = spec.config.clone();
|
||||
candidate.network.upsert_domain_permission(
|
||||
candidate.upsert_domain_permission(
|
||||
"blocked.example.com".to_string(),
|
||||
NetworkDomainPermission::Allow,
|
||||
normalize_host,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
candidate.network.allowed_domains(),
|
||||
candidate.allowed_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
candidate.network.denied_domains(),
|
||||
candidate.denied_domains(),
|
||||
Some(vec!["managed-blocked.example.com".to_string()])
|
||||
);
|
||||
validate_policy_against_constraints(&candidate, &spec.constraints)
|
||||
|
||||
@@ -127,7 +127,7 @@ pub(crate) fn network_proxy_config_from_profile_network(
|
||||
// Profile `network.enabled` controls sandbox network access. Profiles may
|
||||
// provide proxy settings for the feature gate to consume when that network
|
||||
// access is enabled, but they do not start the managed proxy on their own.
|
||||
config.network.enabled = false;
|
||||
config.enabled = false;
|
||||
config
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ fn network_toml_overlays_unix_socket_permissions_by_path() {
|
||||
.apply_to_network_proxy_config(&mut config);
|
||||
|
||||
assert_eq!(
|
||||
config.network.unix_sockets,
|
||||
config.unix_sockets,
|
||||
Some(codex_network_proxy::NetworkUnixSocketPermissions {
|
||||
entries: BTreeMap::from([
|
||||
(
|
||||
@@ -399,7 +399,7 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_bare_network_access() {
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
assert!(!config.network.enabled);
|
||||
assert!(!config.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -417,11 +417,11 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_proxy_policy() {
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
assert!(!config.network.enabled);
|
||||
assert_eq!(config.network.proxy_url, "http://127.0.0.1:43128");
|
||||
assert!(!config.network.enable_socks5);
|
||||
assert!(!config.enabled);
|
||||
assert_eq!(config.proxy_url, "http://127.0.0.1:43128");
|
||||
assert!(!config.enable_socks5);
|
||||
assert_eq!(
|
||||
config.network.domains,
|
||||
config.domains,
|
||||
Some(codex_network_proxy::NetworkDomainPermissions {
|
||||
entries: vec![codex_network_proxy::NetworkDomainPermissionEntry {
|
||||
pattern: "openai.com".to_string(),
|
||||
|
||||
@@ -2974,11 +2974,11 @@ async fn guardian_review_session_config_clears_legacy_notify() {
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
let mut parent_config = test_config().await;
|
||||
let mut parent_network = NetworkProxyConfig::default();
|
||||
parent_network.network.enabled = true;
|
||||
parent_network
|
||||
.network
|
||||
.set_allowed_domains(vec!["parent.example".to_string()]);
|
||||
let mut parent_network = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
};
|
||||
parent_network.set_allowed_domains(vec!["parent.example".to_string()]);
|
||||
parent_config.permissions.network = Some(
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
parent_network,
|
||||
@@ -2988,11 +2988,11 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
.expect("parent network proxy spec"),
|
||||
);
|
||||
|
||||
let mut live_network = NetworkProxyConfig::default();
|
||||
live_network.network.enabled = true;
|
||||
live_network
|
||||
.network
|
||||
.set_allowed_domains(vec!["github.com".to_string()]);
|
||||
let mut live_network = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
};
|
||||
live_network.set_allowed_domains(vec!["github.com".to_string()]);
|
||||
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
|
||||
@@ -162,14 +162,14 @@ fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProx
|
||||
if let Some(domains) = network.domains.as_ref() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
if let Some(allowed_domains) = constraints.allowed_domains.take() {
|
||||
config.network.set_allowed_domains(allowed_domains);
|
||||
config.set_allowed_domains(allowed_domains);
|
||||
}
|
||||
if let Some(denied_domains) = constraints.denied_domains.take() {
|
||||
config.network.set_denied_domains(denied_domains);
|
||||
config.set_denied_domains(denied_domains);
|
||||
}
|
||||
overlay_network_domain_permissions(&mut config, domains);
|
||||
constraints.allowed_domains = config.network.allowed_domains();
|
||||
constraints.denied_domains = config.network.denied_domains();
|
||||
constraints.allowed_domains = config.allowed_domains();
|
||||
constraints.denied_domains = config.denied_domains();
|
||||
}
|
||||
if let Some(unix_sockets) = network.unix_sockets.as_ref() {
|
||||
let allow_unix_sockets = unix_sockets.allow_unix_sockets();
|
||||
@@ -256,11 +256,11 @@ impl NetworkConfigAccumulator {
|
||||
};
|
||||
mitm.validate_action_references(&actions)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
self.config.network.mitm_hooks = mitm.to_runtime_hooks(Some(&actions));
|
||||
self.config.mitm_hooks = mitm.to_runtime_hooks(Some(&actions));
|
||||
}
|
||||
|
||||
self.config.network.mitm = self.config.network.mode == NetworkMode::Limited
|
||||
|| !self.config.network.mitm_hooks.is_empty();
|
||||
self.config.mitm =
|
||||
self.config.mode == NetworkMode::Limited || !self.config.mitm_hooks.is_empty();
|
||||
Ok(self.config)
|
||||
}
|
||||
}
|
||||
@@ -310,9 +310,7 @@ fn upsert_network_domain(
|
||||
host: String,
|
||||
permission: codex_network_proxy::NetworkDomainPermission,
|
||||
) {
|
||||
config
|
||||
.network
|
||||
.upsert_domain_permission(host, permission, normalize_host);
|
||||
config.upsert_domain_permission(host, permission, normalize_host);
|
||||
}
|
||||
|
||||
fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
|
||||
|
||||
@@ -57,14 +57,14 @@ default_permissions = "dev"
|
||||
.expect("higher layer should apply");
|
||||
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec![
|
||||
"lower.example.com".to_string(),
|
||||
"higher.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
config.network.denied_domains(),
|
||||
config.denied_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
}
|
||||
@@ -108,13 +108,13 @@ default_permissions = "dev"
|
||||
.expect("higher layer should apply");
|
||||
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec![
|
||||
"other.example.com".to_string(),
|
||||
"shared.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(config.network.denied_domains(), None);
|
||||
assert_eq!(config.denied_domains(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -169,20 +169,20 @@ strip_request_headers = ["x-api-key"]
|
||||
.expect("higher layer should apply");
|
||||
let config = accumulator.finish().expect("merged config should build");
|
||||
|
||||
assert_eq!(config.network.mode, codex_network_proxy::NetworkMode::Full);
|
||||
assert!(config.network.mitm);
|
||||
assert_eq!(config.mode, codex_network_proxy::NetworkMode::Full);
|
||||
assert!(config.mitm);
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec![
|
||||
"lower.example.com".to_string(),
|
||||
"higher.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(config.network.mitm_hooks.len(), 1);
|
||||
assert_eq!(config.network.mitm_hooks[0].host, "api.github.com");
|
||||
assert_eq!(config.network.mitm_hooks[0].matcher.methods, vec!["POST"]);
|
||||
assert_eq!(config.mitm_hooks.len(), 1);
|
||||
assert_eq!(config.mitm_hooks[0].host, "api.github.com");
|
||||
assert_eq!(config.mitm_hooks[0].matcher.methods, vec!["POST"]);
|
||||
assert_eq!(
|
||||
config.network.mitm_hooks[0].actions.strip_request_headers,
|
||||
config.mitm_hooks[0].actions.strip_request_headers,
|
||||
vec!["x-api-key"]
|
||||
);
|
||||
}
|
||||
@@ -190,12 +190,8 @@ strip_request_headers = ["x-api-key"]
|
||||
#[test]
|
||||
fn execpolicy_network_rules_overlay_network_lists() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config
|
||||
.network
|
||||
.set_allowed_domains(vec!["config.example.com".to_string()]);
|
||||
config
|
||||
.network
|
||||
.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
config.set_allowed_domains(vec!["config.example.com".to_string()]);
|
||||
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
|
||||
|
||||
let mut exec_policy = Policy::empty();
|
||||
exec_policy
|
||||
@@ -218,14 +214,14 @@ fn execpolicy_network_rules_overlay_network_lists() {
|
||||
apply_exec_policy_network_rules(&mut config, &exec_policy);
|
||||
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec![
|
||||
"config.example.com".to_string(),
|
||||
"blocked.example.com".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
config.network.denied_domains(),
|
||||
config.denied_domains(),
|
||||
Some(vec!["api.example.com".to_string()])
|
||||
);
|
||||
}
|
||||
@@ -270,7 +266,7 @@ async fn malformed_custom_rules_preserve_managed_denied_domain() {
|
||||
.expect("proxy state should tolerate malformed custom rules");
|
||||
|
||||
assert_eq!(
|
||||
state.config.network.denied_domains(),
|
||||
state.config.denied_domains(),
|
||||
Some(vec!["blocked.example.com".to_string()])
|
||||
);
|
||||
}
|
||||
@@ -468,7 +464,7 @@ fn config_from_layers_resolves_inherited_profiles_across_layers() {
|
||||
config_from_layers(&layers, &Policy::empty()).expect("inherited profiles should load");
|
||||
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec![
|
||||
"base.example.com".to_string(),
|
||||
"child.example.com".to_string(),
|
||||
@@ -507,10 +503,10 @@ fn config_from_layers_normalizes_profile_network_domains_before_merging_layers()
|
||||
.expect("network domain layer precedence should load");
|
||||
|
||||
assert_eq!(
|
||||
config.network.allowed_domains(),
|
||||
config.allowed_domains(),
|
||||
Some(vec!["example.com".to_string()])
|
||||
);
|
||||
assert_eq!(config.network.denied_domains(), None);
|
||||
assert_eq!(config.denied_domains(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -542,8 +538,8 @@ fn config_from_layers_uses_only_the_final_selected_profile_network() {
|
||||
let config = config_from_layers(&layers, &Policy::empty())
|
||||
.expect("final built-in profile selection should load");
|
||||
|
||||
assert_eq!(config.network.allowed_domains(), None);
|
||||
assert_eq!(config.network.denied_domains(), None);
|
||||
assert_eq!(config.allowed_domains(), None);
|
||||
assert_eq!(config.denied_domains(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -809,7 +809,7 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
|
||||
|
||||
let current_cfg = started_proxy.proxy().current_cfg().await?;
|
||||
assert_eq!(
|
||||
current_cfg.network.allowed_domains(),
|
||||
current_cfg.allowed_domains(),
|
||||
Some(vec!["example.com".to_string()])
|
||||
);
|
||||
Ok(())
|
||||
@@ -854,7 +854,7 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
|
||||
|
||||
let current_cfg = started_proxy.proxy().current_cfg().await?;
|
||||
assert_eq!(
|
||||
current_cfg.network.allowed_domains(),
|
||||
current_cfg.allowed_domains(),
|
||||
Some(vec!["managed.example.com".to_string()])
|
||||
);
|
||||
Ok(())
|
||||
@@ -895,7 +895,7 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R
|
||||
let spec = spec.recompute_for_permission_profile(&PermissionProfile::workspace_write())?;
|
||||
spec.apply_to_started_proxy(&started_proxy).await?;
|
||||
let current_cfg = started_proxy.proxy().current_cfg().await?;
|
||||
assert_eq!(current_cfg.network.allowed_domains(), None);
|
||||
assert_eq!(current_cfg.allowed_domains(), None);
|
||||
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
use tokio::io::AsyncWriteExt as _;
|
||||
@@ -934,9 +934,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
let initial_permission_profile = PermissionProfile::workspace_write();
|
||||
|
||||
let mut network_config = NetworkProxyConfig::default();
|
||||
network_config
|
||||
.network
|
||||
.set_allowed_domains(vec!["evil.com".to_string()]);
|
||||
network_config.set_allowed_domains(vec!["evil.com".to_string()]);
|
||||
let requirements = NetworkConstraints {
|
||||
domains: Some(NetworkDomainPermissionsToml {
|
||||
entries: std::collections::BTreeMap::from([(
|
||||
@@ -962,12 +960,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
started_proxy
|
||||
.proxy()
|
||||
.current_cfg()
|
||||
.await?
|
||||
.network
|
||||
.allowed_domains(),
|
||||
started_proxy.proxy().current_cfg().await?.allowed_domains(),
|
||||
Some(vec!["*.example.com".to_string(), "evil.com".to_string()])
|
||||
);
|
||||
|
||||
@@ -1006,12 +999,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
.load_full()
|
||||
.expect("managed network proxy should be present");
|
||||
assert_eq!(
|
||||
started_proxy
|
||||
.proxy()
|
||||
.current_cfg()
|
||||
.await?
|
||||
.network
|
||||
.allowed_domains(),
|
||||
started_proxy.proxy().current_cfg().await?.allowed_domains(),
|
||||
Some(vec!["*.example.com".to_string()])
|
||||
);
|
||||
|
||||
|
||||
@@ -637,7 +637,7 @@ impl Session {
|
||||
let network_proxy_active = match network_proxy.as_ref() {
|
||||
Some(started_network_proxy) => {
|
||||
match started_network_proxy.proxy().current_cfg().await {
|
||||
Ok(config) => config.network.enabled,
|
||||
Ok(config) => config.enabled,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to read managed network proxy state for turn metrics: {err:#}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::BindConnectionAttribution;
|
||||
use super::write_attribution_frame;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::runtime::network_proxy_state_for_policy;
|
||||
use crate::state::NetworkProxyState;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -28,9 +28,7 @@ fn attribution_frame_has_bounded_binary_prefix() -> io::Result<()> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn framed_connection_receives_registered_execution_state() -> Result<(), BoxError> {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
|
||||
state.register_execution("token-1", "local", "execution-1");
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
|
||||
@@ -15,19 +15,6 @@ use url::Url;
|
||||
|
||||
use crate::mitm_hook::MitmHookConfig;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub struct NetworkProxyConfig {
|
||||
#[serde(default)]
|
||||
pub network: NetworkProxySettings,
|
||||
}
|
||||
|
||||
impl NetworkProxyConfig {
|
||||
pub fn set_credential_broker_enabled(&mut self, enabled: bool) {
|
||||
self.network.credential_broker = enabled;
|
||||
self.network.mitm |= enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// Variant order encodes effective precedence for duplicate patterns:
|
||||
/// `None < Allow < Deny`, so deny wins over allow when entries conflict.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -125,7 +112,7 @@ pub struct NetworkUnixSocketPermissions {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkProxySettings {
|
||||
pub struct NetworkProxyConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_proxy_url")]
|
||||
@@ -156,7 +143,7 @@ pub struct NetworkProxySettings {
|
||||
pub mitm_hooks: Vec<MitmHookConfig>,
|
||||
}
|
||||
|
||||
impl Default for NetworkProxySettings {
|
||||
impl Default for NetworkProxyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
@@ -179,7 +166,12 @@ impl Default for NetworkProxySettings {
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProxySettings {
|
||||
impl NetworkProxyConfig {
|
||||
pub fn set_credential_broker_enabled(&mut self, enabled: bool) {
|
||||
self.credential_broker = enabled;
|
||||
self.mitm |= enabled;
|
||||
}
|
||||
|
||||
pub fn allowed_domains(&self) -> Option<Vec<String>> {
|
||||
self.domain_entries(NetworkDomainPermission::Allow)
|
||||
}
|
||||
@@ -340,7 +332,7 @@ fn clamp_non_loopback(
|
||||
pub(crate) fn clamp_bind_addrs(
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
cfg: &NetworkProxySettings,
|
||||
cfg: &NetworkProxyConfig,
|
||||
) -> (SocketAddr, SocketAddr) {
|
||||
let http_addr = clamp_non_loopback(
|
||||
http_addr,
|
||||
@@ -416,7 +408,7 @@ impl ValidatedUnixSocketPath {
|
||||
}
|
||||
|
||||
pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) -> Result<()> {
|
||||
for (index, socket_path) in cfg.network.allow_unix_sockets().iter().enumerate() {
|
||||
for (index, socket_path) in cfg.allow_unix_sockets().iter().enumerate() {
|
||||
ValidatedUnixSocketPath::parse(socket_path)
|
||||
.with_context(|| format!("invalid network.allow_unix_sockets[{index}]"))?;
|
||||
}
|
||||
@@ -426,11 +418,11 @@ pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) ->
|
||||
pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result<RuntimeConfig> {
|
||||
validate_unix_socket_allowlist_paths(cfg)?;
|
||||
|
||||
let http_addr = resolve_addr(&cfg.network.proxy_url, /*default_port*/ 3128)
|
||||
.with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?;
|
||||
let socks_addr = resolve_addr(&cfg.network.socks_url, /*default_port*/ 8081)
|
||||
.with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?;
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg.network);
|
||||
let http_addr = resolve_addr(&cfg.proxy_url, /*default_port*/ 3128)
|
||||
.with_context(|| format!("invalid network.proxy_url: {}", cfg.proxy_url))?;
|
||||
let socks_addr = resolve_addr(&cfg.socks_url, /*default_port*/ 8081)
|
||||
.with_context(|| format!("invalid network.socks_url: {}", cfg.socks_url))?;
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, cfg);
|
||||
|
||||
Ok(RuntimeConfig {
|
||||
http_addr,
|
||||
@@ -575,8 +567,8 @@ mod tests {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxySettings {
|
||||
let mut settings = NetworkProxySettings::default();
|
||||
fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxyConfig {
|
||||
let mut settings = NetworkProxyConfig::default();
|
||||
if !unix_sockets.is_empty() {
|
||||
settings.set_allow_unix_sockets(
|
||||
unix_sockets
|
||||
@@ -591,8 +583,8 @@ mod tests {
|
||||
#[test]
|
||||
fn network_proxy_settings_default_matches_local_use_baseline() {
|
||||
assert_eq!(
|
||||
NetworkProxySettings::default(),
|
||||
NetworkProxySettings {
|
||||
NetworkProxyConfig::default(),
|
||||
NetworkProxyConfig {
|
||||
enabled: false,
|
||||
proxy_url: "http://127.0.0.1:3128".to_string(),
|
||||
enable_socks5: true,
|
||||
@@ -614,26 +606,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_network_config_uses_struct_defaults_for_missing_fields() {
|
||||
let config: NetworkProxyConfig = serde_json::from_str(
|
||||
r#"{
|
||||
"network": {
|
||||
"enabled": true
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let expected = NetworkProxySettings {
|
||||
fn network_proxy_config_uses_struct_defaults_for_missing_fields() {
|
||||
let config: NetworkProxyConfig = serde_json::from_str(r#"{ "enabled": true }"#).unwrap();
|
||||
let expected = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert_eq!(config.network, expected);
|
||||
assert_eq!(config, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_allowed_domains_preserves_existing_deny_for_same_pattern() {
|
||||
let mut settings = NetworkProxySettings::default();
|
||||
let mut settings = NetworkProxyConfig::default();
|
||||
settings.set_denied_domains(vec!["example.com".to_string()]);
|
||||
|
||||
settings.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
@@ -647,36 +632,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn network_domain_permissions_serialize_to_effective_map_shape() {
|
||||
let mut settings = NetworkProxySettings::default();
|
||||
let mut settings = NetworkProxyConfig::default();
|
||||
settings.set_denied_domains(vec!["example.com".to_string()]);
|
||||
settings.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
let config = NetworkProxyConfig { network: settings };
|
||||
let config = settings;
|
||||
|
||||
let value = serde_json::to_value(&config).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
serde_json::json!({
|
||||
"network": {
|
||||
"enabled": false,
|
||||
"proxy_url": "http://127.0.0.1:3128",
|
||||
"enable_socks5": true,
|
||||
"socks_url": "http://127.0.0.1:8081",
|
||||
"enable_socks5_udp": true,
|
||||
"allow_upstream_proxy": true,
|
||||
"dangerously_allow_non_loopback_proxy": false,
|
||||
"dangerously_allow_all_unix_sockets": false,
|
||||
"mode": "full",
|
||||
"domains": {
|
||||
"example.com": "deny",
|
||||
},
|
||||
"unix_sockets": null,
|
||||
"allow_local_binding": false,
|
||||
"mitm": false,
|
||||
"credential_broker": false,
|
||||
"dangerously_allow_plaintext_credential_injection": false,
|
||||
"mitm_hooks": [],
|
||||
}
|
||||
"enabled": false,
|
||||
"proxy_url": "http://127.0.0.1:3128",
|
||||
"enable_socks5": true,
|
||||
"socks_url": "http://127.0.0.1:8081",
|
||||
"enable_socks5_udp": true,
|
||||
"allow_upstream_proxy": true,
|
||||
"dangerously_allow_non_loopback_proxy": false,
|
||||
"dangerously_allow_all_unix_sockets": false,
|
||||
"mode": "full",
|
||||
"domains": {
|
||||
"example.com": "deny",
|
||||
},
|
||||
"unix_sockets": null,
|
||||
"allow_local_binding": false,
|
||||
"mitm": false,
|
||||
"credential_broker": false,
|
||||
"dangerously_allow_plaintext_credential_injection": false,
|
||||
"mitm_hooks": [],
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -815,7 +798,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clamp_bind_addrs_allows_non_loopback_when_enabled() {
|
||||
let cfg = NetworkProxySettings {
|
||||
let cfg = NetworkProxyConfig {
|
||||
dangerously_allow_non_loopback_proxy: true,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -846,7 +829,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clamp_bind_addrs_forces_loopback_when_all_unix_sockets_enabled() {
|
||||
let cfg = NetworkProxySettings {
|
||||
let cfg = NetworkProxyConfig {
|
||||
dangerously_allow_non_loopback_proxy: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..Default::default()
|
||||
@@ -862,9 +845,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_runtime_rejects_relative_allow_unix_sockets_entries() {
|
||||
let cfg = NetworkProxyConfig {
|
||||
network: settings_with_unix_sockets(&["relative.sock"]),
|
||||
};
|
||||
let cfg = settings_with_unix_sockets(&["relative.sock"]);
|
||||
|
||||
let err = match resolve_runtime(&cfg) {
|
||||
Ok(runtime) => panic!(
|
||||
@@ -881,9 +862,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_runtime_accepts_unix_style_absolute_allow_unix_sockets_entries() {
|
||||
let cfg = NetworkProxyConfig {
|
||||
network: settings_with_unix_sockets(&["/private/tmp/example.sock"]),
|
||||
};
|
||||
let cfg = settings_with_unix_sockets(&["/private/tmp/example.sock"]);
|
||||
|
||||
assert!(
|
||||
resolve_runtime(&cfg).is_ok(),
|
||||
|
||||
@@ -107,7 +107,7 @@ impl TargetPolicy {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::state::network_proxy_state_for_policy;
|
||||
use rama_net::address::HostWithPort;
|
||||
use std::net::Ipv4Addr;
|
||||
@@ -120,7 +120,7 @@ mod tests {
|
||||
.expect("bind local listener");
|
||||
let target = listener.local_addr().expect("local addr");
|
||||
let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
NetworkProxyConfig::default(),
|
||||
)));
|
||||
|
||||
let request: rama_tcp::client::Request =
|
||||
@@ -142,9 +142,9 @@ mod tests {
|
||||
.expect("bind local listener");
|
||||
let target = listener.local_addr().expect("local addr");
|
||||
let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings {
|
||||
NetworkProxyConfig {
|
||||
allow_local_binding: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
},
|
||||
)));
|
||||
|
||||
|
||||
@@ -1087,7 +1087,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::config::NetworkMode;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::runtime::network_proxy_state_for_policy;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rama_http::Method;
|
||||
@@ -1106,7 +1106,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_blocks_in_limited_mode() {
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings::default();
|
||||
let mut policy = NetworkProxyConfig::default();
|
||||
policy.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
policy
|
||||
};
|
||||
@@ -1136,9 +1136,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_allows_allowlisted_host_in_full_mode() {
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings {
|
||||
let mut policy = NetworkProxyConfig {
|
||||
allow_local_binding: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
policy.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
policy
|
||||
@@ -1163,9 +1163,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_passes_environment_id_to_decider() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::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();
|
||||
@@ -1202,10 +1200,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_defers_brokered_host_mitm_until_protocol_detection() {
|
||||
let mut policy = NetworkProxySettings {
|
||||
let mut policy = NetworkProxyConfig {
|
||||
credential_broker: true,
|
||||
mitm: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
policy.set_allowed_domains(vec!["github.com".to_string()]);
|
||||
let state = Arc::new(network_proxy_state_for_policy(policy));
|
||||
@@ -1235,10 +1233,10 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn plaintext_credential_injection_requires_explicit_opt_in() {
|
||||
let real_token = "ghp-real";
|
||||
let mut disabled_network = NetworkProxySettings {
|
||||
let mut disabled_network = NetworkProxyConfig {
|
||||
credential_broker: true,
|
||||
mitm: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
disabled_network.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let disabled_state = Arc::new(network_proxy_state_for_policy(disabled_network));
|
||||
@@ -1263,11 +1261,11 @@ mod tests {
|
||||
Some(&HeaderValue::from_str(&format!("Bearer {dummy_token}")).unwrap())
|
||||
);
|
||||
|
||||
let mut enabled_network = NetworkProxySettings {
|
||||
let mut enabled_network = NetworkProxyConfig {
|
||||
credential_broker: true,
|
||||
dangerously_allow_plaintext_credential_injection: true,
|
||||
mitm: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
enabled_network.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let enabled_state = Arc::new(network_proxy_state_for_policy(enabled_network));
|
||||
@@ -1295,7 +1293,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state() {
|
||||
let mut policy = NetworkProxySettings {
|
||||
let mut policy = NetworkProxyConfig {
|
||||
mitm: true,
|
||||
mitm_hooks: vec![crate::mitm_hook::MitmHookConfig {
|
||||
host: "api.github.com".to_string(),
|
||||
@@ -1352,10 +1350,10 @@ mod tests {
|
||||
});
|
||||
|
||||
let state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings {
|
||||
let mut network = NetworkProxyConfig {
|
||||
credential_broker: true,
|
||||
mitm: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
network.set_allowed_domains(vec!["127.0.0.1".to_string()]);
|
||||
network.allow_local_binding = true;
|
||||
@@ -1416,9 +1414,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn http_plain_proxy_blocks_unix_socket_when_method_not_allowed() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
|
||||
state
|
||||
.set_network_mode(NetworkMode::Limited)
|
||||
.await
|
||||
@@ -1447,9 +1443,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn http_plain_proxy_rejects_unix_socket_when_not_allowlisted() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
|
||||
|
||||
let mut req = Request::builder()
|
||||
.method(Method::GET)
|
||||
@@ -1480,7 +1474,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn http_plain_proxy_attempts_allowed_unix_socket_proxy() {
|
||||
let state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allow_unix_sockets(vec!["/tmp/test.sock".to_string()]);
|
||||
network
|
||||
}));
|
||||
@@ -1504,7 +1498,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn http_connect_accept_denies_denylisted_host() {
|
||||
let policy = {
|
||||
let mut policy = NetworkProxySettings::default();
|
||||
let mut policy = NetworkProxyConfig::default();
|
||||
policy.set_allowed_domains(vec!["**.openai.com".to_string()]);
|
||||
policy.set_denied_domains(vec!["api.openai.com".to_string()]);
|
||||
policy
|
||||
@@ -1533,9 +1527,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_plain_proxy_rejects_absolute_uri_host_header_mismatch() {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
|
||||
let mut req = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("http://raw.githubusercontent.com/openai/codex/main/README.md")
|
||||
|
||||
@@ -78,7 +78,6 @@ pub use runtime::NetworkProxyState;
|
||||
pub use state::NetworkProxyAuditMetadata;
|
||||
pub use state::NetworkProxyConstraintError;
|
||||
pub use state::NetworkProxyConstraints;
|
||||
pub use state::PartialNetworkConfig;
|
||||
pub use state::PartialNetworkProxyConfig;
|
||||
pub use state::build_config_state;
|
||||
pub use state::validate_policy_against_constraints;
|
||||
|
||||
@@ -169,12 +169,12 @@ pub enum HookEvaluation {
|
||||
}
|
||||
|
||||
pub(crate) fn validate_mitm_hook_config(config: &NetworkProxyConfig) -> Result<()> {
|
||||
let hooks = &config.network.mitm_hooks;
|
||||
let hooks = &config.mitm_hooks;
|
||||
if hooks.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !config.network.mitm {
|
||||
if !config.mitm {
|
||||
return Err(anyhow!("network.mitm_hooks requires network.mitm = true"));
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ where
|
||||
validate_mitm_hook_config(config)?;
|
||||
|
||||
let mut hooks_by_host = MitmHooksByHost::new();
|
||||
for hook in &config.network.mitm_hooks {
|
||||
for hook in &config.mitm_hooks {
|
||||
let host = normalize_hook_host(&hook.host)?;
|
||||
let methods = normalize_methods(&hook.matcher.methods)?;
|
||||
let path_prefixes = compile_path_matchers(&hook.matcher.path_prefixes)?;
|
||||
@@ -644,7 +644,7 @@ fn parse_secret_file(path: &str) -> Result<AbsolutePathBuf> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::NetworkMode;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rama_http::Body;
|
||||
use rama_http::Method;
|
||||
@@ -652,11 +652,9 @@ mod tests {
|
||||
|
||||
fn base_config() -> NetworkProxyConfig {
|
||||
NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
mitm: true,
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
mitm: true,
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxyConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,8 +681,8 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_requires_mitm_for_hooks() {
|
||||
let mut config = base_config();
|
||||
config.network.mitm = false;
|
||||
config.network.mitm_hooks = vec![github_hook()];
|
||||
config.mitm = false;
|
||||
config.mitm_hooks = vec![github_hook()];
|
||||
|
||||
let err = validate_mitm_hook_config(&config).expect_err("hooks require mitm");
|
||||
assert!(
|
||||
@@ -696,8 +694,8 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_allows_hooks_in_full_mode() {
|
||||
let mut config = base_config();
|
||||
config.network.mode = NetworkMode::Full;
|
||||
config.network.mitm_hooks = vec![github_hook()];
|
||||
config.mode = NetworkMode::Full;
|
||||
config.mitm_hooks = vec![github_hook()];
|
||||
|
||||
validate_mitm_hook_config(&config).expect("hooks should be allowed in full mode");
|
||||
}
|
||||
@@ -709,7 +707,7 @@ mod tests {
|
||||
hook.matcher.body = Some(MitmHookBodyConfig(serde_json::json!({
|
||||
"repository": "openai/codex"
|
||||
})));
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let err = validate_mitm_hook_config(&config).expect_err("body matchers are reserved");
|
||||
assert!(err.to_string().contains("match.body is reserved"));
|
||||
@@ -721,7 +719,7 @@ mod tests {
|
||||
let mut hook = github_hook();
|
||||
hook.actions.inject_request_headers[0].secret_env_var = None;
|
||||
hook.actions.inject_request_headers[0].secret_file = Some("token.txt".to_string());
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let err = validate_mitm_hook_config(&config).expect_err("secret file must be absolute");
|
||||
assert!(format!("{err:#}").contains("secret_file must be an absolute path"));
|
||||
@@ -732,7 +730,7 @@ mod tests {
|
||||
let mut config = base_config();
|
||||
let mut hook = github_hook();
|
||||
hook.actions.inject_request_headers[0].secret_file = Some("/tmp/github-token".to_string());
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let err = validate_mitm_hook_config(&config).expect_err("dual secret sources invalid");
|
||||
assert!(format!("{err:#}").contains("exactly one of secret_env_var or secret_file"));
|
||||
@@ -741,7 +739,7 @@ mod tests {
|
||||
#[test]
|
||||
fn compile_resolves_env_backed_injected_headers() {
|
||||
let mut config = base_config();
|
||||
config.network.mitm_hooks = vec![github_hook()];
|
||||
config.mitm_hooks = vec![github_hook()];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -772,7 +770,7 @@ mod tests {
|
||||
hook.actions.inject_request_headers[0].secret_env_var = None;
|
||||
hook.actions.inject_request_headers[0].secret_file =
|
||||
Some(secret_file.path().display().to_string());
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks(&config).unwrap();
|
||||
let compiled = hooks.get("api.github.com").unwrap();
|
||||
@@ -789,7 +787,7 @@ mod tests {
|
||||
first.matcher.path_prefixes = vec!["/repos/openai/".to_string()];
|
||||
let mut second = github_hook();
|
||||
second.actions.inject_request_headers[0].prefix = Some("Token ".to_string());
|
||||
config.network.mitm_hooks = vec![first, second];
|
||||
config.mitm_hooks = vec![first, second];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -827,7 +825,7 @@ mod tests {
|
||||
"x-github-api-version".to_string(),
|
||||
vec!["2022-11-28".to_string()],
|
||||
)]);
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -861,7 +859,7 @@ mod tests {
|
||||
"x-github-api-version".to_string(),
|
||||
vec!["pattern:2022*preview".to_string()],
|
||||
)]);
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -889,7 +887,7 @@ mod tests {
|
||||
let mut config = base_config();
|
||||
let mut hook = github_hook();
|
||||
hook.matcher.path_prefixes = vec!["pattern:/repos/[".to_string()];
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let err = validate_mitm_hook_config(&config).expect_err("invalid glob should fail");
|
||||
assert!(format!("{err:#}").contains("invalid glob pattern"));
|
||||
@@ -900,7 +898,7 @@ mod tests {
|
||||
let mut config = base_config();
|
||||
let mut hook = github_hook();
|
||||
hook.matcher.path_prefixes = vec!["pattern:/repos/*/codex/issues*".to_string()];
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -930,7 +928,7 @@ mod tests {
|
||||
"x-github-api-version".to_string(),
|
||||
vec!["2022-11-28[preview]".to_string()],
|
||||
)]);
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -973,7 +971,7 @@ mod tests {
|
||||
"x-github-api-version".to_string(),
|
||||
vec!["literal:pattern:*".to_string()],
|
||||
)]);
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
@@ -1011,7 +1009,7 @@ mod tests {
|
||||
let mut config = base_config();
|
||||
let mut hook = github_hook();
|
||||
hook.matcher.query = BTreeMap::from([("state".to_string(), vec!["open".to_string()])]);
|
||||
config.network.mitm_hooks = vec![hook];
|
||||
config.mitm_hooks = vec![hook];
|
||||
|
||||
let hooks = compile_mitm_hooks_with_resolvers(
|
||||
&config,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::reasons::REASON_METHOD_NOT_ALLOWED;
|
||||
use crate::reasons::REASON_MITM_HOOK_DENIED;
|
||||
use crate::reasons::REASON_NOT_ALLOWED_LOCAL;
|
||||
@@ -139,7 +139,7 @@ fn policy_ctx(
|
||||
#[tokio::test]
|
||||
async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() {
|
||||
let app_state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network
|
||||
}));
|
||||
@@ -178,7 +178,7 @@ async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() {
|
||||
#[tokio::test]
|
||||
async fn mitm_policy_rejects_host_mismatch() {
|
||||
let app_state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network
|
||||
}));
|
||||
@@ -207,7 +207,7 @@ async fn mitm_policy_rejects_host_mismatch() {
|
||||
#[tokio::test]
|
||||
async fn mitm_policy_rechecks_local_private_target_after_connect() {
|
||||
let app_state = Arc::new(network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network.allow_local_binding = false;
|
||||
network
|
||||
@@ -247,11 +247,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() {
|
||||
hook.actions.inject_request_headers[0].secret_env_var = None;
|
||||
hook.actions.inject_request_headers[0].secret_file =
|
||||
Some(secret_file.path().display().to_string());
|
||||
let mut network = NetworkProxySettings {
|
||||
let mut network = NetworkProxyConfig {
|
||||
mitm: true,
|
||||
mitm_hooks: vec![hook],
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
network.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let app_state = Arc::new(network_proxy_state_for_policy(network));
|
||||
@@ -281,11 +281,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() {
|
||||
async fn mitm_policy_blocks_matching_hooked_write_in_limited_mode() {
|
||||
let mut hook = github_write_hook();
|
||||
hook.actions.inject_request_headers.clear();
|
||||
let mut network = NetworkProxySettings {
|
||||
let mut network = NetworkProxyConfig {
|
||||
mitm: true,
|
||||
mitm_hooks: vec![hook],
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
network.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let app_state = Arc::new(network_proxy_state_for_policy(network));
|
||||
@@ -329,11 +329,11 @@ async fn mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_f
|
||||
hook.actions.inject_request_headers[0].secret_env_var = None;
|
||||
hook.actions.inject_request_headers[0].secret_file =
|
||||
Some(secret_file.path().display().to_string());
|
||||
let mut network = NetworkProxySettings {
|
||||
let mut network = NetworkProxyConfig {
|
||||
mitm: true,
|
||||
mitm_hooks: vec![hook],
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
network.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let app_state = Arc::new(network_proxy_state_for_policy(network));
|
||||
|
||||
@@ -554,7 +554,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::NetworkMode;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::reasons::REASON_DENIED;
|
||||
use crate::reasons::REASON_METHOD_NOT_ALLOWED;
|
||||
use crate::reasons::REASON_NOT_ALLOWED;
|
||||
@@ -595,12 +594,12 @@ mod tests {
|
||||
}
|
||||
|
||||
fn state_with_metadata(metadata: NetworkProxyAuditMetadata) -> NetworkProxyState {
|
||||
let network = NetworkProxySettings {
|
||||
let network = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
let config = NetworkProxyConfig { network };
|
||||
let config = network;
|
||||
let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap();
|
||||
let reloader = Arc::new(StaticReloader {
|
||||
state: state.clone(),
|
||||
@@ -628,7 +627,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn evaluate_host_policy_emits_domain_event_for_decider_allow_override() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
let decider: Arc<dyn NetworkPolicyDecider> = Arc::new({
|
||||
let calls = calls.clone();
|
||||
@@ -697,7 +696,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn evaluate_host_policy_emits_execution_id_for_baseline_allow() {
|
||||
let state = network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network
|
||||
});
|
||||
@@ -737,7 +736,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn evaluate_host_policy_emits_domain_event_for_baseline_deny() {
|
||||
let state = network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network.set_denied_domains(vec!["blocked.com".to_string()]);
|
||||
network
|
||||
@@ -789,7 +788,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn evaluate_host_policy_emits_domain_event_for_decider_ask() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
let decider: Arc<dyn NetworkPolicyDecider> =
|
||||
Arc::new(|_req| async { NetworkDecision::ask(REASON_NOT_ALLOWED) });
|
||||
let request = NetworkPolicyRequest::new(NetworkPolicyRequestArgs {
|
||||
@@ -876,7 +875,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn emit_block_decision_audit_event_emits_non_domain_event() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
|
||||
let (_, events) = capture_events(|| async {
|
||||
emit_block_decision_audit_event(
|
||||
@@ -925,7 +924,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn evaluate_host_policy_still_denies_not_allowed_local_without_decider_override() {
|
||||
let state = network_proxy_state_for_policy({
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
network.allow_local_binding = false;
|
||||
network
|
||||
|
||||
@@ -183,55 +183,48 @@ impl NetworkProxyBuilder {
|
||||
.set_blocked_request_observer(self.blocked_request_observer.clone())
|
||||
.await;
|
||||
let current_cfg = state.current_cfg().await?;
|
||||
let (requested_http_addr, requested_socks_addr, reserved_listeners) = if self
|
||||
.managed_by_codex
|
||||
{
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let (managed_http_addr, managed_socks_addr) = config::clamp_bind_addrs(
|
||||
runtime.http_addr,
|
||||
runtime.socks_addr,
|
||||
¤t_cfg.network,
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
let reserved = reserve_windows_managed_listeners(
|
||||
managed_http_addr,
|
||||
managed_socks_addr,
|
||||
current_cfg.network.enable_socks5,
|
||||
)
|
||||
.context("reserve managed loopback proxy listeners")?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let reserved = reserve_loopback_ephemeral_listeners(current_cfg.network.enable_socks5)
|
||||
let (requested_http_addr, requested_socks_addr, reserved_listeners) =
|
||||
if self.managed_by_codex {
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let (managed_http_addr, managed_socks_addr) =
|
||||
config::clamp_bind_addrs(runtime.http_addr, runtime.socks_addr, ¤t_cfg);
|
||||
#[cfg(target_os = "windows")]
|
||||
let reserved = reserve_windows_managed_listeners(
|
||||
managed_http_addr,
|
||||
managed_socks_addr,
|
||||
current_cfg.enable_socks5,
|
||||
)
|
||||
.context("reserve managed loopback proxy listeners")?;
|
||||
let http_addr = reserved.http_addr()?;
|
||||
let socks_addr = reserved.socks_addr(runtime.socks_addr)?;
|
||||
(
|
||||
http_addr,
|
||||
socks_addr,
|
||||
Some(reserved.into_reserved_listeners()),
|
||||
)
|
||||
} else {
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
(
|
||||
self.http_addr.unwrap_or(runtime.http_addr),
|
||||
self.socks_addr.unwrap_or(runtime.socks_addr),
|
||||
None,
|
||||
)
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let reserved = reserve_loopback_ephemeral_listeners(current_cfg.enable_socks5)
|
||||
.context("reserve managed loopback proxy listeners")?;
|
||||
let http_addr = reserved.http_addr()?;
|
||||
let socks_addr = reserved.socks_addr(runtime.socks_addr)?;
|
||||
(
|
||||
http_addr,
|
||||
socks_addr,
|
||||
Some(reserved.into_reserved_listeners()),
|
||||
)
|
||||
} else {
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
(
|
||||
self.http_addr.unwrap_or(runtime.http_addr),
|
||||
self.socks_addr.unwrap_or(runtime.socks_addr),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
|
||||
let (http_addr, socks_addr) = config::clamp_bind_addrs(
|
||||
requested_http_addr,
|
||||
requested_socks_addr,
|
||||
¤t_cfg.network,
|
||||
);
|
||||
let (http_addr, socks_addr) =
|
||||
config::clamp_bind_addrs(requested_http_addr, requested_socks_addr, ¤t_cfg);
|
||||
|
||||
Ok(NetworkProxy {
|
||||
state,
|
||||
http_addr,
|
||||
socks_addr,
|
||||
socks_enabled: current_cfg.network.enable_socks5,
|
||||
socks5_udp_enabled: current_cfg.network.enable_socks5_udp,
|
||||
socks_enabled: current_cfg.enable_socks5,
|
||||
socks5_udp_enabled: current_cfg.enable_socks5_udp,
|
||||
runtime_settings: Arc::new(RwLock::new(NetworkProxyRuntimeSettings::from_config(
|
||||
¤t_cfg,
|
||||
)?)),
|
||||
@@ -317,16 +310,16 @@ struct NetworkProxyRuntimeSettings {
|
||||
|
||||
impl NetworkProxyRuntimeSettings {
|
||||
fn from_config(config: &config::NetworkProxyConfig) -> Result<Self> {
|
||||
let mitm_ca_trust_bundle = if config.network.mitm {
|
||||
let mitm_ca_trust_bundle = if config.mitm {
|
||||
let env = crate::certs::ca_env_from_process();
|
||||
Some(crate::certs::managed_ca_trust_bundle(&env)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
allow_local_binding: config.network.allow_local_binding,
|
||||
allow_unix_sockets: config.network.allow_unix_sockets().into(),
|
||||
dangerously_allow_all_unix_sockets: config.network.dangerously_allow_all_unix_sockets,
|
||||
allow_local_binding: config.allow_local_binding,
|
||||
allow_unix_sockets: config.allow_unix_sockets().into(),
|
||||
dangerously_allow_all_unix_sockets: config.dangerously_allow_all_unix_sockets,
|
||||
mitm_ca_trust_bundle,
|
||||
})
|
||||
}
|
||||
@@ -879,23 +872,23 @@ impl NetworkProxy {
|
||||
pub async fn replace_config_state(&self, new_state: ConfigState) -> Result<()> {
|
||||
let current_cfg = self.state.current_cfg().await?;
|
||||
anyhow::ensure!(
|
||||
new_state.config.network.enabled == current_cfg.network.enabled,
|
||||
new_state.config.enabled == current_cfg.enabled,
|
||||
"cannot update network.enabled on a running proxy"
|
||||
);
|
||||
anyhow::ensure!(
|
||||
new_state.config.network.proxy_url == current_cfg.network.proxy_url,
|
||||
new_state.config.proxy_url == current_cfg.proxy_url,
|
||||
"cannot update network.proxy_url on a running proxy"
|
||||
);
|
||||
anyhow::ensure!(
|
||||
new_state.config.network.socks_url == current_cfg.network.socks_url,
|
||||
new_state.config.socks_url == current_cfg.socks_url,
|
||||
"cannot update network.socks_url on a running proxy"
|
||||
);
|
||||
anyhow::ensure!(
|
||||
new_state.config.network.enable_socks5 == current_cfg.network.enable_socks5,
|
||||
new_state.config.enable_socks5 == current_cfg.enable_socks5,
|
||||
"cannot update network.enable_socks5 on a running proxy"
|
||||
);
|
||||
anyhow::ensure!(
|
||||
new_state.config.network.enable_socks5_udp == current_cfg.network.enable_socks5_udp,
|
||||
new_state.config.enable_socks5_udp == current_cfg.enable_socks5_udp,
|
||||
"cannot update network.enable_socks5_udp on a running proxy"
|
||||
);
|
||||
|
||||
@@ -922,7 +915,7 @@ impl NetworkProxy {
|
||||
"execution-scoped network proxy is already running"
|
||||
);
|
||||
let current_cfg = self.state.current_cfg().await?;
|
||||
if !current_cfg.network.enabled {
|
||||
if !current_cfg.enabled {
|
||||
warn!("network.enabled is false; skipping proxy listeners");
|
||||
return Ok(NetworkProxyHandle::noop());
|
||||
}
|
||||
@@ -963,11 +956,11 @@ impl NetworkProxy {
|
||||
}
|
||||
});
|
||||
|
||||
let socks_task = if current_cfg.network.enable_socks5 {
|
||||
let socks_task = if current_cfg.enable_socks5 {
|
||||
let socks_state = self.state.clone();
|
||||
let socks_decider = self.policy_decider.clone();
|
||||
let socks_addr = self.socks_addr;
|
||||
let enable_socks5_udp = current_cfg.network.enable_socks5_udp;
|
||||
let enable_socks5_udp = current_cfg.enable_socks5_udp;
|
||||
Some(tokio::spawn(async move {
|
||||
match socks_listener {
|
||||
Some(listener) => {
|
||||
@@ -1095,7 +1088,7 @@ impl Drop for NetworkProxyHandle {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::state::network_proxy_state_for_policy;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::net::IpAddr;
|
||||
@@ -1111,10 +1104,10 @@ mod tests {
|
||||
drop(http_listener);
|
||||
drop(socks_listener);
|
||||
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxySettings {
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig {
|
||||
proxy_url: format!("http://{http_addr}"),
|
||||
socks_url: format!("http://{socks_addr}"),
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
}));
|
||||
let proxy = match NetworkProxy::builder().state(state).build().await {
|
||||
Ok(proxy) => proxy,
|
||||
@@ -1145,10 +1138,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn non_codex_managed_proxy_builder_uses_configured_ports() {
|
||||
let settings = NetworkProxySettings {
|
||||
let settings = NetworkProxyConfig {
|
||||
proxy_url: "http://127.0.0.1:43128".to_string(),
|
||||
socks_url: "http://127.0.0.1:48081".to_string(),
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(settings));
|
||||
let proxy = NetworkProxy::builder()
|
||||
@@ -1170,9 +1163,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_for_environment_keeps_env_and_sandbox_ports_in_sync() -> Result<()> {
|
||||
let state = Arc::new(network_proxy_state_for_policy(
|
||||
NetworkProxySettings::default(),
|
||||
));
|
||||
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
|
||||
let proxy = NetworkProxy::builder().state(state).build().await?;
|
||||
let handle = proxy.run().await?;
|
||||
|
||||
@@ -1229,11 +1220,11 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_proxy_builder_does_not_reserve_socks_listener_when_disabled() {
|
||||
let settings = NetworkProxySettings {
|
||||
let settings = NetworkProxyConfig {
|
||||
enable_socks5: false,
|
||||
proxy_url: "http://127.0.0.1:43128".to_string(),
|
||||
socks_url: "http://127.0.0.1:43129".to_string(),
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(settings));
|
||||
let proxy = match NetworkProxy::builder().state(state).build().await {
|
||||
|
||||
@@ -298,7 +298,7 @@ impl NetworkProxyState {
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
credential_broker: CredentialBroker::new(state.config.network.credential_broker),
|
||||
credential_broker: CredentialBroker::new(state.config.credential_broker),
|
||||
state: Arc::new(RwLock::new(state)),
|
||||
reloader,
|
||||
blocked_request_observer: Arc::new(RwLock::new(blocked_request_observer)),
|
||||
@@ -381,7 +381,6 @@ impl NetworkProxyState {
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard
|
||||
.config
|
||||
.network
|
||||
.dangerously_allow_plaintext_credential_injection)
|
||||
}
|
||||
|
||||
@@ -397,15 +396,15 @@ impl NetworkProxyState {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok((
|
||||
guard.config.network.allowed_domains().unwrap_or_default(),
|
||||
guard.config.network.denied_domains().unwrap_or_default(),
|
||||
guard.config.allowed_domains().unwrap_or_default(),
|
||||
guard.config.denied_domains().unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn enabled(&self) -> Result<bool> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.network.enabled)
|
||||
Ok(guard.config.enabled)
|
||||
}
|
||||
|
||||
pub async fn force_reload(&self) -> Result<()> {
|
||||
@@ -457,11 +456,11 @@ impl NetworkProxyState {
|
||||
};
|
||||
let (deny_set, allow_set, allow_local_binding, allowed_domains) = {
|
||||
let guard = self.state.read().await;
|
||||
let allowed_domains = guard.config.network.allowed_domains();
|
||||
let allowed_domains = guard.config.allowed_domains();
|
||||
(
|
||||
guard.deny_set.clone(),
|
||||
guard.allow_set.clone(),
|
||||
guard.config.network.allow_local_binding,
|
||||
guard.config.allow_local_binding,
|
||||
allowed_domains,
|
||||
)
|
||||
};
|
||||
@@ -594,7 +593,7 @@ impl NetworkProxyState {
|
||||
}
|
||||
|
||||
let guard = self.state.read().await;
|
||||
if guard.config.network.dangerously_allow_all_unix_sockets {
|
||||
if guard.config.dangerously_allow_all_unix_sockets {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -604,7 +603,7 @@ impl NetworkProxyState {
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let requested_canonical = std::fs::canonicalize(requested_abs.as_path()).ok();
|
||||
for allowed in &guard.config.network.allow_unix_sockets() {
|
||||
for allowed in &guard.config.allow_unix_sockets() {
|
||||
let allowed_path = match ValidatedUnixSocketPath::parse(allowed) {
|
||||
Ok(ValidatedUnixSocketPath::Native(path)) => path,
|
||||
Ok(ValidatedUnixSocketPath::UnixStyleAbsolute(_)) => continue,
|
||||
@@ -635,25 +634,25 @@ impl NetworkProxyState {
|
||||
pub async fn method_allowed(&self, method: &str) -> Result<bool> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.network.mode.allows_method(method))
|
||||
Ok(guard.config.mode.allows_method(method))
|
||||
}
|
||||
|
||||
pub async fn allow_upstream_proxy(&self) -> Result<bool> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.network.allow_upstream_proxy)
|
||||
Ok(guard.config.allow_upstream_proxy)
|
||||
}
|
||||
|
||||
pub async fn allow_local_binding(&self) -> Result<bool> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.network.allow_local_binding)
|
||||
Ok(guard.config.allow_local_binding)
|
||||
}
|
||||
|
||||
pub async fn network_mode(&self) -> Result<NetworkMode> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.network.mode)
|
||||
Ok(guard.config.mode)
|
||||
}
|
||||
|
||||
pub async fn set_network_mode(&self, mode: NetworkMode) -> Result<()> {
|
||||
@@ -662,7 +661,7 @@ impl NetworkProxyState {
|
||||
let (candidate, constraints) = {
|
||||
let guard = self.state.read().await;
|
||||
let mut candidate = guard.config.clone();
|
||||
candidate.network.mode = mode;
|
||||
candidate.mode = mode;
|
||||
(candidate, guard.constraints.clone())
|
||||
};
|
||||
|
||||
@@ -675,7 +674,7 @@ impl NetworkProxyState {
|
||||
drop(guard);
|
||||
continue;
|
||||
}
|
||||
guard.config.network.mode = mode;
|
||||
guard.config.mode = mode;
|
||||
info!("updated network mode to {mode:?}");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -740,8 +739,8 @@ impl NetworkProxyState {
|
||||
};
|
||||
|
||||
let mut candidate = previous_cfg.clone();
|
||||
let target_entries = target.entries(&candidate.network);
|
||||
let opposite_entries = target.opposite_entries(&candidate.network);
|
||||
let target_entries = target.entries(&candidate);
|
||||
let opposite_entries = target.opposite_entries(&candidate);
|
||||
let target_contains = target_entries
|
||||
.iter()
|
||||
.any(|entry| normalize_host(entry) == normalized_host);
|
||||
@@ -752,7 +751,7 @@ impl NetworkProxyState {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
candidate.network.upsert_domain_permission(
|
||||
candidate.upsert_domain_permission(
|
||||
normalized_host.clone(),
|
||||
target.permission(),
|
||||
normalize_host,
|
||||
@@ -809,7 +808,7 @@ impl NetworkProxyState {
|
||||
|
||||
fn ensure_credential_broker_enablement_unchanged(&self, new_state: &ConfigState) -> Result<()> {
|
||||
anyhow::ensure!(
|
||||
self.credential_broker.enabled() == new_state.config.network.credential_broker,
|
||||
self.credential_broker.enabled() == new_state.config.credential_broker,
|
||||
"network.credential_broker cannot change while the proxy is running"
|
||||
);
|
||||
Ok(())
|
||||
@@ -844,14 +843,14 @@ impl DomainListKind {
|
||||
}
|
||||
}
|
||||
|
||||
fn entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String> {
|
||||
fn entries(self, network: &crate::config::NetworkProxyConfig) -> Vec<String> {
|
||||
match self {
|
||||
Self::Allow => network.allowed_domains().unwrap_or_default(),
|
||||
Self::Deny => network.denied_domains().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn opposite_entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String> {
|
||||
fn opposite_entries(self, network: &crate::config::NetworkProxyConfig) -> Vec<String> {
|
||||
match self {
|
||||
Self::Allow => network.denied_domains().unwrap_or_default(),
|
||||
Self::Deny => network.allowed_domains().unwrap_or_default(),
|
||||
@@ -905,15 +904,15 @@ where
|
||||
}
|
||||
|
||||
fn log_policy_changes(previous: &NetworkProxyConfig, next: &NetworkProxyConfig) {
|
||||
let previous_allowed_domains = previous.network.allowed_domains().unwrap_or_default();
|
||||
let next_allowed_domains = next.network.allowed_domains().unwrap_or_default();
|
||||
let previous_allowed_domains = previous.allowed_domains().unwrap_or_default();
|
||||
let next_allowed_domains = next.allowed_domains().unwrap_or_default();
|
||||
log_domain_list_changes(
|
||||
"allowlist",
|
||||
&previous_allowed_domains,
|
||||
&next_allowed_domains,
|
||||
);
|
||||
let previous_denied_domains = previous.network.denied_domains().unwrap_or_default();
|
||||
let next_denied_domains = next.network.denied_domains().unwrap_or_default();
|
||||
let previous_denied_domains = previous.denied_domains().unwrap_or_default();
|
||||
let next_denied_domains = next.denied_domains().unwrap_or_default();
|
||||
log_domain_list_changes("denylist", &previous_denied_domains, &next_denied_domains);
|
||||
}
|
||||
|
||||
@@ -980,13 +979,13 @@ fn unix_timestamp() -> i64 {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn network_proxy_state_for_policy(
|
||||
mut network: crate::config::NetworkProxySettings,
|
||||
mut network: crate::config::NetworkProxyConfig,
|
||||
) -> NetworkProxyState {
|
||||
network.enabled = true;
|
||||
let config = NetworkProxyConfig { network };
|
||||
let config = network;
|
||||
let state = ConfigState {
|
||||
allow_set: crate::policy::compile_allowlist_globset(
|
||||
&config.network.allowed_domains().unwrap_or_default(),
|
||||
&config.allowed_domains().unwrap_or_default(),
|
||||
)
|
||||
.unwrap(),
|
||||
blocked: VecDeque::new(),
|
||||
@@ -994,7 +993,7 @@ pub(crate) fn network_proxy_state_for_policy(
|
||||
config: config.clone(),
|
||||
constraints: NetworkProxyConstraints::default(),
|
||||
deny_set: crate::policy::compile_denylist_globset(
|
||||
&config.network.denied_domains().unwrap_or_default(),
|
||||
&config.denied_domains().unwrap_or_default(),
|
||||
)
|
||||
.unwrap(),
|
||||
mitm: None,
|
||||
@@ -1027,7 +1026,6 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::policy::compile_allowlist_globset;
|
||||
use crate::policy::compile_denylist_globset;
|
||||
use crate::state::NetworkProxyConstraints;
|
||||
@@ -1060,8 +1058,8 @@ mod tests {
|
||||
entries.iter().map(|entry| (*entry).to_string()).collect()
|
||||
}
|
||||
|
||||
fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxySettings {
|
||||
let mut network = NetworkProxySettings::default();
|
||||
fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxyConfig {
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
if !allowed_domains.is_empty() {
|
||||
network.set_allowed_domains(strings(allowed_domains));
|
||||
}
|
||||
@@ -1075,7 +1073,7 @@ mod tests {
|
||||
allowed_domains: &[&str],
|
||||
denied_domains: &[&str],
|
||||
unix_sockets: &[String],
|
||||
) -> NetworkProxySettings {
|
||||
) -> NetworkProxyConfig {
|
||||
let mut network = network_settings(allowed_domains, denied_domains);
|
||||
if !unix_sockets.is_empty() {
|
||||
network.set_allow_unix_sockets(unix_sockets.to_vec());
|
||||
@@ -1209,13 +1207,8 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_allowed_domain_succeeds_when_managed_baseline_allows_expansion() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["managed.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["managed.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
let constraints = NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(true),
|
||||
@@ -1241,13 +1234,8 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["managed.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["managed.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
let constraints = NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(false),
|
||||
@@ -1271,13 +1259,8 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&[], &["managed.example.com"]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&[], &["managed.example.com"]);
|
||||
config.enabled = true;
|
||||
let constraints = NetworkProxyConstraints {
|
||||
denied_domains: Some(vec!["managed.example.com".to_string()]),
|
||||
denylist_expansion_enabled: Some(false),
|
||||
@@ -1301,7 +1284,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn blocked_snapshot_does_not_consume_entries() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
|
||||
state
|
||||
.record_blocked(BlockedRequest::new(BlockedRequestArgs {
|
||||
@@ -1340,7 +1323,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn drain_blocked_returns_buffered_window() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
|
||||
for idx in 0..(MAX_BLOCKED_EVENTS + 5) {
|
||||
state
|
||||
@@ -1492,7 +1475,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_blocked_requires_exact_scoped_ipv6_allowlist_match() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings {
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig {
|
||||
allow_local_binding: true,
|
||||
..network_settings(&["fe80::1%eth0"], &[])
|
||||
});
|
||||
@@ -1515,7 +1498,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_blocked_denies_scoped_ipv6_literal_before_local_binding() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings {
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig {
|
||||
allow_local_binding: true,
|
||||
..network_settings(&["*"], &["fd00::1"])
|
||||
});
|
||||
@@ -1531,7 +1514,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_blocked_requires_exact_scoped_ipv6_denylist_match() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings {
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig {
|
||||
allow_local_binding: true,
|
||||
..network_settings(&["*"], &["fd00::1%eth0"])
|
||||
});
|
||||
@@ -1564,7 +1547,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_blocked_rejects_loopback_when_allowlist_empty() {
|
||||
let state = network_proxy_state_for_policy(NetworkProxySettings::default());
|
||||
let state = network_proxy_state_for_policy(NetworkProxyConfig::default());
|
||||
|
||||
assert_eq!(
|
||||
state.host_blocked("127.0.0.1", /*port*/ 80).await.unwrap(),
|
||||
@@ -1574,7 +1557,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_blocked_rejects_allowlisted_hostname_when_dns_lookup_fails() {
|
||||
let mut network = NetworkProxySettings::default();
|
||||
let mut network = NetworkProxyConfig::default();
|
||||
network.set_allowed_domains(vec!["does-not-resolve.invalid".to_string()]);
|
||||
let state = network_proxy_state_for_policy(network);
|
||||
|
||||
@@ -1653,13 +1636,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["example.com", "evil.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["example.com", "evil.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1672,13 +1650,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["example.com", "api.openai.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["example.com", "api.openai.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
}
|
||||
@@ -1691,11 +1664,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1708,13 +1679,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["api.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["api.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
}
|
||||
@@ -1726,13 +1692,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["**.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["**.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1744,13 +1705,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["api.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["api.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1763,13 +1719,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["api.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["api.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1782,13 +1733,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["api.example.com"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["api.example.com"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1801,10 +1747,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1818,13 +1762,8 @@ mod tests {
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&[], &["evil.com", "more-evil.com"]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&[], &["evil.com", "more-evil.com"]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
@@ -1837,10 +1776,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1854,11 +1791,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
allow_local_binding: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
allow_local_binding: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1873,11 +1808,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1892,11 +1825,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
@@ -1910,11 +1841,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
@@ -1925,11 +1854,9 @@ mod tests {
|
||||
let constraints = NetworkProxyConstraints::default();
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
enabled: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
@@ -2005,52 +1932,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_config_state_allows_global_wildcard_allowed_domains() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["*"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["*"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_config_state_allows_bracketed_global_wildcard_allowed_domains() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["[*]"], &[]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["[*]"], &[]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_config_state_rejects_global_wildcard_denied_domains() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["example.com"], &["*"]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["example.com"], &["*"]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_config_state_rejects_bracketed_global_wildcard_denied_domains() {
|
||||
let config = NetworkProxyConfig {
|
||||
network: {
|
||||
let mut network = network_settings(&["example.com"], &["[*]"]);
|
||||
network.enabled = true;
|
||||
network
|
||||
},
|
||||
};
|
||||
let mut config = network_settings(&["example.com"], &["[*]"]);
|
||||
config.enabled = true;
|
||||
|
||||
assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err());
|
||||
}
|
||||
|
||||
@@ -809,7 +809,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::NetworkMode;
|
||||
use crate::config::NetworkProxyConfig;
|
||||
use crate::config::NetworkProxySettings;
|
||||
use crate::mitm_hook::MitmHookConfig;
|
||||
use crate::mitm_hook::MitmHookMatchConfig;
|
||||
use crate::network_policy::test_support::POLICY_DECISION_EVENT_NAME;
|
||||
@@ -855,12 +854,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn state_for_settings(network: NetworkProxySettings) -> Arc<NetworkProxyState> {
|
||||
let config = NetworkProxyConfig { network };
|
||||
let _mitm_config_state_guard = config
|
||||
.network
|
||||
.mitm
|
||||
.then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap());
|
||||
fn state_for_settings(network: NetworkProxyConfig) -> Arc<NetworkProxyState> {
|
||||
let config = network;
|
||||
let _mitm_config_state_guard = config.mitm.then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap());
|
||||
let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap();
|
||||
let reloader = Arc::new(StaticReloader {
|
||||
state: state.clone(),
|
||||
@@ -870,10 +866,10 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_emits_block_decision_for_proxy_disabled() {
|
||||
let state = state_for_settings(NetworkProxySettings {
|
||||
let state = state_for_settings(NetworkProxyConfig {
|
||||
enabled: false,
|
||||
mode: NetworkMode::Full,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
});
|
||||
let mut request =
|
||||
TcpRequest::new(HostWithPort::try_from("example.com:443").expect("valid authority"));
|
||||
@@ -912,11 +908,11 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_uses_mitm_in_limited_mode() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Limited,
|
||||
mitm: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -938,10 +934,10 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_blocks_non_https_in_limited_mode() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -985,12 +981,12 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_detects_tls_for_brokered_nonstandard_port_in_full_mode() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
mitm: true,
|
||||
credential_broker: true,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["api.openai.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -1015,10 +1011,10 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_blocks_limited_mode_without_mitm_state() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["example.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -1043,7 +1039,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_uses_mitm_for_hooked_host_in_full_mode() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
mitm: true,
|
||||
@@ -1056,7 +1052,7 @@ mod tests {
|
||||
},
|
||||
..MitmHookConfig::default()
|
||||
}],
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -1078,7 +1074,7 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn handle_socks5_tcp_blocks_hooked_non_https_host_in_full_mode() {
|
||||
let mut settings = NetworkProxySettings {
|
||||
let mut settings = NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Full,
|
||||
mitm: true,
|
||||
@@ -1091,7 +1087,7 @@ mod tests {
|
||||
},
|
||||
..MitmHookConfig::default()
|
||||
}],
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
};
|
||||
settings.set_allowed_domains(vec!["api.github.com".to_string()]);
|
||||
let state = state_for_settings(settings);
|
||||
@@ -1116,10 +1112,10 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn inspect_socks5_udp_emits_block_decision_for_mode_guard_deny() {
|
||||
let state = state_for_settings(NetworkProxySettings {
|
||||
let state = state_for_settings(NetworkProxyConfig {
|
||||
enabled: true,
|
||||
mode: NetworkMode::Limited,
|
||||
..NetworkProxySettings::default()
|
||||
..NetworkProxyConfig::default()
|
||||
});
|
||||
let request = RelayRequest {
|
||||
direction: RelayDirection::South,
|
||||
|
||||
@@ -40,12 +40,6 @@ pub struct NetworkProxyConstraints {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PartialNetworkProxyConfig {
|
||||
#[serde(default)]
|
||||
pub network: PartialNetworkConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
pub struct PartialNetworkConfig {
|
||||
pub enabled: Option<bool>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
@@ -69,20 +63,20 @@ pub fn build_config_state(
|
||||
) -> anyhow::Result<ConfigState> {
|
||||
crate::config::validate_unix_socket_allowlist_paths(&config)?;
|
||||
anyhow::ensure!(
|
||||
!config.network.credential_broker || config.network.mitm,
|
||||
!config.credential_broker || config.mitm,
|
||||
"network.credential_broker requires network.mitm = true"
|
||||
);
|
||||
let allowed_domains = config.network.allowed_domains().unwrap_or_default();
|
||||
let denied_domains = config.network.denied_domains().unwrap_or_default();
|
||||
let allowed_domains = config.allowed_domains().unwrap_or_default();
|
||||
let denied_domains = config.denied_domains().unwrap_or_default();
|
||||
validate_non_global_wildcard_domain_patterns("network.denied_domains", &denied_domains)
|
||||
.map_err(NetworkProxyConstraintError::into_anyhow)?;
|
||||
let deny_set = compile_denylist_globset(&denied_domains)?;
|
||||
let allow_set = compile_allowlist_globset(&allowed_domains)?;
|
||||
let mitm_hooks = compile_mitm_hooks(&config)?;
|
||||
let mitm = if config.network.mitm {
|
||||
let mitm = if config.mitm {
|
||||
Some(Arc::new(MitmState::new(MitmUpstreamConfig {
|
||||
allow_upstream_proxy: config.network.allow_upstream_proxy,
|
||||
allow_local_binding: config.network.allow_local_binding,
|
||||
allow_upstream_proxy: config.allow_upstream_proxy,
|
||||
allow_local_binding: config.allow_local_binding,
|
||||
})?))
|
||||
} else {
|
||||
None
|
||||
@@ -122,14 +116,14 @@ pub fn validate_policy_against_constraints(
|
||||
validator(&candidate)
|
||||
}
|
||||
|
||||
let enabled = config.network.enabled;
|
||||
let config_allowed_domains = config.network.allowed_domains().unwrap_or_default();
|
||||
let config_denied_domains = config.network.denied_domains().unwrap_or_default();
|
||||
let enabled = config.enabled;
|
||||
let config_allowed_domains = config.allowed_domains().unwrap_or_default();
|
||||
let config_denied_domains = config.denied_domains().unwrap_or_default();
|
||||
let denied_domain_overrides: HashSet<String> = config_denied_domains
|
||||
.iter()
|
||||
.map(|entry| entry.to_ascii_lowercase())
|
||||
.collect();
|
||||
let config_allow_unix_sockets = config.network.allow_unix_sockets();
|
||||
let config_allow_unix_sockets = config.allow_unix_sockets();
|
||||
validate_mitm_hook_config(config).map_err(invalid_mitm_hook_configuration)?;
|
||||
validate_non_global_wildcard_domain_patterns("network.denied_domains", &config_denied_domains)?;
|
||||
if let Some(max_enabled) = constraints.enabled {
|
||||
@@ -147,7 +141,7 @@ pub fn validate_policy_against_constraints(
|
||||
}
|
||||
|
||||
if let Some(max_mode) = constraints.mode {
|
||||
validate(config.network.mode, move |candidate| {
|
||||
validate(config.mode, move |candidate| {
|
||||
if network_mode_rank(*candidate) > network_mode_rank(max_mode) {
|
||||
Err(invalid_value(
|
||||
"network.mode",
|
||||
@@ -162,7 +156,7 @@ pub fn validate_policy_against_constraints(
|
||||
|
||||
let allow_upstream_proxy = constraints.allow_upstream_proxy;
|
||||
validate(
|
||||
config.network.allow_upstream_proxy,
|
||||
config.allow_upstream_proxy,
|
||||
move |candidate| match allow_upstream_proxy {
|
||||
Some(true) | None => Ok(()),
|
||||
Some(false) => {
|
||||
@@ -181,7 +175,7 @@ pub fn validate_policy_against_constraints(
|
||||
|
||||
let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy;
|
||||
validate(
|
||||
config.network.dangerously_allow_non_loopback_proxy,
|
||||
config.dangerously_allow_non_loopback_proxy,
|
||||
move |candidate| match allow_non_loopback_proxy {
|
||||
Some(true) | None => Ok(()),
|
||||
Some(false) => {
|
||||
@@ -202,7 +196,7 @@ pub fn validate_policy_against_constraints(
|
||||
.dangerously_allow_all_unix_sockets
|
||||
.unwrap_or(constraints.allow_unix_sockets.is_none());
|
||||
validate(
|
||||
config.network.dangerously_allow_all_unix_sockets,
|
||||
config.dangerously_allow_all_unix_sockets,
|
||||
move |candidate| {
|
||||
if *candidate && !allow_all_unix_sockets {
|
||||
Err(invalid_value(
|
||||
@@ -217,7 +211,7 @@ pub fn validate_policy_against_constraints(
|
||||
)?;
|
||||
|
||||
if let Some(allow_local_binding) = constraints.allow_local_binding {
|
||||
validate(config.network.allow_local_binding, move |candidate| {
|
||||
validate(config.allow_local_binding, move |candidate| {
|
||||
if *candidate && !allow_local_binding {
|
||||
Err(invalid_value(
|
||||
"network.allow_local_binding",
|
||||
|
||||
@@ -658,11 +658,9 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a
|
||||
let network_socket = "/tmp/codex-proxy-use";
|
||||
let explicit_socket = "/tmp/codex-browser-use";
|
||||
let mut network_config = NetworkProxyConfig::default();
|
||||
network_config.network.enabled = true;
|
||||
network_config.network.mode = NetworkMode::Full;
|
||||
network_config
|
||||
.network
|
||||
.set_allow_unix_sockets(vec![network_socket.to_string()]);
|
||||
network_config.enabled = true;
|
||||
network_config.mode = NetworkMode::Full;
|
||||
network_config.set_allow_unix_sockets(vec![network_socket.to_string()]);
|
||||
let state = build_config_state(network_config, NetworkProxyConstraints::default())?;
|
||||
let network_proxy = NetworkProxy::builder()
|
||||
.state(Arc::new(NetworkProxyState::with_reloader(
|
||||
|
||||
Reference in New Issue
Block a user