@@ -51,6 +51,9 @@ const CONTAINER_PREFIX: &str = "openshell-sandbox-";
5151/// Volume name prefix.
5252const VOLUME_PREFIX : & str = "openshell-sandbox-" ;
5353
54+ /// Secret name prefix for per-sandbox gateway JWTs.
55+ const TOKEN_SECRET_PREFIX : & str = "openshell-token-" ;
56+
5457/// Container-side mount paths for client TLS materials and the sandbox token.
5558const TLS_CA_MOUNT_PATH : & str = openshell_core:: driver_utils:: TLS_CA_MOUNT_PATH ;
5659const TLS_CERT_MOUNT_PATH : & str = openshell_core:: driver_utils:: TLS_CERT_MOUNT_PATH ;
@@ -149,6 +152,12 @@ pub fn volume_name(sandbox_id: &str) -> String {
149152 format ! ( "{VOLUME_PREFIX}{sandbox_id}-workspace" )
150153}
151154
155+ /// Build the per-sandbox Podman secret name for the gateway JWT.
156+ #[ must_use]
157+ pub fn token_secret_name ( sandbox_id : & str ) -> String {
158+ format ! ( "{TOKEN_SECRET_PREFIX}{sandbox_id}" )
159+ }
160+
152161/// Truncate a container ID to 12 characters (standard short form).
153162#[ must_use]
154163pub fn short_id ( id : & str ) -> String {
@@ -187,6 +196,8 @@ struct ContainerSpec {
187196 /// environment-variable injection, distinct from `secrets` which only
188197 /// handles file-mounted secrets under `/run/secrets/`.
189198 secret_env : BTreeMap < String , String > ,
199+ /// File-mounted Podman secrets.
200+ secrets : Vec < SecretMount > ,
190201 stop_timeout : u32 ,
191202 /// Extra /etc/hosts entries. Used to inject `host.containers.internal`
192203 /// via Podman's `host-gateway` magic so sandbox containers can reach
@@ -271,6 +282,15 @@ struct HealthConfig {
271282 start_period : u64 ,
272283}
273284
285+ #[ derive( Serialize ) ]
286+ struct SecretMount {
287+ source : String ,
288+ target : String ,
289+ uid : u32 ,
290+ gid : u32 ,
291+ mode : u32 ,
292+ }
293+
274294#[ derive( Serialize ) ]
275295struct ResourceLimits {
276296 cpu : CpuLimits ,
@@ -763,17 +783,17 @@ pub fn build_container_spec(sandbox: &DriverSandbox, config: &PodmanComputeConfi
763783pub fn build_container_spec_with_token (
764784 sandbox : & DriverSandbox ,
765785 config : & PodmanComputeConfig ,
766- token_host_path : Option < & Path > ,
786+ token_secret_name : Option < & str > ,
767787) -> Value {
768- try_build_container_spec_with_token ( sandbox, config, token_host_path )
788+ try_build_container_spec_with_token ( sandbox, config, token_secret_name )
769789 . expect ( "container spec should be valid" )
770790}
771791
772792#[ cfg( test) ]
773793pub fn try_build_container_spec_with_token (
774794 sandbox : & DriverSandbox ,
775795 config : & PodmanComputeConfig ,
776- token_host_path : Option < & Path > ,
796+ token_secret_name : Option < & str > ,
777797) -> Result < Value , ComputeDriverError > {
778798 let driver_config = PodmanSandboxDriverConfig :: from_sandbox ( sandbox) ?;
779799 let gpu_requirements = sandbox
@@ -791,13 +811,13 @@ pub fn try_build_container_spec_with_token(
791811 } else {
792812 None
793813 } ;
794- build_container_spec_with_token_and_gpu_devices ( sandbox, config, token_host_path , cdi_devices)
814+ build_container_spec_with_token_and_gpu_devices ( sandbox, config, token_secret_name , cdi_devices)
795815}
796816
797817pub fn build_container_spec_with_token_and_gpu_devices (
798818 sandbox : & DriverSandbox ,
799819 config : & PodmanComputeConfig ,
800- token_host_path : Option < & Path > ,
820+ token_secret_name : Option < & str > ,
801821 gpu_device_ids : Option < & [ String ] > ,
802822) -> Result < Value , ComputeDriverError > {
803823 let image = resolve_image ( sandbox, config) ;
@@ -809,6 +829,16 @@ pub fn build_container_spec_with_token_and_gpu_devices(
809829 let resource_limits = build_resource_limits ( sandbox, config) ;
810830 let user_mounts = podman_user_mounts ( sandbox, config. enable_bind_mounts )
811831 . map_err ( ComputeDriverError :: InvalidArgument ) ?;
832+ if sandbox
833+ . spec
834+ . as_ref ( )
835+ . is_some_and ( |spec| !spec. sandbox_token . is_empty ( ) )
836+ && token_secret_name. is_none ( )
837+ {
838+ return Err ( ComputeDriverError :: Precondition (
839+ "podman sandbox token secret is required when sandbox token is set" . to_string ( ) ,
840+ ) ) ;
841+ }
812842 let devices = gpu_device_ids. map ( |device_ids| {
813843 device_ids
814844 . iter ( )
@@ -953,6 +983,15 @@ pub fn build_container_spec_with_token_and_gpu_devices(
953983 } ,
954984 resource_limits,
955985 secret_env : BTreeMap :: new ( ) ,
986+ secrets : token_secret_name. map_or_else ( Vec :: new, |source| {
987+ vec ! [ SecretMount {
988+ source: source. to_string( ) ,
989+ target: SANDBOX_TOKEN_MOUNT_PATH . into( ) ,
990+ uid: 0 ,
991+ gid: 0 ,
992+ mode: 0o400 ,
993+ } ]
994+ } ) ,
956995 stop_timeout : config. stop_timeout_secs ,
957996 // Inject stable host aliases into /etc/hosts so sandbox containers can
958997 // reach services on the host. `host.openshell.internal` is the driver-
@@ -1012,18 +1051,6 @@ pub fn build_container_spec_with_token_and_gpu_devices(
10121051 options : ro,
10131052 } ) ;
10141053 }
1015- if let Some ( path) = token_host_path {
1016- let mut ro = vec ! [ "ro" . into( ) , "rbind" . into( ) ] ;
1017- if is_selinux_enabled ( ) {
1018- ro. push ( "z" . into ( ) ) ;
1019- }
1020- m. push ( Mount {
1021- kind : "bind" . into ( ) ,
1022- source : path. display ( ) . to_string ( ) ,
1023- destination : SANDBOX_TOKEN_MOUNT_PATH . into ( ) ,
1024- options : ro,
1025- } ) ;
1026- }
10271054 m. extend ( user_mounts. mounts ) ;
10281055 m
10291056 } ,
@@ -2215,7 +2242,7 @@ mod tests {
22152242 }
22162243
22172244 #[ test]
2218- fn container_spec_uses_token_file_mount_without_raw_token_env ( ) {
2245+ fn container_spec_uses_token_secret_mount_without_raw_token_env ( ) {
22192246 use openshell_core:: proto:: compute:: v1:: DriverSandboxSpec ;
22202247
22212248 let mut sandbox = test_sandbox ( "token-id" , "token-name" ) ;
@@ -2224,9 +2251,9 @@ mod tests {
22242251 ..Default :: default ( )
22252252 } ) ;
22262253 let config = test_config ( ) ;
2227- let token_path = Path :: new ( "/host/token.jwt" ) ;
2254+ let secret_name = token_secret_name ( & sandbox . id ) ;
22282255
2229- let spec = build_container_spec_with_token ( & sandbox, & config, Some ( token_path ) ) ;
2256+ let spec = build_container_spec_with_token ( & sandbox, & config, Some ( & secret_name ) ) ;
22302257
22312258 let env_map = spec[ "env" ] . as_object ( ) . expect ( "env should be an object" ) ;
22322259 assert_eq ! (
@@ -2241,14 +2268,22 @@ mod tests {
22412268 . and_then( |v| v. as_str( ) ) ,
22422269 Some ( "/etc/openshell/auth/sandbox.jwt" )
22432270 ) ;
2271+ let secrets = spec[ "secrets" ]
2272+ . as_array ( )
2273+ . expect ( "secrets should be an array" ) ;
2274+ assert ! ( secrets. iter( ) . any( |secret| {
2275+ secret[ "source" ] . as_str( ) == Some ( secret_name. as_str( ) )
2276+ && secret[ "target" ] . as_str( ) == Some ( "/etc/openshell/auth/sandbox.jwt" )
2277+ && secret[ "mode" ] . as_u64( ) == Some ( 0o400 )
2278+ } ) ) ;
22442279 let mounts = spec[ "mounts" ]
22452280 . as_array ( )
22462281 . expect ( "mounts should be an array" ) ;
2247- assert ! ( mounts . iter ( ) . any ( |m| {
2248- m [ "type" ] . as_str ( ) == Some ( "bind" )
2249- && m [ "source" ] . as_str ( ) == Some ( "/host/token.jwt" )
2250- && m[ "destination" ] . as_str( ) == Some ( "/etc/openshell/auth/sandbox.jwt" )
2251- } ) ) ;
2282+ assert ! (
2283+ !mounts
2284+ . iter ( )
2285+ . any ( |m| { m[ "destination" ] . as_str( ) == Some ( "/etc/openshell/auth/sandbox.jwt" ) } )
2286+ ) ;
22522287 }
22532288
22542289 #[ test]
0 commit comments