Skip to content

Commit cbc7aa2

Browse files
committed
refactor(isolation): clarify outer fence ownership
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent a344728 commit cbc7aa2

11 files changed

Lines changed: 46 additions & 40 deletions

File tree

‎architecture/sandbox.md‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,20 @@ replacement from granting authority.
8484
6. Exec, signaling, PTY, DNS, TCP, and loopback-forwarding operations cross the
8585
authenticated channel for the lifetime of the sandbox generation.
8686

87-
The shared isolation contract receives only the driver's normalized outer-fence
88-
guarantees: egress is default-deny, there is no unmanaged egress path, the
89-
evidence is bound to the sandbox generation, revocation has been verified, and
90-
controller loss fails closed. A digest commits those guarantees to the native
91-
driver evidence without teaching the shared contract about container networks,
87+
The shared isolation contract receives only normalized outer-fence guarantees:
88+
egress is default-deny, there is no unmanaged egress path, the evidence is bound
89+
to the sandbox generation, revocation has been verified, and controller loss
90+
fails closed. A digest commits those guarantees to the native
91+
evidence without teaching the shared contract about container networks,
9292
Kubernetes objects, VM devices, or accelerator resources.
9393

94-
Each driver makes that projection explicitly. Non-empty native evidence alone
95-
does not establish a guarantee:
94+
The component that owns the outer fence also validates its native evidence and
95+
makes that projection explicitly. In the current Docker, Podman, Kubernetes,
96+
and VM placements, that component is the compute driver. A delegated isolation
97+
backend may own the fence and make the same projection instead. Non-empty native
98+
evidence alone does not establish a guarantee:
9699

97-
| Driver | Native evidence | Guarantees projected by the driver |
100+
| Current enforcement owner | Native evidence | Guarantees projected by the owner |
98101
|---|---|---|
99102
| Docker | Pinned container ID, `network_mode=none`, and no unexpected network attachments | No workload route establishes default-deny, revocation, and controller-loss behavior; the attachment inspection establishes that no unmanaged route exists. |
100103
| Podman | Pinned container ID, `--network=none`, and no unexpected network attachments | The same container-network facts establish the same four guarantees. |
@@ -129,7 +132,7 @@ OpenShell uses overlapping controls rather than a single sandbox primitive:
129132
| Filesystem policy | Landlock restricts the paths the agent can read or write. |
130133
| Process policy | Sandbox and children run as one immutable non-root identity with zero capabilities. |
131134
| Seccomp notification | Virtualizes supported INET sockets and sends DNS/TCP decisions to the supervisor without nftables or proxy environment variables. |
132-
| Driver outer fence | Docker `network_mode=none`, a NIC-less VM, or Kubernetes NetworkPolicy prevents any missed or unsupported kernel path from escaping. |
135+
| Outer network fence | The component that owns network enforcement prevents any missed or unsupported kernel path from escaping. Current examples are Docker `network_mode=none`, a NIC-less VM, and Kubernetes NetworkPolicy. |
133136
| Policy proxy | Evaluates destination, binary identity, TLS/L7 rules, SSRF checks, and inference interception. |
134137

135138
The supervisor may enrich baseline filesystem allowances for runtime-required
@@ -230,7 +233,7 @@ cannot transfer that approval to another socket.
230233

231234
The outer fence remains mandatory. If notification handling misses a syscall,
232235
loses the supervisor, exceeds a bound, or encounters an unsupported socket
233-
type, the request fails and the driver-owned fence still blocks direct egress.
236+
type, the request fails and the outer fence still blocks direct egress.
234237

235238
CONNECT and absolute-form forward HTTP are explicit-proxy adapters over the same
236239
egress pipeline. Each adapter normalizes its request into an egress intent, and

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl DockerOuterFenceEvidence<'_> {
5252
BackendError::Descriptor(format!("encode Docker outer fence evidence: {error}"))
5353
})?;
5454
let projection =
55-
OuterFenceGuarantees::from_driver_evidence(generation, established, &encoded)?;
55+
OuterFenceGuarantees::from_enforcement_evidence(generation, established, &encoded)?;
5656
projection.validate(generation)?;
5757
Ok(projection)
5858
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl KubernetesOuterFenceEvidence<'_> {
6262
BackendError::Descriptor(format!("encode Kubernetes outer fence evidence: {error}"))
6363
})?;
6464
let projection =
65-
OuterFenceGuarantees::from_driver_evidence(generation, established, &encoded)?;
65+
OuterFenceGuarantees::from_enforcement_evidence(generation, established, &encoded)?;
6666
projection.validate(generation)?;
6767
Ok(projection)
6868
}

‎crates/openshell-driver-podman/src/isolation.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl PodmanOuterFenceEvidence<'_> {
5656
}
5757
let encoded = serde_json::to_vec(self).map_err(invalid)?;
5858
let projection =
59-
OuterFenceGuarantees::from_driver_evidence(generation, established, &encoded)
59+
OuterFenceGuarantees::from_enforcement_evidence(generation, established, &encoded)
6060
.map_err(invalid)?;
6161
projection.validate(generation).map_err(invalid)?;
6262
Ok(projection)

‎crates/openshell-driver-vm/src/isolation/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl VmOuterFenceEvidence<'_> {
4343
let encoded = serde_json::to_vec(self).map_err(|error| {
4444
BackendError::Descriptor(format!("encode VM outer fence evidence: {error}"))
4545
})?;
46-
let projection = OuterFenceGuarantees::from_driver_evidence(
46+
let projection = OuterFenceGuarantees::from_enforcement_evidence(
4747
self.generation,
4848
established.into_iter().flatten(),
4949
&encoded,

‎crates/openshell-isolation-interface/src/contract.rs‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -386,43 +386,46 @@ pub trait BoundBoundary: Send {
386386
async fn confirm(self: Box<Self>) -> Result<ConfirmedBoundary, BackendError>;
387387
}
388388

389-
/// Backend-neutral guarantees established by the compute driver's outer fence.
389+
/// Backend-neutral guarantees established by the component that owns the outer
390+
/// network fence.
390391
///
391-
/// Each driver owns its native evidence schema and the code that validates it.
392-
/// After validation, the driver projects that evidence into these guarantees
393-
/// and supplies a digest that binds the original evidence to this generation.
394-
/// The common runtime only validates and compares this projection; it never
395-
/// interprets runtime- or accelerator-specific fields.
392+
/// The enforcement owner may be a compute driver or a delegated isolation
393+
/// backend. It owns its native evidence schema and the code that validates it.
394+
/// After validation, it projects that evidence into these guarantees and
395+
/// supplies a digest that binds the original evidence to this generation. The
396+
/// common runtime only validates and compares this projection; it never
397+
/// interprets backend- or runtime-specific fields.
396398
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
397399
#[serde(rename_all = "snake_case")]
398400
pub enum OuterFenceGuarantee {
399401
/// No workload packet can leave without an explicit mediated decision.
400402
DefaultDenyEgress,
401-
/// The driver found no network path outside the mediated boundary.
403+
/// The enforcement owner found no network path outside the mediated boundary.
402404
NoUnmanagedEgressPath,
403-
/// Previously granted access can be revoked by the driver-owned fence.
405+
/// Previously granted access can be revoked by the enforcement owner.
404406
RevocationVerified,
405-
/// Loss of the driver or its controller does not open network access.
407+
/// Loss of the fence's controller does not open network access.
406408
ControllerLossFailsClosed,
407409
}
408410

409411
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
410412
pub struct OuterFenceGuarantees {
411413
/// Sandbox generation for which the evidence was collected.
412414
pub generation: String,
413-
/// Complete set of normalized guarantees established by the driver.
415+
/// Complete set of normalized guarantees established by the enforcement owner.
414416
pub established: BTreeSet<OuterFenceGuarantee>,
415-
/// Commitment to the driver-owned native evidence used for this projection.
417+
/// Commitment to the enforcement owner's native evidence.
416418
pub evidence_digest: Sha256Digest,
417419
}
418420

419421
impl OuterFenceGuarantees {
420-
/// Bind the guarantees explicitly established by driver-owned evidence.
422+
/// Bind guarantees explicitly established by validated enforcement evidence.
421423
///
422424
/// This constructor deliberately does not infer guarantees from the mere
423-
/// presence of evidence. The driver must inspect its native state and
424-
/// project each established guarantee before calling this function.
425-
pub fn from_driver_evidence(
425+
/// presence of evidence. The enforcement owner must inspect its native
426+
/// state and project each established guarantee before calling this
427+
/// function.
428+
pub fn from_enforcement_evidence(
426429
generation: impl Into<String>,
427430
established: impl IntoIterator<Item = OuterFenceGuarantee>,
428431
native_evidence: &[u8],
@@ -529,7 +532,7 @@ pub struct BoundaryConfirmation {
529532
pub authenticated_supervisor: bool,
530533
pub session_id: SandboxSessionId,
531534
pub outer_fence: OuterFenceGuarantees,
532-
/// The driver-owned containment primitive terminates the workload when its
535+
/// The backend-owned containment primitive terminates the workload when its
533536
/// Sandbox Runtime exits.
534537
pub runtime_exit_terminates_workload: bool,
535538
pub resource_claims: BTreeMap<String, String>,

‎crates/openshell-isolation-interface/tests/backend_conformance.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ fn workload_identity() -> ResolvedWorkloadIdentity {
369369
}
370370

371371
fn complete_outer_fence(generation: &str, evidence: &[u8]) -> OuterFenceGuarantees {
372-
OuterFenceGuarantees::from_driver_evidence(
372+
OuterFenceGuarantees::from_enforcement_evidence(
373373
generation,
374374
[
375375
OuterFenceGuarantee::DefaultDenyEgress,
@@ -425,10 +425,10 @@ fn outer_fence_guarantees_are_backend_neutral_and_fail_closed() {
425425
assert!(incomplete.validate("generation-1").is_err());
426426
}
427427

428-
assert!(OuterFenceGuarantees::from_driver_evidence("", [], b"evidence").is_err());
429-
assert!(OuterFenceGuarantees::from_driver_evidence("generation-1", [], b"").is_err());
428+
assert!(OuterFenceGuarantees::from_enforcement_evidence("", [], b"evidence").is_err());
429+
assert!(OuterFenceGuarantees::from_enforcement_evidence("generation-1", [], b"").is_err());
430430

431-
let unproven = OuterFenceGuarantees::from_driver_evidence(
431+
let unproven = OuterFenceGuarantees::from_enforcement_evidence(
432432
"generation-1",
433433
[OuterFenceGuarantee::DefaultDenyEgress],
434434
b"native-driver-evidence",

‎crates/openshell-sandbox-backend/src/boundary_protocol.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ pub struct SandboxRuntimeDescriptor {
426426
/// example pod UID, VM generation, or container ID).
427427
#[serde(default)]
428428
pub resource_claims: std::collections::BTreeMap<String, String>,
429-
/// Backend-neutral projection of the driver-validated outer fence.
429+
/// Backend-neutral projection of the validated outer network fence.
430430
pub outer_fence: OuterFenceGuarantees,
431431
}
432432

@@ -494,7 +494,7 @@ pub struct BoundaryConfig {
494494
pub resource_claim_files: std::collections::BTreeMap<String, PathBuf>,
495495
/// Exact identity already applied by the runtime to the sandbox process.
496496
pub workload_identity: openshell_isolation_interface::contract::ResolvedWorkloadIdentity,
497-
/// Backend-neutral projection of the driver-validated outer fence.
497+
/// Backend-neutral projection of the validated outer network fence.
498498
pub outer_fence: OuterFenceGuarantees,
499499
/// Driver-resolved environment exposed only to workload processes.
500500
#[serde(default)]

‎crates/openshell-sandbox-backend/src/runtime.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,7 +1944,7 @@ mod tests {
19441944
fn test_outer_fence() -> openshell_isolation_interface::contract::OuterFenceGuarantees {
19451945
use openshell_isolation_interface::contract::OuterFenceGuarantee;
19461946

1947-
openshell_isolation_interface::contract::OuterFenceGuarantees::from_driver_evidence(
1947+
openshell_isolation_interface::contract::OuterFenceGuarantees::from_enforcement_evidence(
19481948
"test-generation",
19491949
[
19501950
OuterFenceGuarantee::DefaultDenyEgress,
@@ -2737,7 +2737,7 @@ mod tests {
27372737
async fn remote_confirm_rejects_outer_fence_digest_mismatch_before_monitoring() {
27382738
let mut confirmation = test_confirmation();
27392739
let different_fence =
2740-
openshell_isolation_interface::contract::OuterFenceGuarantees::from_driver_evidence(
2740+
openshell_isolation_interface::contract::OuterFenceGuarantees::from_enforcement_evidence(
27412741
"test-generation",
27422742
confirmation.outer_fence.established.iter().copied(),
27432743
b"different evidence",

‎crates/openshell-sandbox/src/boundary_server.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4271,7 +4271,7 @@ mod linux {
42714271
fn test_outer_fence() -> openshell_isolation_interface::contract::OuterFenceGuarantees {
42724272
use openshell_isolation_interface::contract::OuterFenceGuarantee;
42734273

4274-
openshell_isolation_interface::contract::OuterFenceGuarantees::from_driver_evidence(
4274+
openshell_isolation_interface::contract::OuterFenceGuarantees::from_enforcement_evidence(
42754275
"generation-1",
42764276
[
42774277
OuterFenceGuarantee::DefaultDenyEgress,

0 commit comments

Comments
 (0)