mcp: add bearer token verification seam
This commit is contained in:
+46
-11
@@ -41,6 +41,7 @@ use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
auth::{SharedMachineCredentialVerifier, VerifiedMachineCredential},
|
||||
catalog::PublishedToolCatalog,
|
||||
jsonrpc::{
|
||||
CURRENT_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, is_notification, is_request,
|
||||
@@ -70,6 +71,7 @@ pub struct AppState {
|
||||
api_rate_limiter: RequestRateLimiter,
|
||||
secret_crypto: SecretCrypto,
|
||||
sessions: SharedSessionStore,
|
||||
credential_verifier: SharedMachineCredentialVerifier,
|
||||
allowed_origins: AllowedOrigins,
|
||||
}
|
||||
|
||||
@@ -133,6 +135,7 @@ pub fn build_app(
|
||||
runtime: RuntimeExecutor,
|
||||
api_rate_limiter: RequestRateLimiter,
|
||||
sessions: SharedSessionStore,
|
||||
credential_verifier: SharedMachineCredentialVerifier,
|
||||
) -> Router {
|
||||
let state = Arc::new(AppState {
|
||||
registry: registry.clone(),
|
||||
@@ -141,6 +144,7 @@ pub fn build_app(
|
||||
api_rate_limiter,
|
||||
secret_crypto,
|
||||
sessions,
|
||||
credential_verifier,
|
||||
allowed_origins: AllowedOrigins::new(public_base_url),
|
||||
});
|
||||
|
||||
@@ -174,7 +178,7 @@ async fn mcp_get(
|
||||
}
|
||||
|
||||
if let Err(status) =
|
||||
require_platform_api_key(&state, &path, &headers, PlatformApiKeyScope::Read).await
|
||||
require_machine_access(&state, &path, &headers, PlatformApiKeyScope::Read).await
|
||||
{
|
||||
return status.into_response();
|
||||
}
|
||||
@@ -222,7 +226,7 @@ async fn mcp_delete(
|
||||
headers: HeaderMap,
|
||||
) -> Response {
|
||||
if let Err(status) =
|
||||
require_platform_api_key(&state, &path, &headers, PlatformApiKeyScope::Read).await
|
||||
require_machine_access(&state, &path, &headers, PlatformApiKeyScope::Read).await
|
||||
{
|
||||
return status.into_response();
|
||||
}
|
||||
@@ -321,7 +325,7 @@ async fn mcp_post(
|
||||
Some("tools/call") => PlatformApiKeyScope::Write,
|
||||
_ => PlatformApiKeyScope::Read,
|
||||
};
|
||||
if let Err(status) = require_platform_api_key(&state, &path, &headers, required_scope).await {
|
||||
if let Err(status) = require_machine_access(&state, &path, &headers, required_scope).await {
|
||||
return with_request_id_header(status.into_response(), &transport_request_id);
|
||||
}
|
||||
|
||||
@@ -1803,13 +1807,44 @@ async fn require_initialized_session(
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
async fn require_platform_api_key(
|
||||
async fn require_machine_access(
|
||||
state: &Arc<AppState>,
|
||||
path: &AgentRoutePath,
|
||||
headers: &HeaderMap,
|
||||
required_scope: PlatformApiKeyScope,
|
||||
) -> Result<(), StatusCode> {
|
||||
) -> Result<VerifiedMachineCredential, StatusCode> {
|
||||
let secret = bearer_token(headers).ok_or(StatusCode::UNAUTHORIZED)?;
|
||||
let credential = resolve_machine_credential(state, path, secret).await?;
|
||||
|
||||
if !allows_scope(&credential.scopes, required_scope) {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
Ok(credential)
|
||||
}
|
||||
|
||||
async fn resolve_machine_credential(
|
||||
state: &Arc<AppState>,
|
||||
path: &AgentRoutePath,
|
||||
token: &str,
|
||||
) -> Result<VerifiedMachineCredential, StatusCode> {
|
||||
if let Some(credential) = verify_static_agent_key(state, path, token).await? {
|
||||
return Ok(credential);
|
||||
}
|
||||
|
||||
state
|
||||
.credential_verifier
|
||||
.verify_bearer_token(&path.workspace_slug, &path.agent_slug, token)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.ok_or(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
|
||||
async fn verify_static_agent_key(
|
||||
state: &Arc<AppState>,
|
||||
path: &AgentRoutePath,
|
||||
secret: &str,
|
||||
) -> Result<Option<VerifiedMachineCredential>, StatusCode> {
|
||||
let secret_hash = hash_access_secret(secret);
|
||||
let Some(api_key) = state
|
||||
.registry
|
||||
@@ -1821,13 +1856,9 @@ async fn require_platform_api_key(
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
else {
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if !allows_scope(&api_key.api_key.scopes, required_scope) {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
let used_at = OffsetDateTime::now_utc();
|
||||
state
|
||||
.registry
|
||||
@@ -1835,7 +1866,11 @@ async fn require_platform_api_key(
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(())
|
||||
Ok(Some(VerifiedMachineCredential {
|
||||
machine_access_mode: crank_core::MachineAccessMode::StaticAgentKey,
|
||||
max_security_level: crank_core::OperationSecurityLevel::Standard,
|
||||
scopes: api_key.api_key.scopes,
|
||||
}))
|
||||
}
|
||||
|
||||
fn bearer_token(headers: &HeaderMap) -> Option<&str> {
|
||||
|
||||
Reference in New Issue
Block a user