Preserve local-binding inheritance in environment network policies (#47898)

## Why

Treating an omitted `allow_local_binding` setting as `false` prevents environment policies from inheriting local-binding permissions.

## What changed

Keep `allow_local_binding` optional when composing controller and environment policies. Preserve explicit denials from either policy and resolve executor defaults after composition. Without a controller, default to denying local binding unless the environment explicitly allows it.

## Testing

Add coverage for omitted, allowed, and denied settings, managed constraints, executor defaults, and remote launch rejection when local-binding restrictions cannot be enforced. Extend remote network approval tests with loopback requests to verify controller and environment policy composition.

GitOrigin-RevId: aa0d15e8be87a2741255cd3a5a2cabffa03214ac
This commit is contained in:
Sean Huang
2026-09-24 18:05:40 +00:00
committed by copyberry
parent a33fb9751c
commit 2457571e4c
5 changed files with 248 additions and 14 deletions
@@ -16,7 +16,8 @@ pub struct EnvironmentNetworkPolicy {
pub unix_sockets: Option<NetworkUnixSocketPermissions>,
pub allow_upstream_proxy: bool,
pub dangerously_allow_all_unix_sockets: bool,
pub allow_local_binding: bool,
/// Omission inherits local-binding policy; an explicit false remains a restriction.
pub allow_local_binding: Option<bool>,
pub managed_allowed_domains_only: bool,
}
@@ -30,7 +31,7 @@ impl EnvironmentNetworkPolicy {
dangerously_allow_all_unix_sockets: config
.dangerously_allow_all_unix_sockets
.unwrap_or(false),
allow_local_binding: config.allow_local_binding(),
allow_local_binding: config.allow_local_binding,
managed_allowed_domains_only,
}
}
@@ -82,11 +83,15 @@ impl EnvironmentNetworkPolicy {
}
}
// Enable permissions only when both controller and owner allow them.
// Socket and upstream-proxy permissions require both controller and owner grants.
config.unix_sockets = (!effective_sockets.entries.is_empty()).then_some(effective_sockets);
config.dangerously_allow_all_unix_sockets =
Some(inherited_permits_all && owner_permits_all);
config.allow_upstream_proxy &= self.allow_upstream_proxy;
config.allow_local_binding = Some(config.allow_local_binding() && self.allow_local_binding);
// Either explicit denial wins; defer unresolved defaults to the executor.
config.allow_local_binding = match (config.allow_local_binding, self.allow_local_binding) {
(Some(controller), Some(owner)) => Some(controller && owner),
(controller, owner) => controller.or(owner),
};
}
}
+1 -1
View File
@@ -979,10 +979,10 @@ impl NetworkProxy {
config.mitm = false;
}
}
config.allow_local_binding = Some(local_binding_policy.resolve(&config));
if let Some(policy) = environment_policy {
policy.apply_to(&mut config);
}
config.allow_local_binding = Some(local_binding_policy.resolve(&config));
anyhow::ensure!(
environment_policy.is_none() || config.enabled,
"environment network policy requires an enabled executor proxy"