mirror of
https://github.com/openai/codex.git
synced 2026-04-29 02:41:12 +03:00
refactor: remove proxy admin endpoint (#13687)
## Summary - delete the network proxy admin server and its runtime listener/task plumbing - remove the admin endpoint config, runtime, requirement, protocol, schema, and debug-surface fields - update proxy docs to reflect the remaining HTTP and SOCKS listeners only
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
use crate::config::NetworkMode;
|
||||
use crate::responses::json_response;
|
||||
use crate::responses::text_response;
|
||||
use crate::state::NetworkProxyState;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use rama_core::rt::Executor;
|
||||
use rama_core::service::service_fn;
|
||||
use rama_http::Body;
|
||||
use rama_http::Request;
|
||||
use rama_http::Response;
|
||||
use rama_http::StatusCode;
|
||||
use rama_http_backend::server::HttpServer;
|
||||
use rama_tcp::server::TcpListener;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
use std::sync::Arc;
|
||||
use tracing::error;
|
||||
use tracing::info;
|
||||
|
||||
pub async fn run_admin_api(state: Arc<NetworkProxyState>, addr: SocketAddr) -> Result<()> {
|
||||
// Debug-only admin API (health/config/patterns/blocked + mode/reload). Policy is config-driven
|
||||
// and constraint-enforced; this endpoint should not become a second policy/approval plane.
|
||||
let listener = TcpListener::build()
|
||||
.bind(addr)
|
||||
.await
|
||||
// See `http_proxy.rs` for details on why we wrap `BoxError` before converting to anyhow.
|
||||
.map_err(rama_core::error::OpaqueError::from)
|
||||
.map_err(anyhow::Error::from)
|
||||
.with_context(|| format!("bind admin API: {addr}"))?;
|
||||
|
||||
run_admin_api_with_listener(state, listener).await
|
||||
}
|
||||
|
||||
pub async fn run_admin_api_with_std_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: StdTcpListener,
|
||||
) -> Result<()> {
|
||||
let listener =
|
||||
TcpListener::try_from(listener).context("convert std listener to admin API listener")?;
|
||||
run_admin_api_with_listener(state, listener).await
|
||||
}
|
||||
|
||||
async fn run_admin_api_with_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: TcpListener,
|
||||
) -> Result<()> {
|
||||
let addr = listener
|
||||
.local_addr()
|
||||
.context("read admin API listener local addr")?;
|
||||
|
||||
let server_state = state.clone();
|
||||
let server = HttpServer::auto(Executor::new()).service(service_fn(move |req| {
|
||||
let state = server_state.clone();
|
||||
async move { handle_admin_request(state, req).await }
|
||||
}));
|
||||
info!("admin API listening on {addr}");
|
||||
listener.serve(server).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_admin_request(
|
||||
state: Arc<NetworkProxyState>,
|
||||
req: Request,
|
||||
) -> Result<Response, Infallible> {
|
||||
const MODE_BODY_LIMIT: usize = 8 * 1024;
|
||||
|
||||
let method = req.method().clone();
|
||||
let path = req.uri().path().to_string();
|
||||
let response = match (method.as_str(), path.as_str()) {
|
||||
("GET", "/health") => Response::new(Body::from("ok")),
|
||||
("GET", "/config") => match state.current_cfg().await {
|
||||
Ok(cfg) => json_response(&cfg),
|
||||
Err(err) => {
|
||||
error!("failed to load config: {err}");
|
||||
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
|
||||
}
|
||||
},
|
||||
("GET", "/patterns") => match state.current_patterns().await {
|
||||
Ok((allow, deny)) => json_response(&PatternsResponse {
|
||||
allowed: allow,
|
||||
denied: deny,
|
||||
}),
|
||||
Err(err) => {
|
||||
error!("failed to load patterns: {err}");
|
||||
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
|
||||
}
|
||||
},
|
||||
("GET", "/blocked") => match state.blocked_snapshot().await {
|
||||
Ok(blocked) => json_response(&BlockedResponse { blocked }),
|
||||
Err(err) => {
|
||||
error!("failed to read blocked queue: {err}");
|
||||
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
|
||||
}
|
||||
},
|
||||
("POST", "/mode") => {
|
||||
let mut body = req.into_body();
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
let chunk = match body.chunk().await {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => {
|
||||
error!("failed to read mode body: {err}");
|
||||
return Ok(text_response(StatusCode::BAD_REQUEST, "invalid body"));
|
||||
}
|
||||
};
|
||||
let Some(chunk) = chunk else {
|
||||
break;
|
||||
};
|
||||
|
||||
if buf.len().saturating_add(chunk.len()) > MODE_BODY_LIMIT {
|
||||
return Ok(text_response(
|
||||
StatusCode::PAYLOAD_TOO_LARGE,
|
||||
"body too large",
|
||||
));
|
||||
}
|
||||
buf.extend_from_slice(&chunk);
|
||||
}
|
||||
|
||||
if buf.is_empty() {
|
||||
return Ok(text_response(StatusCode::BAD_REQUEST, "missing body"));
|
||||
}
|
||||
let update: ModeUpdate = match serde_json::from_slice(&buf) {
|
||||
Ok(update) => update,
|
||||
Err(err) => {
|
||||
error!("failed to parse mode update: {err}");
|
||||
return Ok(text_response(StatusCode::BAD_REQUEST, "invalid json"));
|
||||
}
|
||||
};
|
||||
match state.set_network_mode(update.mode).await {
|
||||
Ok(()) => json_response(&ModeUpdateResponse {
|
||||
status: "ok",
|
||||
mode: update.mode,
|
||||
}),
|
||||
Err(err) => {
|
||||
error!("mode update failed: {err}");
|
||||
text_response(StatusCode::INTERNAL_SERVER_ERROR, "mode update failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
("POST", "/reload") => match state.force_reload().await {
|
||||
Ok(()) => json_response(&ReloadResponse { status: "reloaded" }),
|
||||
Err(err) => {
|
||||
error!("reload failed: {err}");
|
||||
text_response(StatusCode::INTERNAL_SERVER_ERROR, "reload failed")
|
||||
}
|
||||
},
|
||||
_ => text_response(StatusCode::NOT_FOUND, "not found"),
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ModeUpdate {
|
||||
mode: NetworkMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PatternsResponse {
|
||||
allowed: Vec<String>,
|
||||
denied: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct BlockedResponse<T> {
|
||||
blocked: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ModeUpdateResponse {
|
||||
status: &'static str,
|
||||
mode: NetworkMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ReloadResponse {
|
||||
status: &'static str,
|
||||
}
|
||||
@@ -23,8 +23,6 @@ pub struct NetworkProxySettings {
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_proxy_url")]
|
||||
pub proxy_url: String,
|
||||
#[serde(default = "default_admin_url")]
|
||||
pub admin_url: String,
|
||||
pub enable_socks5: bool,
|
||||
#[serde(default = "default_socks_url")]
|
||||
pub socks_url: String,
|
||||
@@ -33,8 +31,6 @@ pub struct NetworkProxySettings {
|
||||
#[serde(default)]
|
||||
pub dangerously_allow_non_loopback_proxy: bool,
|
||||
#[serde(default)]
|
||||
pub dangerously_allow_non_loopback_admin: bool,
|
||||
#[serde(default)]
|
||||
pub dangerously_allow_all_unix_sockets: bool,
|
||||
#[serde(default)]
|
||||
pub mode: NetworkMode,
|
||||
@@ -54,13 +50,11 @@ impl Default for NetworkProxySettings {
|
||||
Self {
|
||||
enabled: false,
|
||||
proxy_url: default_proxy_url(),
|
||||
admin_url: default_admin_url(),
|
||||
enable_socks5: true,
|
||||
socks_url: default_socks_url(),
|
||||
enable_socks5_udp: true,
|
||||
allow_upstream_proxy: true,
|
||||
dangerously_allow_non_loopback_proxy: false,
|
||||
dangerously_allow_non_loopback_admin: false,
|
||||
dangerously_allow_all_unix_sockets: false,
|
||||
mode: NetworkMode::default(),
|
||||
allowed_domains: Vec::new(),
|
||||
@@ -98,16 +92,17 @@ fn default_proxy_url() -> String {
|
||||
"http://127.0.0.1:3128".to_string()
|
||||
}
|
||||
|
||||
fn default_admin_url() -> String {
|
||||
"http://127.0.0.1:8080".to_string()
|
||||
}
|
||||
|
||||
fn default_socks_url() -> String {
|
||||
"http://127.0.0.1:8081".to_string()
|
||||
}
|
||||
|
||||
/// Clamp non-loopback bind addresses to loopback unless explicitly allowed.
|
||||
fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) -> SocketAddr {
|
||||
fn clamp_non_loopback(
|
||||
addr: SocketAddr,
|
||||
allow_non_loopback: bool,
|
||||
name: &str,
|
||||
override_setting_name: &str,
|
||||
) -> SocketAddr {
|
||||
if addr.ip().is_loopback() {
|
||||
return addr;
|
||||
}
|
||||
@@ -118,7 +113,7 @@ fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) ->
|
||||
}
|
||||
|
||||
warn!(
|
||||
"{name} requested non-loopback bind ({addr}); clamping to 127.0.0.1:{port} (set dangerously_allow_non_loopback_proxy or dangerously_allow_non_loopback_admin to override)",
|
||||
"{name} requested non-loopback bind ({addr}); clamping to 127.0.0.1:{port} (set {override_setting_name} to override)",
|
||||
port = addr.port()
|
||||
);
|
||||
SocketAddr::from(([127, 0, 0, 1], addr.port()))
|
||||
@@ -127,30 +122,26 @@ fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) ->
|
||||
pub(crate) fn clamp_bind_addrs(
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
admin_addr: SocketAddr,
|
||||
cfg: &NetworkProxySettings,
|
||||
) -> (SocketAddr, SocketAddr, SocketAddr) {
|
||||
) -> (SocketAddr, SocketAddr) {
|
||||
let http_addr = clamp_non_loopback(
|
||||
http_addr,
|
||||
cfg.dangerously_allow_non_loopback_proxy,
|
||||
"HTTP proxy",
|
||||
"dangerously_allow_non_loopback_proxy",
|
||||
);
|
||||
let socks_addr = clamp_non_loopback(
|
||||
socks_addr,
|
||||
cfg.dangerously_allow_non_loopback_proxy,
|
||||
"SOCKS5 proxy",
|
||||
);
|
||||
let admin_addr = clamp_non_loopback(
|
||||
admin_addr,
|
||||
cfg.dangerously_allow_non_loopback_admin,
|
||||
"admin API",
|
||||
"dangerously_allow_non_loopback_proxy",
|
||||
);
|
||||
if cfg.allow_unix_sockets.is_empty() && !cfg.dangerously_allow_all_unix_sockets {
|
||||
return (http_addr, socks_addr, admin_addr);
|
||||
return (http_addr, socks_addr);
|
||||
}
|
||||
|
||||
// `x-unix-socket` is intentionally a local escape hatch. If the proxy (or admin API) is
|
||||
// reachable from outside the machine, it can become a remote bridge into local daemons
|
||||
// `x-unix-socket` is intentionally a local escape hatch. If the proxy is reachable from
|
||||
// outside the machine, it can become a remote bridge into local daemons
|
||||
// (e.g. docker.sock). To avoid footguns, enforce loopback binding whenever unix sockets
|
||||
// are enabled.
|
||||
if cfg.dangerously_allow_non_loopback_proxy && !http_addr.ip().is_loopback() {
|
||||
@@ -163,22 +154,15 @@ pub(crate) fn clamp_bind_addrs(
|
||||
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_proxy and clamping SOCKS5 proxy to loopback"
|
||||
);
|
||||
}
|
||||
if cfg.dangerously_allow_non_loopback_admin && !admin_addr.ip().is_loopback() {
|
||||
warn!(
|
||||
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_admin and clamping admin API to loopback"
|
||||
);
|
||||
}
|
||||
(
|
||||
SocketAddr::from(([127, 0, 0, 1], http_addr.port())),
|
||||
SocketAddr::from(([127, 0, 0, 1], socks_addr.port())),
|
||||
SocketAddr::from(([127, 0, 0, 1], admin_addr.port())),
|
||||
)
|
||||
}
|
||||
|
||||
pub struct RuntimeConfig {
|
||||
pub http_addr: SocketAddr,
|
||||
pub socks_addr: SocketAddr,
|
||||
pub admin_addr: SocketAddr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -228,15 +212,11 @@ pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result<RuntimeConfig> {
|
||||
.with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?;
|
||||
let socks_addr = resolve_addr(&cfg.network.socks_url, 8081)
|
||||
.with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?;
|
||||
let admin_addr = resolve_addr(&cfg.network.admin_url, 8080)
|
||||
.with_context(|| format!("invalid network.admin_url: {}", cfg.network.admin_url))?;
|
||||
let (http_addr, socks_addr, admin_addr) =
|
||||
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg.network);
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg.network);
|
||||
|
||||
Ok(RuntimeConfig {
|
||||
http_addr,
|
||||
socks_addr,
|
||||
admin_addr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -384,13 +364,11 @@ mod tests {
|
||||
NetworkProxySettings {
|
||||
enabled: false,
|
||||
proxy_url: "http://127.0.0.1:3128".to_string(),
|
||||
admin_url: "http://127.0.0.1:8080".to_string(),
|
||||
enable_socks5: true,
|
||||
socks_url: "http://127.0.0.1:8081".to_string(),
|
||||
enable_socks5_udp: true,
|
||||
allow_upstream_proxy: true,
|
||||
dangerously_allow_non_loopback_proxy: false,
|
||||
dangerously_allow_non_loopback_admin: false,
|
||||
dangerously_allow_all_unix_sockets: false,
|
||||
mode: NetworkMode::Full,
|
||||
allowed_domains: Vec::new(),
|
||||
@@ -545,59 +523,47 @@ mod tests {
|
||||
fn clamp_bind_addrs_allows_non_loopback_when_enabled() {
|
||||
let cfg = NetworkProxySettings {
|
||||
dangerously_allow_non_loopback_proxy: true,
|
||||
dangerously_allow_non_loopback_admin: true,
|
||||
..Default::default()
|
||||
};
|
||||
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
|
||||
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
|
||||
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
|
||||
|
||||
let (http_addr, socks_addr, admin_addr) =
|
||||
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
|
||||
|
||||
assert_eq!(http_addr, "0.0.0.0:3128".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(socks_addr, "0.0.0.0:8081".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(admin_addr, "0.0.0.0:8080".parse::<SocketAddr>().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_bind_addrs_forces_loopback_when_unix_sockets_enabled() {
|
||||
let cfg = NetworkProxySettings {
|
||||
dangerously_allow_non_loopback_proxy: true,
|
||||
dangerously_allow_non_loopback_admin: true,
|
||||
allow_unix_sockets: vec!["/tmp/docker.sock".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
|
||||
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
|
||||
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
|
||||
|
||||
let (http_addr, socks_addr, admin_addr) =
|
||||
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
|
||||
|
||||
assert_eq!(http_addr, "127.0.0.1:3128".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(socks_addr, "127.0.0.1:8081".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(admin_addr, "127.0.0.1:8080".parse::<SocketAddr>().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_bind_addrs_forces_loopback_when_all_unix_sockets_enabled() {
|
||||
let cfg = NetworkProxySettings {
|
||||
dangerously_allow_non_loopback_proxy: true,
|
||||
dangerously_allow_non_loopback_admin: true,
|
||||
dangerously_allow_all_unix_sockets: true,
|
||||
..Default::default()
|
||||
};
|
||||
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
|
||||
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
|
||||
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
|
||||
|
||||
let (http_addr, socks_addr, admin_addr) =
|
||||
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
|
||||
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
|
||||
|
||||
assert_eq!(http_addr, "127.0.0.1:3128".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(socks_addr, "127.0.0.1:8081".parse::<SocketAddr>().unwrap());
|
||||
assert_eq!(admin_addr, "127.0.0.1:8080".parse::<SocketAddr>().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
mod admin;
|
||||
mod certs;
|
||||
mod config;
|
||||
mod http_proxy;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::admin;
|
||||
use crate::config;
|
||||
use crate::http_proxy;
|
||||
use crate::network_policy::NetworkPolicyDecider;
|
||||
@@ -26,15 +25,13 @@ pub struct Args {}
|
||||
struct ReservedListeners {
|
||||
http: Mutex<Option<StdTcpListener>>,
|
||||
socks: Mutex<Option<StdTcpListener>>,
|
||||
admin: Mutex<Option<StdTcpListener>>,
|
||||
}
|
||||
|
||||
impl ReservedListeners {
|
||||
fn new(http: StdTcpListener, socks: Option<StdTcpListener>, admin: StdTcpListener) -> Self {
|
||||
fn new(http: StdTcpListener, socks: Option<StdTcpListener>) -> Self {
|
||||
Self {
|
||||
http: Mutex::new(Some(http)),
|
||||
socks: Mutex::new(socks),
|
||||
admin: Mutex::new(Some(admin)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +50,6 @@ impl ReservedListeners {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
guard.take()
|
||||
}
|
||||
|
||||
fn take_admin(&self) -> Option<StdTcpListener> {
|
||||
let mut guard = self
|
||||
.admin
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
guard.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -68,7 +57,6 @@ pub struct NetworkProxyBuilder {
|
||||
state: Option<Arc<NetworkProxyState>>,
|
||||
http_addr: Option<SocketAddr>,
|
||||
socks_addr: Option<SocketAddr>,
|
||||
admin_addr: Option<SocketAddr>,
|
||||
managed_by_codex: bool,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
@@ -80,7 +68,6 @@ impl Default for NetworkProxyBuilder {
|
||||
state: None,
|
||||
http_addr: None,
|
||||
socks_addr: None,
|
||||
admin_addr: None,
|
||||
managed_by_codex: true,
|
||||
policy_decider: None,
|
||||
blocked_request_observer: None,
|
||||
@@ -104,11 +91,6 @@ impl NetworkProxyBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn admin_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.admin_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn managed_by_codex(mut self, managed_by_codex: bool) -> Self {
|
||||
self.managed_by_codex = managed_by_codex;
|
||||
self
|
||||
@@ -153,10 +135,10 @@ 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, requested_admin_addr, reserved_listeners) =
|
||||
let (requested_http_addr, requested_socks_addr, reserved_listeners) =
|
||||
if self.managed_by_codex {
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
let (http_listener, socks_listener, admin_listener) =
|
||||
let (http_listener, socks_listener) =
|
||||
reserve_loopback_ephemeral_listeners(current_cfg.network.enable_socks5)
|
||||
.context("reserve managed loopback proxy listeners")?;
|
||||
let http_addr = http_listener
|
||||
@@ -169,17 +151,12 @@ impl NetworkProxyBuilder {
|
||||
} else {
|
||||
runtime.socks_addr
|
||||
};
|
||||
let admin_addr = admin_listener
|
||||
.local_addr()
|
||||
.context("failed to read reserved admin API address")?;
|
||||
(
|
||||
http_addr,
|
||||
socks_addr,
|
||||
admin_addr,
|
||||
Some(Arc::new(ReservedListeners::new(
|
||||
http_listener,
|
||||
socks_listener,
|
||||
admin_listener,
|
||||
))),
|
||||
)
|
||||
} else {
|
||||
@@ -187,16 +164,14 @@ impl NetworkProxyBuilder {
|
||||
(
|
||||
self.http_addr.unwrap_or(runtime.http_addr),
|
||||
self.socks_addr.unwrap_or(runtime.socks_addr),
|
||||
self.admin_addr.unwrap_or(runtime.admin_addr),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
|
||||
let (http_addr, socks_addr, admin_addr) = config::clamp_bind_addrs(
|
||||
let (http_addr, socks_addr) = config::clamp_bind_addrs(
|
||||
requested_http_addr,
|
||||
requested_socks_addr,
|
||||
requested_admin_addr,
|
||||
¤t_cfg.network,
|
||||
);
|
||||
|
||||
@@ -210,7 +185,6 @@ impl NetworkProxyBuilder {
|
||||
dangerously_allow_all_unix_sockets: current_cfg
|
||||
.network
|
||||
.dangerously_allow_all_unix_sockets,
|
||||
admin_addr,
|
||||
reserved_listeners,
|
||||
policy_decider: self.policy_decider,
|
||||
})
|
||||
@@ -219,7 +193,7 @@ impl NetworkProxyBuilder {
|
||||
|
||||
fn reserve_loopback_ephemeral_listeners(
|
||||
reserve_socks_listener: bool,
|
||||
) -> Result<(StdTcpListener, Option<StdTcpListener>, StdTcpListener)> {
|
||||
) -> Result<(StdTcpListener, Option<StdTcpListener>)> {
|
||||
let http_listener =
|
||||
reserve_loopback_ephemeral_listener().context("reserve HTTP proxy listener")?;
|
||||
let socks_listener = if reserve_socks_listener {
|
||||
@@ -227,9 +201,7 @@ fn reserve_loopback_ephemeral_listeners(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let admin_listener =
|
||||
reserve_loopback_ephemeral_listener().context("reserve admin API listener")?;
|
||||
Ok((http_listener, socks_listener, admin_listener))
|
||||
Ok((http_listener, socks_listener))
|
||||
}
|
||||
|
||||
fn reserve_loopback_ephemeral_listener() -> Result<StdTcpListener> {
|
||||
@@ -246,7 +218,6 @@ pub struct NetworkProxy {
|
||||
allow_local_binding: bool,
|
||||
allow_unix_sockets: Vec<String>,
|
||||
dangerously_allow_all_unix_sockets: bool,
|
||||
admin_addr: SocketAddr,
|
||||
reserved_listeners: Option<Arc<ReservedListeners>>,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
}
|
||||
@@ -258,7 +229,6 @@ impl std::fmt::Debug for NetworkProxy {
|
||||
f.debug_struct("NetworkProxy")
|
||||
.field("http_addr", &self.http_addr)
|
||||
.field("socks_addr", &self.socks_addr)
|
||||
.field("admin_addr", &self.admin_addr)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -268,7 +238,6 @@ impl PartialEq for NetworkProxy {
|
||||
self.http_addr == other.http_addr
|
||||
&& self.socks_addr == other.socks_addr
|
||||
&& self.allow_local_binding == other.allow_local_binding
|
||||
&& self.admin_addr == other.admin_addr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,10 +390,6 @@ impl NetworkProxy {
|
||||
self.socks_addr
|
||||
}
|
||||
|
||||
pub fn admin_addr(&self) -> SocketAddr {
|
||||
self.admin_addr
|
||||
}
|
||||
|
||||
pub async fn add_allowed_domain(&self, host: &str) -> Result<()> {
|
||||
self.state.add_allowed_domain(host).await
|
||||
}
|
||||
@@ -475,7 +440,6 @@ impl NetworkProxy {
|
||||
let reserved_listeners = self.reserved_listeners.as_ref();
|
||||
let http_listener = reserved_listeners.and_then(|listeners| listeners.take_http());
|
||||
let socks_listener = reserved_listeners.and_then(|listeners| listeners.take_socks());
|
||||
let admin_listener = reserved_listeners.and_then(|listeners| listeners.take_admin());
|
||||
|
||||
let http_state = self.state.clone();
|
||||
let http_decider = self.policy_decider.clone();
|
||||
@@ -520,21 +484,10 @@ impl NetworkProxy {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let admin_state = self.state.clone();
|
||||
let admin_addr = self.admin_addr;
|
||||
let admin_task = tokio::spawn(async move {
|
||||
match admin_listener {
|
||||
Some(listener) => {
|
||||
admin::run_admin_api_with_std_listener(admin_state, listener).await
|
||||
}
|
||||
None => admin::run_admin_api(admin_state, admin_addr).await,
|
||||
}
|
||||
});
|
||||
|
||||
Ok(NetworkProxyHandle {
|
||||
http_task: Some(http_task),
|
||||
socks_task,
|
||||
admin_task: Some(admin_task),
|
||||
completed: false,
|
||||
})
|
||||
}
|
||||
@@ -543,7 +496,6 @@ impl NetworkProxy {
|
||||
pub struct NetworkProxyHandle {
|
||||
http_task: Option<JoinHandle<Result<()>>>,
|
||||
socks_task: Option<JoinHandle<Result<()>>>,
|
||||
admin_task: Option<JoinHandle<Result<()>>>,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
@@ -552,24 +504,20 @@ impl NetworkProxyHandle {
|
||||
Self {
|
||||
http_task: Some(tokio::spawn(async { Ok(()) })),
|
||||
socks_task: None,
|
||||
admin_task: Some(tokio::spawn(async { Ok(()) })),
|
||||
completed: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait(mut self) -> Result<()> {
|
||||
let http_task = self.http_task.take().context("missing http proxy task")?;
|
||||
let admin_task = self.admin_task.take().context("missing admin proxy task")?;
|
||||
let socks_task = self.socks_task.take();
|
||||
let http_result = http_task.await;
|
||||
let admin_result = admin_task.await;
|
||||
let socks_result = match socks_task {
|
||||
Some(task) => Some(task.await),
|
||||
None => None,
|
||||
};
|
||||
self.completed = true;
|
||||
http_result??;
|
||||
admin_result??;
|
||||
if let Some(socks_result) = socks_result {
|
||||
socks_result??;
|
||||
}
|
||||
@@ -577,12 +525,7 @@ impl NetworkProxyHandle {
|
||||
}
|
||||
|
||||
pub async fn shutdown(mut self) -> Result<()> {
|
||||
abort_tasks(
|
||||
self.http_task.take(),
|
||||
self.socks_task.take(),
|
||||
self.admin_task.take(),
|
||||
)
|
||||
.await;
|
||||
abort_tasks(self.http_task.take(), self.socks_task.take()).await;
|
||||
self.completed = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -598,11 +541,9 @@ async fn abort_task(task: Option<JoinHandle<Result<()>>>) {
|
||||
async fn abort_tasks(
|
||||
http_task: Option<JoinHandle<Result<()>>>,
|
||||
socks_task: Option<JoinHandle<Result<()>>>,
|
||||
admin_task: Option<JoinHandle<Result<()>>>,
|
||||
) {
|
||||
abort_task(http_task).await;
|
||||
abort_task(socks_task).await;
|
||||
abort_task(admin_task).await;
|
||||
}
|
||||
|
||||
impl Drop for NetworkProxyHandle {
|
||||
@@ -612,9 +553,8 @@ impl Drop for NetworkProxyHandle {
|
||||
}
|
||||
let http_task = self.http_task.take();
|
||||
let socks_task = self.socks_task.take();
|
||||
let admin_task = self.admin_task.take();
|
||||
tokio::spawn(async move {
|
||||
abort_tasks(http_task, socks_task, admin_task).await;
|
||||
abort_tasks(http_task, socks_task).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -648,10 +588,8 @@ mod tests {
|
||||
|
||||
assert!(proxy.http_addr.ip().is_loopback());
|
||||
assert!(proxy.socks_addr.ip().is_loopback());
|
||||
assert!(proxy.admin_addr.ip().is_loopback());
|
||||
assert_ne!(proxy.http_addr.port(), 0);
|
||||
assert_ne!(proxy.socks_addr.port(), 0);
|
||||
assert_ne!(proxy.admin_addr.port(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -659,7 +597,6 @@ mod tests {
|
||||
let settings = NetworkProxySettings {
|
||||
proxy_url: "http://127.0.0.1:43128".to_string(),
|
||||
socks_url: "http://127.0.0.1:48081".to_string(),
|
||||
admin_url: "http://127.0.0.1:48080".to_string(),
|
||||
..NetworkProxySettings::default()
|
||||
};
|
||||
let state = Arc::new(network_proxy_state_for_policy(settings));
|
||||
@@ -678,10 +615,6 @@ mod tests {
|
||||
proxy.socks_addr,
|
||||
"127.0.0.1:48081".parse::<SocketAddr>().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
proxy.admin_addr,
|
||||
"127.0.0.1:48080".parse::<SocketAddr>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -706,7 +639,6 @@ mod tests {
|
||||
};
|
||||
|
||||
assert!(proxy.http_addr.ip().is_loopback());
|
||||
assert!(proxy.admin_addr.ip().is_loopback());
|
||||
assert_eq!(
|
||||
proxy.socks_addr,
|
||||
"127.0.0.1:43129".parse::<SocketAddr>().unwrap()
|
||||
|
||||
@@ -1251,42 +1251,6 @@ mod tests {
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_disallows_non_loopback_admin_without_managed_opt_in() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
dangerously_allow_non_loopback_admin: Some(false),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_non_loopback_admin: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_allows_non_loopback_admin_with_managed_opt_in() {
|
||||
let constraints = NetworkProxyConstraints {
|
||||
dangerously_allow_non_loopback_admin: Some(true),
|
||||
..NetworkProxyConstraints::default()
|
||||
};
|
||||
|
||||
let config = NetworkProxyConfig {
|
||||
network: NetworkProxySettings {
|
||||
enabled: true,
|
||||
dangerously_allow_non_loopback_admin: true,
|
||||
..NetworkProxySettings::default()
|
||||
},
|
||||
};
|
||||
|
||||
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_policy_against_constraints_disallows_allow_all_unix_sockets_without_managed_opt_in()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,6 @@ pub struct NetworkProxyConstraints {
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
@@ -41,7 +40,6 @@ pub struct PartialNetworkConfig {
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
@@ -149,25 +147,6 @@ pub fn validate_policy_against_constraints(
|
||||
},
|
||||
)?;
|
||||
|
||||
let allow_non_loopback_admin = constraints.dangerously_allow_non_loopback_admin;
|
||||
validate(
|
||||
config.network.dangerously_allow_non_loopback_admin,
|
||||
move |candidate| match allow_non_loopback_admin {
|
||||
Some(true) | None => Ok(()),
|
||||
Some(false) => {
|
||||
if *candidate {
|
||||
Err(invalid_value(
|
||||
"network.dangerously_allow_non_loopback_admin",
|
||||
"true",
|
||||
"false (disabled by managed config)",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy;
|
||||
validate(
|
||||
config.network.dangerously_allow_non_loopback_proxy,
|
||||
|
||||
Reference in New Issue
Block a user