fix: enforce repository access and cache boundaries

This commit is contained in:
2026-08-26 14:39:37 +09:00
parent 3a3c89e0b4
commit ffb2a34ae5
8 changed files with 533 additions and 70 deletions
+47 -7
View File
@@ -25,8 +25,9 @@ use worker_runtime::auth::{CapabilityTokenSigner, capability_claims};
use worker_runtime::catalog::{
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, ProfileSourceArchiveHttpRef,
ProfileSourceArchiveSource, WorkerDetail as EmbeddedWorkerDetail,
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
WorkingDirectoryStatus, WorkingDirectorySummary, WorkspaceApiRef,
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim,
WorkingDirectoryRepositoryAccessRequest, WorkingDirectoryRequest, WorkingDirectoryStatus,
WorkingDirectorySummary, WorkspaceApiRef,
};
use worker_runtime::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
#[cfg(test)]
@@ -39,11 +40,11 @@ use worker_runtime::execution::WorkerExecutionRunState;
use worker_runtime::fs_store::FsRuntimeStoreOptions;
use worker_runtime::http_server::{
RuntimeHttpConfigBundleAvailabilityResponse, RuntimeHttpConfigBundleSyncRequest,
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpWorkerCompletionsRequest,
RuntimeHttpWorkerCompletionsResponse, RuntimeHttpWorkerDeleteResponse,
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse,
RuntimeHttpWorkerWorkspaceApiRequest, RuntimeHttpWorkersResponse,
RuntimeHttpErrorResponse, RuntimeHttpRepositoryAccessResponse, RuntimeHttpSummaryResponse,
RuntimeHttpWorkerCompletionsRequest, RuntimeHttpWorkerCompletionsResponse,
RuntimeHttpWorkerDeleteResponse, RuntimeHttpWorkerInputResponse,
RuntimeHttpWorkerLifecycleRequest, RuntimeHttpWorkerLifecycleResponse,
RuntimeHttpWorkerResponse, RuntimeHttpWorkerWorkspaceApiRequest, RuntimeHttpWorkersResponse,
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
RuntimeHttpWorkspacePromptProjectionRequest, RuntimeHttpWorkspacePromptProjectionResponse,
};
@@ -854,6 +855,16 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
}
}
fn authorize_working_directory_repository_access(
&self,
_request: WorkingDirectoryRepositoryAccessRequest,
) -> std::result::Result<(), Error> {
Err(Error::InvalidInput(
"Runtime does not support working directory Repository access authorization"
.to_string(),
))
}
fn list_working_directories(&self) -> RuntimeList<WorkingDirectoryStatus> {
RuntimeList::new(Vec::new(), Vec::new())
}
@@ -1427,6 +1438,23 @@ impl RuntimeRegistry {
Ok(runtime.create_working_directory(request))
}
pub fn authorize_working_directory_repository_access(
&self,
runtime_id: &str,
request: WorkingDirectoryRepositoryAccessRequest,
) -> Result<(), RuntimeRegistryError> {
validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("working_directory_id", &request.working_directory_id)?;
let runtime = self.runtime(runtime_id)?;
runtime
.authorize_working_directory_repository_access(request)
.map_err(|error| RuntimeRegistryError::RuntimeOperationFailed {
runtime_id: runtime_id.to_string(),
code: "working_directory_repository_access_failed".to_string(),
message: error.to_string(),
})
}
pub fn list_working_directories(
&self,
runtime_id: &str,
@@ -3205,6 +3233,18 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
}
}
fn authorize_working_directory_repository_access(
&self,
request: WorkingDirectoryRepositoryAccessRequest,
) -> std::result::Result<(), Error> {
self.post_json::<_, RuntimeHttpRepositoryAccessResponse>(
"/v1/working-directories/repository-access",
&request,
)
.map(|_| ())
.map_err(|diagnostic| Error::RegistryInconsistency(diagnostic.message))
}
fn list_working_directories(&self) -> RuntimeList<WorkingDirectoryStatus> {
match self.get_json::<RuntimeHttpWorkingDirectoriesResponse>("/v1/working-directories") {
Ok(response) => RuntimeList::new(response.working_directories, Vec::new()),
+74
View File
@@ -1437,6 +1437,18 @@ impl WorkspaceApi {
self.validate_worker_spawn_repository_scope(&request)?;
let workspace_api = self.workspace_api_ref(runtime_id);
request.resolved_workspace_api = Some(workspace_api.clone());
if let Some(working_directory) = request.resolved_working_directory.as_ref()
&& let Some(access) = repository_access_request_for_workdir(
self,
runtime_id,
&working_directory.working_directory_id,
&format!("worker-spawn:{}", WorkerId::now_v7()),
)?
{
self.runtime
.authorize_working_directory_repository_access(runtime_id, access)
.map_err(RuntimeRegistryError::into_error)?;
}
let attachment_reservation =
request
.resolved_working_directory
@@ -1652,6 +1664,22 @@ impl WorkspaceApi {
&self,
worker: &RuntimeWorkerRef,
) -> ApiResult<WorkerRestoreResult> {
if let Some(link) = self
.store
.list_worker_workdir_links(&self.config.workspace_id, worker)?
.into_iter()
.find(|link| link.unlinked_at.is_none())
&& let Some(access) = repository_access_request_for_workdir(
self,
&worker.runtime_id,
&link.workdir_id,
&format!("worker-restore:{}", WorkerId::now_v7()),
)?
{
self.runtime
.authorize_working_directory_repository_access(&worker.runtime_id, access)
.map_err(RuntimeRegistryError::into_error)?;
}
let binding = self
.runtime
.replace_worker_workspace_api(worker, self.workspace_api_ref(&worker.runtime_id))
@@ -13967,6 +13995,11 @@ fn authorize_repository_materialization(
host_trust_id: lease.host_trust_id,
host_trust_revision: lease.host_trust_revision,
access: binding.access,
expires_at_epoch_seconds: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
.saturating_add(300),
private_key: SensitiveString::new(lease.private_key.as_str()),
known_hosts_entry: SensitiveString::new(lease.known_hosts_entry),
})
@@ -13985,6 +14018,47 @@ fn authorize_repository_materialization(
Ok(())
}
fn repository_access_request_for_workdir(
api: &WorkspaceApi,
runtime_id: &str,
working_directory_id: &str,
operation_id: &str,
) -> ApiResult<Option<worker_runtime::catalog::WorkingDirectoryRepositoryAccessRequest>> {
let record = api
.config_store
.get_workdir_registry(&api.config.workspace_id, working_directory_id)?
.ok_or_else(|| {
settings_bad_request(
"working_directory_not_found",
"Working directory is not registered in this Workspace",
)
})?;
if record.runtime_id != runtime_id {
return Err(settings_bad_request(
"working_directory_runtime_mismatch",
"Working directory is owned by a different Runtime",
));
}
let repository = api.require_configured_workspace_repository(&record.repository_id)?;
if repository.source.kind != workspace_api::RepositorySourceKind::Ssh {
return Ok(None);
}
let projection = active_repository_access_projection(api, &api.config.workspace_id)?;
let mut request = working_directory_request_from_repository(&repository, None);
authorize_repository_materialization(api, runtime_id, operation_id, &projection, &mut request)?;
Ok(Some(
worker_runtime::catalog::WorkingDirectoryRepositoryAccessRequest {
working_directory_id: working_directory_id.to_string(),
materialization: request.materialization.ok_or_else(|| {
settings_bad_request(
"working_directory_remote_repository_access_required",
"SSH Repository access authority is unavailable",
)
})?,
},
))
}
fn working_directory_request_for_browser(
api: &WorkspaceApi,
request: BrowserWorkingDirectoryCreateRequest,