Skip to content

Commit a2443f0

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

9 files changed

Lines changed: 1256 additions & 2 deletions

File tree

‎Cargo.lock‎

Lines changed: 267 additions & 1 deletion
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
@@ -78,6 +78,7 @@ rustix = { version = "1.1", features = ["process"] }
7878
socket2 = "0.6"
7979

8080
# Serialization
81+
container-device-interface = { git = "https://github.com/cncf-tags/container-device-interface-rs", tag = "v1.1.1" }
8182
serde = { version = "1", features = ["derive"] }
8283
serde_json = "1"
8384
serde_yml = "0.0.12"

‎crates/openshell-core/Cargo.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ tempfile = { version = "3", optional = true }
3636
[target.'cfg(unix)'.dependencies]
3737
nix = { workspace = true }
3838

39+
[target.'cfg(target_os = "linux")'.dependencies]
40+
container-device-interface = { workspace = true }
41+
3942
[features]
4043
default = ["telemetry"]
4144
## Compile in anonymous telemetry emission support. On by default; disable with

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

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

0 commit comments

Comments
 (0)