mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
Inject Codex auth into credential route proxy
This commit is contained in:
@@ -204,12 +204,17 @@ impl Client {
|
||||
}
|
||||
|
||||
fn headers(&self) -> HeaderMap {
|
||||
let mut h = HeaderMap::new();
|
||||
let mut h = self.auth_headers();
|
||||
if let Some(ua) = &self.user_agent {
|
||||
h.insert(USER_AGENT, ua.clone());
|
||||
} else {
|
||||
h.insert(USER_AGENT, HeaderValue::from_static("codex-cli"));
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
fn auth_headers(&self) -> HeaderMap {
|
||||
let mut h = HeaderMap::new();
|
||||
self.auth_provider.add_auth_headers(&mut h);
|
||||
if let Some(acc) = &self.chatgpt_account_id
|
||||
&& let Ok(name) = HeaderName::from_bytes(b"ChatGPT-Account-Id")
|
||||
@@ -423,6 +428,10 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn credential_routes_proxy_auth_headers(&self) -> HeaderMap {
|
||||
self.auth_headers()
|
||||
}
|
||||
|
||||
/// Create a new task (user turn) by POSTing to the appropriate backend path
|
||||
/// based on `path_style`. Returns the created task id.
|
||||
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {
|
||||
|
||||
@@ -97,6 +97,7 @@ fn credentialed_routes_add_runtime_allowlist_and_mitm_hooks() {
|
||||
auth_type: CredentialRouteAuthType::OAuth,
|
||||
base_url: "https://api.example.com/v1".to_string(),
|
||||
}],
|
||||
proxy_headers: Vec::new(),
|
||||
proxy_url: Some("https://chatgpt.com/backend-api/wham/credential_routes/proxy".to_string()),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ use codex_backend_client::Client as BackendClient;
|
||||
use codex_backend_client::ResolvedCredentialRoute;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_network_proxy::CredentialedRouteProxyActionConfig;
|
||||
use codex_network_proxy::CredentialedRouteProxyHeader;
|
||||
use codex_network_proxy::MitmHookActionsConfig;
|
||||
use codex_network_proxy::MitmHookConfig;
|
||||
use codex_network_proxy::MitmHookMatchConfig;
|
||||
use http::HeaderMap;
|
||||
use tracing::debug;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
@@ -12,6 +14,7 @@ use url::Url;
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct CredentialedRoutesSessionConfig {
|
||||
pub(crate) routes: Vec<ResolvedCredentialRoute>,
|
||||
pub(crate) proxy_headers: Vec<CredentialedRouteProxyHeader>,
|
||||
pub(crate) proxy_url: Option<String>,
|
||||
}
|
||||
|
||||
@@ -39,6 +42,9 @@ pub(crate) async fn load_for_session(
|
||||
);
|
||||
CredentialedRoutesSessionConfig {
|
||||
routes: response.routes,
|
||||
proxy_headers: credentialed_route_proxy_headers(
|
||||
client.credential_routes_proxy_auth_headers(),
|
||||
),
|
||||
proxy_url: Some(client.credential_routes_proxy_url()),
|
||||
}
|
||||
}
|
||||
@@ -57,25 +63,28 @@ impl CredentialedRoutesSessionConfig {
|
||||
|
||||
self.routes
|
||||
.iter()
|
||||
.filter_map(|route| match route_mitm_hook(route, proxy_url) {
|
||||
Ok(hook) => Some(hook),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
connector_id = %route.connector_id,
|
||||
link_id = %route.link_id,
|
||||
base_url = %route.base_url,
|
||||
error = %err,
|
||||
"skipping invalid credentialed route"
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(
|
||||
|route| match route_mitm_hook(route, &self.proxy_headers, proxy_url) {
|
||||
Ok(hook) => Some(hook),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
connector_id = %route.connector_id,
|
||||
link_id = %route.link_id,
|
||||
base_url = %route.base_url,
|
||||
error = %err,
|
||||
"skipping invalid credentialed route"
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn route_mitm_hook(
|
||||
route: &ResolvedCredentialRoute,
|
||||
proxy_headers: &[CredentialedRouteProxyHeader],
|
||||
proxy_url: &str,
|
||||
) -> anyhow::Result<MitmHookConfig> {
|
||||
let base_url = Url::parse(&route.base_url)?;
|
||||
@@ -117,6 +126,7 @@ fn route_mitm_hook(
|
||||
credentialed_route_proxy: Some(CredentialedRouteProxyActionConfig {
|
||||
connector_id: route.connector_id.clone(),
|
||||
link_id: route.link_id.clone(),
|
||||
proxy_headers: proxy_headers.to_vec(),
|
||||
proxy_url: proxy_url.to_string(),
|
||||
}),
|
||||
..MitmHookActionsConfig::default()
|
||||
@@ -124,6 +134,16 @@ fn route_mitm_hook(
|
||||
})
|
||||
}
|
||||
|
||||
fn credentialed_route_proxy_headers(headers: HeaderMap) -> Vec<CredentialedRouteProxyHeader> {
|
||||
headers
|
||||
.iter()
|
||||
.map(|(name, value)| CredentialedRouteProxyHeader {
|
||||
name: name.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -139,6 +159,10 @@ mod tests {
|
||||
auth_type: CredentialRouteAuthType::OAuth,
|
||||
base_url: "https://api.example.com/v1".to_string(),
|
||||
}],
|
||||
proxy_headers: vec![CredentialedRouteProxyHeader {
|
||||
name: http::header::AUTHORIZATION,
|
||||
value: http::HeaderValue::from_static("Bearer codex-token"),
|
||||
}],
|
||||
proxy_url: Some(
|
||||
"https://chatgpt.com/backend-api/wham/credential_routes/proxy".to_string(),
|
||||
),
|
||||
@@ -154,6 +178,10 @@ mod tests {
|
||||
Some(CredentialedRouteProxyActionConfig {
|
||||
connector_id: "connector_123".to_string(),
|
||||
link_id: "link_123".to_string(),
|
||||
proxy_headers: vec![CredentialedRouteProxyHeader {
|
||||
name: http::header::AUTHORIZATION,
|
||||
value: http::HeaderValue::from_static("Bearer codex-token"),
|
||||
}],
|
||||
proxy_url: "https://chatgpt.com/backend-api/wham/credential_routes/proxy"
|
||||
.to_string(),
|
||||
})
|
||||
|
||||
@@ -25,6 +25,7 @@ pub use config::NetworkUnixSocketPermission;
|
||||
pub use config::NetworkUnixSocketPermissions;
|
||||
pub use config::host_and_port_from_network_addr;
|
||||
pub use mitm_hook::CredentialedRouteProxyActionConfig;
|
||||
pub use mitm_hook::CredentialedRouteProxyHeader;
|
||||
pub use mitm_hook::InjectedHeaderConfig;
|
||||
pub use mitm_hook::MitmHookActionsConfig;
|
||||
pub use mitm_hook::MitmHookBodyConfig;
|
||||
|
||||
@@ -584,6 +584,9 @@ fn apply_credentialed_route_proxy_headers(
|
||||
action: &crate::mitm_hook::CredentialedRouteProxyAction,
|
||||
request_uri: &Uri,
|
||||
) -> Result<()> {
|
||||
for proxy_header in &action.proxy_headers {
|
||||
headers.insert(proxy_header.name.clone(), proxy_header.value.clone());
|
||||
}
|
||||
headers.insert(
|
||||
HeaderName::from_static(CREDENTIAL_ROUTE_CONNECTOR_ID_HEADER),
|
||||
HeaderValue::from_str(&action.connector_id)?,
|
||||
|
||||
@@ -116,6 +116,7 @@ pub enum SecretSource {
|
||||
pub struct CredentialedRouteProxyActionConfig {
|
||||
pub connector_id: String,
|
||||
pub link_id: String,
|
||||
pub proxy_headers: Vec<CredentialedRouteProxyHeader>,
|
||||
pub proxy_url: String,
|
||||
}
|
||||
|
||||
@@ -123,9 +124,25 @@ pub struct CredentialedRouteProxyActionConfig {
|
||||
pub struct CredentialedRouteProxyAction {
|
||||
pub connector_id: String,
|
||||
pub link_id: String,
|
||||
pub proxy_headers: Vec<CredentialedRouteProxyHeader>,
|
||||
pub proxy_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct CredentialedRouteProxyHeader {
|
||||
pub name: HeaderName,
|
||||
pub value: HeaderValue,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CredentialedRouteProxyHeader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CredentialedRouteProxyHeader")
|
||||
.field("name", &self.name)
|
||||
.field("value", &"<redacted>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MitmHookBodyMatcher {
|
||||
pub raw: serde_json::Value,
|
||||
@@ -339,6 +356,7 @@ where
|
||||
.map(|action| CredentialedRouteProxyAction {
|
||||
connector_id: action.connector_id.clone(),
|
||||
link_id: action.link_id.clone(),
|
||||
proxy_headers: action.proxy_headers.clone(),
|
||||
proxy_url: action.proxy_url.clone(),
|
||||
});
|
||||
|
||||
|
||||
@@ -282,10 +282,18 @@ fn credentialed_route_proxy_request_rewrites_upstream_target() {
|
||||
let action = crate::mitm_hook::CredentialedRouteProxyAction {
|
||||
connector_id: "connector_123".to_string(),
|
||||
link_id: "link_123".to_string(),
|
||||
proxy_headers: vec![crate::mitm_hook::CredentialedRouteProxyHeader {
|
||||
name: HeaderName::from_static("authorization"),
|
||||
value: HeaderValue::from_static("Bearer codex-token"),
|
||||
}],
|
||||
proxy_url: "http://localhost:8080/api/codex/credential_routes/proxy".to_string(),
|
||||
};
|
||||
let request_uri: Uri = "https://api.example.com/v1/items?limit=5".parse().unwrap();
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
HeaderName::from_static("authorization"),
|
||||
HeaderValue::from_static("Bearer provider-token"),
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static(CREDENTIAL_ROUTE_REQUEST_URL_HEADER),
|
||||
HeaderValue::from_static("https://spoofed.example.com"),
|
||||
@@ -299,6 +307,10 @@ fn credentialed_route_proxy_request_rewrites_upstream_target() {
|
||||
"http://localhost:8080/api/codex/credential_routes/proxy"
|
||||
);
|
||||
assert_eq!(proxy_authority, "localhost:8080");
|
||||
assert_eq!(
|
||||
headers.get("authorization"),
|
||||
Some(&HeaderValue::from_static("Bearer codex-token"))
|
||||
);
|
||||
assert_eq!(
|
||||
headers.get(CREDENTIAL_ROUTE_CONNECTOR_ID_HEADER),
|
||||
Some(&HeaderValue::from_static("connector_123"))
|
||||
|
||||
Reference in New Issue
Block a user