Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/openshell-core/src/extension_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub const COMPUTE_SUSPEND_RESUME: &str = "openshell.compute.suspend-resume";
/// Optional stopped-state workload configuration updates retaining the resource and storage.
pub const COMPUTE_UPDATE_SANDBOX_CONFIGURATION: &str =
"openshell.compute.update-sandbox-configuration";
/// Driver declares the proxy prerequisites admitted before runtime signing.
pub const COMPUTE_PROXY_FILESYSTEM_BASELINE: &str = "openshell.compute.proxy-filesystem-baseline";

const MAX_IMPLEMENTATION_NAME_BYTES: usize = 128;
const MAX_IMPLEMENTATION_VERSION_BYTES: usize = 128;
Expand Down Expand Up @@ -133,6 +135,7 @@ pub fn gateway_metadata(family: ExtensionFamily) -> PeerMetadata {
supported_capabilities.push(COMPUTE_LAUNCH_AUTHENTICATION.to_string());
supported_capabilities.push(COMPUTE_ISOLATION_BACKEND.to_string());
supported_capabilities.push(COMPUTE_UPDATE_SANDBOX_CONFIGURATION.to_string());
supported_capabilities.push(COMPUTE_PROXY_FILESYSTEM_BASELINE.to_string());
}
PeerMetadata {
protocol_version: Some(ProtocolVersion {
Expand Down
101 changes: 101 additions & 0 deletions crates/openshell-core/src/filesystem_baseline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Trusted backend prerequisites composed before effective policy admission.

use std::collections::BTreeSet;

use crate::proto::SandboxPolicy;
use crate::proto::compute::v1::ProxyFilesystemBaseline;

/// Proxy runtime paths selected by trusted backend composition.
/// An explicit empty baseline suppresses legacy defaults.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FilesystemBaseline {
pub read_only: Vec<String>,
pub read_write: Vec<String>,
}

impl From<ProxyFilesystemBaseline> for FilesystemBaseline {
fn from(value: ProxyFilesystemBaseline) -> Self {
Self {
read_only: value.read_only,
read_write: value.read_write,
}
}
}

impl From<FilesystemBaseline> for ProxyFilesystemBaseline {
fn from(value: FilesystemBaseline) -> Self {
Self {
read_only: value.read_only,
read_write: value.read_write,
}
}
}

impl FilesystemBaseline {
/// Validate bounded, canonical POSIX path syntax without inspecting the
/// gateway host filesystem, which need not be the workload's platform.
pub fn validate(&self) -> Result<(), &'static str> {
if self.read_only.len() + self.read_write.len() > 64 {
return Err("proxy filesystem baseline has too many paths");
}
let mut seen = BTreeSet::new();
for path in self.read_only.iter().chain(&self.read_write) {
if path.len() > 4096
|| !path.starts_with('/')
|| path.chars().any(char::is_control)
|| (path != "/"
&& path[1..]
.split('/')
.any(|part| matches!(part, "" | "." | "..")))
|| !seen.insert(path)
{
return Err(
"proxy filesystem baseline requires unique canonical POSIX absolute paths",
);
}
}
Ok(())
}

/// Add prerequisites to a proxy-mode policy without replacing explicit
/// grants. An explicit read-only path stays read-only. `path_exists` is
/// used only by legacy image-local Linux defaults; advertised prerequisites
/// are unconditional and callers pass a predicate that always returns true.
/// Validate the descriptor at the trusted admission boundary before use.
pub fn enrich(&self, policy: &mut SandboxPolicy, path_exists: impl Fn(&str) -> bool) -> bool {
if policy.network_policies.is_empty()
|| (self.read_only.is_empty() && self.read_write.is_empty())
{
return false;
}
let filesystem = policy
.filesystem
.get_or_insert_with(|| crate::proto::FilesystemPolicy {
include_workdir: true,
..Default::default()
});
let mut modified = false;
for path in &self.read_only {
if !filesystem.read_only.contains(path)
&& !filesystem.read_write.contains(path)
&& path_exists(path)
{
filesystem.read_only.push(path.clone());
modified = true;
}
}
for path in &self.read_write {
if !filesystem.read_only.contains(path)
&& !filesystem.read_write.contains(path)
&& path_exists(path)
{
filesystem.read_write.push(path.clone());
modified = true;
}
}
modified
}
}
1 change: 1 addition & 0 deletions crates/openshell-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod error;
pub mod extension_protocol;
#[cfg(unix)]
pub mod external_driver_socket;
pub mod filesystem_baseline;
pub mod forward;
pub mod google_cloud;
pub mod gpu;
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,7 @@ impl DockerComputeDriver {
}),
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
openshell_core::extension_protocol::ExtensionFamily::Compute,
"openshell/docker",
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-kubernetes/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,7 @@ impl KubernetesComputeDriver {
}),
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
openshell_core::extension_protocol::ExtensionFamily::Compute,
"openshell/kubernetes",
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-mxc/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ impl MxcComputeBackend {
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
openshell_core::extension_protocol::ExtensionFamily::Compute,
"openshell/mxc",
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-podman/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ impl PodmanComputeDriver {
}),
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
openshell_core::extension_protocol::ExtensionFamily::Compute,
"openshell/podman",
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-vm/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,7 @@ impl VmDriver {
.to_string_lossy()
.into_owned(),
rootfs_tar_max_bytes: self.config.rootfs_tar_max_bytes(),
proxy_filesystem_baseline: None,
extension: Some(extension),
}
}
Expand Down
116 changes: 116 additions & 0 deletions crates/openshell-server/src/compute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use hyper_util::rt::TokioIo;
use openshell_core::extension_protocol::{
ExtensionFamily, NegotiatedExtension, gateway_metadata, negotiate,
};
use openshell_core::filesystem_baseline::FilesystemBaseline;
#[cfg(test)]
use openshell_core::proto::ServiceAuthorizationMode;
use openshell_core::proto::compute::v1::{
Expand Down Expand Up @@ -513,6 +514,8 @@ pub struct ComputeDriverInfoSnapshot {
pub driver_reports_runtime_readiness: bool,
/// Static portable resource request forms from the startup capability snapshot.
pub resource_capabilities: Option<DriverResourceCapabilities>,
/// Trusted workload proxy prerequisites, present only after negotiation.
pub proxy_filesystem_baseline: Option<FilesystemBaseline>,
/// Directory where rootfs tar files must be staged.
pub rootfs_tar_staging_dir: String,
/// Maximum rootfs tar file size in bytes.
Expand Down Expand Up @@ -1002,6 +1005,32 @@ impl ComputeRuntime {
capabilities.extension.clone(),
)
.map_err(|error| ComputeError::Message(error.to_string()))?;
let declares_baseline =
negotiated_extension
.supported_capabilities
.iter()
.any(|capability| {
capability
== openshell_core::extension_protocol::COMPUTE_PROXY_FILESYSTEM_BASELINE
});
let proxy_filesystem_baseline = match (
declares_baseline,
capabilities.proxy_filesystem_baseline.clone(),
) {
(false, None) => None,
(true, Some(descriptor)) => {
let baseline = FilesystemBaseline::from(descriptor);
baseline
.validate()
.map_err(|error| ComputeError::Message(error.to_string()))?;
Some(baseline)
}
_ => {
return Err(ComputeError::Message(
"compute proxy filesystem baseline capability and descriptor must agree".into(),
));
}
};
info!(
configured_driver = %driver_name,
advertised_driver = %capabilities.driver_name,
Expand All @@ -1016,6 +1045,7 @@ impl ComputeRuntime {
supports_sandbox_authentication: capabilities.supports_sandbox_authentication,
driver_reports_runtime_readiness: capabilities.driver_reports_runtime_readiness,
resource_capabilities: capabilities.resource_capabilities,
proxy_filesystem_baseline,
rootfs_tar_staging_dir: capabilities.rootfs_tar_staging_dir,
rootfs_tar_max_bytes: capabilities.rootfs_tar_max_bytes,
};
Expand Down Expand Up @@ -1313,6 +1343,10 @@ impl ComputeRuntime {
std::slice::from_ref(&self.driver_info)
}

pub(crate) fn proxy_filesystem_baseline(&self) -> Option<&FilesystemBaseline> {
self.driver_info.proxy_filesystem_baseline.as_ref()
}

#[must_use]
pub(crate) fn rootfs_tar_staging(&self) -> &rootfs_tar::RootfsTarStagingRegistry {
&self.rootfs_tar_staging
Expand Down Expand Up @@ -8425,6 +8459,7 @@ impl ComputeDriver for NoopTestDriver {
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
ExtensionFamily::Compute,
"openshell/noop-test-driver",
Expand Down Expand Up @@ -8620,6 +8655,7 @@ pub fn new_test_runtime_with_driver(
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
},
telemetry_compute_driver: TelemetryComputeDriver::custom(),
driver_process: None,
Expand Down Expand Up @@ -9058,6 +9094,7 @@ mod tests {
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: (!self.omit_protocol_metadata).then(|| {
let mut metadata = openshell_core::extension_protocol::extension_metadata(
ExtensionFamily::Compute,
Expand Down Expand Up @@ -9524,6 +9561,7 @@ mod tests {
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
extension: Some(openshell_core::extension_protocol::extension_metadata(
ExtensionFamily::Compute,
"openshell/controlled-test-driver",
Expand Down Expand Up @@ -9824,6 +9862,7 @@ mod tests {
resource_capabilities: None,
rootfs_tar_staging_dir: String::new(),
rootfs_tar_max_bytes: 0,
proxy_filesystem_baseline: None,
},
telemetry_compute_driver: TelemetryComputeDriver::custom(),
driver_process: None,
Expand Down Expand Up @@ -18778,6 +18817,83 @@ endpoint = { kind = "unix", socket_path = "/run/agent-substrate/isolation.sock"
assert!(runtime.image_update_driver.is_none());
}

#[tokio::test]
async fn proxy_filesystem_baseline_handshake_requires_a_valid_matching_descriptor() {
use crate::test_support::FakeComputeDriver;
use openshell_core::extension_protocol::COMPUTE_PROXY_FILESYSTEM_BASELINE;
use openshell_core::proto::compute::v1::ProxyFilesystemBaseline;

let valid = ProxyFilesystemBaseline {
read_only: vec!["/System".into()],
read_write: vec!["/dev".into()],
};
for (declared, descriptor, accepted) in [
(false, None, true),
(true, Some(ProxyFilesystemBaseline::default()), true),
(true, Some(valid.clone()), true),
(true, None, false),
(false, Some(valid), false),
(
true,
Some(ProxyFilesystemBaseline {
read_only: vec!["/a/../b".into()],
..Default::default()
}),
false,
),
(
true,
Some(ProxyFilesystemBaseline {
read_only: vec!["/same".into()],
read_write: vec!["/same".into()],
}),
false,
),
(
true,
Some(ProxyFilesystemBaseline {
read_only: vec!["/a".into(); 65],
..Default::default()
}),
false,
),
] {
let expected = descriptor.clone().map(FilesystemBaseline::from);
let driver = Arc::new(FakeComputeDriver::new().with_capabilities(
GetCapabilitiesResponse {
proxy_filesystem_baseline: descriptor,
extension: Some(openshell_core::extension_protocol::extension_metadata(
ExtensionFamily::Compute,
"openshell/test",
"test",
if declared {
vec![COMPUTE_PROXY_FILESYSTEM_BASELINE.to_string()]
} else {
vec![]
},
)),
..Default::default()
},
));
let store = Arc::new(Store::connect("sqlite::memory:").await.unwrap());
let result = ComputeRuntime::from_driver(
"test".into(),
driver,
None,
store,
SandboxIndex::new(),
SandboxWatchBus::new(),
TracingLogBus::new(),
Arc::new(SupervisorSessionRegistry::new()),
)
.await;
assert_eq!(result.is_ok(), accepted);
if let Ok(runtime) = result {
assert_eq!(runtime.proxy_filesystem_baseline(), expected.as_ref());
}
}
}

#[tokio::test]
async fn negotiated_launch_requirement_rejects_create_before_driver_or_store() {
let driver = Arc::new(TestDriver {
Expand Down
Loading
Loading