fix: verify signed Workspace query targets

This commit is contained in:
2026-08-25 01:12:24 +09:00
parent ff94161fc0
commit 11be777fc0
2 changed files with 97 additions and 12 deletions
+71 -3
View File
@@ -903,6 +903,12 @@ async fn resolve_server_actor(
resolve_request_actor(api.store.as_ref(), headers, &cookie_name).await
}
fn signed_request_target(uri: &Uri) -> &str {
uri.path_and_query()
.map(|path_and_query| path_and_query.as_str())
.unwrap_or_else(|| uri.path())
}
async fn authorize_scoped_workspace_request(
api: &WorkspaceServerApi,
workspace_id: &str,
@@ -915,7 +921,7 @@ async fn authorize_scoped_workspace_request(
.map(str::to_owned);
if let Some(proof) = proof {
let method = request.method().as_str().to_owned();
let path = request.uri().path().to_owned();
let path = signed_request_target(request.uri()).to_owned();
let body = std::mem::take(request.body_mut());
let body = axum::body::to_bytes(body, 16 * 1024 * 1024)
.await
@@ -987,7 +993,7 @@ async fn authorize_workspace_api_request(
.map(str::to_owned);
if let Some(proof) = proof {
let method = request.method().as_str().to_owned();
let path = request.uri().path().to_owned();
let path = signed_request_target(request.uri()).to_owned();
let body = std::mem::take(request.body_mut());
let Ok(body) = axum::body::to_bytes(body, 16 * 1024 * 1024).await else {
return StatusCode::BAD_REQUEST.into_response();
@@ -10851,7 +10857,7 @@ async fn scoped_post_internal_runtime_resource_fetch(
.get::<crate::worker_source::VerifiedRuntimeRequestSource>()
.cloned();
let method = request.method().as_str().to_owned();
let path = request.uri().path().to_owned();
let path = signed_request_target(request.uri()).to_owned();
let body = axum::body::to_bytes(request.into_body(), 16 * 1024 * 1024)
.await
.map_err(|error| {
@@ -19859,6 +19865,68 @@ mod tests {
));
}
#[tokio::test]
async fn runtime_request_proof_verifies_path_and_query_for_ticket_search() {
let workspace = tempfile::tempdir().unwrap();
let mut api = test_api(workspace.path()).await;
let identity =
worker_runtime::auth::RuntimeIdentityMaterial::generate("runtime-test").unwrap();
configure_runtime_request_auth(&mut api, &identity, "runtime-test");
seed_worker_source_member(&api, "runtime-test", "worker-test");
let signer = worker_runtime::auth::RuntimeRequestSourceSigner::from_identity(&identity);
let signed_target =
format!("/api/w/{TEST_WORKSPACE_ID}/tickets/search?state=active&limit=20");
let tampered_target =
format!("/api/w/{TEST_WORKSPACE_ID}/tickets/search?state=all&limit=20");
let issue = |target: &str| {
signer
.issue(
"server-test",
TEST_WORKSPACE_ID,
Some("worker-test"),
worker_runtime::auth::WORKSPACE_REQUEST_PERMISSION,
"GET",
target,
b"",
i64::try_from(worker_runtime::auth::unix_now_seconds()).unwrap_or(i64::MAX),
30,
)
.unwrap()
};
let app = build_router(api);
let tampered = app
.clone()
.oneshot(
Request::builder()
.uri(&tampered_target)
.header(
worker_runtime::auth::RUNTIME_REQUEST_SOURCE_PROOF_HEADER,
issue(&signed_target),
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(tampered.status(), StatusCode::UNAUTHORIZED);
let valid = app
.oneshot(
Request::builder()
.uri(&signed_target)
.header(
worker_runtime::auth::RUNTIME_REQUEST_SOURCE_PROOF_HEADER,
issue(&signed_target),
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(valid.status(), StatusCode::OK);
}
#[tokio::test]
async fn internal_resource_fetch_rest_returns_typed_missing_resource() {
let workspace = tempfile::tempdir().unwrap();