mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
Prepare managed policy validation for Windows sandbox provisioning (#42344)
## What changed - Add a Windows sandbox service validator that loads managed configuration while impersonating the provisioning client. - Reject elevated sandbox or network settings that conflict with managed requirements, including local binding and HTTP or SOCKS proxy-port restrictions. - Add a one-shot cloud configuration loader that bypasses the disk cache so policy checks use a fresh backend response without modifying cached configuration. - Keep the provisioning integration disabled until authenticated transport can supply the policy inputs. ## Testing - Cover elevated sandbox restrictions, disabled networking, local binding, proxy-port classification, malformed policy, impersonation failure, and cache bypass behavior. GitOrigin-RevId: b76aa8959c515c6507c2eaf61af768efa1305253
This commit is contained in:
5
codex-rs/Cargo.lock
generated
5
codex-rs/Cargo.lock
generated
@@ -4977,7 +4977,12 @@ name = "codex-windows-sandbox-service"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-cloud-config",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-windows-sandbox",
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -88,24 +88,45 @@ pub async fn cloud_config_bundle_loader_for_storage(
|
||||
auth_config: AuthConfig,
|
||||
enable_codex_api_key_env: bool,
|
||||
) -> std::io::Result<CloudConfigBundleLoader> {
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_auth_config(auth_config.clone(), enable_codex_api_key_env).await?;
|
||||
Ok(cloud_config_bundle_loader_from_auth_config(
|
||||
auth_config,
|
||||
auth_manager,
|
||||
))
|
||||
let service =
|
||||
cloud_config_bundle_service_for_storage(auth_config, enable_codex_api_key_env).await?;
|
||||
let (loader, refresh_task) = cloud_config_bundle_loader_for_service(service);
|
||||
replace_refresh_task(refresher_task_slot(), refresh_task);
|
||||
Ok(loader)
|
||||
}
|
||||
|
||||
fn cloud_config_bundle_loader_from_auth_config(
|
||||
/// Fetches directly from the network on each load, without reading or writing
|
||||
/// the disk cache or starting a background refresher.
|
||||
pub async fn cloud_config_bundle_loader_for_storage_without_cache(
|
||||
auth_config: AuthConfig,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
) -> CloudConfigBundleLoader {
|
||||
cloud_config_bundle_loader(
|
||||
auth_manager,
|
||||
auth_config
|
||||
.chatgpt_base_url
|
||||
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()),
|
||||
auth_config.codex_home,
|
||||
auth_config.auth_route_config.http_client_factory().clone(),
|
||||
)
|
||||
enable_codex_api_key_env: bool,
|
||||
) -> std::io::Result<CloudConfigBundleLoader> {
|
||||
let service = Arc::new(
|
||||
cloud_config_bundle_service_for_storage(auth_config, enable_codex_api_key_env)
|
||||
.await?
|
||||
.without_cache(),
|
||||
);
|
||||
Ok(CloudConfigBundleLoader::from_getter(move || {
|
||||
let service = Arc::clone(&service);
|
||||
async move { service.load_startup_bundle_with_timeout().await }
|
||||
}))
|
||||
}
|
||||
|
||||
async fn cloud_config_bundle_service_for_storage(
|
||||
auth_config: AuthConfig,
|
||||
enable_codex_api_key_env: bool,
|
||||
) -> std::io::Result<CloudConfigBundleService<BackendBundleClient>> {
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_auth_config(auth_config.clone(), enable_codex_api_key_env).await?;
|
||||
Ok(CloudConfigBundleService::new(
|
||||
auth_manager,
|
||||
Arc::new(BackendBundleClient::new(
|
||||
auth_config
|
||||
.chatgpt_base_url
|
||||
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()),
|
||||
auth_config.auth_route_config.http_client_factory().clone(),
|
||||
)),
|
||||
auth_config.codex_home,
|
||||
CLOUD_CONFIG_BUNDLE_TIMEOUT,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -12,3 +12,4 @@ mod validation;
|
||||
|
||||
pub use bundle_loader::cloud_config_bundle_loader;
|
||||
pub use bundle_loader::cloud_config_bundle_loader_for_storage;
|
||||
pub use bundle_loader::cloud_config_bundle_loader_for_storage_without_cache;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//!
|
||||
//! Startup loads a shared bundle from cache or backend, and background refresh
|
||||
//! updates both the on-disk cache and the bundle observed by future config loads.
|
||||
//! One-shot network loads can disable disk-cache reads and writes.
|
||||
|
||||
use crate::backend::BundleClient;
|
||||
use crate::backend::BundleRequestError;
|
||||
@@ -78,6 +79,7 @@ pub(crate) struct CloudConfigBundleService<C> {
|
||||
auth_manager: Arc<AuthManager>,
|
||||
client: Arc<C>,
|
||||
cache: CloudConfigBundleCache,
|
||||
cache_enabled: bool,
|
||||
codex_home: AbsolutePathBuf,
|
||||
timeout: Duration,
|
||||
latest_bundle: OnceCell<Mutex<Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>>,
|
||||
@@ -98,12 +100,18 @@ where
|
||||
auth_manager,
|
||||
client,
|
||||
cache: CloudConfigBundleCache::new(codex_home.clone()),
|
||||
cache_enabled: true,
|
||||
codex_home,
|
||||
timeout,
|
||||
latest_bundle: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn without_cache(mut self) -> Self {
|
||||
self.cache_enabled = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) async fn get_latest(
|
||||
&self,
|
||||
) -> Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError> {
|
||||
@@ -182,15 +190,17 @@ where
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Startup prefers a valid, identity-matched cache entry. The backend is
|
||||
// only consulted on cache miss or invalid cache contents.
|
||||
let (chatgpt_user_id, account_id) = auth_identity(&auth);
|
||||
match self
|
||||
.load_valid_cached_bundle(chatgpt_user_id.as_deref(), account_id.as_deref())
|
||||
.await
|
||||
{
|
||||
CachedBundleLookup::Hit(bundle) => return Ok(bundle),
|
||||
CachedBundleLookup::Miss => {}
|
||||
if self.cache_enabled {
|
||||
// Startup prefers a valid, identity-matched cache entry. The backend is
|
||||
// only consulted on cache miss or invalid cache contents.
|
||||
let (chatgpt_user_id, account_id) = auth_identity(&auth);
|
||||
match self
|
||||
.load_valid_cached_bundle(chatgpt_user_id.as_deref(), account_id.as_deref())
|
||||
.await
|
||||
{
|
||||
CachedBundleLookup::Hit(bundle) => return Ok(bundle),
|
||||
CachedBundleLookup::Miss => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.fetch_remote_bundle_and_update_cache_with_retries(auth, "startup")
|
||||
@@ -322,10 +332,11 @@ where
|
||||
}
|
||||
|
||||
let (chatgpt_user_id, account_id) = auth_identity(auth);
|
||||
if let Err(err) = self
|
||||
.cache
|
||||
.save(chatgpt_user_id, account_id, bundle.clone())
|
||||
.await
|
||||
if self.cache_enabled
|
||||
&& let Err(err) = self
|
||||
.cache
|
||||
.save(chatgpt_user_id, account_id, bundle.clone())
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
|
||||
@@ -634,6 +634,74 @@ async fn get_bundle_uses_cache_when_valid() {
|
||||
assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_bundle_without_cache_ignores_and_preserves_valid_cache() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
let cache = create_test_cache(codex_home.path());
|
||||
cache
|
||||
.save(
|
||||
Some("user-12345".to_string()),
|
||||
Some("account-12345".to_string()),
|
||||
CloudConfigBundle::default(),
|
||||
)
|
||||
.await
|
||||
.expect("write empty cache");
|
||||
let cached_bytes = std::fs::read(cache.path()).expect("read cache");
|
||||
let bundle = test_bundle();
|
||||
let fetcher = Arc::new(StaticBundleClient::new(bundle.clone()));
|
||||
let service = CloudConfigBundleService::new(
|
||||
auth_manager_with_plan("business").await,
|
||||
fetcher.clone(),
|
||||
codex_home.path().to_path_buf(),
|
||||
CLOUD_CONFIG_BUNDLE_TIMEOUT,
|
||||
)
|
||||
.without_cache();
|
||||
|
||||
assert_eq!(
|
||||
service.load_startup_bundle_with_timeout().await,
|
||||
Ok(Some(bundle))
|
||||
);
|
||||
assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(
|
||||
std::fs::read(cache.path()).expect("read unchanged cache"),
|
||||
cached_bytes
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn get_bundle_without_cache_fails_closed_on_request_failure() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
create_test_cache(codex_home.path())
|
||||
.save(
|
||||
Some("user-12345".to_string()),
|
||||
Some("account-12345".to_string()),
|
||||
test_bundle(),
|
||||
)
|
||||
.await
|
||||
.expect("write valid cache");
|
||||
let fetcher = Arc::new(SequenceBundleClient::new(vec![
|
||||
Err(request_error());
|
||||
CLOUD_CONFIG_BUNDLE_MAX_ATTEMPTS
|
||||
]));
|
||||
let service = CloudConfigBundleService::new(
|
||||
auth_manager_with_plan("business").await,
|
||||
fetcher.clone(),
|
||||
codex_home.path().to_path_buf(),
|
||||
CLOUD_CONFIG_BUNDLE_TIMEOUT,
|
||||
)
|
||||
.without_cache();
|
||||
|
||||
let err = service
|
||||
.load_startup_bundle_with_timeout()
|
||||
.await
|
||||
.expect_err("request failure must not fall back to the cache");
|
||||
assert_eq!(err.code(), CloudConfigBundleLoadErrorCode::RequestFailed);
|
||||
assert_eq!(
|
||||
fetcher.request_count.load(Ordering::SeqCst),
|
||||
CLOUD_CONFIG_BUNDLE_MAX_ATTEMPTS
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_bundle_ignores_cache_for_different_auth_identity() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
|
||||
@@ -18,7 +18,14 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-cloud-config = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-windows-sandbox = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
toml = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.52"
|
||||
|
||||
@@ -2,6 +2,10 @@ use anyhow::Result;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod ipc;
|
||||
// Provisioning remains disabled until authenticated transport connects this policy.
|
||||
#[cfg(windows)]
|
||||
#[allow(dead_code)]
|
||||
mod machine_policy;
|
||||
#[cfg(windows)]
|
||||
mod service;
|
||||
|
||||
|
||||
138
codex-rs/windows-sandbox-service/src/machine_policy.rs
Normal file
138
codex-rs/windows-sandbox-service/src/machine_policy.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
//! Validates provisioning requests against the standard managed configuration layers.
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use codex_cloud_config::cloud_config_bundle_loader_for_storage_without_cache;
|
||||
use codex_config::ConfigLoadOptions;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_config::types::WindowsSandboxModeToml;
|
||||
use codex_core::config::bootstrap_auth_config;
|
||||
use codex_core::config::load_config_toml_with_layer_stack;
|
||||
use codex_windows_sandbox::WindowsSandboxProvisioningSettings;
|
||||
use codex_windows_sandbox::WindowsSandboxProxyListeners;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Notify;
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::Security::ImpersonateLoggedOnUser;
|
||||
|
||||
pub(crate) fn validate_provisioning_settings(
|
||||
codex_home: &Path,
|
||||
settings: &WindowsSandboxProvisioningSettings,
|
||||
listeners: &WindowsSandboxProxyListeners,
|
||||
impersonation_token: HANDLE,
|
||||
) -> Result<()> {
|
||||
let impersonation_failure = Arc::new(Notify::new());
|
||||
let worker_impersonation_failure = Arc::clone(&impersonation_failure);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.on_thread_start(move || {
|
||||
if unsafe { ImpersonateLoggedOnUser(impersonation_token) } == 0 {
|
||||
let error = std::io::Error::last_os_error();
|
||||
worker_impersonation_failure.notify_one();
|
||||
panic!(
|
||||
"failed to impersonate provisioning client on configuration runtime thread: {error}"
|
||||
);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
.context("start managed configuration runtime")?;
|
||||
let requirements = runtime.block_on(async {
|
||||
tokio::select! {
|
||||
biased;
|
||||
() = impersonation_failure.notified() => {
|
||||
Err(anyhow::anyhow!("configuration runtime worker failed to impersonate the provisioning client"))
|
||||
},
|
||||
result = async {
|
||||
let mut bootstrap_config = load_config_toml_with_layer_stack(
|
||||
codex_home,
|
||||
/*cwd*/ None,
|
||||
Vec::new(),
|
||||
ConfigLoadOptions::default(),
|
||||
)
|
||||
.await
|
||||
.context("load bootstrap configuration")?;
|
||||
// Use the default cloud-policy endpoint unless managed requirements override it.
|
||||
bootstrap_config.config_toml.chatgpt_base_url = None;
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage_without_cache(
|
||||
bootstrap_auth_config(codex_home, &bootstrap_config)
|
||||
.context("resolve cloud configuration authentication")?,
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await
|
||||
.context("initialize cloud configuration authentication")?;
|
||||
let config = load_config_toml_with_layer_stack(
|
||||
codex_home,
|
||||
/*cwd*/ None,
|
||||
Vec::new(),
|
||||
ConfigLoadOptions {
|
||||
cloud_config_bundle,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("load managed configuration")?;
|
||||
|
||||
Ok::<_, anyhow::Error>(config.config_layer_stack.requirements_toml().clone())
|
||||
} => result,
|
||||
}
|
||||
})?;
|
||||
|
||||
validate_requirements(settings, listeners, &requirements)
|
||||
.context("enforce managed provisioning requirements")
|
||||
}
|
||||
|
||||
fn validate_requirements(
|
||||
settings: &WindowsSandboxProvisioningSettings,
|
||||
listeners: &WindowsSandboxProxyListeners,
|
||||
requirements: &ConfigRequirementsToml,
|
||||
) -> Result<()> {
|
||||
if requirements
|
||||
.windows
|
||||
.as_ref()
|
||||
.and_then(|windows| windows.allowed_sandbox_implementations.as_ref())
|
||||
.is_some_and(|allowed| !allowed.contains(&WindowsSandboxModeToml::Elevated))
|
||||
{
|
||||
bail!("managed policy does not permit the elevated Windows sandbox");
|
||||
}
|
||||
|
||||
let Some(network) = requirements.network.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
if network.enabled == Some(false)
|
||||
&& (settings.allow_local_binding || !settings.proxy_ports.is_empty())
|
||||
{
|
||||
bail!("managed policy disables sandbox network access");
|
||||
}
|
||||
if settings.allow_local_binding && network.allow_local_binding == Some(false) {
|
||||
bail!("managed policy does not permit local network binding");
|
||||
}
|
||||
if let Some(required) = network.http_port
|
||||
&& let Some(actual) = listeners.http_ports.iter().find(|port| **port != required)
|
||||
{
|
||||
bail!("managed policy does not permit HTTP proxy port {actual}");
|
||||
}
|
||||
if let Some(required) = network.socks_port
|
||||
&& let Some(actual) = listeners.socks_ports.iter().find(|port| **port != required)
|
||||
{
|
||||
bail!("managed policy does not permit SOCKS proxy port {actual}");
|
||||
}
|
||||
// Omitting listener identities must not bypass managed port restrictions.
|
||||
if network.http_port.is_some() || network.socks_port.is_some() {
|
||||
for port in &settings.proxy_ports {
|
||||
if !listeners.http_ports.contains(port)
|
||||
&& !listeners.socks_ports.contains(port)
|
||||
&& network.http_port != Some(*port)
|
||||
&& network.socks_port != Some(*port)
|
||||
{
|
||||
bail!("managed policy does not permit unclassified proxy port {port}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "machine_policy_tests.rs"]
|
||||
mod tests;
|
||||
277
codex-rs/windows-sandbox-service/src/machine_policy_tests.rs
Normal file
277
codex-rs/windows-sandbox-service/src/machine_policy_tests.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use anyhow::Result;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_windows_sandbox::WindowsSandboxProvisioningSettings;
|
||||
use codex_windows_sandbox::WindowsSandboxProxyListeners;
|
||||
|
||||
fn validate_requirements(
|
||||
settings: &WindowsSandboxProvisioningSettings,
|
||||
listeners: WindowsSandboxProxyListeners,
|
||||
contents: &str,
|
||||
) -> Result<()> {
|
||||
let requirements: ConfigRequirementsToml = toml::from_str(contents)?;
|
||||
super::validate_requirements(settings, &listeners, &requirements)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_worker_impersonation_failure_rejects_provisioning() {
|
||||
let error = super::validate_provisioning_settings(
|
||||
&std::env::temp_dir(),
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
&WindowsSandboxProxyListeners::default(),
|
||||
/*impersonation_token*/ 0,
|
||||
)
|
||||
.expect_err("runtime workers must not load configuration without impersonating the client");
|
||||
|
||||
assert!(error.to_string().contains("failed to impersonate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmanaged_policy_allows_default_and_requested_network_settings() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"",
|
||||
)?;
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128, 8080, 9000],
|
||||
allow_local_binding: true,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![8080],
|
||||
},
|
||||
"allowed_sandbox_modes = [\"workspace-write\"]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elevated_sandbox_is_allowed_when_included() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"[windows]\nallowed_sandbox_implementations = [\"unelevated\", \"elevated\"]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elevated_sandbox_is_rejected_when_prohibited_or_empty() {
|
||||
for implementations in ["[\"unelevated\"]", "[]"] {
|
||||
let policy = format!("[windows]\nallowed_sandbox_implementations = {implementations}");
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
&policy,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_local_binding_is_rejected() {
|
||||
let settings = WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: Vec::new(),
|
||||
allow_local_binding: true,
|
||||
};
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&settings,
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"[experimental_network]\nallow_local_binding = false"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_network_rejects_proxy_ports_and_local_binding() -> Result<()> {
|
||||
let policy = "[experimental_network]\nenabled = false";
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
policy,
|
||||
)?;
|
||||
for settings in [
|
||||
WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: Vec::new(),
|
||||
allow_local_binding: true,
|
||||
},
|
||||
] {
|
||||
assert!(
|
||||
validate_requirements(&settings, WindowsSandboxProxyListeners::default(), policy)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enabled_network_allows_unrestricted_proxy_ports() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128, 8080, 8081, 9000],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128, 8080],
|
||||
socks_ports: vec![8081],
|
||||
},
|
||||
"[experimental_network]\nenabled = true",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_local_binding_is_allowed_when_policy_permits_binding() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"[experimental_network]\nallow_local_binding = true",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_proxy_ports_allow_only_configured_ports() -> Result<()> {
|
||||
let policy = "[experimental_network]\nhttp_port = 3128\nsocks_port = 1080";
|
||||
let allowed = WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![1080, 3128],
|
||||
allow_local_binding: false,
|
||||
};
|
||||
for listeners in [
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![1080],
|
||||
},
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
] {
|
||||
validate_requirements(&allowed, listeners, policy)?;
|
||||
}
|
||||
let prohibited = WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![1080, 3128, 8080],
|
||||
allow_local_binding: false,
|
||||
};
|
||||
for listeners in [
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128, 8080],
|
||||
socks_ports: vec![1080],
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![1080],
|
||||
},
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
] {
|
||||
assert!(validate_requirements(&prohibited, listeners, policy).is_err());
|
||||
}
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
policy,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_http_port_does_not_restrict_the_unmanaged_socks_listener() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128, 8081],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![8081],
|
||||
},
|
||||
"[experimental_network]\nhttp_port = 3128",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_socks_port_does_not_restrict_the_unmanaged_http_listener() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![1080, 3128],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![1080],
|
||||
},
|
||||
"[experimental_network]\nsocks_port = 1080",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_proxy_ports_reject_swapped_listener_roles() {
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![1080, 3128],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![1080],
|
||||
socks_ports: vec![3128],
|
||||
},
|
||||
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_socks_port_rejects_a_mismatched_socks_listener() {
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![1080, 3128, 8081],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: vec![1080, 8081],
|
||||
},
|
||||
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_socks_port_does_not_require_a_disabled_socks_listener() -> Result<()> {
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings {
|
||||
proxy_ports: vec![3128],
|
||||
allow_local_binding: false,
|
||||
},
|
||||
WindowsSandboxProxyListeners {
|
||||
http_ports: vec![3128],
|
||||
socks_ports: Vec::new(),
|
||||
},
|
||||
"[experimental_network]\nhttp_port = 3128\nsocks_port = 1080",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_machine_policy_fails_closed() {
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"[experimental_network]\nhttp_port = \"invalid\""
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
validate_requirements(
|
||||
&WindowsSandboxProvisioningSettings::default(),
|
||||
WindowsSandboxProxyListeners::default(),
|
||||
"[windows]\nallowed_sandbox_implementations = [\"invalid\"]"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user