mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
feat(config): add permissions.network proxy config wiring (#12054)
## Summary Implements the `ConfigToml.permissions.network` and uses it to populate `NetworkProxyConfig`. We now parse a new nested permissions/network config shape which is converted into the proxy’s runtime config. When managed requirements exist, we still apply those constraints on top of user settings (so managed policy still wins). * Cleaned up the old constructor path so it now accepts both user config + managed constraints directly. * Updated the reload path so live proxy config reloads respect [permissions.network] too, while still supporting the existing top-level [network] format. ### Behavior - User-defined `[permissions.network]` values are now honored. - Managed constraints still take effect and are validated against the resulting policy.
This commit is contained in:
@@ -625,6 +625,70 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkModeSchema": {
|
||||
"enum": [
|
||||
"limited",
|
||||
"full"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"admin_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"allow_local_binding": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allow_unix_sockets": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"allow_upstream_proxy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowed_domains": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"dangerously_allow_non_loopback_admin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"dangerously_allow_non_loopback_proxy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"denied_domains": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"enable_socks5": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_socks5_udp": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/NetworkModeSchema"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"socks_url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Notice": {
|
||||
"description": "Settings for notices we display to users via the tui and app-server clients (primarily the Codex IDE extension). NOTE: these are different from notifications - notices are warnings, NUX screens, acknowledgements, etc.",
|
||||
"properties": {
|
||||
@@ -866,6 +930,20 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PermissionsToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"network": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/NetworkToml"
|
||||
}
|
||||
],
|
||||
"description": "Network proxy settings from `[permissions.network]`. User config can enable the proxy; managed requirements may still constrain values."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
@@ -1718,6 +1796,15 @@
|
||||
],
|
||||
"description": "OTEL configuration."
|
||||
},
|
||||
"permissions": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PermissionsToml"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Nested permissions settings."
|
||||
},
|
||||
"personality": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -85,12 +85,14 @@ use tempfile::tempdir;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
type MacOsSeatbeltProfileExtensions = ();
|
||||
|
||||
use crate::config::permissions::network_proxy_config_from_permissions;
|
||||
use crate::config::profile::ConfigProfile;
|
||||
use toml::Value as TomlValue;
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
pub mod edit;
|
||||
mod network_proxy_spec;
|
||||
mod permissions;
|
||||
pub mod profile;
|
||||
pub mod schema;
|
||||
pub mod service;
|
||||
@@ -101,6 +103,8 @@ pub use codex_config::ConstraintResult;
|
||||
|
||||
pub use network_proxy_spec::NetworkProxySpec;
|
||||
pub use network_proxy_spec::StartedNetworkProxy;
|
||||
pub use permissions::NetworkToml;
|
||||
pub use permissions::PermissionsToml;
|
||||
pub use service::ConfigService;
|
||||
pub use service::ConfigServiceError;
|
||||
|
||||
@@ -954,6 +958,10 @@ pub struct ConfigToml {
|
||||
/// Sandbox configuration to apply if `sandbox` is `WorkspaceWrite`.
|
||||
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
|
||||
|
||||
/// Nested permissions settings.
|
||||
#[serde(default)]
|
||||
pub permissions: Option<PermissionsToml>,
|
||||
|
||||
/// Optional external command to spawn for end-user notifications.
|
||||
#[serde(default)]
|
||||
pub notify: Option<Vec<String>>,
|
||||
@@ -1590,6 +1598,8 @@ impl Config {
|
||||
.clone(),
|
||||
None => ConfigProfile::default(),
|
||||
};
|
||||
let configured_network_proxy_config =
|
||||
network_proxy_config_from_permissions(cfg.permissions.as_ref());
|
||||
|
||||
let feature_overrides = FeatureOverrides {
|
||||
include_apply_patch_tool: include_apply_patch_tool_override,
|
||||
@@ -1902,18 +1912,29 @@ impl Config {
|
||||
let mcp_servers = constrain_mcp_servers(cfg.mcp_servers.clone(), mcp_servers.as_ref())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?;
|
||||
|
||||
let network = match network_requirements {
|
||||
Some(Sourced { value, source }) => {
|
||||
let network = NetworkProxySpec::from_constraints(&config_layer_stack, value)
|
||||
.map_err(|err| {
|
||||
std::io::Error::new(
|
||||
err.kind(),
|
||||
format!("failed to build managed network proxy from {source}: {err}"),
|
||||
)
|
||||
})?;
|
||||
Some(network)
|
||||
let (network_requirements, network_requirements_source) = match network_requirements {
|
||||
Some(Sourced { value, source }) => (Some(value), Some(source)),
|
||||
None => (None, None),
|
||||
};
|
||||
let has_network_requirements = network_requirements.is_some();
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
configured_network_proxy_config,
|
||||
network_requirements,
|
||||
)
|
||||
.map_err(|err| {
|
||||
if let Some(source) = network_requirements_source.as_ref() {
|
||||
std::io::Error::new(
|
||||
err.kind(),
|
||||
format!("failed to build managed network proxy from {source}: {err}"),
|
||||
)
|
||||
} else {
|
||||
err
|
||||
}
|
||||
None => None,
|
||||
})?;
|
||||
let network = if has_network_requirements {
|
||||
Some(network)
|
||||
} else {
|
||||
network.enabled().then_some(network)
|
||||
};
|
||||
|
||||
let config = Self {
|
||||
@@ -2353,6 +2374,95 @@ phase_2_model = "gpt-5"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_toml_deserializes_permissions_network() {
|
||||
let toml = r#"
|
||||
[permissions.network]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43128"
|
||||
enable_socks5 = false
|
||||
allow_upstream_proxy = false
|
||||
allowed_domains = ["openai.com"]
|
||||
"#;
|
||||
let cfg: ConfigToml = toml::from_str(toml)
|
||||
.expect("TOML deserialization should succeed for permissions.network");
|
||||
|
||||
assert_eq!(
|
||||
cfg.permissions
|
||||
.and_then(|permissions| permissions.network)
|
||||
.expect("permissions.network should deserialize"),
|
||||
NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
admin_url: None,
|
||||
enable_socks5: Some(false),
|
||||
socks_url: None,
|
||||
enable_socks5_udp: None,
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: None,
|
||||
dangerously_allow_non_loopback_admin: None,
|
||||
mode: None,
|
||||
allowed_domains: Some(vec!["openai.com".to_string()]),
|
||||
denied_domains: None,
|
||||
allow_unix_sockets: None,
|
||||
allow_local_binding: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_network_enabled_populates_runtime_network_proxy_spec() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cfg = ConfigToml {
|
||||
permissions: Some(PermissionsToml {
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
enable_socks5: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
cfg,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
let network = config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.expect("enabled permissions.network should produce a NetworkProxySpec");
|
||||
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
|
||||
assert!(!network.socks_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_network_disabled_by_default_does_not_start_proxy() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cfg = ConfigToml {
|
||||
permissions: Some(PermissionsToml {
|
||||
network: Some(NetworkToml {
|
||||
allowed_domains: Some(vec!["openai.com".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
cfg,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
assert!(config.permissions.network.is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tui_config_missing_notifications_field_defaults_to_enabled() {
|
||||
let cfg = r#"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::config;
|
||||
use crate::config_loader::NetworkConstraints;
|
||||
use async_trait::async_trait;
|
||||
use codex_network_proxy::BlockedRequestObserver;
|
||||
@@ -68,6 +67,10 @@ impl ConfigReloader for StaticNetworkProxyReloader {
|
||||
}
|
||||
|
||||
impl NetworkProxySpec {
|
||||
pub(crate) fn enabled(&self) -> bool {
|
||||
self.config.network.enabled
|
||||
}
|
||||
|
||||
pub fn proxy_host_and_port(&self) -> String {
|
||||
host_and_port_from_network_addr(&self.config.network.proxy_url, 3128)
|
||||
}
|
||||
@@ -76,14 +79,15 @@ impl NetworkProxySpec {
|
||||
self.config.network.enable_socks5
|
||||
}
|
||||
|
||||
pub(crate) fn from_constraints(
|
||||
_config_layer_stack: &config::ConfigLayerStack,
|
||||
requirements: NetworkConstraints,
|
||||
pub(crate) fn from_config_and_constraints(
|
||||
config: NetworkProxyConfig,
|
||||
requirements: Option<NetworkConstraints>,
|
||||
) -> std::io::Result<Self> {
|
||||
// TODO(mbolin): Use ConfigLayerStack once we are ready to start
|
||||
// honoring network configuration in config.toml.
|
||||
let config = NetworkProxyConfig::default();
|
||||
let (config, constraints) = Self::apply_requirements(config, &requirements);
|
||||
let (config, constraints) = if let Some(requirements) = requirements {
|
||||
Self::apply_requirements(config, &requirements)
|
||||
} else {
|
||||
(config, NetworkProxyConstraints::default())
|
||||
};
|
||||
validate_policy_against_constraints(&config, &constraints).map_err(|err| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
|
||||
110
codex-rs/core/src/config/permissions.rs
Normal file
110
codex-rs/core/src/config/permissions.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use codex_network_proxy::NetworkMode;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct PermissionsToml {
|
||||
/// Network proxy settings from `[permissions.network]`.
|
||||
/// User config can enable the proxy; managed requirements may still constrain values.
|
||||
pub network: Option<NetworkToml>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct NetworkToml {
|
||||
pub enabled: Option<bool>,
|
||||
pub proxy_url: Option<String>,
|
||||
pub admin_url: Option<String>,
|
||||
pub enable_socks5: Option<bool>,
|
||||
pub socks_url: Option<String>,
|
||||
pub enable_socks5_udp: Option<bool>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
#[schemars(with = "Option<NetworkModeSchema>")]
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum NetworkModeSchema {
|
||||
Limited,
|
||||
Full,
|
||||
}
|
||||
|
||||
impl NetworkToml {
|
||||
pub(crate) fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig) {
|
||||
if let Some(enabled) = self.enabled {
|
||||
config.network.enabled = enabled;
|
||||
}
|
||||
if let Some(proxy_url) = self.proxy_url.as_ref() {
|
||||
config.network.proxy_url = proxy_url.clone();
|
||||
}
|
||||
if let Some(admin_url) = self.admin_url.as_ref() {
|
||||
config.network.admin_url = admin_url.clone();
|
||||
}
|
||||
if let Some(enable_socks5) = self.enable_socks5 {
|
||||
config.network.enable_socks5 = enable_socks5;
|
||||
}
|
||||
if let Some(socks_url) = self.socks_url.as_ref() {
|
||||
config.network.socks_url = socks_url.clone();
|
||||
}
|
||||
if let Some(enable_socks5_udp) = self.enable_socks5_udp {
|
||||
config.network.enable_socks5_udp = enable_socks5_udp;
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = self.allow_upstream_proxy {
|
||||
config.network.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;
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_admin) =
|
||||
self.dangerously_allow_non_loopback_admin
|
||||
{
|
||||
config.network.dangerously_allow_non_loopback_admin =
|
||||
dangerously_allow_non_loopback_admin;
|
||||
}
|
||||
if let Some(mode) = self.mode {
|
||||
config.network.mode = mode;
|
||||
}
|
||||
if let Some(allowed_domains) = self.allowed_domains.as_ref() {
|
||||
config.network.allowed_domains = allowed_domains.clone();
|
||||
}
|
||||
if let Some(denied_domains) = self.denied_domains.as_ref() {
|
||||
config.network.denied_domains = denied_domains.clone();
|
||||
}
|
||||
if let Some(allow_unix_sockets) = self.allow_unix_sockets.as_ref() {
|
||||
config.network.allow_unix_sockets = allow_unix_sockets.clone();
|
||||
}
|
||||
if let Some(allow_local_binding) = self.allow_local_binding {
|
||||
config.network.allow_local_binding = allow_local_binding;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_network_proxy_config(&self) -> NetworkProxyConfig {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
self.apply_to_network_proxy_config(&mut config);
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn network_proxy_config_from_permissions(
|
||||
permissions: Option<&PermissionsToml>,
|
||||
) -> NetworkProxyConfig {
|
||||
permissions
|
||||
.and_then(|permissions| permissions.network.as_ref())
|
||||
.map_or_else(
|
||||
NetworkProxyConfig::default,
|
||||
NetworkToml::to_network_proxy_config,
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::config::CONFIG_TOML_FILE;
|
||||
use crate::config::NetworkToml;
|
||||
use crate::config::PermissionsToml;
|
||||
use crate::config::find_codex_home;
|
||||
use crate::config_loader::CloudRequirementsLoader;
|
||||
use crate::config_loader::ConfigLayerStack;
|
||||
@@ -15,9 +17,9 @@ use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::NetworkProxyConstraintError;
|
||||
use codex_network_proxy::NetworkProxyConstraints;
|
||||
use codex_network_proxy::NetworkProxyState;
|
||||
use codex_network_proxy::PartialNetworkProxyConfig;
|
||||
use codex_network_proxy::build_config_state;
|
||||
use codex_network_proxy::validate_policy_against_constraints;
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -47,10 +49,7 @@ async fn build_config_state_with_mtimes() -> Result<(ConfigState, Vec<LayerMtime
|
||||
.await
|
||||
.context("failed to load Codex config")?;
|
||||
|
||||
let merged_toml = config_layer_stack.effective_config();
|
||||
let config: NetworkProxyConfig = merged_toml
|
||||
.try_into()
|
||||
.context("failed to deserialize network proxy config")?;
|
||||
let config = config_from_layers(&config_layer_stack)?;
|
||||
|
||||
let constraints = enforce_trusted_constraints(&config_layer_stack, &config)?;
|
||||
let layer_mtimes = collect_layer_mtimes(&config_layer_stack);
|
||||
@@ -100,50 +99,88 @@ fn network_constraints_from_trusted_layers(
|
||||
continue;
|
||||
}
|
||||
|
||||
let partial: PartialNetworkProxyConfig = layer
|
||||
.config
|
||||
.clone()
|
||||
.try_into()
|
||||
.context("failed to deserialize trusted config layer")?;
|
||||
|
||||
if let Some(enabled) = partial.network.enabled {
|
||||
constraints.enabled = Some(enabled);
|
||||
let parsed = network_tables_from_toml(&layer.config)?;
|
||||
if let Some(network) = parsed.network {
|
||||
apply_network_constraints(network, &mut constraints);
|
||||
}
|
||||
if let Some(mode) = partial.network.mode {
|
||||
constraints.mode = Some(mode);
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = partial.network.allow_upstream_proxy {
|
||||
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_proxy) =
|
||||
partial.network.dangerously_allow_non_loopback_proxy
|
||||
if let Some(network) = parsed
|
||||
.permissions
|
||||
.and_then(|permissions| permissions.network)
|
||||
{
|
||||
constraints.dangerously_allow_non_loopback_proxy =
|
||||
Some(dangerously_allow_non_loopback_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_admin) =
|
||||
partial.network.dangerously_allow_non_loopback_admin
|
||||
{
|
||||
constraints.dangerously_allow_non_loopback_admin =
|
||||
Some(dangerously_allow_non_loopback_admin);
|
||||
}
|
||||
|
||||
if let Some(allowed_domains) = partial.network.allowed_domains {
|
||||
constraints.allowed_domains = Some(allowed_domains);
|
||||
}
|
||||
if let Some(denied_domains) = partial.network.denied_domains {
|
||||
constraints.denied_domains = Some(denied_domains);
|
||||
}
|
||||
if let Some(allow_unix_sockets) = partial.network.allow_unix_sockets {
|
||||
constraints.allow_unix_sockets = Some(allow_unix_sockets);
|
||||
}
|
||||
if let Some(allow_local_binding) = partial.network.allow_local_binding {
|
||||
constraints.allow_local_binding = Some(allow_local_binding);
|
||||
apply_network_constraints(network, &mut constraints);
|
||||
}
|
||||
}
|
||||
Ok(constraints)
|
||||
}
|
||||
|
||||
fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProxyConstraints) {
|
||||
if let Some(enabled) = network.enabled {
|
||||
constraints.enabled = Some(enabled);
|
||||
}
|
||||
if let Some(mode) = network.mode {
|
||||
constraints.mode = Some(mode);
|
||||
}
|
||||
if let Some(allow_upstream_proxy) = network.allow_upstream_proxy {
|
||||
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_proxy) = network.dangerously_allow_non_loopback_proxy
|
||||
{
|
||||
constraints.dangerously_allow_non_loopback_proxy =
|
||||
Some(dangerously_allow_non_loopback_proxy);
|
||||
}
|
||||
if let Some(dangerously_allow_non_loopback_admin) = network.dangerously_allow_non_loopback_admin
|
||||
{
|
||||
constraints.dangerously_allow_non_loopback_admin =
|
||||
Some(dangerously_allow_non_loopback_admin);
|
||||
}
|
||||
if let Some(allowed_domains) = network.allowed_domains {
|
||||
constraints.allowed_domains = Some(allowed_domains);
|
||||
}
|
||||
if let Some(denied_domains) = network.denied_domains {
|
||||
constraints.denied_domains = Some(denied_domains);
|
||||
}
|
||||
if let Some(allow_unix_sockets) = network.allow_unix_sockets {
|
||||
constraints.allow_unix_sockets = Some(allow_unix_sockets);
|
||||
}
|
||||
if let Some(allow_local_binding) = network.allow_local_binding {
|
||||
constraints.allow_local_binding = Some(allow_local_binding);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct NetworkTablesToml {
|
||||
network: Option<NetworkToml>,
|
||||
permissions: Option<PermissionsToml>,
|
||||
}
|
||||
|
||||
fn network_tables_from_toml(value: &toml::Value) -> Result<NetworkTablesToml> {
|
||||
value
|
||||
.clone()
|
||||
.try_into()
|
||||
.context("failed to deserialize network tables from config")
|
||||
}
|
||||
|
||||
fn apply_network_tables(config: &mut NetworkProxyConfig, parsed: NetworkTablesToml) {
|
||||
if let Some(network) = parsed.network {
|
||||
network.apply_to_network_proxy_config(config);
|
||||
}
|
||||
if let Some(network) = parsed
|
||||
.permissions
|
||||
.and_then(|permissions| permissions.network)
|
||||
{
|
||||
network.apply_to_network_proxy_config(config);
|
||||
}
|
||||
}
|
||||
|
||||
fn config_from_layers(layers: &ConfigLayerStack) -> Result<NetworkProxyConfig> {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
for layer in layers.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, false) {
|
||||
let parsed = network_tables_from_toml(&layer.config)?;
|
||||
apply_network_tables(&mut config, parsed);
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
|
||||
matches!(
|
||||
layer,
|
||||
@@ -215,3 +252,40 @@ impl ConfigReloader for MtimeConfigReloader {
|
||||
Ok(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn higher_precedence_network_table_beats_lower_permissions_network_table() {
|
||||
let lower_permissions: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[permissions.network]
|
||||
allowed_domains = ["lower.example.com"]
|
||||
"#,
|
||||
)
|
||||
.expect("lower layer should parse");
|
||||
let higher_network: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[network]
|
||||
allowed_domains = ["higher.example.com"]
|
||||
"#,
|
||||
)
|
||||
.expect("higher layer should parse");
|
||||
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
apply_network_tables(
|
||||
&mut config,
|
||||
network_tables_from_toml(&lower_permissions).expect("lower layer should deserialize"),
|
||||
);
|
||||
apply_network_tables(
|
||||
&mut config,
|
||||
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
|
||||
);
|
||||
|
||||
assert_eq!(config.network.allowed_domains, vec!["higher.example.com"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user