Clean up transport fingerprint configuration

Remove legacy tls_profile handling, keep header fingerprint under transport profiles, and drop the duplicate auth_modules migration.
This commit is contained in:
fawney19
2026-05-06 13:54:33 +08:00
parent 6fbb867f5f
commit 68216bf868
53 changed files with 267 additions and 238 deletions

View File

@@ -191,8 +191,12 @@ mod tests {
expires_at_unix_secs: None,
proxy: None,
fingerprint: Some(json!({
"tls_profile": "chrome_136",
"user_agent": "Mozilla/5.0"
"transport_profile": {
"profile_id": "chrome_136",
"header_fingerprint": {
"user_agent": "Mozilla/5.0"
}
}
})),
decrypted_api_key: "sk-test".to_string(),
decrypted_auth_config: None,
@@ -271,11 +275,11 @@ mod tests {
assert_eq!(metadata["transport_diagnostics"]["provider_type"], "codex");
assert_eq!(
metadata["transport_diagnostics"]["fingerprint"]["tls_profile"],
metadata["transport_diagnostics"]["fingerprint"]["transport_profile"]["profile_id"],
"chrome_136"
);
assert_eq!(
metadata["transport_diagnostics"]["resolved_tls_profile"],
metadata["transport_diagnostics"]["resolved_transport_profile_id"],
"chrome_136"
);
assert_eq!(

View File

@@ -72,10 +72,9 @@ pub(crate) use aether_provider_transport::{
resolve_gemini_files_auth, resolve_openai_image_auth, resolve_same_format_provider_direct_auth,
resolve_transport_execution_timeouts, resolve_transport_profile,
resolve_transport_proxy_snapshot, resolve_transport_proxy_snapshot_with_tunnel_affinity,
resolve_transport_tls_profile, resolve_video_create_auth,
same_format_provider_transport_supported, same_format_provider_transport_unsupported_reason,
should_skip_upstream_passthrough_header, should_try_same_format_provider_oauth_auth,
supports_local_gemini_transport_with_network,
resolve_video_create_auth, same_format_provider_transport_supported,
same_format_provider_transport_unsupported_reason, should_skip_upstream_passthrough_header,
should_try_same_format_provider_oauth_auth, supports_local_gemini_transport_with_network,
supports_local_generic_oauth_request_auth_resolution,
supports_local_oauth_request_auth_resolution, transport_proxy_is_locally_supported,
video_create_transport_unsupported_reason, CandidateTransportPolicyFacts,

View File

@@ -635,7 +635,7 @@ mod tests {
content_type: Some("application/json".to_string()),
model_name: Some("sora-2".to_string()),
proxy: None,
tls_profile: None,
transport_profile: None,
timeouts: None,
},
});

View File

@@ -697,7 +697,7 @@ async fn data_state_reads_decrypted_provider_transport_snapshot() {
Some(serde_json::json!(["gpt-4.1", "gpt-4.1-mini"])),
Some(1_800_000_000),
Some(serde_json::json!({"node_id":"proxy-node-1"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(

View File

@@ -19,7 +19,6 @@ use flate2::write::GzEncoder;
use flate2::Compression;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::redirect::Policy;
use reqwest::tls::Version;
use serde::Serialize;
use serde_json::json;
use serde_json::Value;
@@ -36,8 +35,6 @@ use crate::{AppState, GatewayError};
const HUB_RELAY_CONTENT_TYPE: &str = "application/vnd.aether.tunnel-envelope";
const HUB_RELAY_ERROR_HEADER: &str = "x-aether-tunnel-error";
const TUNNEL_RELAY_PATH_PREFIX: &str = "/api/internal/tunnel/relay";
const CLAUDE_CODE_TLS_PROFILE: &str = "claude_code_nodejs";
pub(crate) fn format_upstream_request_error(err: &reqwest::Error) -> String {
let mut kinds = Vec::new();
if err.is_connect() {
@@ -892,10 +889,7 @@ fn build_client(
..HttpClientConfig::default()
},
);
builder = apply_tls_profile(
builder,
transport_profile.map(|profile| profile.profile_id.as_str()),
);
builder = apply_transport_profile(builder, transport_profile);
if transport_controls.accept_invalid_certs {
builder = builder.danger_accept_invalid_certs(true);
}
@@ -938,39 +932,24 @@ fn transport_profile_http1_only(transport_profile: Option<&ResolvedTransportProf
.unwrap_or(false)
}
fn apply_tls_profile(
fn apply_transport_profile(
builder: reqwest::ClientBuilder,
profile_id: Option<&str>,
transport_profile: Option<&ResolvedTransportProfile>,
) -> reqwest::ClientBuilder {
let profile = normalize_tls_profile(profile_id);
if profile.is_none() {
let Some(profile) = transport_profile else {
return builder;
};
let profile_id = profile.profile_id.trim();
if profile_id.is_empty() {
return builder;
}
let _ = rustls::crypto::ring::default_provider().install_default();
let tls_config = build_best_effort_tls_config();
let builder = builder
.use_preconfigured_tls(tls_config)
.min_tls_version(Version::TLS_1_2)
.max_tls_version(Version::TLS_1_3);
if profile.as_deref() == Some(CLAUDE_CODE_TLS_PROFILE) {
return builder;
}
builder
builder.use_preconfigured_tls(build_best_effort_transport_tls_config())
}
fn normalize_tls_profile(tls_profile: Option<&str>) -> Option<String> {
let profile = tls_profile
.map(str::trim)
.filter(|profile| !profile.is_empty())?
.to_ascii_lowercase();
Some(profile)
}
fn build_best_effort_tls_config() -> rustls::ClientConfig {
fn build_best_effort_transport_tls_config() -> rustls::ClientConfig {
let root_store =
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = rustls::ClientConfig::builder_with_protocol_versions(&[
@@ -1199,6 +1178,7 @@ mod tests {
use aether_contracts::{
ExecutionPlan, ExecutionTimeouts, ProxySnapshot, RequestBody, ResolvedTransportProfile,
EXECUTION_REQUEST_FOLLOW_REDIRECTS_HEADER, EXECUTION_REQUEST_HTTP1_ONLY_HEADER,
TRANSPORT_BACKEND_REQWEST_RUSTLS,
};
use aether_data::repository::proxy_nodes::{
InMemoryProxyNodeRepository, ProxyNodeReadRepository, StoredProxyNode,
@@ -2135,9 +2115,14 @@ mod tests {
provider_api_format: "provider_ops:verify".into(),
model_name: Some("verify-auth".into()),
proxy: Some(tunnel_proxy_snapshot(format!("http://{addr}"))),
transport_profile: Some(ResolvedTransportProfile::from_legacy_tls_profile(
"relay-profile",
)),
transport_profile: Some(ResolvedTransportProfile {
profile_id: "relay-profile".into(),
backend: TRANSPORT_BACKEND_REQWEST_RUSTLS.into(),
http_mode: "auto".into(),
pool_scope: "key".into(),
header_fingerprint: None,
extra: None,
}),
timeouts: Some(ExecutionTimeouts {
connect_ms: Some(5_000),
total_ms: Some(5_000),
@@ -2157,7 +2142,7 @@ mod tests {
}
#[tokio::test]
async fn direct_sync_execution_runtime_allows_tls_profile_best_effort() {
async fn direct_sync_execution_runtime_allows_transport_profile_best_effort() {
let listener = crate::test_support::bind_loopback_listener()
.await
.expect("listener should bind");
@@ -2167,7 +2152,7 @@ mod tests {
post(|| async {
(
axum::http::StatusCode::OK,
Json(json!({"tls_profile": true})),
Json(json!({"transport_profile": true})),
)
}),
);
@@ -2205,14 +2190,14 @@ mod tests {
}),
})
.await
.expect("sync execution with tls profile should succeed");
.expect("sync execution with transport profile should succeed");
server.abort();
assert_eq!(result.status_code, 200);
assert_eq!(
result.body.and_then(|body| body.json_body),
Some(json!({"tls_profile": true}))
Some(json!({"transport_profile": true}))
);
}
@@ -2223,6 +2208,7 @@ mod tests {
backend: "utls".into(),
http_mode: "auto".into(),
pool_scope: "key".into(),
header_fingerprint: None,
extra: None,
};

View File

@@ -214,13 +214,6 @@ impl<'a> AdminAppState<'a> {
)
}
pub(crate) fn resolve_transport_tls_profile(
&self,
transport: &AdminGatewayProviderTransportSnapshot,
) -> Option<String> {
crate::provider_transport::resolve_transport_tls_profile(transport)
}
pub(crate) fn resolve_transport_profile(
&self,
transport: &AdminGatewayProviderTransportSnapshot,

View File

@@ -1646,10 +1646,10 @@ fn local_execution_runtime_miss_skip_reason_label(reason: &str) -> &str {
"transport_header_rules_apply_failed" => "Header 规则应用失败",
"transport_oauth_resolution_unsupported" => "OAuth 认证解析不支持本地执行",
"transport_provider_type_unsupported" => "提供商类型不支持本地执行",
"transport_proxy_or_tls_unsupported" => "代理或 TLS 配置不支持本地执行",
"transport_proxy_or_profile_unsupported" => "代理或传输指纹配置不支持本地执行",
"transport_proxy_unsupported" => "代理配置不支持本地执行",
"transport_snapshot_missing" => "提供商传输配置缺失",
"transport_tls_profile_unsupported" => "TLS 指纹配置不支持本地执行",
"transport_profile_unsupported" => "传输指纹配置不支持本地执行",
"transport_unsupported" => "传输配置不支持本地执行",
"upstream_url_missing" => "无法构建上游请求地址",
other => other,

View File

@@ -730,7 +730,7 @@ async fn gateway_executes_kiro_claude_cli_sync_upstream_stream_via_local_finaliz
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-kiro-cli-finalize-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -1089,7 +1089,7 @@ async fn gateway_executes_openai_chat_stream_with_custom_path_via_local_decision
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-openai-custom-stream"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -195,7 +195,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-openai-compact-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -245,7 +245,7 @@ async fn gateway_executes_kiro_claude_cli_stream_via_local_provider_catalog_cand
Some(
serde_json::json!({"enabled": true, "node_id":"proxy-node-kiro-cli-local-stream"}),
),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -734,7 +734,7 @@ async fn gateway_executes_claude_cli_stream_via_local_decision_gate_without_wait
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -1196,8 +1196,12 @@ async fn gateway_executes_claude_code_cli_stream_via_local_decision_gate_with_lo
serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-code-cli-local"}),
),
Some(serde_json::json!({
"tls_profile":"claude_code_nodejs",
"user_agent":"Claude-Code/9.9"
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"user_agent":"Claude-Code/9.9"
}
}
})),
)
.expect("key transport should build")
@@ -1709,7 +1713,7 @@ async fn gateway_executes_claude_chat_stream_via_local_decision_gate_with_local_
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-chat-stream"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -172,7 +172,7 @@ async fn gateway_executes_gemini_chat_stream_via_local_decision_gate_with_local_
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-chat-stream"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -169,7 +169,7 @@ async fn gateway_executes_gemini_cli_stream_via_local_decision_gate_with_local_s
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -617,7 +617,7 @@ async fn gateway_executes_gemini_cli_stream_via_local_decision_gate_after_oauth_
Some(
serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-cli-oauth-local"}),
),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -2751,7 +2751,7 @@ async fn gateway_executes_openai_chat_sync_with_custom_path_via_local_decision_g
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-openai-custom-path"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -214,11 +214,15 @@ async fn gateway_executes_claude_code_cli_sync_via_local_decision_gate_with_loca
serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-code-cli-local"}),
),
Some(serde_json::json!({
"tls_profile":"claude_code_nodejs",
"user_agent":"Claude-Code/9.9",
"stainless_package_version":"1.0.5",
"stainless_runtime_version":"v22.12.0",
"stainless_timeout":"900"
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"user_agent":"Claude-Code/9.9",
"stainless_package_version":"1.0.5",
"stainless_runtime_version":"v22.12.0",
"stainless_timeout":"900"
}
}
})),
)
.expect("key transport should build")

View File

@@ -264,7 +264,7 @@ async fn gateway_executes_kiro_claude_cli_sync_via_local_provider_catalog_candid
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-kiro-cli-local-sync"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -199,7 +199,7 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-chat-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -768,7 +768,7 @@ async fn gateway_returns_claude_chat_error_for_local_sync_failure_impl() {
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-chat-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -199,7 +199,7 @@ async fn gateway_executes_claude_cli_sync_via_local_decision_gate_with_local_syn
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -644,7 +644,7 @@ async fn gateway_returns_claude_cli_error_for_local_sync_failure_impl() {
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-claude-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -191,7 +191,7 @@ async fn gateway_executes_openai_responses_sync_via_local_decision_gate_with_loc
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-openai-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -1418,7 +1418,7 @@ async fn gateway_returns_openai_responses_error_for_local_sync_failure() {
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-openai-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -199,7 +199,7 @@ async fn gateway_executes_gemini_cli_sync_via_local_decision_gate_with_local_syn
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -630,7 +630,7 @@ async fn gateway_returns_gemini_cli_error_for_local_sync_failure_impl() {
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-cli-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -936,7 +936,7 @@ async fn gateway_executes_gemini_cli_sync_via_local_decision_gate_after_oauth_re
Some(
serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-cli-oauth-local"}),
),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -199,7 +199,7 @@ async fn gateway_executes_gemini_chat_sync_via_local_decision_gate_with_local_sy
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-chat-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -620,7 +620,7 @@ async fn gateway_returns_gemini_chat_error_for_local_sync_failure_impl() {
None,
None,
Some(serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-chat-local"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -552,7 +552,7 @@ async fn gateway_cancels_openai_video_task_via_internal_async_task_endpoint() {
"content_type": "application/json",
"model_name": "sora-2-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}
@@ -714,7 +714,7 @@ async fn gateway_cancels_openai_video_task_via_internal_async_task_endpoint_with
"content_type": "application/json",
"model_name": "sora-2-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -581,7 +581,7 @@ async fn gateway_cancels_admin_video_task_locally_with_trusted_admin_principal()
"content_type": "application/json",
"model_name": "sora-2-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -198,7 +198,7 @@ async fn gateway_executes_gemini_files_download_via_local_decision_gate_with_loc
key.proxy = Some(
serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-files-download-local"}),
);
key.fingerprint = Some(serde_json::json!({"tls_profile":"chrome_136"}));
key.fingerprint = Some(serde_json::json!({"transport_profile":"chrome_136"}));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![endpoint],

View File

@@ -202,7 +202,7 @@ async fn gateway_executes_gemini_files_upload_via_local_decision_gate_with_local
key.proxy = Some(
serde_json::json!({"enabled": true, "node_id":"proxy-node-gemini-files-upload-local"}),
);
key.fingerprint = Some(serde_json::json!({"tls_profile":"chrome_136"}));
key.fingerprint = Some(serde_json::json!({"transport_profile":"chrome_136"}));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![endpoint],

View File

@@ -108,7 +108,7 @@ fn sample_gemini_video_task(
"content_type": "application/json",
"model_name": "veo-3-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -189,7 +189,7 @@ async fn gateway_executes_gemini_video_create_via_local_decision_gate_with_local
None,
None,
None,
Some(json!({"tls_profile":"chrome_136"})),
Some(json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}

View File

@@ -232,7 +232,7 @@ async fn gateway_executes_gemini_video_cancel_via_data_backed_local_follow_up_wi
"content_type": "application/json",
"model_name": "veo-3-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -194,7 +194,7 @@ async fn gateway_executes_openai_video_create_via_local_decision_gate_with_local
None,
None,
None,
Some(json!({"tls_profile":"chrome_136"})),
Some(json!({"transport_profile":"chrome_136"})),
)
.expect("key transport should build")
}
@@ -726,7 +726,7 @@ async fn gateway_executes_openai_video_remix_via_data_backed_local_follow_up_wit
"content_type": "application/json",
"model_name": "sora-2-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -96,7 +96,7 @@ fn sample_due_openai_task(upstream_base_url: &str) -> UpsertVideoTask {
"content_type": "application/json",
"model_name": "sora-2-upstream",
"proxy": null,
"tls_profile": null,
"transport_profile": null,
"timeouts": null
}
}

View File

@@ -1702,6 +1702,7 @@ mod tests {
backend: "reqwest_rustls".to_string(),
http_mode: "auto".to_string(),
pool_scope: "key".to_string(),
header_fingerprint: None,
extra: None,
});
let mut second = first.clone();

View File

@@ -589,6 +589,7 @@ mod tests {
backend: TRANSPORT_BACKEND_REQWEST_RUSTLS.to_string(),
http_mode: "auto".to_string(),
pool_scope: "key".to_string(),
header_fingerprint: None,
extra: None,
};
let pool_key = upstream_client_pool_key(