Skip to content

Commit 114b493

Browse files
committed
refactor(runtime): extract driver-independent MXC foundations
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 8829cb2 commit 114b493

49 files changed

Lines changed: 2441 additions & 364 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/openshell-core/README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ configuration validation live in `openshell_core::middleware`. Policy admission
5858
and the supervisor runtime consume the same contract without introducing a
5959
dependency from the policy crate to the supervisor implementation.
6060

61+
## Extension Capabilities
62+
63+
Extensions advertise namespaced string capabilities through `PeerMetadata`.
64+
Use shared constants in `extension_protocol` and pass optional capabilities to
65+
`extension_metadata`; do not add per-driver booleans for policy features.
66+
Negotiation validates and retains the extension's supported capability list.
67+
68+
`POLICY_UI_V1` (`openshell.policy.ui.v1`) declares complete startup UI-policy
69+
support. The gateway checks the effective policy before driver validation or
70+
creation and rejects any explicit UI section when the capability is absent.
71+
Supporting drivers validate and enforce the concrete settings. Omitted UI
72+
preserves existing driver behavior; changing UI requires sandbox recreation.
73+
6174
## Protobuf Struct Conversion
6275

6376
Use `openshell_core::proto_struct` when crossing between `serde_json` values and

‎crates/openshell-core/src/extension_protocol.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ pub const PROTOCOL_MINOR: u32 = 0;
1919
/// configured signer before accepting sandbox creation.
2020
pub const COMPUTE_LAUNCH_AUTHENTICATION: &str = "openshell.compute.launch-authentication";
2121

22+
/// Compute drivers advertising this capability enforce the complete startup UI
23+
/// policy contract. Absence means explicit UI policy must be rejected.
24+
pub const POLICY_UI_V1: &str = "openshell.policy.ui.v1";
25+
2226
const MAX_IMPLEMENTATION_NAME_BYTES: usize = 128;
2327
const MAX_IMPLEMENTATION_VERSION_BYTES: usize = 128;
2428
const MAX_CAPABILITY_BYTES: usize = 128;
@@ -398,6 +402,36 @@ mod tests {
398402
);
399403
}
400404

405+
#[test]
406+
fn ui_policy_capability_uses_existing_optional_negotiation() {
407+
let gateway = gateway_metadata(ExtensionFamily::Compute);
408+
let extension = extension_metadata(
409+
ExtensionFamily::Compute,
410+
"openshell/test-ui-driver",
411+
"test",
412+
[POLICY_UI_V1.to_string()],
413+
);
414+
let result = negotiate(
415+
ExtensionFamily::Compute,
416+
"test-ui-driver",
417+
&gateway,
418+
Some(extension),
419+
)
420+
.unwrap();
421+
assert!(
422+
result
423+
.supported_capabilities
424+
.iter()
425+
.any(|value| value == POLICY_UI_V1)
426+
);
427+
assert!(
428+
!result
429+
.required_capabilities
430+
.iter()
431+
.any(|value| value == POLICY_UI_V1)
432+
);
433+
}
434+
401435
#[test]
402436
fn missing_metadata_and_major_skew_are_actionable() {
403437
let (gateway, mut extension) = compatible();

‎crates/openshell-core/src/paths.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ pub fn openshell_state_dir() -> Result<PathBuf> {
5757
Ok(xdg_state_dir()?.join("openshell"))
5858
}
5959

60+
/// Platform default for gateway audit files. Sink configuration and writing
61+
/// remain portable; only the directory convention depends on the host.
62+
pub fn gateway_audit_log_dir() -> PathBuf {
63+
#[cfg(target_os = "windows")]
64+
{
65+
if let Some(directory) = std::env::var_os("ProgramData") {
66+
return PathBuf::from(directory).join("OpenShell").join("logs");
67+
}
68+
std::env::temp_dir().join("openshell").join("logs")
69+
}
70+
#[cfg(not(target_os = "windows"))]
71+
{
72+
openshell_state_dir()
73+
.unwrap_or_else(|_| std::env::temp_dir().join("openshell"))
74+
.join("logs")
75+
}
76+
}
77+
6078
/// Resolve the XDG data base directory.
6179
///
6280
/// Returns `$XDG_DATA_HOME` if set, otherwise `$HOME/.local/share`.

‎crates/openshell-core/src/policy.rs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ impl TryFrom<ProtoSandboxPolicy> for SandboxPolicy {
113113
type Error = miette::Report;
114114

115115
fn try_from(proto: ProtoSandboxPolicy) -> Result<Self, Self::Error> {
116+
// UI capabilities are intentionally absent from the portable supervisor
117+
// runtime. Non-Windows compute paths do not expose them, so even a
118+
// schema-level UI allowance cannot grant a UI surface there. A capable
119+
// compute driver consumes the typed proto directly.
116120
// In cluster mode we always run with proxy networking so all egress
117121
// can be evaluated by OPA.
118122
let network = NetworkPolicy {
@@ -192,6 +196,7 @@ impl From<ProtoProcessPolicy> for ProcessPolicy {
192196
#[cfg(test)]
193197
mod tests {
194198
use super::*;
199+
use crate::proto::{UiClipboardAccess, UiPolicy};
195200

196201
#[test]
197202
fn try_from_maps_known_compatibility_values() {
@@ -233,4 +238,26 @@ mod tests {
233238
assert!(!is_valid_landlock_compatibility("nope"));
234239
assert!(!is_valid_landlock_compatibility("BestEffort"));
235240
}
241+
242+
#[test]
243+
fn portable_runtime_does_not_activate_ui_allowances() {
244+
let converted = SandboxPolicy::try_from(ProtoSandboxPolicy {
245+
version: 1,
246+
ui: Some(UiPolicy {
247+
allow_graphical_ui: true,
248+
clipboard: UiClipboardAccess::All as i32,
249+
allow_input_injection: true,
250+
}),
251+
..Default::default()
252+
})
253+
.expect("portable policy conversion succeeds");
254+
255+
assert_eq!(converted.version, 1);
256+
assert!(matches!(converted.network.mode, NetworkMode::Proxy));
257+
assert!(converted.network.proxy.is_some());
258+
assert!(converted.filesystem.read_only.is_empty());
259+
assert!(converted.filesystem.read_write.is_empty());
260+
assert!(converted.process.run_as_user.is_none());
261+
assert!(converted.process.run_as_group.is_none());
262+
}
236263
}

‎crates/openshell-core/src/provider_credentials.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,18 @@ impl ProviderCredentialState {
399399
.revision
400400
}
401401

402+
/// Whether this snapshot contains endpoint-bound material that must be
403+
/// resolved by a network proxy rather than exposed to the child process.
404+
#[must_use]
405+
pub fn requires_proxy_resolution(&self) -> bool {
406+
let inner = self
407+
.inner
408+
.read()
409+
.expect("provider credential state poisoned");
410+
!inner.static_credential_bindings.is_empty()
411+
|| !inner.current.dynamic_credentials.is_empty()
412+
}
413+
402414
/// Remove a key from the credential snapshot's child env.
403415
///
404416
/// Used when a sandbox-side service (e.g., metadata server) fails to start

‎crates/openshell-driver-mxc/tests/policy_mapper_matrix.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ fn handled_fields_inventory() {
11091109
// ── (1) Top-level keys: serialize a SandboxPolicy with every section
11101110
// present-but-minimal, then collect YAML keys. ──────────────────────────
11111111
let full_toplevel_policy = SandboxPolicy {
1112+
ui: None,
11121113
version: 1,
11131114
filesystem: Some(FilesystemPolicy {
11141115
include_workdir: false,

‎crates/openshell-isolation-interface/src/contract.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ pub trait BoundaryLoopbackConnector: Send + Sync {
857857
/// that owns a connection or one of its executable ancestors. A missing digest
858858
/// is `None`, never an empty value; binary-scoped policy cannot authorize an
859859
/// executable whose digest is unavailable.
860-
#[derive(Debug, Clone, PartialEq, Eq)]
860+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
861861
pub struct ExecutableIdentity {
862862
/// Absolute executable path in the workload filesystem namespace.
863863
pub path: PathBuf,
@@ -871,7 +871,7 @@ pub struct ExecutableIdentity {
871871
///
872872
/// How a backend resolves identity is private to that backend; the shape and
873873
/// fail-closed semantics do not change.
874-
#[derive(Debug, Clone)]
874+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
875875
pub struct BinaryIdentity {
876876
/// Executable that owns the accepted connection.
877877
pub executable: ExecutableIdentity,

‎crates/openshell-policy-schema/src/lib.rs‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ pub struct PolicyDocument {
193193
skip_serializing_if = "Option::is_none"
194194
)]
195195
pub process: Option<ProcessPolicy>,
196+
#[serde(
197+
default,
198+
deserialize_with = "deserialize_non_null_optional_field",
199+
skip_serializing_if = "Option::is_none"
200+
)]
201+
pub ui: Option<UiPolicy>,
196202
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
197203
pub network_policies: BTreeMap<String, NetworkPolicyRule>,
198204
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
@@ -234,6 +240,34 @@ pub struct ProcessPolicy {
234240
pub run_as_group: String,
235241
}
236242

243+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
244+
#[serde(deny_unknown_fields)]
245+
pub struct UiPolicy {
246+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
247+
pub allow_graphical_ui: bool,
248+
#[serde(default, skip_serializing_if = "UiClipboardAccess::is_none")]
249+
pub clipboard: UiClipboardAccess,
250+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
251+
pub allow_input_injection: bool,
252+
}
253+
254+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
255+
#[serde(rename_all = "snake_case")]
256+
pub enum UiClipboardAccess {
257+
#[default]
258+
None,
259+
Read,
260+
Write,
261+
All,
262+
}
263+
264+
impl UiClipboardAccess {
265+
#[must_use]
266+
pub const fn is_none(&self) -> bool {
267+
matches!(self, Self::None)
268+
}
269+
}
270+
237271
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238272
#[serde(deny_unknown_fields)]
239273
pub struct NetworkPolicyRule {
@@ -698,6 +732,7 @@ fn inspect_document(root: &serde_yml::Value) -> InspectionResult {
698732
"filesystem_policy",
699733
"landlock",
700734
"process",
735+
"ui",
701736
"network_policies",
702737
"network_middlewares",
703738
],
@@ -717,6 +752,11 @@ fn inspect_document(root: &serde_yml::Value) -> InspectionResult {
717752
"process",
718753
&["run_as_user", "run_as_group"],
719754
)?;
755+
inspect_named(
756+
root.get("ui"),
757+
"ui",
758+
&["allow_graphical_ui", "clipboard", "allow_input_injection"],
759+
)?;
720760

721761
for (name, rule) in open_map(root.get("network_policies")) {
722762
let path = join("network_policies", name);
@@ -1176,6 +1216,7 @@ mod tests {
11761216
for source in [
11771217
"version: 1\nfilesystem_policy: null\n",
11781218
"version: 1\nprocess: null\n",
1219+
"version: 1\nui: null\n",
11791220
"version: 1\nmetadata: null\n",
11801221
"version: 1\nnetwork_policies:\n x:\n endpoints:\n - host: x\n port: 443\n mcp: null\n",
11811222
] {
@@ -1212,6 +1253,7 @@ mod tests {
12121253
"landlock.future",
12131254
),
12141255
("version: 1\nprocess: { future: true }\n", "process.future"),
1256+
("version: 1\nui: { future: true }\n", "ui.future"),
12151257
(
12161258
"version: 1\nnetwork_policies: { api: { future: true } }\n",
12171259
"network_policies.api.future",

0 commit comments

Comments
 (0)