@@ -184,9 +184,9 @@ pub fn create_manifest_from_vm_config(
184184 Some ( gpus) => resolve_gpus_with_config ( gpus, cvm_config) ?,
185185 None => GpuConfig :: default ( ) ,
186186 } ;
187- let app_compose : AppCompose = serde_json :: from_str ( & request. compose_file )
188- . context ( "invalid app-compose in VM configuration" ) ?;
189- let volumes = resolve_volumes ( & app_compose . verity_volumes , cvm_config) ?;
187+ let verity_volumes = extract_verity_volumes ( & request. compose_file ) ? ;
188+ dstack_types :: validate_verity_volumes ( & verity_volumes ) . map_err ( anyhow :: Error :: msg ) ?;
189+ let volumes = resolve_volumes ( & verity_volumes, cvm_config) ?;
190190
191191 Ok ( Manifest {
192192 id,
@@ -209,6 +209,19 @@ pub fn create_manifest_from_vm_config(
209209 } )
210210}
211211
212+ /// Extract only the field understood by this VMM. Keep every other app-compose
213+ /// field opaque so newer guest schemas and legacy third-party clients remain
214+ /// compatible with older VMMs.
215+ fn extract_verity_volumes ( compose : & str ) -> Result < Vec < dstack_types:: VerityVolume > > {
216+ let Ok ( compose) = serde_json:: from_str :: < serde_json:: Value > ( compose) else {
217+ return Ok ( vec ! [ ] ) ;
218+ } ;
219+ let Some ( volumes) = compose. get ( "verity_volumes" ) else {
220+ return Ok ( vec ! [ ] ) ;
221+ } ;
222+ serde_json:: from_value ( volumes. clone ( ) ) . context ( "invalid verity_volumes in app-compose" )
223+ }
224+
212225/// Resolve requested volumes against `cvm.volumes_dir`. Each `source` must be a
213226/// bare file name under that directory; the host attaches the bytes, and the
214227/// guest verifies content against the measured `verity_root`.
@@ -229,11 +242,6 @@ fn resolve_volumes(
229242 let real = resolve_volume_source ( & base, & v. source ) ?;
230243 Ok ( crate :: app:: VmVolume {
231244 source : real. to_string_lossy ( ) . into_owned ( ) ,
232- // Verity volumes are always read-only: the backing file is shared
233- // content-addressed data, so a writable attach could only let one
234- // guest corrupt it for every other tenant. Force it regardless of
235- // what the client asked for.
236- read_only : true ,
237245 } )
238246 } )
239247 . collect ( )
@@ -905,8 +913,7 @@ mod tests {
905913 VmConfiguration {
906914 name : "vm-test" . to_string ( ) ,
907915 image : "dstack-test" . to_string ( ) ,
908- compose_file : r#"{"manifest_version":2,"name":"test","runner":"docker-compose"}"#
909- . to_string ( ) ,
916+ compose_file : "{}" . to_string ( ) ,
910917 vcpu : 1 ,
911918 memory : 1024 ,
912919 disk_size : 10 ,
@@ -934,6 +941,25 @@ mod tests {
934941 assert ! ( manifest. networks. is_empty( ) ) ;
935942 }
936943
944+ #[ test]
945+ fn volume_extraction_keeps_other_compose_fields_opaque ( ) -> Result < ( ) > {
946+ assert ! ( extract_verity_volumes( "not json" ) ?. is_empty( ) ) ;
947+ assert ! ( extract_verity_volumes( r#"{"future_manifest":true}"# ) ?. is_empty( ) ) ;
948+
949+ let compose = serde_json:: json!( {
950+ "unknown_future_field" : { "anything" : true } ,
951+ "verity_volumes" : [ {
952+ "source" : "volume.img" ,
953+ "verity_root" : "11" . repeat( 32 ) ,
954+ "target" : "/run/volume"
955+ } ]
956+ } ) ;
957+ let volumes = extract_verity_volumes ( & compose. to_string ( ) ) ?;
958+ assert_eq ! ( volumes. len( ) , 1 ) ;
959+ assert_eq ! ( volumes[ 0 ] . verity_root, [ 0x11 ; 32 ] ) ;
960+ Ok ( ( ) )
961+ }
962+
937963 #[ test]
938964 fn explicit_user_networking_is_resolved_before_persist ( ) {
939965 let mut request = test_vm_configuration ( ) ;
@@ -1009,7 +1035,7 @@ mod tests {
10091035 }
10101036
10111037 #[ test]
1012- fn resolve_volumes_forces_read_only ( ) -> Result < ( ) > {
1038+ fn resolve_volumes_resolves_measured_source ( ) -> Result < ( ) > {
10131039 let tmp = tempfile:: tempdir ( ) ?;
10141040 fs:: write ( tmp. path ( ) . join ( "volume.img" ) , b"volume" ) ?;
10151041 let mut cvm_config = test_cvm_config ( ) ;
@@ -1025,7 +1051,10 @@ mod tests {
10251051 ) ?;
10261052
10271053 assert_eq ! ( volumes. len( ) , 1 ) ;
1028- assert ! ( volumes[ 0 ] . read_only) ;
1054+ assert_eq ! (
1055+ volumes[ 0 ] . source,
1056+ tmp. path( ) . join( "volume.img" ) . display( ) . to_string( )
1057+ ) ;
10291058 Ok ( ( ) )
10301059 }
10311060}
0 commit comments