mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
refactor: remove trusted proxy config
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -57,9 +57,6 @@ pub struct ServerConfig {
|
||||
pub host: String,
|
||||
/// 监听端口。
|
||||
pub port: u16,
|
||||
/// 可信反向代理 IP 或 CIDR。非空时只有这些 peer 的转发 IP 头会被采信。
|
||||
#[serde(default)]
|
||||
pub trusted_proxies: Vec<String>,
|
||||
}
|
||||
|
||||
/// 上游 API 地址配置。
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::{
|
||||
extract::{connect_info::ConnectInfo, Request, State},
|
||||
extract::{connect_info::ConnectInfo, Request},
|
||||
http::{HeaderMap, HeaderValue},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
@@ -19,7 +19,7 @@ const CF_CONNECTING_IP_HEADER: &str = "cf-connecting-ip";
|
||||
const REAL_IP_HEADER: &str = "x-real-ip";
|
||||
const X_FORWARDED_FOR_HEADER: &str = "x-forwarded-for";
|
||||
|
||||
/// 私有 / 回环 / 唯一本地地址网段,用于在自动模式下从 `X-Forwarded-For` 中跳过内网跳板 IP。
|
||||
/// 私有 / 回环 / 唯一本地地址网段,用于从 `X-Forwarded-For` 中跳过内网跳板 IP。
|
||||
static PRIVATE_NETWORKS: LazyLock<Vec<IpNet>> = LazyLock::new(|| {
|
||||
[
|
||||
"10.0.0.0/8",
|
||||
@@ -38,40 +38,6 @@ fn is_private_ip(ip: IpAddr) -> bool {
|
||||
PRIVATE_NETWORKS.iter().any(|network| network.contains(&ip))
|
||||
}
|
||||
|
||||
/// 可信反向代理配置。
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct TrustedProxyConfig {
|
||||
networks: Vec<IpNet>,
|
||||
}
|
||||
|
||||
impl TrustedProxyConfig {
|
||||
/// 从 IP 或 CIDR 字符串构造可信代理配置。
|
||||
pub fn from_entries(entries: &[String]) -> Result<Self, TrustedProxyConfigError> {
|
||||
let networks = entries
|
||||
.iter()
|
||||
.map(|entry| parse_trusted_proxy_entry(entry))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(Self { networks })
|
||||
}
|
||||
|
||||
fn is_trusted(&self, ip: IpAddr) -> bool {
|
||||
self.networks.iter().any(|network| network.contains(&ip))
|
||||
}
|
||||
|
||||
/// 是否未配置任何可信代理(决定是否启用自动真实 IP 探测)。
|
||||
fn is_empty(&self) -> bool {
|
||||
self.networks.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// 可信代理配置错误。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum TrustedProxyConfigError {
|
||||
/// 配置项不是 IP 地址或 CIDR。
|
||||
#[error("invalid trusted proxy entry `{entry}`; expected an IP address or CIDR network")]
|
||||
InvalidEntry { entry: String },
|
||||
}
|
||||
|
||||
/// 请求 ID。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RequestId(String);
|
||||
@@ -97,7 +63,7 @@ impl RequestId {
|
||||
}
|
||||
}
|
||||
|
||||
/// 由连接来源和可信代理链解析出的客户端 IP。
|
||||
/// 由连接来源和转发头解析出的客户端 IP。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientIp(String);
|
||||
|
||||
@@ -118,17 +84,7 @@ fn valid_request_id(value: &str) -> bool {
|
||||
|
||||
/// 为请求附加请求 ID,并在响应头中回写。
|
||||
pub async fn attach_request_id(mut request: Request, next: Next) -> Response {
|
||||
attach_request_context(&TrustedProxyConfig::default(), &mut request);
|
||||
run_with_request_id(request, next).await
|
||||
}
|
||||
|
||||
/// 为请求附加请求上下文,并按可信代理配置解析真实客户端 IP。
|
||||
pub async fn attach_request_id_with_proxy_config(
|
||||
State(trusted_proxies): State<TrustedProxyConfig>,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
attach_request_context(&trusted_proxies, &mut request);
|
||||
attach_request_context(&mut request);
|
||||
run_with_request_id(request, next).await
|
||||
}
|
||||
|
||||
@@ -147,17 +103,16 @@ async fn run_with_request_id(mut request: Request, next: Next) -> Response {
|
||||
response
|
||||
}
|
||||
|
||||
fn attach_request_context(trusted_proxies: &TrustedProxyConfig, request: &mut Request) {
|
||||
attach_client_ip_from_connection(trusted_proxies, request);
|
||||
fn attach_request_context(request: &mut Request) {
|
||||
attach_client_ip_from_connection(request);
|
||||
}
|
||||
|
||||
fn attach_client_ip_from_connection(trusted_proxies: &TrustedProxyConfig, request: &mut Request) {
|
||||
fn attach_client_ip_from_connection(request: &mut Request) {
|
||||
let peer_ip = request
|
||||
.extensions()
|
||||
.get::<ConnectInfo<SocketAddr>>()
|
||||
.map(|ConnectInfo(addr)| addr.ip());
|
||||
let client_ip =
|
||||
peer_ip.map(|peer_ip| client_ip_from_peer(peer_ip, request.headers(), trusted_proxies));
|
||||
let client_ip = peer_ip.map(|peer_ip| client_ip_from_peer(peer_ip, request.headers()));
|
||||
|
||||
let Some(client_ip) = client_ip else {
|
||||
return;
|
||||
@@ -165,28 +120,12 @@ fn attach_client_ip_from_connection(trusted_proxies: &TrustedProxyConfig, reques
|
||||
request.extensions_mut().insert(ClientIp(client_ip));
|
||||
}
|
||||
|
||||
fn client_ip_from_peer(
|
||||
peer_ip: IpAddr,
|
||||
headers: &HeaderMap,
|
||||
trusted_proxies: &TrustedProxyConfig,
|
||||
) -> String {
|
||||
if trusted_proxies.is_empty() {
|
||||
// 未配置可信代理:自动模式,按 CF-Connecting-IP → X-Real-IP →
|
||||
// X-Forwarded-For(首个公网 IP)→ peer IP 的优先级解析真实客户端 IP。
|
||||
return auto_forwarded_client_ip(headers).unwrap_or_else(|| peer_ip.to_string());
|
||||
}
|
||||
if trusted_proxies.is_trusted(peer_ip) {
|
||||
// 已配置可信代理且直连来源可信:只采信转发头。
|
||||
trusted_forwarded_client_ip(headers).unwrap_or_else(|| peer_ip.to_string())
|
||||
} else {
|
||||
// 已配置可信代理但直连来源不可信:忽略转发头,使用 peer IP。
|
||||
peer_ip.to_string()
|
||||
}
|
||||
fn client_ip_from_peer(peer_ip: IpAddr, headers: &HeaderMap) -> String {
|
||||
auto_forwarded_client_ip(headers).unwrap_or_else(|| peer_ip.to_string())
|
||||
}
|
||||
|
||||
/// 自动模式下解析转发头中的真实客户端 IP。
|
||||
/// 解析转发头中的真实客户端 IP。
|
||||
///
|
||||
/// 未配置可信代理时使用,对齐常见反代(Cloudflare / Nginx)默认行为:
|
||||
/// `CF-Connecting-IP` → `X-Real-IP` → `X-Forwarded-For` 首个公网 IP。
|
||||
fn auto_forwarded_client_ip(headers: &HeaderMap) -> Option<String> {
|
||||
if let Some(cf_ip) = header_string(headers, CF_CONNECTING_IP_HEADER) {
|
||||
@@ -212,13 +151,6 @@ fn forwarded_for_public_ip(headers: &HeaderMap) -> Option<String> {
|
||||
.map(ToString::to_string)
|
||||
}
|
||||
|
||||
fn trusted_forwarded_client_ip(headers: &HeaderMap) -> Option<String> {
|
||||
header_string(headers, CF_CONNECTING_IP_HEADER)
|
||||
.or_else(|| header_string(headers, REAL_IP_HEADER))
|
||||
.or_else(|| forwarded_for_public_ip(headers))
|
||||
.or_else(|| first_forwarded_for_value(headers))
|
||||
}
|
||||
|
||||
fn header_string(headers: &HeaderMap, name: &str) -> Option<String> {
|
||||
headers
|
||||
.get(name)
|
||||
@@ -237,19 +169,3 @@ fn first_forwarded_for_value(headers: &HeaderMap) -> Option<String> {
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToString::to_string)
|
||||
}
|
||||
|
||||
fn parse_trusted_proxy_entry(entry: &str) -> Result<IpNet, TrustedProxyConfigError> {
|
||||
if let Ok(network) = entry.parse::<IpNet>() {
|
||||
return Ok(network);
|
||||
}
|
||||
entry
|
||||
.parse::<IpAddr>()
|
||||
.map(IpNet::from)
|
||||
.map_err(|_| invalid_trusted_proxy_entry(entry))
|
||||
}
|
||||
|
||||
fn invalid_trusted_proxy_entry(entry: &str) -> TrustedProxyConfigError {
|
||||
TrustedProxyConfigError::InvalidEntry {
|
||||
entry: entry.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ use std::path::Path;
|
||||
use axum::{extract::State, http::StatusCode, middleware, routing::get, Router};
|
||||
|
||||
use crate::admin;
|
||||
use crate::http::middleware::{
|
||||
request_id::{attach_request_id_with_proxy_config, TrustedProxyConfig},
|
||||
trace::http_trace_layer,
|
||||
};
|
||||
use crate::http::middleware::{request_id::attach_request_id, trace::http_trace_layer};
|
||||
use crate::proxy;
|
||||
use crate::runtime::state::AppState;
|
||||
use crate::web;
|
||||
@@ -18,34 +15,18 @@ pub const DEFAULT_ASSET_DIST_DIR: &str = "web/dist";
|
||||
|
||||
/// 构造整个 HTTP 服务路由。
|
||||
pub fn router() -> Router<AppState> {
|
||||
router_with_trusted_proxies(TrustedProxyConfig::default())
|
||||
}
|
||||
|
||||
/// 使用指定可信代理配置构造整个 HTTP 服务路由。
|
||||
pub fn router_with_trusted_proxies(trusted_proxies: TrustedProxyConfig) -> Router<AppState> {
|
||||
router_with_assets_and_trusted_proxies(DEFAULT_ASSET_DIST_DIR, trusted_proxies)
|
||||
router_with_assets(DEFAULT_ASSET_DIST_DIR)
|
||||
}
|
||||
|
||||
/// 使用指定前端构建产物目录构造整个 HTTP 服务路由。
|
||||
pub fn router_with_assets(dist_dir: impl AsRef<Path>) -> Router<AppState> {
|
||||
router_with_assets_and_trusted_proxies(dist_dir, TrustedProxyConfig::default())
|
||||
}
|
||||
|
||||
/// 使用指定前端构建产物目录和可信代理配置构造整个 HTTP 服务路由。
|
||||
pub fn router_with_assets_and_trusted_proxies(
|
||||
dist_dir: impl AsRef<Path>,
|
||||
trusted_proxies: TrustedProxyConfig,
|
||||
) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/healthz", get(healthz))
|
||||
.merge(proxy::router::router())
|
||||
.merge(admin::router::router())
|
||||
.fallback_service(web::assets::spa_router(dist_dir))
|
||||
.layer(http_trace_layer())
|
||||
.layer(middleware::from_fn_with_state(
|
||||
trusted_proxies,
|
||||
attach_request_id_with_proxy_config,
|
||||
))
|
||||
.layer(middleware::from_fn(attach_request_id))
|
||||
}
|
||||
|
||||
async fn healthz(State(state): State<AppState>) -> StatusCode {
|
||||
|
||||
@@ -5,7 +5,6 @@ use chrono::Utc;
|
||||
|
||||
use crate::config::schema::AppConfig;
|
||||
use crate::config::settings::RuntimeSettingsService;
|
||||
use crate::http::middleware::request_id::TrustedProxyConfig;
|
||||
use crate::infra::database::connect_sqlite;
|
||||
use crate::infra::logging::{init_tracing, LogGuard, RotationConfig};
|
||||
use crate::infra::paths::{ensure_data_dir, load_or_create_installation_id};
|
||||
@@ -113,11 +112,10 @@ async fn build_application(
|
||||
);
|
||||
|
||||
let task_coordinator = TaskCoordinator::start(&runtime_config, &services);
|
||||
let trusted_proxies = TrustedProxyConfig::from_entries(&config.server.trusted_proxies)?;
|
||||
let app_state = AppState { services };
|
||||
|
||||
Ok((
|
||||
crate::http::router::router_with_trusted_proxies(trusted_proxies).with_state(app_state),
|
||||
crate::http::router::router().with_state(app_state),
|
||||
task_coordinator,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ const DEFAULT_CONFIG_YAML: &str = r#"
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
trusted_proxies: []
|
||||
api:
|
||||
base_url: https://chatgpt.com/backend-api
|
||||
database:
|
||||
@@ -80,7 +79,6 @@ logging:
|
||||
fn default_config_keeps_only_codex_backend() {
|
||||
let cfg: AppConfig = parse_yaml_config(DEFAULT_CONFIG_YAML).unwrap();
|
||||
assert_eq!(cfg.server.host, "0.0.0.0");
|
||||
assert!(cfg.server.trusted_proxies.is_empty());
|
||||
assert_eq!(cfg.api.base_url, "https://chatgpt.com/backend-api");
|
||||
assert!(cfg.model_aliases.is_empty());
|
||||
assert_eq!(cfg.auth.refresh_margin_seconds, 300);
|
||||
@@ -259,7 +257,6 @@ ws_pool:
|
||||
|
||||
assert_eq!(cfg.server.host, "127.0.0.1");
|
||||
assert_eq!(cfg.server.port, 8080);
|
||||
assert!(cfg.server.trusted_proxies.is_empty());
|
||||
assert_eq!(
|
||||
cfg.database.url,
|
||||
"sqlite://.runtime/data/codex-proxy-rs.sqlite"
|
||||
@@ -280,30 +277,15 @@ ws_pool:
|
||||
assert_eq!(cfg.logging.retention_days, 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_should_parse_trusted_proxy_entries() {
|
||||
let cfg: AppConfig = parse_yaml_config(&DEFAULT_CONFIG_YAML.replace(
|
||||
"trusted_proxies: []",
|
||||
"trusted_proxies:\n - 127.0.0.1\n - 10.0.0.0/8",
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cfg.server.trusted_proxies,
|
||||
vec!["127.0.0.1".to_string(), "10.0.0.0/8".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_config_should_reject_unknown_fields() {
|
||||
let err = parse_yaml_config::<AppConfig>(&DEFAULT_CONFIG_YAML.replace(
|
||||
"trusted_proxies: []",
|
||||
"trusted_proxies: []\n trusted_proxy: 127.0.0.1",
|
||||
))
|
||||
let err = parse_yaml_config::<AppConfig>(
|
||||
&DEFAULT_CONFIG_YAML.replace(" port: 8080", " port: 8080\n trusted_proxies: []"),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("trusted_proxy"),
|
||||
err.to_string().contains("trusted_proxies"),
|
||||
"expected unknown server field to be rejected, got {err}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@ use axum::{
|
||||
extract::connect_info::ConnectInfo,
|
||||
http::HeaderMap,
|
||||
http::{Request, StatusCode},
|
||||
middleware::{from_fn, from_fn_with_state},
|
||||
middleware::from_fn,
|
||||
routing::get,
|
||||
Extension, Router,
|
||||
};
|
||||
use codex_proxy_rs::http::middleware::{
|
||||
request_id::{
|
||||
attach_request_id, attach_request_id_with_proxy_config, ClientIp, TrustedProxyConfig,
|
||||
},
|
||||
request_id::{attach_request_id, ClientIp},
|
||||
trace::http_trace_layer,
|
||||
};
|
||||
use tokio::time::{sleep, Duration};
|
||||
@@ -285,7 +283,7 @@ async fn request_context_should_auto_detect_forwarded_ip_without_trusted_proxy_c
|
||||
let response = app.oneshot(request).await.expect("response");
|
||||
let bytes = to_bytes(response.into_body(), 1024).await.unwrap();
|
||||
|
||||
// 未配置可信代理时进入自动模式:CF-Connecting-IP 优先于 X-Real-IP / X-Forwarded-For。
|
||||
// CF-Connecting-IP 优先于 X-Real-IP / X-Forwarded-For。
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&bytes).unwrap(),
|
||||
"203.0.113.43|cf=203.0.113.43|xff=203.0.113.42, 10.0.0.2|real=203.0.113.44"
|
||||
@@ -319,45 +317,7 @@ async fn request_context_should_skip_private_forwarded_hops_in_auto_mode() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_context_should_accept_forwarded_ip_from_trusted_proxy() {
|
||||
let trusted_proxies =
|
||||
TrustedProxyConfig::from_entries(&["127.0.0.0/8".to_string()]).expect("trusted proxy");
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/ip",
|
||||
get(
|
||||
|Extension(client_ip): Extension<ClientIp>, headers: HeaderMap| async move {
|
||||
let forwarded_present = headers.contains_key("x-forwarded-for");
|
||||
format!("{}|forwarded={forwarded_present}", client_ip.as_str())
|
||||
},
|
||||
),
|
||||
)
|
||||
.layer(from_fn_with_state(
|
||||
trusted_proxies,
|
||||
attach_request_id_with_proxy_config,
|
||||
));
|
||||
let mut request = Request::builder()
|
||||
.uri("/ip")
|
||||
.header("x-forwarded-for", "203.0.113.42, 10.0.0.2")
|
||||
.body(Body::empty())
|
||||
.expect("request should build");
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 61234))));
|
||||
|
||||
let response = app.oneshot(request).await.expect("response");
|
||||
let bytes = to_bytes(response.into_body(), 1024).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&bytes).unwrap(),
|
||||
"203.0.113.42|forwarded=true"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_context_should_accept_real_ip_from_trusted_proxy_when_forwarded_for_missing() {
|
||||
let trusted_proxies =
|
||||
TrustedProxyConfig::from_entries(&["127.0.0.1".to_string()]).expect("trusted proxy");
|
||||
async fn request_context_should_accept_real_ip_when_forwarded_for_missing() {
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/ip",
|
||||
@@ -365,10 +325,7 @@ async fn request_context_should_accept_real_ip_from_trusted_proxy_when_forwarded
|
||||
client_ip.as_str().to_string()
|
||||
}),
|
||||
)
|
||||
.layer(from_fn_with_state(
|
||||
trusted_proxies,
|
||||
attach_request_id_with_proxy_config,
|
||||
));
|
||||
.layer(from_fn(attach_request_id));
|
||||
let mut request = Request::builder()
|
||||
.uri("/ip")
|
||||
.header("x-real-ip", "198.51.100.23")
|
||||
@@ -385,9 +342,7 @@ async fn request_context_should_accept_real_ip_from_trusted_proxy_when_forwarded
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_context_should_prefer_real_ip_over_forwarded_for_from_trusted_proxy() {
|
||||
let trusted_proxies =
|
||||
TrustedProxyConfig::from_entries(&["127.0.0.1".to_string()]).expect("trusted proxy");
|
||||
async fn request_context_should_prefer_real_ip_over_forwarded_for() {
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/ip",
|
||||
@@ -395,10 +350,7 @@ async fn request_context_should_prefer_real_ip_over_forwarded_for_from_trusted_p
|
||||
client_ip.as_str().to_string()
|
||||
}),
|
||||
)
|
||||
.layer(from_fn_with_state(
|
||||
trusted_proxies,
|
||||
attach_request_id_with_proxy_config,
|
||||
));
|
||||
.layer(from_fn(attach_request_id));
|
||||
let mut request = Request::builder()
|
||||
.uri("/ip")
|
||||
.header("x-forwarded-for", "198.51.100.99, 203.0.113.42")
|
||||
@@ -415,36 +367,6 @@ async fn request_context_should_prefer_real_ip_over_forwarded_for_from_trusted_p
|
||||
assert_eq!(std::str::from_utf8(&bytes).unwrap(), "203.0.113.42");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_context_should_ignore_forwarded_ip_from_untrusted_peer() {
|
||||
let trusted_proxies =
|
||||
TrustedProxyConfig::from_entries(&["10.0.0.0/8".to_string()]).expect("trusted proxy");
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/ip",
|
||||
get(|Extension(client_ip): Extension<ClientIp>| async move {
|
||||
client_ip.as_str().to_string()
|
||||
}),
|
||||
)
|
||||
.layer(from_fn_with_state(
|
||||
trusted_proxies,
|
||||
attach_request_id_with_proxy_config,
|
||||
));
|
||||
let mut request = Request::builder()
|
||||
.uri("/ip")
|
||||
.header("x-forwarded-for", "203.0.113.42")
|
||||
.body(Body::empty())
|
||||
.expect("request should build");
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 61234))));
|
||||
|
||||
let response = app.oneshot(request).await.expect("response");
|
||||
let bytes = to_bytes(response.into_body(), 1024).await.unwrap();
|
||||
|
||||
assert_eq!(std::str::from_utf8(&bytes).unwrap(), "127.0.0.1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_context_should_not_inject_real_ip_header() {
|
||||
let app = Router::new()
|
||||
@@ -474,13 +396,3 @@ async fn request_context_should_not_inject_real_ip_header() {
|
||||
|
||||
assert_eq!(std::str::from_utf8(&bytes).unwrap(), "missing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_proxy_config_should_reject_invalid_entries() {
|
||||
let error = TrustedProxyConfig::from_entries(&["not-an-ip".to_string()]).unwrap_err();
|
||||
|
||||
assert!(
|
||||
error.to_string().contains("not-an-ip"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ pub(crate) fn test_config(database_url: String) -> AppConfig {
|
||||
server: ServerConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
trusted_proxies: Vec::new(),
|
||||
},
|
||||
api: ApiConfig {
|
||||
base_url: "https://chatgpt.com/backend-api".to_string(),
|
||||
|
||||
+1
-3
@@ -19,9 +19,7 @@ cp deploy/config.example.yaml .runtime/config.yaml
|
||||
|
||||
复制后必须设置 `admin.default_password`。空值、长度不足 12 位或常见弱口令会被后端拒绝。
|
||||
|
||||
如果前面还有 Nginx、Caddy、Cloudflare Tunnel 等反向代理,把后端实际看到的代理 peer IP 或 CIDR 写入 `server.trusted_proxies`。经宿主机反代访问 Docker 端口时,这个 peer 通常是 Compose 网络网关,例如 `172.18.0.1/32`,不是宿主 `127.0.0.1/32`。
|
||||
|
||||
`server.trusted_proxies` 非空时,只有这些 peer 的 `CF-Connecting-IP` / `X-Real-IP` / `X-Forwarded-For` 会被采信;为空时进入自动解析模式,按同一组头解析真实 IP。
|
||||
如果前面还有 Nginx、Caddy、Cloudflare Tunnel 等反向代理,确保反代正常传递 `CF-Connecting-IP`、`X-Real-IP` 或 `X-Forwarded-For`。后端会按这组头自动解析真实客户端 IP;没有这些头时回落到直连 peer IP。
|
||||
|
||||
构建并启动:
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@ server:
|
||||
host: '0.0.0.0'
|
||||
# 容器内服务端口,需与 docker-compose.yml 的 target port 一致。
|
||||
port: 8080
|
||||
# 可信反向代理 IP 或 CIDR。非空时只有这些 peer 的转发 IP 头会被采信。
|
||||
# Docker 端口映射经宿主反代访问时,后端看到的 peer 通常是 Compose 网络网关
|
||||
# (例如 172.18.0.1),不是宿主 127.0.0.1。
|
||||
# 为空时进入自动解析模式,按 CF-Connecting-IP / X-Real-IP / X-Forwarded-For 解析。
|
||||
trusted_proxies: []
|
||||
|
||||
api:
|
||||
# ChatGPT / Codex 上游 API 地址。
|
||||
|
||||
Reference in New Issue
Block a user