Skip to content

Commit 70acb90

Browse files
committed
fix(kubernetes): reject unknown driver config fields
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent f07792d commit 70acb90

2 files changed

Lines changed: 77 additions & 23 deletions

File tree

crates/openshell-driver-kubernetes/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ openshell sandbox create \
9393
```
9494

9595
Resource keys use native Kubernetes resource names and quantity strings. The
96-
POC parser renders the keys listed above and ignores unknown fields.
96+
POC parser renders the keys listed above and rejects unknown fields.
9797
`pod.runtime_class_name` maps to PodSpec `runtimeClassName` and overrides the
9898
driver's configured `default_runtime_class_name`; the typed public
9999
`SandboxTemplate.runtime_class_name` still takes precedence when set. Use the

crates/openshell-driver-kubernetes/src/driver.rs

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum KubernetesDriverError {
4545
#[error("sandbox already exists")]
4646
AlreadyExists,
4747
#[error("{0}")]
48+
InvalidArgument(String),
49+
#[error("{0}")]
4850
Precondition(String),
4951
#[error("{0}")]
5052
Message(String),
@@ -63,6 +65,7 @@ impl From<KubernetesDriverError> for openshell_core::ComputeDriverError {
6365
fn from(err: KubernetesDriverError) -> Self {
6466
match err {
6567
KubernetesDriverError::AlreadyExists => Self::AlreadyExists,
68+
KubernetesDriverError::InvalidArgument(m) => Self::InvalidArgument(m),
6669
KubernetesDriverError::Precondition(m) => Self::Precondition(m),
6770
KubernetesDriverError::Message(m) => Self::Message(m),
6871
}
@@ -89,14 +92,38 @@ const SPIFFE_WORKLOAD_API_VOLUME_NAME: &str = "spiffe-workload-api";
8992
// translation layer; the RFC boundary is Struct at the gateway, typed config in
9093
// the selected driver.
9194
#[derive(Debug, Clone, Default, Deserialize)]
92-
#[serde(default)]
95+
#[serde(default, deny_unknown_fields)]
9396
struct KubernetesSandboxDriverConfig {
9497
pod: KubernetesPodDriverConfig,
9598
containers: KubernetesDriverContainersConfig,
9699
}
97100

101+
impl KubernetesSandboxDriverConfig {
102+
fn from_sandbox(sandbox: &Sandbox) -> Result<Self, String> {
103+
let Some(template) = sandbox
104+
.spec
105+
.as_ref()
106+
.and_then(|spec| spec.template.as_ref())
107+
else {
108+
return Ok(Self::default());
109+
};
110+
111+
Self::from_template(template)
112+
}
113+
114+
fn from_template(template: &SandboxTemplate) -> Result<Self, String> {
115+
let Some(config) = template.driver_config.as_ref() else {
116+
return Ok(Self::default());
117+
};
118+
119+
let json = serde_json::Value::Object(struct_to_json_object(config));
120+
serde_json::from_value(json)
121+
.map_err(|err| format!("invalid kubernetes driver_config: {err}"))
122+
}
123+
}
124+
98125
#[derive(Debug, Clone, Default, Deserialize)]
99-
#[serde(default)]
126+
#[serde(default, deny_unknown_fields)]
100127
struct KubernetesPodDriverConfig {
101128
node_selector: BTreeMap<String, String>,
102129
runtime_class_name: String,
@@ -105,19 +132,19 @@ struct KubernetesPodDriverConfig {
105132
}
106133

107134
#[derive(Debug, Clone, Default, Deserialize)]
108-
#[serde(default)]
135+
#[serde(default, deny_unknown_fields)]
109136
struct KubernetesDriverContainersConfig {
110137
agent: KubernetesContainerDriverConfig,
111138
}
112139

113140
#[derive(Debug, Clone, Default, Deserialize)]
114-
#[serde(default)]
141+
#[serde(default, deny_unknown_fields)]
115142
struct KubernetesContainerDriverConfig {
116143
resources: KubernetesContainerResourceConfig,
117144
}
118145

119146
#[derive(Debug, Clone, Default, Deserialize)]
120-
#[serde(default)]
147+
#[serde(default, deny_unknown_fields)]
121148
struct KubernetesContainerResourceConfig {
122149
requests: BTreeMap<String, String>,
123150
limits: BTreeMap<String, String>,
@@ -252,6 +279,8 @@ impl KubernetesComputeDriver {
252279
}
253280

254281
pub async fn validate_sandbox_create(&self, sandbox: &Sandbox) -> Result<(), tonic::Status> {
282+
let _ = KubernetesSandboxDriverConfig::from_sandbox(sandbox)
283+
.map_err(tonic::Status::invalid_argument)?;
255284
let gpu_requested = sandbox.spec.as_ref().is_some_and(|spec| spec.gpu);
256285
if gpu_requested
257286
&& !self.has_gpu_capacity().await.map_err(|err| {
@@ -345,6 +374,8 @@ impl KubernetesComputeDriver {
345374
}
346375

347376
pub async fn create_sandbox(&self, sandbox: &Sandbox) -> Result<(), KubernetesDriverError> {
377+
let _ = KubernetesSandboxDriverConfig::from_sandbox(sandbox)
378+
.map_err(KubernetesDriverError::InvalidArgument)?;
348379
let name = sandbox.name.as_str();
349380
info!(
350381
sandbox_id = %sandbox.id,
@@ -1144,18 +1175,8 @@ fn spec_pod_env(spec: Option<&SandboxSpec>) -> std::collections::HashMap<String,
11441175
}
11451176

11461177
fn kubernetes_driver_config(template: &SandboxTemplate) -> KubernetesSandboxDriverConfig {
1147-
let Some(config) = template.driver_config.as_ref() else {
1148-
return KubernetesSandboxDriverConfig::default();
1149-
};
1150-
1151-
let json = serde_json::Value::Object(struct_to_json_object(config));
1152-
match serde_json::from_value(json) {
1153-
Ok(config) => config,
1154-
Err(err) => {
1155-
warn!(error = %err, "Ignoring invalid Kubernetes driver_config");
1156-
KubernetesSandboxDriverConfig::default()
1157-
}
1158-
}
1178+
KubernetesSandboxDriverConfig::from_template(template)
1179+
.expect("validated Kubernetes driver_config")
11591180
}
11601181

11611182
fn sandbox_to_k8s_spec(
@@ -1947,19 +1968,52 @@ mod tests {
19471968
}
19481969

19491970
#[test]
1950-
fn driver_config_ignores_invalid_shape() {
1971+
fn driver_config_rejects_invalid_shape() {
19511972
let template = SandboxTemplate {
19521973
driver_config: Some(json_struct(serde_json::json!({
19531974
"pod": "not-an-object"
19541975
}))),
19551976
..SandboxTemplate::default()
19561977
};
19571978

1958-
let config = kubernetes_driver_config(&template);
1979+
let err = KubernetesSandboxDriverConfig::from_template(&template).unwrap_err();
1980+
1981+
assert!(err.contains("invalid kubernetes driver_config"));
1982+
}
1983+
1984+
#[test]
1985+
fn driver_config_rejects_unknown_fields() {
1986+
let template = SandboxTemplate {
1987+
driver_config: Some(json_struct(serde_json::json!({
1988+
"cdi_devices": ["nvidia.com/gpu=0"]
1989+
}))),
1990+
..SandboxTemplate::default()
1991+
};
1992+
1993+
let err = KubernetesSandboxDriverConfig::from_template(&template).unwrap_err();
1994+
1995+
assert!(err.contains("unknown field"));
1996+
}
1997+
1998+
#[test]
1999+
fn driver_config_from_sandbox_rejects_unknown_fields() {
2000+
let sandbox = Sandbox {
2001+
id: "sandbox-123".to_string(),
2002+
spec: Some(SandboxSpec {
2003+
gpu: true,
2004+
template: Some(SandboxTemplate {
2005+
driver_config: Some(json_struct(serde_json::json!({
2006+
"cdi_devices": ["nvidia.com/gpu=0"]
2007+
}))),
2008+
..Default::default()
2009+
}),
2010+
..Default::default()
2011+
}),
2012+
..Default::default()
2013+
};
19592014

1960-
assert!(config.pod.node_selector.is_empty());
1961-
assert!(config.containers.agent.resources.requests.is_empty());
1962-
assert!(config.containers.agent.resources.limits.is_empty());
2015+
let err = KubernetesSandboxDriverConfig::from_sandbox(&sandbox).unwrap_err();
2016+
assert!(err.contains("unknown field"));
19632017
}
19642018

19652019
#[test]

0 commit comments

Comments
 (0)