codex_helper_core/proxy/
admin.rs1use std::net::SocketAddr;
2
3use axum::body::Body;
4use axum::extract::{ConnectInfo, State};
5use axum::http::{Request, Response, StatusCode};
6use axum::middleware::Next;
7
8use crate::dashboard_core::RemoteAdminAccessCapabilities;
9
10use super::{ADMIN_PORT_OFFSET, ADMIN_TOKEN_ENV_VAR, ADMIN_TOKEN_HEADER};
11
12#[derive(Clone)]
13pub(super) struct AdminAccessConfig {
14 token: Option<String>,
15}
16
17impl AdminAccessConfig {
18 pub(super) fn from_env() -> Self {
19 let token = std::env::var(ADMIN_TOKEN_ENV_VAR)
20 .ok()
21 .map(|value| value.trim().to_string())
22 .filter(|value| !value.is_empty());
23 Self { token }
24 }
25}
26
27pub(super) fn admin_access_capabilities() -> RemoteAdminAccessCapabilities {
28 let access = AdminAccessConfig::from_env();
29 RemoteAdminAccessCapabilities {
30 loopback_without_token: true,
31 remote_requires_token: true,
32 remote_enabled: access.token.is_some(),
33 token_header: ADMIN_TOKEN_HEADER.to_string(),
34 token_env_var: ADMIN_TOKEN_ENV_VAR.to_string(),
35 }
36}
37
38pub fn admin_port_for_proxy_port(proxy_port: u16) -> u16 {
39 if proxy_port <= u16::MAX - ADMIN_PORT_OFFSET {
40 proxy_port + ADMIN_PORT_OFFSET
41 } else if proxy_port > ADMIN_PORT_OFFSET {
42 proxy_port - ADMIN_PORT_OFFSET
43 } else {
44 1
45 }
46}
47
48pub fn local_proxy_base_url(port: u16) -> String {
49 format!("http://127.0.0.1:{port}")
50}
51
52pub fn local_admin_base_url_for_proxy_port(proxy_port: u16) -> String {
53 local_proxy_base_url(admin_port_for_proxy_port(proxy_port))
54}
55
56pub fn admin_base_url_from_proxy_base_url(proxy_base_url: &str) -> Option<String> {
57 let mut url = reqwest::Url::parse(proxy_base_url).ok()?;
58 let port = url.port_or_known_default()?;
59 url.set_port(Some(admin_port_for_proxy_port(port))).ok()?;
60 Some(url.to_string().trim_end_matches('/').to_string())
61}
62
63pub fn admin_loopback_addr_for_proxy_port(proxy_port: u16) -> SocketAddr {
64 SocketAddr::from(([127, 0, 0, 1], admin_port_for_proxy_port(proxy_port)))
65}
66
67fn is_admin_path(path: &str) -> bool {
68 path == "/__codex_helper" || path.starts_with("/__codex_helper/")
69}
70
71#[derive(Clone, serde::Serialize)]
72pub(super) struct ProxyAdminDiscovery {
73 pub(super) api_version: u32,
74 pub(super) service_name: &'static str,
75 pub(super) admin_base_url: String,
76}
77
78pub(super) async fn require_admin_access(
79 State(access): State<AdminAccessConfig>,
80 ConnectInfo(peer_addr): ConnectInfo<SocketAddr>,
81 req: Request<Body>,
82 next: Next,
83) -> Result<Response<Body>, (StatusCode, String)> {
84 if peer_addr.ip().is_loopback() {
85 return Ok(next.run(req).await);
86 }
87
88 let provided = req
89 .headers()
90 .get(ADMIN_TOKEN_HEADER)
91 .and_then(|value| value.to_str().ok())
92 .map(str::trim)
93 .filter(|value| !value.is_empty());
94 let remote_allowed = access
95 .token
96 .as_deref()
97 .is_some_and(|expected| Some(expected) == provided);
98 if remote_allowed {
99 return Ok(next.run(req).await);
100 }
101
102 Err((
103 StatusCode::FORBIDDEN,
104 format!(
105 "admin routes are loopback-only; set {ADMIN_TOKEN_ENV_VAR} and send {ADMIN_TOKEN_HEADER} to allow remote access"
106 ),
107 ))
108}
109
110pub(super) async fn require_admin_path_only(
111 req: Request<Body>,
112 next: Next,
113) -> Result<Response<Body>, StatusCode> {
114 if is_admin_path(req.uri().path()) {
115 return Ok(next.run(req).await);
116 }
117 Err(StatusCode::NOT_FOUND)
118}
119
120pub(super) async fn reject_admin_paths_from_proxy(
121 req: Request<Body>,
122 next: Next,
123) -> Result<Response<Body>, StatusCode> {
124 if is_admin_path(req.uri().path()) {
125 return Err(StatusCode::NOT_FOUND);
126 }
127 Ok(next.run(req).await)
128}