Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions crates/openshell-driver-docker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ path = "src/main.rs"

[dependencies]
openshell-core = { path = "../openshell-core", default-features = false, features = ["driver-extraction"] }
openshell-isolation-interface = { path = "../openshell-isolation-interface" }
openshell-sandbox-backend = { path = "../openshell-sandbox-backend" }
openshell-otel = { path = "../openshell-otel" }

opentelemetry = { workspace = true }
Expand All @@ -38,15 +40,19 @@ miette = { workspace = true }
toml = { workspace = true }
tower-http = { workspace = true }
http = { workspace = true }
rand = { workspace = true }
sha2 = { workspace = true }
rustix = { workspace = true }
libc = "0.2"
tar = "0.4"
tempfile = "3"

[dev-dependencies]
openshell-otel-test-support = { path = "../openshell-otel-test-support" }
opentelemetry = { workspace = true }
opentelemetry_sdk = { workspace = true, features = ["testing"] }
prost-types = { workspace = true }
tar = "0.4"
temp-env = { version = "0.3", features = ["async_closure"] }
tempfile = "3"
temp-env = "0.3"
tracing-subscriber = { workspace = true }

[lints]
Expand Down
303 changes: 121 additions & 182 deletions crates/openshell-driver-docker/README.md

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions crates/openshell-driver-docker/src/isolation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Docker provisioning for the shared authenticated boundary protocol.
//!
//! Docker owns only container placement, the protected socket, and immutable OCI resource
//! claims. Lifecycle, process, network, identity, and wire behavior live in
//! `openshell-isolation-interface` and `openshell-sandbox`.

use std::collections::{BTreeMap, HashMap};
use std::net::IpAddr;
use std::path::PathBuf;

use openshell_isolation_interface::contract::{DriverFenceEvidence, ResolvedWorkloadIdentity};
use openshell_sandbox_backend::boundary_protocol::{
BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor,
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
};

/// Driver-owned inputs that bind one Docker container to one boundary.
pub struct DockerBoundarySpec {
pub boundary_id: String,
pub generation: String,
pub session_id: openshell_core::SandboxSessionId,
pub session_rotation: openshell_core::jwt::SessionRotation,
pub gateway_id: String,
pub verification_keys: Vec<GatewayVerificationKey>,
pub container_id: String,
pub image_identity: String,
pub listener_socket: PathBuf,
pub control_socket: PathBuf,
pub sandbox_tls: SandboxTlsServerConfig,
pub supervisor_tls: SandboxTlsClientConfig,
pub host_gateway_ip: Option<IpAddr>,
pub workload_identity: ResolvedWorkloadIdentity,
pub child_env: HashMap<String, String>,
}

/// Protected container config and matching host descriptor.
pub struct DockerBoundaryProvisioning {
pub boundary_config: BoundaryConfig,
pub runtime_descriptor: SandboxRuntimeDescriptor,
}

impl DockerBoundarySpec {
/// Produce both sides of the common protocol from the same immutable
/// Docker coordinates so attach cannot bind a different container.
#[must_use]
pub fn provision(self) -> DockerBoundaryProvisioning {
let resource_claims = BTreeMap::from([
("docker.container_id".to_string(), self.container_id),
("docker.image_identity".to_string(), self.image_identity),
]);
let driver_fence = DriverFenceEvidence::Docker {
container_id: resource_claims["docker.container_id"].clone(),
network_mode: "none".to_string(),
unexpected_networks: Vec::new(),
};
DockerBoundaryProvisioning {
boundary_config: BoundaryConfig {
boundary_id: self.boundary_id.clone(),
generation: self.generation.clone(),
session_id: self.session_id,
session_rotation: self.session_rotation,
gateway_id: self.gateway_id,
verification_keys: self.verification_keys,
listener: BoundaryListener::Unix {
socket_path: self.listener_socket,
tls: self.sandbox_tls,
},
resource_claims: resource_claims.clone(),
resource_claim_files: BTreeMap::new(),
workload_identity: self.workload_identity.clone(),
driver_fence: driver_fence.clone(),
child_env: self.child_env,
},
runtime_descriptor: SandboxRuntimeDescriptor {
boundary_id: self.boundary_id,
generation: self.generation,
session_id: self.session_id,
workload_identity: self.workload_identity,
transport: SandboxTransport::Unix {
socket_path: self.control_socket,
},
tls: self.supervisor_tls,
host_gateway_ip: self.host_gateway_ip,
resource_claims,
driver_fence,
},
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn provisioning_binds_container_and_image_claims() {
let session_id = openshell_core::SandboxSessionId::new();
let tls =
openshell_sandbox_backend::boundary_protocol::generate_sandbox_tls_material(session_id)
.unwrap();
let provisioned = DockerBoundarySpec {
boundary_id: "sandbox-1".to_string(),
generation: "generation-1".to_string(),
session_id,
session_rotation: openshell_core::jwt::SessionRotation::new(1).unwrap(),
gateway_id: "gateway-1".to_string(),
verification_keys: vec![GatewayVerificationKey {
key_id: "key-1".to_string(),
public_key_pem: "public-key".to_string(),
}],
container_id: "sha256:container".to_string(),
image_identity: "sha256:image".to_string(),
listener_socket: PathBuf::from("/run/openshell/boundary/control.sock"),
control_socket: PathBuf::from("/host/control.sock"),
sandbox_tls: SandboxTlsServerConfig {
certificate_chain_path: PathBuf::from("/run/openshell/boundary/server.crt"),
private_key_path: PathBuf::from("/run/openshell/boundary/server.key"),
},
supervisor_tls: SandboxTlsClientConfig {
server_name: tls.server_name,
trust_anchor_pem: tls.trust_anchor_pem,
},
host_gateway_ip: Some(IpAddr::from([127, 0, 0, 1])),
workload_identity: ResolvedWorkloadIdentity::new(
1000,
1000,
Vec::new(),
"image".to_string(),
"sha256:image".to_string(),
)
.unwrap(),
child_env: HashMap::new(),
}
.provision();

assert_eq!(
provisioned.boundary_config.resource_claims,
provisioned.runtime_descriptor.resource_claims
);
assert_eq!(
provisioned.runtime_descriptor.resource_claims["docker.container_id"],
"sha256:container"
);
assert_eq!(
provisioned.boundary_config.driver_fence,
provisioned.runtime_descriptor.driver_fence
);
assert!(
provisioned
.runtime_descriptor
.driver_fence
.validate()
.is_ok()
);
}
}
Loading
Loading