Skip to content

Commit 93c9ace

Browse files
committed
fix(docker): bind CDI context read-only
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent d5418c6 commit 93c9ace

2 files changed

Lines changed: 157 additions & 130 deletions

File tree

‎crates/openshell-driver-docker/src/lib.rs‎

Lines changed: 134 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
#![allow(clippy::result_large_err)]
77

8+
use bollard::Docker;
89
use bollard::errors::Error as BollardError;
910
use bollard::models::{
1011
ContainerCreateBody, ContainerState, ContainerStateStatusEnum, ContainerSummary,
@@ -15,9 +16,7 @@ use bollard::models::{
1516
use bollard::query_parameters::{
1617
CreateContainerOptionsBuilder, CreateImageOptions, DownloadFromContainerOptionsBuilder,
1718
ListContainersOptionsBuilder, RemoveContainerOptionsBuilder, StopContainerOptionsBuilder,
18-
UploadToContainerOptionsBuilder,
1919
};
20-
use bollard::{Docker, body_full};
2120
use bytes::Bytes;
2221
use futures::{Stream, StreamExt};
2322
use openshell_core::cdi::{CdiContext, CdiSpecDirectory, cdi_spec_mount_path};
@@ -58,7 +57,6 @@ use openshell_core::proto_struct::{
5857
};
5958
use openshell_core::{Config, Error, Result as CoreResult};
6059
use std::collections::{HashMap, HashSet};
61-
use std::io::Cursor;
6260
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
6361
use std::path::{Path, PathBuf};
6462
use std::pin::Pin;
@@ -861,11 +859,22 @@ impl DockerComputeDriver {
861859
.map_err(|status| {
862860
DockerProvisioningFailure::new("ContainerCreateFailed", status.message())
863861
})?;
864-
write_sandbox_token_file(sandbox, &self.config)
865-
.await
866-
.map_err(|status| {
867-
DockerProvisioningFailure::new("SandboxTokenWriteFailed", status.message())
868-
})?;
862+
if let Some(cdi_context) = cdi_context.as_ref()
863+
&& let Err(status) = write_cdi_context_file(sandbox, &self.config, cdi_context)
864+
{
865+
cleanup_cdi_context_file(sandbox, &self.config);
866+
return Err(DockerProvisioningFailure::new(
867+
"CdiContextWriteFailed",
868+
status.message(),
869+
));
870+
}
871+
if let Err(status) = write_sandbox_token_file(sandbox, &self.config).await {
872+
cleanup_cdi_context_file(sandbox, &self.config);
873+
return Err(DockerProvisioningFailure::new(
874+
"SandboxTokenWriteFailed",
875+
status.message(),
876+
));
877+
}
869878
if let Err(err) = self
870879
.docker
871880
.create_container(
@@ -878,7 +887,7 @@ impl DockerComputeDriver {
878887
)
879888
.await
880889
{
881-
cleanup_sandbox_token_file(sandbox, &self.config);
890+
cleanup_sandbox_state_files(sandbox, &self.config);
882891
return Err(DockerProvisioningFailure::from_status(
883892
"ContainerCreateFailed",
884893
create_status_from_docker_error("create docker sandbox container", err),
@@ -891,30 +900,14 @@ impl DockerComputeDriver {
891900
HashMap::from([("container_name".to_string(), container_name.clone())]),
892901
);
893902

894-
if let Some(cdi_context) = cdi_context
895-
&& let Err(err) = self.upload_cdi_context(&container_name, &cdi_context).await
896-
{
897-
self.cleanup_created_container_after_failure(
898-
&sandbox.id,
899-
&container_name,
900-
"CDI context upload failure",
901-
)
902-
.await;
903-
cleanup_sandbox_token_file(sandbox, &self.config);
904-
return Err(DockerProvisioningFailure::from_status(
905-
"CdiContextUploadFailed",
906-
err,
907-
));
908-
}
909-
910903
if let Err(err) = self.docker.start_container(&container_name, None).await {
911904
self.cleanup_created_container_after_failure(
912905
&sandbox.id,
913906
&container_name,
914907
"container start failure",
915908
)
916909
.await;
917-
cleanup_sandbox_token_file(sandbox, &self.config);
910+
cleanup_sandbox_state_files(sandbox, &self.config);
918911
return Err(DockerProvisioningFailure::from_status(
919912
"ContainerStartFailed",
920913
create_status_from_docker_error("start docker sandbox container", err),
@@ -964,27 +957,6 @@ impl DockerComputeDriver {
964957
}
965958
}
966959

967-
async fn upload_cdi_context(
968-
&self,
969-
container_name: &str,
970-
context: &CdiContext,
971-
) -> Result<(), Status> {
972-
let archive = build_cdi_context_archive(context).map_err(Status::internal)?;
973-
self.docker
974-
.upload_to_container(
975-
container_name,
976-
Some(
977-
UploadToContainerOptionsBuilder::default()
978-
.path("/run")
979-
.no_overwrite_dir_non_dir("true")
980-
.build(),
981-
),
982-
body_full(Bytes::from(archive)),
983-
)
984-
.await
985-
.map_err(|err| internal_status("upload CDI context to Docker container", err))
986-
}
987-
988960
async fn delete_sandbox_inner(
989961
&self,
990962
sandbox_id: &str,
@@ -1012,11 +984,11 @@ impl DockerComputeDriver {
1012984
.await
1013985
{
1014986
Ok(()) => {
1015-
cleanup_sandbox_token_file(&record.sandbox, &self.config);
987+
cleanup_sandbox_state_files(&record.sandbox, &self.config);
1016988
return Ok(true);
1017989
}
1018990
Err(err) if is_not_found_error(&err) => {
1019-
cleanup_sandbox_token_file(&record.sandbox, &self.config);
991+
cleanup_sandbox_state_files(&record.sandbox, &self.config);
1020992
return Ok(true);
1021993
}
1022994
Err(err) => {
@@ -1039,11 +1011,11 @@ impl DockerComputeDriver {
10391011
.await
10401012
{
10411013
Ok(()) => {
1042-
cleanup_sandbox_token_file_for_delete(sandbox_id, pending.as_ref(), &self.config);
1014+
cleanup_sandbox_state_files_for_delete(sandbox_id, pending.as_ref(), &self.config);
10431015
Ok(true)
10441016
}
10451017
Err(err) if is_not_found_error(&err) => {
1046-
cleanup_sandbox_token_file_for_delete(sandbox_id, pending.as_ref(), &self.config);
1018+
cleanup_sandbox_state_files_for_delete(sandbox_id, pending.as_ref(), &self.config);
10471019
Ok(pending.is_some())
10481020
}
10491021
Err(err) => Err(internal_status("delete docker sandbox container", err)),
@@ -1059,7 +1031,7 @@ impl DockerComputeDriver {
10591031
if let Some(task) = record.task {
10601032
task.abort();
10611033
}
1062-
cleanup_sandbox_token_file(&record.sandbox, &self.config);
1034+
cleanup_sandbox_state_files(&record.sandbox, &self.config);
10631035
self.publish_deleted(record.sandbox.id);
10641036
return Ok(());
10651037
}
@@ -1287,7 +1259,7 @@ impl DockerComputeDriver {
12871259
sandbox: &DriverSandbox,
12881260
failure: &DockerProvisioningFailure,
12891261
) {
1290-
cleanup_sandbox_token_file(sandbox, &self.config);
1262+
cleanup_sandbox_state_files(sandbox, &self.config);
12911263
let snapshot = pending_sandbox_snapshot(
12921264
sandbox,
12931265
&self.config.sandbox_namespace,
@@ -2375,6 +2347,7 @@ fn docker_volume_is_bind_backed(volume: &bollard::models::Volume) -> bool {
23752347
fn build_binds(
23762348
sandbox: &DriverSandbox,
23772349
config: &DockerDriverRuntimeConfig,
2350+
gpu_device_ids: Option<&[String]>,
23782351
) -> Result<Vec<String>, Status> {
23792352
let mut binds = vec![format!(
23802353
"{}:{}:ro,z",
@@ -2401,6 +2374,13 @@ fn build_binds(
24012374
SANDBOX_TOKEN_MOUNT_PATH
24022375
));
24032376
}
2377+
if cdi_context_requested(gpu_device_ids) {
2378+
binds.push(format!(
2379+
"{}:{}:ro,z",
2380+
cdi_context_host_path(sandbox, config)?.display(),
2381+
openshell_core::cdi::CDI_CONTEXT_PATH
2382+
));
2383+
}
24042384
Ok(binds)
24052385
}
24062386

@@ -2427,6 +2407,57 @@ fn sandbox_token_host_path_by_id(
24272407
})
24282408
}
24292409

2410+
fn cdi_context_host_path(
2411+
sandbox: &DriverSandbox,
2412+
config: &DockerDriverRuntimeConfig,
2413+
) -> Result<PathBuf, Status> {
2414+
cdi_context_host_path_by_id(&sandbox.id, config)
2415+
}
2416+
2417+
fn cdi_context_host_path_by_id(
2418+
sandbox_id: &str,
2419+
config: &DockerDriverRuntimeConfig,
2420+
) -> Result<PathBuf, Status> {
2421+
openshell_core::driver_utils::sandbox_token_path(
2422+
"docker-cdi-contexts",
2423+
Some(&config.sandbox_namespace),
2424+
sandbox_id,
2425+
)
2426+
.map(|path| path.with_file_name(openshell_core::cdi::CDI_CONTEXT_FILE_NAME))
2427+
.map_err(|err| Status::internal(format!("resolve CDI context state directory failed: {err}")))
2428+
}
2429+
2430+
fn write_cdi_context_file(
2431+
sandbox: &DriverSandbox,
2432+
config: &DockerDriverRuntimeConfig,
2433+
context: &CdiContext,
2434+
) -> Result<(), Status> {
2435+
let path = cdi_context_host_path(sandbox, config)?;
2436+
if let Some(parent) = path.parent() {
2437+
openshell_core::paths::create_dir_restricted(parent).map_err(|err| {
2438+
Status::internal(format!(
2439+
"create CDI context directory {} failed: {err}",
2440+
parent.display()
2441+
))
2442+
})?;
2443+
}
2444+
let json = serde_json::to_vec(context)
2445+
.map_err(|err| Status::internal(format!("encode CDI context failed: {err}")))?;
2446+
std::fs::write(&path, json).map_err(|err| {
2447+
Status::internal(format!(
2448+
"write CDI context file {} failed: {err}",
2449+
path.display()
2450+
))
2451+
})?;
2452+
openshell_core::paths::set_file_owner_only(&path).map_err(|err| {
2453+
Status::internal(format!(
2454+
"restrict CDI context file {} failed: {err}",
2455+
path.display()
2456+
))
2457+
})?;
2458+
Ok(())
2459+
}
2460+
24302461
async fn write_sandbox_token_file(
24312462
sandbox: &DriverSandbox,
24322463
config: &DockerDriverRuntimeConfig,
@@ -2467,6 +2498,15 @@ fn cleanup_sandbox_token_file(sandbox: &DriverSandbox, config: &DockerDriverRunt
24672498
cleanup_sandbox_token_file_by_id(&sandbox.id, config);
24682499
}
24692500

2501+
fn cleanup_cdi_context_file(sandbox: &DriverSandbox, config: &DockerDriverRuntimeConfig) {
2502+
cleanup_cdi_context_file_by_id(&sandbox.id, config);
2503+
}
2504+
2505+
fn cleanup_sandbox_state_files(sandbox: &DriverSandbox, config: &DockerDriverRuntimeConfig) {
2506+
cleanup_sandbox_token_file(sandbox, config);
2507+
cleanup_cdi_context_file(sandbox, config);
2508+
}
2509+
24702510
fn cleanup_sandbox_token_file_for_delete(
24712511
sandbox_id: &str,
24722512
pending: Option<&PendingSandboxRecord>,
@@ -2479,6 +2519,27 @@ fn cleanup_sandbox_token_file_for_delete(
24792519
}
24802520
}
24812521

2522+
fn cleanup_cdi_context_file_for_delete(
2523+
sandbox_id: &str,
2524+
pending: Option<&PendingSandboxRecord>,
2525+
config: &DockerDriverRuntimeConfig,
2526+
) {
2527+
if !sandbox_id.is_empty() {
2528+
cleanup_cdi_context_file_by_id(sandbox_id, config);
2529+
} else if let Some(record) = pending {
2530+
cleanup_cdi_context_file(&record.sandbox, config);
2531+
}
2532+
}
2533+
2534+
fn cleanup_sandbox_state_files_for_delete(
2535+
sandbox_id: &str,
2536+
pending: Option<&PendingSandboxRecord>,
2537+
config: &DockerDriverRuntimeConfig,
2538+
) {
2539+
cleanup_sandbox_token_file_for_delete(sandbox_id, pending, config);
2540+
cleanup_cdi_context_file_for_delete(sandbox_id, pending, config);
2541+
}
2542+
24822543
fn cleanup_sandbox_token_file_by_id(sandbox_id: &str, config: &DockerDriverRuntimeConfig) {
24832544
let Ok(path) = sandbox_token_host_path_by_id(sandbox_id, config) else {
24842545
return;
@@ -2498,6 +2559,25 @@ fn cleanup_sandbox_token_file_by_id(sandbox_id: &str, config: &DockerDriverRunti
24982559
}
24992560
}
25002561

2562+
fn cleanup_cdi_context_file_by_id(sandbox_id: &str, config: &DockerDriverRuntimeConfig) {
2563+
let Ok(path) = cdi_context_host_path_by_id(sandbox_id, config) else {
2564+
return;
2565+
};
2566+
if let Err(err) = std::fs::remove_file(&path)
2567+
&& err.kind() != std::io::ErrorKind::NotFound
2568+
{
2569+
warn!(
2570+
sandbox_id = %sandbox_id,
2571+
path = %path.display(),
2572+
error = %err,
2573+
"Failed to remove Docker CDI context file"
2574+
);
2575+
}
2576+
if let Some(dir) = path.parent() {
2577+
let _ = std::fs::remove_dir(dir);
2578+
}
2579+
}
2580+
25012581
#[cfg(test)]
25022582
fn build_environment(
25032583
sandbox: &DriverSandbox,
@@ -2663,64 +2743,6 @@ fn cdi_context_requested(gpu_device_ids: Option<&[String]>) -> bool {
26632743
gpu_device_ids.is_some_and(|device_ids| !device_ids.is_empty())
26642744
}
26652745

2666-
fn build_cdi_context_archive(context: &CdiContext) -> Result<Vec<u8>, String> {
2667-
let json = serde_json::to_vec_pretty(context).map_err(|err| err.to_string())?;
2668-
let mut archive = DockerTarArchiveBuilder::new();
2669-
archive.append_dir("openshell", 0o700)?;
2670-
archive.append_dir("openshell/supervisor", 0o700)?;
2671-
archive.append_file(
2672-
&format!(
2673-
"openshell/supervisor/{}",
2674-
openshell_core::cdi::CDI_CONTEXT_FILE_NAME
2675-
),
2676-
0o600,
2677-
json,
2678-
)?;
2679-
archive.into_inner()
2680-
}
2681-
2682-
struct DockerTarArchiveBuilder {
2683-
inner: tar::Builder<Vec<u8>>,
2684-
}
2685-
2686-
impl DockerTarArchiveBuilder {
2687-
fn new() -> Self {
2688-
Self {
2689-
inner: tar::Builder::new(Vec::new()),
2690-
}
2691-
}
2692-
2693-
fn append_dir(&mut self, path: &str, mode: u32) -> Result<(), String> {
2694-
let mut header = tar::Header::new_gnu();
2695-
header.set_entry_type(tar::EntryType::Directory);
2696-
header.set_size(0);
2697-
header.set_mode(mode);
2698-
header.set_uid(0);
2699-
header.set_gid(0);
2700-
header.set_cksum();
2701-
self.inner
2702-
.append_data(&mut header, path, std::io::empty())
2703-
.map_err(|err| err.to_string())
2704-
}
2705-
2706-
fn append_file(&mut self, path: &str, mode: u32, contents: Vec<u8>) -> Result<(), String> {
2707-
let mut header = tar::Header::new_gnu();
2708-
header.set_entry_type(tar::EntryType::Regular);
2709-
header.set_size(u64::try_from(contents.len()).map_err(|err| err.to_string())?);
2710-
header.set_mode(mode);
2711-
header.set_uid(0);
2712-
header.set_gid(0);
2713-
header.set_cksum();
2714-
self.inner
2715-
.append_data(&mut header, path, Cursor::new(contents))
2716-
.map_err(|err| err.to_string())
2717-
}
2718-
2719-
fn into_inner(self) -> Result<Vec<u8>, String> {
2720-
self.inner.into_inner().map_err(|err| err.to_string())
2721-
}
2722-
}
2723-
27242746
#[cfg(test)]
27252747
fn build_container_create_body(
27262748
sandbox: &DriverSandbox,
@@ -2874,7 +2896,7 @@ fn build_container_create_body_for_image(
28742896
pids_limit: docker_pids_limit(config.sandbox_pids_limit)?,
28752897
device_requests,
28762898
binds: {
2877-
let mut binds = build_binds(sandbox, config)?;
2899+
let mut binds = build_binds(sandbox, config, gpu_device_ids)?;
28782900
binds.extend(config.gpu.cdi_spec_bind_strings(gpu_device_ids)?);
28792901
binds.extend(user_bind_strings);
28802902
Some(binds)

0 commit comments

Comments
 (0)