Skip to content

Commit 4913587

Browse files
committed
feat(core): add CDI policy resolver
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 07d4ac5 commit 4913587

11 files changed

Lines changed: 2560 additions & 123 deletions

File tree

‎Cargo.lock‎

Lines changed: 252 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ rustix = { version = "1.1", features = ["process"] }
8383
socket2 = "0.6"
8484

8585
# Serialization
86+
container-device-interface = { git = "https://github.com/cncf-tags/container-device-interface-rs", rev = "b8a056e92dbd159423c129cd306c93e410651b18" }
8687
serde = { version = "1", features = ["derive"] }
8788
serde_json = "1"
8889
serde_yml = { package = "noyalib", version = "0.0.28", default-features = false, features = ["std", "compat-serde-yaml"] }

‎crates/openshell-core/Cargo.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ tempfile = { version = "3", optional = true }
4545
nix = { workspace = true }
4646
rustix = { workspace = true }
4747

48+
[target.'cfg(target_os = "linux")'.dependencies]
49+
container-device-interface = { workspace = true }
50+
4851
[features]
4952
default = ["telemetry"]
5053
## Compile in anonymous telemetry emission support. On by default; disable with

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

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Shared CDI context schema and resolver helpers.
5+
6+
use std::path::{Path, PathBuf};
7+
8+
use serde::{Deserialize, Serialize};
9+
10+
pub const CDI_CONTEXT_VERSION: u32 = 1;
11+
12+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13+
pub struct CdiContext {
14+
pub version: u32,
15+
pub selected_devices: Vec<String>,
16+
pub spec_dirs: Vec<CdiSpecDirectory>,
17+
}
18+
19+
impl CdiContext {
20+
#[must_use]
21+
pub fn new(selected_devices: Vec<String>, spec_dirs: Vec<CdiSpecDirectory>) -> Self {
22+
Self {
23+
version: CDI_CONTEXT_VERSION,
24+
selected_devices,
25+
spec_dirs,
26+
}
27+
}
28+
}
29+
30+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31+
pub struct CdiSpecDirectory {
32+
pub path: String,
33+
pub source: String,
34+
}
35+
36+
impl CdiSpecDirectory {
37+
#[must_use]
38+
pub fn new(path: impl Into<String>, source: impl Into<String>) -> Self {
39+
Self {
40+
path: path.into(),
41+
source: source.into(),
42+
}
43+
}
44+
}
45+
46+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
47+
pub struct CdiDerivedRequirements {
48+
pub device_node_paths: Vec<String>,
49+
pub read_only_mount_paths: Vec<String>,
50+
pub read_write_mount_paths: Vec<String>,
51+
pub additional_gids: Vec<u32>,
52+
}
53+
54+
#[derive(Debug, thiserror::Error)]
55+
pub enum CdiError {
56+
#[error("CDI policy resolution is unavailable on this platform")]
57+
UnsupportedPlatform,
58+
#[error("failed to read CDI context '{}': {source}", path.display())]
59+
ContextRead {
60+
path: PathBuf,
61+
source: std::io::Error,
62+
},
63+
#[error("failed to parse CDI context '{}': {source}", path.display())]
64+
ContextParse {
65+
path: PathBuf,
66+
source: serde_json::Error,
67+
},
68+
#[error("unsupported CDI context version {0}")]
69+
UnsupportedContextVersion(u32),
70+
#[error("CDI spec dir '{path}' from source '{diagnostic_source}' is unsafe: {reason}")]
71+
UnsafeSpecDir {
72+
path: String,
73+
diagnostic_source: String,
74+
reason: &'static str,
75+
},
76+
#[error("selected CDI device '{0}' was not found in mounted CDI specs")]
77+
MissingDevice(String),
78+
#[error(
79+
"selected CDI device '{device}' was not found in mounted CDI specs after CDI spec refresh reported: {refresh_error}"
80+
)]
81+
MissingDeviceAfterRefresh {
82+
device: String,
83+
refresh_error: String,
84+
},
85+
#[error("failed to merge CDI edits for '{device}': {error}")]
86+
EditMerge { device: String, error: String },
87+
#[error("failed to encode resolved CDI edits: {source}")]
88+
EditEncode { source: serde_json::Error },
89+
#[error("failed to decode resolved CDI edits: {source}")]
90+
EditDecode { source: serde_json::Error },
91+
#[error("CDI-derived path '{path}' is unsafe: {reason}")]
92+
UnsafePolicyPath { path: String, reason: &'static str },
93+
#[error("CDI path '{path}' requested conflicting access modes")]
94+
ConflictingAccess { path: String },
95+
#[error(
96+
"CDI writable mount '{path}' is not explicitly listed in the sandbox policy read_write paths"
97+
)]
98+
WritableMountNotAllowed { path: String },
99+
#[error("CDI writable mount '{path}' must target a single file, found {kind}")]
100+
WritableMountNotFile { path: String, kind: String },
101+
#[error("CDI device node '{path}' must target a character or block device, found {kind}")]
102+
DeviceNodeNotDevice { path: String, kind: String },
103+
#[error("CDI additionalGids must not contain root GID 0")]
104+
RootAdditionalGid,
105+
#[error("CDI mount '{path}' has conflicting ro/rw options")]
106+
ConflictingMountOptions { path: String },
107+
}
108+
109+
pub fn read_context(path: impl AsRef<Path>) -> Result<CdiContext, CdiError> {
110+
let path = path.as_ref();
111+
let json = std::fs::read_to_string(path).map_err(|source| CdiError::ContextRead {
112+
path: path.to_path_buf(),
113+
source,
114+
})?;
115+
serde_json::from_str(&json).map_err(|source| CdiError::ContextParse {
116+
path: path.to_path_buf(),
117+
source,
118+
})
119+
}
120+
121+
#[cfg(target_os = "linux")]
122+
#[path = "cdi_linux.rs"]
123+
mod cdi_linux;
124+
#[cfg(target_os = "linux")]
125+
pub use cdi_linux::resolve_cdi_context;
126+
127+
#[cfg(not(target_os = "linux"))]
128+
#[path = "cdi_stub.rs"]
129+
mod cdi_stub;
130+
#[cfg(not(target_os = "linux"))]
131+
pub use cdi_stub::resolve_cdi_context;

0 commit comments

Comments
 (0)