Skip to content

Commit 56fc0b3

Browse files
committed
fix(vm): repair rebase integration
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 15abc80 commit 56fc0b3

4 files changed

Lines changed: 45 additions & 106 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 37 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::rootfs::{
1414
clone_or_copy_sparse_file, create_ext4_image_from_dir_with_size, create_rootfs_image_from_dir,
1515
extract_host_supervisor, extract_rootfs_archive_to, prepare_sandbox_rootfs_from_image_root,
1616
recover_rootfs_image, sandbox_guest_init_path, sandbox_guest_runtime_identity,
17+
sandbox_guest_user_ids_from_image, sandbox_guest_user_ids_from_overlay_image,
1718
set_rootfs_image_file_mode, validate_host_supervisor, write_rootfs_image_file,
1819
};
1920
use crate::runtime::VmBackend;
@@ -276,67 +277,6 @@ pub struct VmDriverConfig {
276277
#[serde(default, skip_serializing_if = "Option::is_none")]
277278
pub sandbox_gid: Option<u32>,
278279

279-
/// Corporate forward proxy URL (`http://host:port` or `https://host:port`)
280-
/// passed to the host supervisor.
281-
///
282-
/// The supervisor chains policy-approved TLS tunnels through this proxy
283-
/// with HTTP CONNECT instead of dialing destinations directly. This is an
284-
/// operator-owned egress boundary: it travels on the supervisor's argv,
285-
/// which sandbox spec/template environment and image `ENV` cannot
286-
/// influence. `host.openshell.internal` resolves to host loopback for the
287-
/// host supervisor.
288-
#[serde(default, skip_serializing_if = "Option::is_none")]
289-
pub https_proxy: Option<String>,
290-
291-
/// Comma-separated `NO_PROXY` list passed alongside the proxy URL.
292-
///
293-
/// Matching destinations are dialed directly instead of through the
294-
/// corporate proxy. This bypasses only the corporate proxy, never
295-
/// `OpenShell` policy evaluation.
296-
#[serde(default, skip_serializing_if = "Option::is_none")]
297-
pub no_proxy: Option<String>,
298-
299-
/// Path (on the gateway host) to a file containing the corporate proxy
300-
/// credential in `user:pass` form.
301-
///
302-
/// The driver validates it at sandbox-create time and stages it into the
303-
/// per-sandbox overlay at [`GUEST_UPSTREAM_PROXY_AUTH_PATH`], root-only.
304-
/// Credentials are never embedded in the proxy URL and never reach the
305-
/// guest environment.
306-
#[serde(default, skip_serializing_if = "Option::is_none")]
307-
pub proxy_auth_file: Option<String>,
308-
309-
/// Explicit acknowledgement that proxy credentials are sent in cleartext.
310-
///
311-
/// `Proxy-Authorization: Basic` over the plain-TCP connection to an
312-
/// `http://` proxy is recoverable by anyone on the network path, so
313-
/// [`Self::proxy_auth_file`] requires this acknowledgement. An `https://`
314-
/// proxy carries the credential inside the verified TLS session and does
315-
/// not need it.
316-
#[serde(default, skip_serializing_if = "Option::is_none")]
317-
pub proxy_auth_allow_insecure: Option<bool>,
318-
319-
/// Send the destination hostname in CONNECT requests instead of a
320-
/// validated IP.
321-
///
322-
/// The default binds the tunnel to an address that passed the sandbox's
323-
/// SSRF and `allowed_ips` validation. Set this only when the proxy's ACLs
324-
/// filter on hostnames and reject IP CONNECT targets: the proxy then
325-
/// resolves the name itself and its own ACLs become the effective egress
326-
/// control for proxied TLS.
327-
#[serde(default, skip_serializing_if = "Option::is_none")]
328-
pub proxy_connect_by_hostname: Option<bool>,
329-
330-
/// Path (on the gateway host) to a PEM CA bundle trusted for the
331-
/// corporate proxy.
332-
///
333-
/// The driver stages it into the per-sandbox overlay at
334-
/// [`GUEST_PROXY_CA_PATH`] and passes that path via
335-
/// `--upstream-proxy-ca-bundle`. It is trusted both for the handshake
336-
/// with an `https://` proxy and for server certificates re-signed by a
337-
/// TLS-intercepting proxy.
338-
#[serde(default, skip_serializing_if = "Option::is_none")]
339-
pub proxy_ca_bundle: Option<String>,
340280
/// Directory where rootfs tar files must be staged before they can be
341281
/// referenced in a `CreateSandbox` request. Defaults to `<state_dir>/rootfs-tar-staging`.
342282
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -513,29 +453,6 @@ impl VmDriverConfig {
513453
Ok(())
514454
}
515455

516-
/// Validate the operator's corporate upstream-proxy settings, fail-closed.
517-
///
518-
/// Delegates to the validator shared with the Podman and Kubernetes
519-
/// drivers and with the host supervisor, so a value accepted here is
520-
/// never rejected by the host supervisor — and no misconfiguration can silently
521-
/// degrade to a direct dial.
522-
///
523-
/// # Errors
524-
///
525-
/// Returns a message naming the offending key.
526-
pub fn validate_proxy_config(&self) -> Result<(), String> {
527-
openshell_core::driver_utils::validate_upstream_proxy_settings(
528-
&openshell_core::driver_utils::UpstreamProxySettings {
529-
url: self.https_proxy.as_deref(),
530-
no_proxy: self.no_proxy.as_deref(),
531-
auth_file: self.proxy_auth_file.as_deref(),
532-
auth_allow_insecure: self.proxy_auth_allow_insecure,
533-
connect_by_hostname: self.proxy_connect_by_hostname,
534-
ca_bundle: self.proxy_ca_bundle.as_deref(),
535-
},
536-
)
537-
}
538-
539456
fn rootfs_tar_staging_dir(&self) -> PathBuf {
540457
self.rootfs_tar_staging_dir
541458
.clone()
@@ -896,7 +813,7 @@ impl VmDriver {
896813
) -> Result<Child, Status> {
897814
let supervisor_binary = self.host_supervisor_binary().await?;
898815
let (openshell_endpoint, gateway_tls_server_name) =
899-
host_control_openshell_endpoint(&self.config.openshell_endpoint)
816+
host_control_openshell_endpoint(&self.config.grpc_endpoint)
900817
.map_err(Status::failed_precondition)?;
901818
let auth_bundle_path = state_dir.join(HOST_AUTH_BUNDLE_FILE);
902819
let encoded_auth_bundle = serde_json::to_vec(auth_bundle)
@@ -1350,8 +1267,13 @@ impl VmDriver {
13501267
"Preparing writable VM overlay disk".to_string(),
13511268
),
13521269
);
1353-
if let Err(err) = self
1354-
.prepare_runtime_overlay(&overlay_disk, overlay_preparation)
1270+
let sandbox_owner_state = self
1271+
.prepare_runtime_overlay(
1272+
&state_dir,
1273+
&overlay_disk,
1274+
&owner_source_disk,
1275+
overlay_preparation,
1276+
)
13551277
.await
13561278
.map_err(|err| Status::internal(format!("prepare guest overlay disk failed: {err}")))?;
13571279
self.ensure_provisioning_active(&sandbox.id).await?;
@@ -2637,6 +2559,7 @@ impl VmDriver {
26372559
&self,
26382560
state_dir: &Path,
26392561
overlay_disk: &Path,
2562+
owner_source_disk: &Path,
26402563
preparation: OverlayPreparation,
26412564
) -> Result<SandboxOwnerIdentity, String> {
26422565
let span_status = openshell_otel::ErrorStatusGuard::current();
@@ -2700,7 +2623,10 @@ impl VmDriver {
27002623
.await
27012624
.map_err(|error| format!("overlay recovery panicked: {error}"))??;
27022625
}
2703-
span_status.finish(Ok(()))
2626+
if write_owner_state && !owner_state_written_before_prepare {
2627+
write_sandbox_owner_state(state_dir, owner_state).await?;
2628+
}
2629+
span_status.finish(Ok(owner_state))
27042630
}
27052631

27062632
fn resolved_sandbox_image(&self, sandbox: &Sandbox) -> Option<String> {
@@ -6287,9 +6213,9 @@ fn upstream_proxy_cli_args(config: &VmDriverConfig) -> Result<Vec<String>, Strin
62876213
args.push("--upstream-no-proxy".to_string());
62886214
args.push(list.clone());
62896215
}
6290-
if let Some(path) = &config.proxy_auth_file {
6216+
if let Some(path) = &config.upstream_proxy.proxy_auth_file {
62916217
args.push("--upstream-proxy-auth-file".to_string());
6292-
args.push(path.clone());
6218+
args.push(path.display().to_string());
62936219
}
62946220
// Config validation guarantees the acknowledgement is `true` whenever an
62956221
// auth file is configured against an http:// proxy; the supervisor
@@ -6304,7 +6230,7 @@ fn upstream_proxy_cli_args(config: &VmDriverConfig) -> Result<Vec<String>, Strin
63046230
}
63056231
if let Some(path) = &config.proxy_ca_bundle {
63066232
args.push("--upstream-proxy-ca-bundle".to_string());
6307-
args.push(path.clone());
6233+
args.push(path.display().to_string());
63086234
}
63096235
Ok(args)
63106236
}
@@ -7327,7 +7253,12 @@ mod tests {
73277253
let parent = tracing::info_span!("vm.provision");
73287254

73297255
let result = driver
7330-
.prepare_runtime_overlay(Path::new("/unused"), OverlayPreparation::Fresh)
7256+
.prepare_runtime_overlay(
7257+
Path::new("/unused"),
7258+
Path::new("/unused"),
7259+
Path::new("/unused"),
7260+
OverlayPreparation::Fresh,
7261+
)
73317262
.instrument(parent)
73327263
.await;
73337264
assert!(result.is_err(), "overflow should stop before disk I/O");
@@ -9331,6 +9262,8 @@ mod tests {
93319262

93329263
#[test]
93339264
fn prepared_image_cache_identity_includes_rootfs_layout_and_openshell_version() {
9265+
let image = "sha256:local-image";
9266+
let image_account = prepared_image_cache_identity(image, &VmDriverConfig::default());
93349267
assert_eq!(
93359268
image_account,
93369269
format!(
@@ -10191,7 +10124,11 @@ mod tests {
1019110124
}
1019210125

1019310126
/// A driver config carrying only corporate proxy settings.
10194-
fn proxy_config(https_proxy: Option<&str>, auth_file: Option<&str>) -> VmDriverConfig {
10127+
fn proxy_config(
10128+
https_proxy: Option<&str>,
10129+
auth_file: Option<&str>,
10130+
ca_bundle: Option<&str>,
10131+
) -> VmDriverConfig {
1019510132
VmDriverConfig {
1019610133
grpc_endpoint: "http://127.0.0.1:8080".to_string(),
1019710134
upstream_proxy: UpstreamProxyConfig {
@@ -10200,6 +10137,7 @@ mod tests {
1020010137
proxy_auth_allow_insecure: auth_file.map(|_| true),
1020110138
..UpstreamProxyConfig::default()
1020210139
},
10140+
proxy_ca_bundle: ca_bundle.map(PathBuf::from),
1020310141
..Default::default()
1020410142
}
1020510143
}
@@ -10213,6 +10151,7 @@ mod tests {
1021310151
proxy_config(
1021410152
Some("http://user:secret@proxy.corp.test:3128"),
1021510153
Some("/etc/openshell/secrets/proxy-auth"),
10154+
None,
1021610155
)
1021710156
);
1021810157
assert!(
@@ -10244,6 +10183,7 @@ mod tests {
1024410183
let config = proxy_config(
1024510184
Some("http://proxy.corp.test:3128"),
1024610185
Some("/etc/openshell/secrets/proxy-auth"),
10186+
Some("/etc/openshell/tls/corp-ca.pem"),
1024710187
);
1024810188
let args = upstream_proxy_cli_args(&config).unwrap();
1024910189

@@ -10264,7 +10204,7 @@ mod tests {
1026410204
#[test]
1026510205
fn upstream_proxy_args_pass_only_explicit_opt_ins() {
1026610206
let mut config = proxy_config(Some("https://proxy.corp.test:3130"), None, None);
10267-
config.no_proxy = Some("10.0.0.0/8,.svc.cluster.local".to_string());
10207+
config.upstream_proxy.no_proxy = Some("10.0.0.0/8,.svc.cluster.local".to_string());
1026810208
let args = upstream_proxy_cli_args(&config).unwrap();
1026910209
assert_eq!(
1027010210
args,
@@ -10326,7 +10266,7 @@ mod tests {
1032610266
.expect_err("a bypass list without a proxy would hide a fail-open state");
1032710267
assert!(err.contains("no_proxy"), "{err}");
1032810268

10329-
let config = proxy_config(Some("http://proxy.corp.test:3128"), None);
10269+
let config = proxy_config(Some("http://proxy.corp.test:3128"), None, None);
1033010270
config
1033110271
.validate_runtime_security_config()
1033210272
.expect("a lone proxy URL is a complete configuration");
@@ -10346,6 +10286,7 @@ mod tests {
1034610286
let mut config = proxy_config(
1034710287
Some("http://proxy.corp.test:3128"),
1034810288
Some("/etc/openshell/secrets/proxy-auth"),
10289+
None,
1034910290
);
1035010291
config.upstream_proxy.proxy_auth_allow_insecure = None;
1035110292
let err = config
@@ -10374,6 +10315,7 @@ mod tests {
1037410315
let config = proxy_config(
1037510316
Some("http://proxy.corp.test:3128"),
1037610317
Some("/etc/openshell/secrets/proxy-auth"),
10318+
None,
1037710319
);
1037810320
let sandbox = Sandbox {
1037910321
id: "sb-proxy".to_string(),

‎crates/openshell-driver-vm/src/rootfs.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use std::process::Command;
1313
use std::sync::OnceLock;
1414
use std::sync::atomic::{AtomicU64, Ordering};
1515

16+
use crate::driver::DEFAULT_SANDBOX_UID;
17+
1618
const SANDBOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/openshell-sandbox.zst"));
1719
const SUPERVISOR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/openshell-supervisor.zst"));
1820
const SUPERVISOR_RUNTIME: &[u8] =
@@ -25,8 +27,6 @@ pub const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
2527
const SANDBOX_GUEST_INIT_PATH: &str = "/srv/openshell-vm-sandbox-init.sh";
2628
const SANDBOX_SUPERVISOR_PATH: &str = openshell_core::driver_utils::SUPERVISOR_CONTAINER_BINARY;
2729
const SANDBOX_UMOCI_PATH: &str = openshell_core::container_paths::VM_UMOCI_PATH;
28-
const SANDBOX_OWNER_NORMALIZED_MARKER: &str =
29-
openshell_core::container_paths::VM_SANDBOX_OWNER_NORMALIZED_MARKER;
3030
const SANDBOX_SUPERVISOR_RUNTIME_PATH: &str = "/opt/openshell/bin/openshell-runtime";
3131
const ROOTFS_IMAGE_MIN_SIZE_BYTES: u64 = 512 * 1024 * 1024;
3232
const ROOTFS_IMAGE_MIN_HEADROOM_BYTES: u64 = 256 * 1024 * 1024;
@@ -52,7 +52,7 @@ pub fn sandbox_guest_runtime_identity() -> String {
5252

5353
/// Materialize the supervisor embedded in the VM driver for host-side use.
5454
pub fn extract_host_supervisor(path: &Path) -> Result<(), String> {
55-
if SANDBOX.is_empty() {
55+
if SUPERVISOR.is_empty() {
5656
return Err(
5757
"host supervisor is not embedded; run `mise run vm:supervisor` and rebuild openshell-driver-vm"
5858
.to_string(),

‎crates/openshell-gateway/src/vm.rs‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,10 @@ pub struct VmComputeConfig {
112112
/// Host-side private key for the guest's mTLS client bundle.
113113
pub guest_tls_key: Option<PathBuf>,
114114

115-
/// Corporate forward proxy URL (`http://host:port` or `https://host:port`)
116-
/// for policy-approved TLS egress from VM sandboxes.
117-
///
118-
/// Deployment-level configuration, not a per-sandbox setting: it is passed
119-
/// to the driver, which passes it to the host supervisor. The supervisor
120-
/// resolves `host.openshell.internal` to host loopback.
121-
pub https_proxy: Option<String>,
115+
/// Corporate forward-proxy settings passed to the VM driver. Flattening
116+
/// preserves the shared local-driver TOML field names.
117+
#[serde(flatten)]
118+
pub upstream_proxy: UpstreamProxyConfig,
122119

123120
/// Path on the gateway host to a PEM CA bundle trusted for the corporate
124121
/// proxy and for server certificates re-signed by a TLS-intercepting proxy.

0 commit comments

Comments
 (0)