@@ -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} ;
1920use 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 ( ) ,
0 commit comments