fix: harden provider ref review boundaries
This commit is contained in:
@@ -1970,6 +1970,33 @@ fn status_for_runtime_error(error: &RuntimeError) -> StatusCode {
|
||||
{
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
RuntimeError::WorkingDirectory(diagnostic)
|
||||
if matches!(
|
||||
diagnostic.code.as_str(),
|
||||
"repository_ref_provider_unavailable"
|
||||
| "repository_ref_provider_timeout"
|
||||
| "repository_access_provider_unavailable"
|
||||
) =>
|
||||
{
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
}
|
||||
RuntimeError::WorkingDirectory(diagnostic)
|
||||
if matches!(
|
||||
diagnostic.code.as_str(),
|
||||
"repository_ref_provider_auth_failed"
|
||||
| "repository_access_credential_expired"
|
||||
| "repository_access_credential_unavailable"
|
||||
| "repository_access_credential_unauthorized"
|
||||
| "repository_access_credential_invalid"
|
||||
) =>
|
||||
{
|
||||
StatusCode::FORBIDDEN
|
||||
}
|
||||
RuntimeError::WorkingDirectory(diagnostic)
|
||||
if diagnostic.code == "repository_ref_not_found" =>
|
||||
{
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
RuntimeError::RuntimeStopped
|
||||
| RuntimeError::WorkerExecutionUnavailable { .. }
|
||||
| RuntimeError::ExecutionBackendUnavailable { .. }
|
||||
@@ -1980,8 +2007,8 @@ fn status_for_runtime_error(error: &RuntimeError) -> StatusCode {
|
||||
| RuntimeError::InvalidInitialInputKind { .. }
|
||||
| RuntimeError::ConfigBundleDigestMismatch { .. }
|
||||
| RuntimeError::InvalidProfileSelector { .. }
|
||||
| RuntimeError::UnsupportedConfigDeclaration { .. }
|
||||
| RuntimeError::WorkingDirectory(_) => StatusCode::BAD_REQUEST,
|
||||
| RuntimeError::UnsupportedConfigDeclaration { .. } => StatusCode::BAD_REQUEST,
|
||||
RuntimeError::WorkingDirectory(_) => StatusCode::BAD_REQUEST,
|
||||
RuntimeError::StoreIo { .. }
|
||||
| RuntimeError::StoreMissing { .. }
|
||||
| RuntimeError::StoreCorrupt { .. }
|
||||
@@ -2967,17 +2994,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn workdir_runtime_errors_preserve_diagnostic_code() {
|
||||
let error =
|
||||
RuntimeError::WorkingDirectory(crate::working_directory::WorkingDirectoryDiagnostic {
|
||||
code: "working_directory_not_found".to_string(),
|
||||
message: "working directory missing-workdir was not found".to_string(),
|
||||
});
|
||||
|
||||
assert_eq!(status_for_runtime_error(&error), StatusCode::NOT_FOUND);
|
||||
assert_eq!(
|
||||
code_for_runtime_error(&error),
|
||||
"working_directory_not_found"
|
||||
);
|
||||
let cases = [
|
||||
("working_directory_not_found", StatusCode::NOT_FOUND),
|
||||
(
|
||||
"repository_ref_provider_timeout",
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
),
|
||||
("repository_ref_provider_auth_failed", StatusCode::FORBIDDEN),
|
||||
("repository_ref_not_found", StatusCode::NOT_FOUND),
|
||||
];
|
||||
for (code, expected_status) in cases {
|
||||
let error = RuntimeError::WorkingDirectory(
|
||||
crate::working_directory::WorkingDirectoryDiagnostic {
|
||||
code: code.to_string(),
|
||||
message: "bounded diagnostic".to_string(),
|
||||
},
|
||||
);
|
||||
assert_eq!(status_for_runtime_error(&error), expected_status);
|
||||
assert_eq!(code_for_runtime_error(&error), code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3002,20 +3002,35 @@ fn worker_status_from_run_state(run_state: WorkerExecutionRunState) -> WorkerSta
|
||||
}
|
||||
|
||||
fn repository_resource_error(error: BackendResourceError) -> RuntimeError {
|
||||
let category = match error {
|
||||
BackendResourceError::Expired => "expired",
|
||||
BackendResourceError::Unauthorized { .. } => "unauthorized",
|
||||
BackendResourceError::UnsupportedKind => "unsupported_kind",
|
||||
BackendResourceError::MissingResource => "missing_resource",
|
||||
BackendResourceError::Oversized { .. } => "oversized",
|
||||
BackendResourceError::DigestMismatch { .. } => "digest_mismatch",
|
||||
BackendResourceError::ContentTypeMismatch { .. } => "content_type_mismatch",
|
||||
BackendResourceError::InvalidResponse { .. } => "invalid_response",
|
||||
BackendResourceError::Transport { .. } => "transport",
|
||||
let (code, message) = match error {
|
||||
BackendResourceError::Expired => (
|
||||
"repository_access_credential_expired",
|
||||
"Repository access credential lease expired",
|
||||
),
|
||||
BackendResourceError::Unauthorized { .. } => (
|
||||
"repository_access_credential_unauthorized",
|
||||
"Repository access credential lease was rejected",
|
||||
),
|
||||
BackendResourceError::MissingResource => (
|
||||
"repository_access_credential_unavailable",
|
||||
"Repository access credential lease is unavailable or already consumed",
|
||||
),
|
||||
BackendResourceError::Transport { .. } => (
|
||||
"repository_access_provider_unavailable",
|
||||
"Repository access credential provider is unavailable",
|
||||
),
|
||||
BackendResourceError::UnsupportedKind
|
||||
| BackendResourceError::Oversized { .. }
|
||||
| BackendResourceError::DigestMismatch { .. }
|
||||
| BackendResourceError::ContentTypeMismatch { .. }
|
||||
| BackendResourceError::InvalidResponse { .. } => (
|
||||
"repository_access_credential_invalid",
|
||||
"Repository access credential response is invalid",
|
||||
),
|
||||
};
|
||||
RuntimeError::InvalidRequest(format!(
|
||||
"Backend Repository SSH access resource fetch failed: {category}"
|
||||
))
|
||||
RuntimeError::WorkingDirectory(
|
||||
crate::working_directory::WorkingDirectoryDiagnostic::rejected(code, message),
|
||||
)
|
||||
}
|
||||
|
||||
fn durable_create_worker_request(request: &CreateWorkerRequest) -> CreateWorkerRequest {
|
||||
@@ -3217,6 +3232,33 @@ mod tests {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
fn repository_resource_failures_keep_typed_credential_diagnostics() {
|
||||
let cases = [
|
||||
(
|
||||
BackendResourceError::Expired,
|
||||
"repository_access_credential_expired",
|
||||
),
|
||||
(
|
||||
BackendResourceError::MissingResource,
|
||||
"repository_access_credential_unavailable",
|
||||
),
|
||||
(
|
||||
BackendResourceError::Unauthorized {
|
||||
message: "denied".to_string(),
|
||||
},
|
||||
"repository_access_credential_unauthorized",
|
||||
),
|
||||
];
|
||||
for (error, expected_code) in cases {
|
||||
let RuntimeError::WorkingDirectory(diagnostic) = repository_resource_error(error)
|
||||
else {
|
||||
panic!("Repository resource failure lost its typed diagnostic")
|
||||
};
|
||||
assert_eq!(diagnostic.code, expected_code);
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_worker_ref(
|
||||
session_id: &str,
|
||||
parent_session_id: Option<&str>,
|
||||
|
||||
@@ -954,13 +954,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
||||
request: &RepositoryRefObservationRequest,
|
||||
) -> Result<RepositoryRefObservation, WorkingDirectoryDiagnostic> {
|
||||
let selector = request.selector.trim();
|
||||
validate_selector(selector)?;
|
||||
if !selector.starts_with("refs/heads/") {
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"repository_ref_selector_invalid",
|
||||
"Repository ref observation requires an exact branch selector",
|
||||
));
|
||||
}
|
||||
validate_exact_branch_selector(selector)?;
|
||||
|
||||
let working_request = WorkingDirectoryRequest {
|
||||
repository: request.repository.clone(),
|
||||
@@ -2100,6 +2094,40 @@ fn repository_git_command(
|
||||
command
|
||||
}
|
||||
|
||||
fn validate_exact_branch_selector(selector: &str) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||
validate_selector(selector).map_err(|_| {
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"repository_ref_selector_invalid",
|
||||
"Repository ref observation requires a valid exact branch selector",
|
||||
)
|
||||
})?;
|
||||
if !selector.starts_with("refs/heads/") {
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"repository_ref_selector_invalid",
|
||||
"Repository ref observation requires an exact branch selector",
|
||||
));
|
||||
}
|
||||
let status = Command::new("git")
|
||||
.args(["check-ref-format", selector])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.map_err(|_| {
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"repository_ref_provider_unavailable",
|
||||
"Git ref validation could not be started",
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"repository_ref_selector_invalid",
|
||||
"Repository ref observation requires a valid exact branch selector",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_bounded_command_output(mut reader: impl Read) -> Vec<u8> {
|
||||
const MAX_CAPTURE_BYTES: usize = 8192;
|
||||
let mut captured = Vec::new();
|
||||
@@ -2729,12 +2757,20 @@ mod tests {
|
||||
assert_eq!(missing.code, "repository_ref_not_found");
|
||||
let non_branch = materializer
|
||||
.observe_repository_ref(&RepositoryRefObservationRequest {
|
||||
repository,
|
||||
repository: repository.clone(),
|
||||
selector: "HEAD".to_string(),
|
||||
materialization: None,
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(non_branch.code, "repository_ref_selector_invalid");
|
||||
let wildcard = materializer
|
||||
.observe_repository_ref(&RepositoryRefObservationRequest {
|
||||
repository,
|
||||
selector: "refs/heads/release/*".to_string(),
|
||||
materialization: None,
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(wildcard.code, "repository_ref_selector_invalid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user