mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 02:47:45 +08:00
Merge remote-tracking branch 'origin/main' into codex/provider-policy-hardening
This commit is contained in:
@@ -37,6 +37,10 @@ const ROUTING_GROUP_SELECTION_CACHE_TTL: Duration = Duration::from_secs(30);
|
||||
const ROUTING_GROUP_SELECTION_CACHE_STALE_TTL: Duration = Duration::from_secs(120);
|
||||
const CODEX_ACCOUNT_ID_HEADER: &str = "chatgpt-account-id";
|
||||
const CODEX_FEDRAMP_HEADER: &str = "x-openai-fedramp";
|
||||
const INVALID_ROUTING_PROVIDER_CONTRACT_MESSAGE: &str =
|
||||
"routing provider request violates provider contract";
|
||||
const INVALID_ROUTING_PROVIDER_HEADERS_MESSAGE: &str =
|
||||
"invalid provider request headers in routing mutation";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ResolvedLocalDecisionAuthInput {
|
||||
@@ -292,6 +296,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
crate::ai_serving::openai_responses_reasoning_replay_policy(
|
||||
transport.provider.provider_type.as_str(),
|
||||
transport.endpoint.base_url.as_str(),
|
||||
provider_model.as_str(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
@@ -311,10 +316,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
)
|
||||
}
|
||||
}
|
||||
.map_err(|violation| GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: format!("routing provider_request violates provider contract: {violation:?}"),
|
||||
})?;
|
||||
.map_err(|_| invalid_routing_provider_contract())?;
|
||||
}
|
||||
let provider_model = provider_request_body
|
||||
.get("model")
|
||||
@@ -649,21 +651,17 @@ pub(crate) async fn attach_routing_policy_to_local_requested_model_input(
|
||||
GatewayRoutingSelectionError::NotFound(explicit_group.unwrap_or_default()),
|
||||
));
|
||||
}
|
||||
None
|
||||
return Err(routing_selection_error(
|
||||
GatewayRoutingSelectionError::NoDefault,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let Some((group_id, group_version, group_config_json, selection_source)) = selected_group
|
||||
else {
|
||||
input.client_session_affinity = client_session_affinity_from_api_request(
|
||||
client_api_format,
|
||||
&parts.headers,
|
||||
Some(body_json),
|
||||
);
|
||||
input.routing_policy = None;
|
||||
input.routing_trace_seed = None;
|
||||
input.routing_context = None;
|
||||
return Ok(());
|
||||
return Err(routing_selection_error(
|
||||
GatewayRoutingSelectionError::NoDefault,
|
||||
));
|
||||
};
|
||||
|
||||
if try_attach_static_default_routing_policy_to_input(
|
||||
@@ -887,10 +885,36 @@ fn routing_selection_error(error: GatewayRoutingSelectionError) -> GatewayError
|
||||
GatewayRoutingSelectionError::Repository(message) => {
|
||||
GatewayError::Internal(format!("routing group repository lookup failed: {message}"))
|
||||
}
|
||||
error => GatewayError::Client {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
message: error.to_string(),
|
||||
GatewayRoutingSelectionError::NoDefault => GatewayError::Client {
|
||||
status: StatusCode::SERVICE_UNAVAILABLE,
|
||||
message: "no enabled routing strategy is configured for this request".to_string(),
|
||||
},
|
||||
GatewayRoutingSelectionError::NotFound(_) => GatewayError::Client {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
message: "requested routing group was not found".to_string(),
|
||||
},
|
||||
GatewayRoutingSelectionError::Disabled(_) => GatewayError::Client {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
message: "requested routing group is not enabled".to_string(),
|
||||
},
|
||||
GatewayRoutingSelectionError::Forbidden(_) => GatewayError::Client {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
message: "requested routing group is not allowed for this principal".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_routing_provider_contract() -> GatewayError {
|
||||
GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: INVALID_ROUTING_PROVIDER_CONTRACT_MESSAGE.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_routing_provider_headers() -> GatewayError {
|
||||
GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: INVALID_ROUTING_PROVIDER_HEADERS_MESSAGE.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,14 +969,9 @@ fn btree_headers_to_header_map(
|
||||
) -> Result<HeaderMap, GatewayError> {
|
||||
let mut output = HeaderMap::new();
|
||||
for (name, value) in headers {
|
||||
let name = HeaderName::from_bytes(name.as_bytes()).map_err(|err| GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: format!("invalid provider request header name in routing mutation: {err}"),
|
||||
})?;
|
||||
let value = HeaderValue::from_str(value).map_err(|err| GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: format!("invalid provider request header value in routing mutation: {err}"),
|
||||
})?;
|
||||
let name = HeaderName::from_bytes(name.as_bytes())
|
||||
.map_err(|_| invalid_routing_provider_headers())?;
|
||||
let value = HeaderValue::from_str(value).map_err(|_| invalid_routing_provider_headers())?;
|
||||
output.insert(name, value);
|
||||
}
|
||||
Ok(output)
|
||||
@@ -1097,6 +1116,7 @@ fn ensure_report_context_routing_trace(
|
||||
endpoint_id: decision.endpoint_id.clone().unwrap_or_default(),
|
||||
model_id,
|
||||
key_id,
|
||||
api_format: decision.provider_api_format.clone(),
|
||||
provider_priority,
|
||||
key_priority,
|
||||
},
|
||||
@@ -1174,6 +1194,50 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routing_selection_errors_do_not_echo_explicit_group() {
|
||||
let secret = "private-group?token=Bearer-secret";
|
||||
|
||||
for error in [
|
||||
GatewayRoutingSelectionError::NotFound(secret.to_string()),
|
||||
GatewayRoutingSelectionError::Disabled(secret.to_string()),
|
||||
GatewayRoutingSelectionError::Forbidden(secret.to_string()),
|
||||
] {
|
||||
let error = routing_selection_error(error);
|
||||
assert!(matches!(
|
||||
error,
|
||||
GatewayError::Client {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
ref message,
|
||||
} if !message.contains(secret)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routing_provider_errors_do_not_echo_dynamic_details() {
|
||||
let secret = "https://internal.example/?token=Bearer-secret";
|
||||
let contract_error = invalid_routing_provider_contract();
|
||||
let header_error = btree_headers_to_header_map(&BTreeMap::from([(
|
||||
format!("Authorization: {secret}"),
|
||||
secret.to_string(),
|
||||
)]))
|
||||
.expect_err("invalid header should fail");
|
||||
|
||||
for (error, expected_message) in [
|
||||
(contract_error, INVALID_ROUTING_PROVIDER_CONTRACT_MESSAGE),
|
||||
(header_error, INVALID_ROUTING_PROVIDER_HEADERS_MESSAGE),
|
||||
] {
|
||||
assert!(matches!(
|
||||
error,
|
||||
GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
ref message,
|
||||
} if message == expected_message && !message.contains(secret)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn explicit_auth_snapshot_override_does_not_fall_back_to_the_planner_cache() {
|
||||
// AppState::new has no auth snapshot repository. Without the explicit
|
||||
@@ -1230,6 +1294,7 @@ mod tests {
|
||||
description: None,
|
||||
enabled: true,
|
||||
is_system_default: false,
|
||||
sort_order: 0,
|
||||
config_json: json!({}),
|
||||
version: 1,
|
||||
created_at: 1,
|
||||
|
||||
Reference in New Issue
Block a user