mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-21 16:00:49 +00:00
Adds opt-in client-credential forwarding for Anthropic passthrough (single-key/BYOK deployments), plus fixes found in review: - Startup safeguard now shares one check (server/middleware/auth.rs) with the live admin PUT /admin/api/config path, closing a bypass where PROXY_OPEN_RELAY=true alongside 2+ PROXY_API_KEYS entries slipped past the old startup-only check. - x-goog-api-key is now recognized as a forwardable credential (renamed to x-api-key upstream, since Anthropic doesn't understand that header name), matching validate_auth's precedence. - Managed (admin-API) backends no longer carry a dead forward_client_auth field that could never take effect. - ClientAuthPath forwarding decisions are now double-checked against live VirtualKeyContext/JwtClaims presence, not just the enum, to fail closed if the two ever desync. - Moved from a per-backend BackendConfig field to a global RuntimeConfig field (like anthropic_thinking_repair), making it live-toggleable from the admin UI with no restart, and uniform across every Anthropic-kind backend in a multi-backend deployment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
68 lines
2.9 KiB
Rust
68 lines
2.9 KiB
Rust
// Regression test for the ANTHROPIC_FORWARD_CLIENT_AUTH misconfiguration
|
|
// guard when the toggle is flipped live via the admin API (not just at
|
|
// startup). See server/middleware/auth.rs::forward_client_auth_misconfigured
|
|
// and admin/routes/config.rs::put_config.
|
|
//
|
|
// This lives in its own integration test binary (its own process) rather
|
|
// than crates/proxy/src/admin/routes/tests.rs deliberately: the check reads
|
|
// server/middleware/auth.rs's ALLOWED_KEY_HASHES/OPEN_RELAY `LazyLock`
|
|
// statics, which evaluate ONCE per process on first access and are cached
|
|
// forever after. That file's shared `--lib` test binary already has other
|
|
// tests (e.g. config::env_aliases's) that mutate PROXY_API_KEYS, and its own
|
|
// existing forward_client_auth-adjacent tests never need PROXY_API_KEYS set
|
|
// with 2+ entries -- adding that scenario there would make the outcome
|
|
// depend on which test happens to touch those statics first. A dedicated
|
|
// binary guarantees this test is the first (and only) thing to do so.
|
|
|
|
use anyllm_proxy::admin::state::SharedState;
|
|
use axum::body::Body;
|
|
use axum::extract::ConnectInfo;
|
|
use axum::http::{Request, StatusCode};
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use tower::ServiceExt;
|
|
|
|
#[tokio::test]
|
|
async fn put_config_rejects_forward_client_auth_when_misconfigured() {
|
|
// No PROXY_OPEN_RELAY set, and 2 distinct PROXY_API_KEYS entries: exactly
|
|
// the combination forward_client_auth_misconfigured rejects, matching the
|
|
// startup safeguard's rule.
|
|
std::env::remove_var("PROXY_OPEN_RELAY");
|
|
std::env::set_var("PROXY_API_KEYS", "key-one,key-two");
|
|
|
|
let shared = SharedState::new_for_test();
|
|
let token_str = "k".repeat(64);
|
|
shared.issued_csrf_tokens.insert(token_str.clone(), ());
|
|
let app = anyllm_proxy::admin::routes::admin_router(
|
|
shared.clone(),
|
|
Arc::new(zeroize::Zeroizing::new("test-token".to_string())),
|
|
);
|
|
|
|
let req = Request::put("/admin/api/config")
|
|
.header("host", "localhost:9090")
|
|
.header("authorization", "Bearer test-token")
|
|
.header("content-type", "application/json")
|
|
.header("x-csrf-token", &token_str)
|
|
.header("cookie", format!("csrf_token={token_str}"))
|
|
.extension(ConnectInfo("127.0.0.1:9090".parse::<SocketAddr>().unwrap()))
|
|
.body(Body::from(r#"{"forward_client_auth":true}"#))
|
|
.unwrap();
|
|
|
|
let resp = app.oneshot(req).await.unwrap();
|
|
|
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
assert!(
|
|
!shared.runtime_config.read().unwrap().forward_client_auth,
|
|
"runtime config must not have been flipped on"
|
|
);
|
|
|
|
let conn = shared.db.lock().unwrap();
|
|
let overrides = anyllm_proxy::admin::db::get_config_overrides(&conn).unwrap();
|
|
assert!(
|
|
!overrides
|
|
.iter()
|
|
.any(|(key, _, _)| key == "forward_client_auth"),
|
|
"rejected toggle must not be persisted as an override"
|
|
);
|
|
}
|