mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(network-proxy): add embedded OTEL policy audit logging (#12046)
**PR Summary** This PR adds embedded-only OTEL policy audit logging for `codex-network-proxy` and threads audit metadata from `codex-core` into managed proxy startup. ### What changed - Added structured audit event emission in `network_policy.rs` with target `codex_otel.network_proxy`. - Emitted: - `codex.network_proxy.domain_policy_decision` once per domain-policy evaluation. - `codex.network_proxy.block_decision` for non-domain denies. - Added required policy/network fields, RFC3339 UTC millisecond `event.timestamp`, and fallback defaults (`http.request.method="none"`, `client.address="unknown"`). - Added non-domain deny audit emission in HTTP/SOCKS handlers for mode-guard and proxy-state denies, including unix-socket deny paths. - Added `REASON_UNIX_SOCKET_UNSUPPORTED` and used it for unsupported unix-socket auditing. - Added `NetworkProxyAuditMetadata` to runtime/state, re-exported from `lib.rs` and `state.rs`. - Added `start_proxy_with_audit_metadata(...)` in core config, with `start_proxy()` delegating to default metadata. - Wired metadata construction in `codex.rs` from session/auth context, including originator sanitization for OTEL-safe tagging. - Updated `network-proxy/README.md` with embedded-mode audit schema and behavior notes. - Refactored HTTP block-audit emission to a small local helper to reduce duplication. - Preserved existing unix-socket proxy-disabled host/path behavior for responses and blocked history while using an audit-only endpoint override (`server.address="unix-socket"`, `server.port=0`). ### Explicit exclusions - No standalone proxy OTEL startup work. - No `main.rs` binary wiring. - No `standalone_otel.rs`. - No standalone docs/tests. ### Tests - Extended `network_policy.rs` tests for event mapping, metadata propagation, fallbacks, timestamp format, and target prefix. - Extended HTTP tests to assert unix-socket deny block audit events. - Extended SOCKS tests to cover deny emission from handler deny branches. - Added/updated core tests to verify audit metadata threading into managed proxy state. ### Validation run - `just fmt` - `cargo test -p codex-network-proxy` ✅ - `cargo test -p codex-core` ran with one unrelated flaky timeout (`shell_snapshot::tests::snapshot_shell_does_not_inherit_stdin`), and the test passed when rerun directly ✅ --------- Co-authored-by: viyatb-oai <viyatb@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
8362b79cb4
commit
9a393c9b6f
@@ -58,6 +58,7 @@ use codex_hooks::HookResult;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_hooks::HooksConfig;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::normalize_host;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment;
|
||||
@@ -877,6 +878,7 @@ impl Session {
|
||||
network_policy_decider: Option<Arc<dyn codex_network_proxy::NetworkPolicyDecider>>,
|
||||
blocked_request_observer: Option<Arc<dyn codex_network_proxy::BlockedRequestObserver>>,
|
||||
managed_network_requirements_enabled: bool,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
) -> anyhow::Result<(StartedNetworkProxy, SessionNetworkProxyRuntime)> {
|
||||
let network_proxy = spec
|
||||
.start_proxy(
|
||||
@@ -884,6 +886,7 @@ impl Session {
|
||||
network_policy_decider,
|
||||
blocked_request_observer,
|
||||
managed_network_requirements_enabled,
|
||||
audit_metadata,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| anyhow::anyhow!("failed to start managed network proxy: {err}"))?;
|
||||
@@ -1198,21 +1201,37 @@ impl Session {
|
||||
|
||||
let auth = auth.as_ref();
|
||||
let auth_mode = auth.map(CodexAuth::auth_mode).map(TelemetryAuthMode::from);
|
||||
let account_id = auth.and_then(CodexAuth::get_account_id);
|
||||
let account_email = auth.and_then(CodexAuth::get_account_email);
|
||||
let originator = crate::default_client::originator().value;
|
||||
let terminal_type = terminal::user_agent();
|
||||
let session_model = session_configuration.collaboration_mode.model().to_string();
|
||||
let mut otel_manager = OtelManager::new(
|
||||
conversation_id,
|
||||
session_configuration.collaboration_mode.model(),
|
||||
session_configuration.collaboration_mode.model(),
|
||||
auth.and_then(CodexAuth::get_account_id),
|
||||
auth.and_then(CodexAuth::get_account_email),
|
||||
session_model.as_str(),
|
||||
session_model.as_str(),
|
||||
account_id.clone(),
|
||||
account_email.clone(),
|
||||
auth_mode,
|
||||
crate::default_client::originator().value,
|
||||
originator.clone(),
|
||||
config.otel.log_user_prompt,
|
||||
terminal::user_agent(),
|
||||
terminal_type.clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
);
|
||||
if let Some(service_name) = session_configuration.metrics_service_name.as_deref() {
|
||||
otel_manager = otel_manager.with_metrics_service_name(service_name);
|
||||
}
|
||||
let network_proxy_audit_metadata = NetworkProxyAuditMetadata {
|
||||
conversation_id: Some(conversation_id.to_string()),
|
||||
app_version: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
user_account_id: account_id,
|
||||
auth_mode: auth_mode.map(|mode| mode.to_string()),
|
||||
originator: Some(originator),
|
||||
user_email: account_email,
|
||||
terminal_type: Some(terminal_type),
|
||||
model: Some(session_model.clone()),
|
||||
slug: Some(session_model),
|
||||
};
|
||||
config.features.emit_metrics(&otel_manager);
|
||||
otel_manager.counter(
|
||||
"codex.thread.started",
|
||||
@@ -1319,6 +1338,7 @@ impl Session {
|
||||
network_policy_decider.as_ref().map(Arc::clone),
|
||||
blocked_request_observer.as_ref().map(Arc::clone),
|
||||
managed_network_requirements_enabled,
|
||||
network_proxy_audit_metadata,
|
||||
)
|
||||
.await?;
|
||||
(Some(network_proxy), Some(session_network_proxy))
|
||||
|
||||
@@ -100,6 +100,7 @@ pub mod types;
|
||||
pub use codex_config::Constrained;
|
||||
pub use codex_config::ConstraintError;
|
||||
pub use codex_config::ConstraintResult;
|
||||
pub use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
|
||||
pub use network_proxy_spec::NetworkProxySpec;
|
||||
pub use network_proxy_spec::StartedNetworkProxy;
|
||||
|
||||
@@ -6,6 +6,7 @@ use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkDecision;
|
||||
use codex_network_proxy::NetworkPolicyDecider;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::NetworkProxyConstraints;
|
||||
use codex_network_proxy::NetworkProxyHandle;
|
||||
@@ -106,13 +107,9 @@ impl NetworkProxySpec {
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
enable_network_approval_flow: bool,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
) -> std::io::Result<StartedNetworkProxy> {
|
||||
let state =
|
||||
build_config_state(self.config.clone(), self.constraints.clone()).map_err(|err| {
|
||||
std::io::Error::other(format!("failed to build network proxy state: {err}"))
|
||||
})?;
|
||||
let reloader = Arc::new(StaticNetworkProxyReloader::new(state.clone()));
|
||||
let state = NetworkProxyState::with_reloader(state, reloader);
|
||||
let state = self.build_state_with_audit_metadata(audit_metadata)?;
|
||||
let mut builder = NetworkProxy::builder().state(Arc::new(state));
|
||||
if enable_network_approval_flow
|
||||
&& matches!(
|
||||
@@ -142,6 +139,22 @@ impl NetworkProxySpec {
|
||||
Ok(StartedNetworkProxy::new(proxy, handle))
|
||||
}
|
||||
|
||||
fn build_state_with_audit_metadata(
|
||||
&self,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
) -> std::io::Result<NetworkProxyState> {
|
||||
let state =
|
||||
build_config_state(self.config.clone(), self.constraints.clone()).map_err(|err| {
|
||||
std::io::Error::other(format!("failed to build network proxy state: {err}"))
|
||||
})?;
|
||||
let reloader = Arc::new(StaticNetworkProxyReloader::new(state.clone()));
|
||||
Ok(NetworkProxyState::with_reloader_and_audit_metadata(
|
||||
state,
|
||||
reloader,
|
||||
audit_metadata,
|
||||
))
|
||||
}
|
||||
|
||||
fn apply_requirements(
|
||||
mut config: NetworkProxyConfig,
|
||||
requirements: &NetworkConstraints,
|
||||
@@ -205,3 +218,28 @@ impl NetworkProxySpec {
|
||||
(config, constraints)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn build_state_with_audit_metadata_threads_metadata_to_state() {
|
||||
let spec = NetworkProxySpec {
|
||||
config: NetworkProxyConfig::default(),
|
||||
constraints: NetworkProxyConstraints::default(),
|
||||
};
|
||||
let metadata = NetworkProxyAuditMetadata {
|
||||
conversation_id: Some("conversation-1".to_string()),
|
||||
app_version: Some("1.2.3".to_string()),
|
||||
user_account_id: Some("acct-1".to_string()),
|
||||
..NetworkProxyAuditMetadata::default()
|
||||
};
|
||||
|
||||
let state = spec
|
||||
.build_state_with_audit_metadata(metadata.clone())
|
||||
.expect("state should build");
|
||||
assert_eq!(state.audit_metadata(), &metadata);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user