Skip to content

Commit 1d590ae

Browse files
committed
refactor(verity): simplify volume envelope
1 parent 41ff80f commit 1d590ae

6 files changed

Lines changed: 41 additions & 135 deletions

File tree

dstack/Cargo.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ flate2 = "1.1"
143143
borsh = { version = "1.5.7", default-features = false, features = ["derive"] }
144144
bon = { version = "3.4.0", default-features = false }
145145
base64 = "0.22.1"
146-
hex = { version = "0.4.3", default-features = false, features = ["serde"] }
146+
hex = { version = "0.4.3", default-features = false }
147147
hex_fmt = "0.3.0"
148148
hex-literal = "1.0.0"
149149
prost = "0.13.5"

dstack/crates/dstack-verity/src/volume.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,8 @@ mod tests {
532532
img.seek(SeekFrom::Start(metadata.first_lba * SECTOR))?;
533533
img.read_exact(&mut header)?;
534534
assert_eq!(&header[..16], b"DSTACK_VOLUME\0\0\0");
535-
assert_eq!(&header[32..64], &hex::decode(root_hash)?);
535+
let decoded = DstackVolumeHeader::decode(&header)?;
536+
assert_eq!(decoded.root_hash.as_slice(), hex::decode(root_hash)?);
536537
let mut buf = vec![0; data.len()];
537538
img.seek(SeekFrom::Start(data_partition.first_lba * SECTOR))?;
538539
img.read_exact(&mut buf)?;

dstack/dstack-types/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub struct AppCompose {
133133
pub struct VerityVolume {
134134
/// dm-verity root hash (hex): the volume's content identity and integrity
135135
/// check. The guest matches attached devices against it.
136-
#[serde(with = "hex::serde")]
136+
#[serde(with = "hex_bytes")]
137137
pub verity_root: [u8; 32],
138138
/// `"docker"` (seed the docker overlay2 image store), or an absolute path
139139
/// where the volume's filesystem is mounted (e.g. model weights).

dstack/dstack-types/src/volume.rs

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,21 @@ use binrw::{binrw, BinRead, BinWrite};
1010

1111
pub const DSTACK_VOLUME_MAGIC: &[u8; 16] = b"DSTACK_VOLUME\0\0\0";
1212
pub const DSTACK_VOLUME_HEADER_SIZE: usize = 4096;
13-
pub const DSTACK_VOLUME_FORMAT_VERSION: u16 = 1;
1413
pub const DSTACK_VOLUME_KIND_VERITY: u32 = 1;
1514

1615
#[binrw]
1716
#[brw(little, magic = b"DSTACK_VOLUME\0\0\0")]
1817
#[derive(Debug, Clone, PartialEq, Eq)]
1918
pub struct DstackVolumeHeader {
20-
pub format_version: u16,
21-
pub header_size: u16,
2219
pub kind: u32,
23-
pub kind_version: u32,
24-
pub flags: u32,
2520
pub root_hash: [u8; 32],
26-
pub data_block_size: u32,
27-
pub hash_block_size: u32,
2821
}
2922

3023
impl DstackVolumeHeader {
3124
pub fn new_verity(root_hash: [u8; 32]) -> Self {
3225
Self {
33-
format_version: DSTACK_VOLUME_FORMAT_VERSION,
34-
header_size: DSTACK_VOLUME_HEADER_SIZE as u16,
3526
kind: DSTACK_VOLUME_KIND_VERITY,
36-
kind_version: 1,
37-
flags: 0,
3827
root_hash,
39-
data_block_size: 4096,
40-
hash_block_size: 4096,
4128
}
4229
}
4330

@@ -57,23 +44,7 @@ impl DstackVolumeHeader {
5744
),
5845
});
5946
}
60-
let header = Self::read(&mut Cursor::new(block))?;
61-
if header.format_version != DSTACK_VOLUME_FORMAT_VERSION {
62-
return Err(binrw::Error::AssertFail {
63-
pos: 16,
64-
message: format!(
65-
"unsupported volume format version {}",
66-
header.format_version
67-
),
68-
});
69-
}
70-
if header.header_size as usize != DSTACK_VOLUME_HEADER_SIZE {
71-
return Err(binrw::Error::AssertFail {
72-
pos: 18,
73-
message: format!("invalid volume header size {}", header.header_size),
74-
});
75-
}
76-
Ok(header)
47+
Self::read(&mut Cursor::new(block))
7748
}
7849
}
7950

@@ -91,14 +62,18 @@ mod tests {
9162
}
9263

9364
#[test]
94-
fn verity_volume_validates_root_and_target_during_deserialization() {
65+
fn verity_volume_validates_root_and_target_during_deserialization(
66+
) -> Result<(), serde_json::Error> {
9567
let volume: VerityVolume = serde_json::from_value(serde_json::json!({
9668
"verity_root": "5a".repeat(32),
9769
"target": "/run/models"
98-
}))
99-
.unwrap();
70+
}))?;
10071
assert_eq!(volume.verity_root, [0x5a; 32]);
10172
assert_eq!(volume.target, VolumeTarget::Mount("/run/models".into()));
73+
assert_eq!(
74+
serde_json::to_value(&volume)?["verity_root"],
75+
"5a".repeat(32)
76+
);
10277

10378
assert!(serde_json::from_value::<VerityVolume>(serde_json::json!({
10479
"verity_root": "abcd",
@@ -110,5 +85,6 @@ mod tests {
11085
"target": "relative/path"
11186
}))
11287
.is_err());
88+
Ok(())
11389
}
11490
}

dstack/guest-agent/src/bin/dstack-volume.rs

Lines changed: 28 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use std::io::Read;
1515
use std::os::unix::ffi::OsStrExt;
1616
use std::os::unix::fs::MetadataExt;
1717
use std::path::{Path, PathBuf};
18-
use std::process::{Command, Output};
1918

2019
use anyhow::{bail, Context, Result};
20+
use cmd_lib::{run_cmd, run_fun};
2121
use dstack_types::volume::{
2222
DstackVolumeHeader, DSTACK_VOLUME_HEADER_SIZE, DSTACK_VOLUME_KIND_VERITY, DSTACK_VOLUME_MAGIC,
2323
};
@@ -43,7 +43,7 @@ struct VerityVolume {
4343
}
4444

4545
fn main() -> Result<()> {
46-
tracing_subscriber::fmt().with_target(false).init();
46+
tracing_subscriber::fmt().init();
4747
let compose_path = std::env::args_os()
4848
.nth(1)
4949
.map(PathBuf::from)
@@ -56,8 +56,8 @@ fn main() -> Result<()> {
5656
return Ok(());
5757
}
5858

59-
let _ = run(Command::new("modprobe").arg("dm-verity"));
60-
let _ = run(Command::new("udevadm").args(["settle", "--timeout=5"]));
59+
let _ = run_cmd!(modprobe dm-verity);
60+
let _ = run_cmd!(udevadm settle --timeout=5);
6161

6262
let volumes = discover_volumes()?;
6363
info!(
@@ -194,19 +194,6 @@ fn parse_header(bytes: &[u8]) -> Result<Option<DstackVolumeHeader>> {
194194
}
195195

196196
fn resolve_verity(disk: &BlockDisk, header: DstackVolumeHeader) -> Result<VerityVolume> {
197-
if header.kind_version != 1 {
198-
bail!("unsupported verity version {}", header.kind_version);
199-
}
200-
if header.flags != 0 {
201-
bail!("unsupported verity flags {:#x}", header.flags);
202-
}
203-
if header.data_block_size != 4096 || header.hash_block_size != 4096 {
204-
bail!(
205-
"unsupported verity block sizes {}/{}",
206-
header.data_block_size,
207-
header.hash_block_size
208-
);
209-
}
210197
if disk.partitions.is_empty() {
211198
bail!("raw verity layout is not defined by kind version 1");
212199
}
@@ -249,27 +236,19 @@ fn activate_requested(
249236
info!(mapper = mapper_name, "reused active verity mapping");
250237
return Ok(());
251238
}
252-
checked(
253-
Command::new("veritysetup").args(["close", &mapper_name]),
254-
"closing stale verity mapping",
255-
)?;
239+
run_cmd!(veritysetup close $mapper_name).context("closing stale verity mapping")?;
256240
}
257241

258242
// The on-disk root only selected a candidate. Pass the root from the
259243
// measured compose to veritysetup, which is the actual trust decision.
260-
checked(
261-
Command::new("veritysetup")
262-
.arg("open")
263-
.arg(&candidate.data)
264-
.arg(&mapper_name)
265-
.arg(&candidate.hash)
266-
.arg(&expected_root),
267-
"opening dm-verity volume",
268-
)?;
244+
let data = &candidate.data;
245+
let hash = &candidate.hash;
246+
run_cmd!(veritysetup open $data $mapper_name $hash $expected_root)
247+
.context("opening dm-verity volume")?;
269248
if let Err(err) =
270249
verify_first_block(&mapped).and_then(|_| mount_volume(index, requested, &mapped))
271250
{
272-
let _ = run(Command::new("veritysetup").args(["close", &mapper_name]));
251+
let _ = run_cmd!(veritysetup close $mapper_name);
273252
return Err(err);
274253
}
275254
used.insert(candidate_index);
@@ -284,12 +263,7 @@ fn verify_first_block(path: &Path) -> Result<()> {
284263
}
285264

286265
fn mount_volume(index: usize, requested: &RequestedVolume, mapped: &Path) -> Result<()> {
287-
let fs_type = command_stdout(
288-
Command::new("blkid")
289-
.args(["-o", "value", "-s", "TYPE"])
290-
.arg(mapped),
291-
)
292-
.unwrap_or_default();
266+
let fs_type = run_fun!(blkid -o value -s TYPE $mapped).unwrap_or_default();
293267
match &requested.target {
294268
VolumeTarget::DockerSeed => {
295269
let mountpoint = PathBuf::from(format!("/run/dstack-verity/{index}"));
@@ -300,7 +274,7 @@ fn mount_volume(index: usize, requested: &RequestedVolume, mapped: &Path) -> Res
300274
mount_read_only(mapped, &mountpoint, fs_type.trim())?;
301275
}
302276
if let Err(err) = seed_docker(&mountpoint) {
303-
let _ = run(Command::new("umount").arg(&mountpoint));
277+
let _ = run_cmd!(umount $mountpoint);
304278
return Err(err);
305279
}
306280
info!(root = %hex::encode(requested.verity_root), "seeded docker from verity volume");
@@ -331,14 +305,12 @@ fn mount_read_only(device: &Path, target: &Path, fs_type: &str) -> Result<()> {
331305
} else {
332306
"ro"
333307
};
334-
let mut command = Command::new("mount");
335-
if !fs_type.is_empty() {
336-
command.args(["-t", fs_type]);
337-
}
338-
checked(
339-
command.arg("-o").arg(options).arg(device).arg(target),
340-
"mounting verity volume",
341-
)?;
308+
if fs_type.is_empty() {
309+
run_cmd!(mount -o $options $device $target).context("mounting verity volume")?;
310+
} else {
311+
run_cmd!(mount -t $fs_type -o $options $device $target)
312+
.context("mounting verity volume")?;
313+
}
342314
Ok(())
343315
}
344316

@@ -360,23 +332,15 @@ fn seed_docker(volume: &Path) -> Result<()> {
360332
let target = store.join("overlay2").join(layer_id).join("diff");
361333
fs::create_dir_all(&target)?;
362334
if !is_mountpoint(&target)? {
363-
if let Err(err) = checked(
364-
Command::new("mount")
365-
.args(["--bind"])
366-
.arg(&source)
367-
.arg(&target),
368-
"binding docker layer",
369-
) {
335+
if let Err(err) = run_cmd!(mount --bind $source $target).context("binding docker layer")
336+
{
370337
unwind_binds(&bound);
371338
return Err(err);
372339
}
373340
// A bind inherits neither the intended policy nor all mount flags.
374-
if let Err(err) = checked(
375-
Command::new("mount")
376-
.args(["-o", "remount,bind,ro"])
377-
.arg(&target),
378-
"making docker layer read-only",
379-
) {
341+
if let Err(err) =
342+
run_cmd!(mount -o remount,bind,ro $target).context("making docker layer read-only")
343+
{
380344
bound.push(target);
381345
unwind_binds(&bound);
382346
return Err(err);
@@ -419,13 +383,8 @@ fn child_directories(path: &Path) -> Result<Vec<PathBuf>> {
419383

420384
fn copy_contents(source: &Path, target: &Path) -> Result<()> {
421385
fs::create_dir_all(target)?;
422-
checked(
423-
Command::new("cp")
424-
.args(["-a"])
425-
.arg(source.join("."))
426-
.arg(target),
427-
"copying docker metadata",
428-
)?;
386+
let source_contents = source.join(".");
387+
run_cmd!(cp -a $source_contents $target).context("copying docker metadata")?;
429388
Ok(())
430389
}
431390

@@ -474,10 +433,7 @@ fn copy_layer_metadata(volume: &Path, store: &Path) -> Result<()> {
474433
if source.file_name() == Some(OsStr::new("diff")) {
475434
continue;
476435
}
477-
checked(
478-
Command::new("cp").args(["-a"]).arg(&source).arg(&target),
479-
"copying docker layer metadata",
480-
)?;
436+
run_cmd!(cp -a $source $target).context("copying docker layer metadata")?;
481437
}
482438
}
483439
Ok(())
@@ -549,20 +505,12 @@ fn unescape_mountinfo(value: &[u8]) -> Vec<u8> {
549505

550506
fn unwind_binds(paths: &[PathBuf]) {
551507
for path in paths.iter().rev() {
552-
let _ = run(Command::new("umount").arg(path));
553-
}
554-
}
555-
556-
fn command_stdout(command: &mut Command) -> Result<String> {
557-
let output = command.output()?;
558-
if !output.status.success() {
559-
bail!("command failed with {}", output.status);
508+
let _ = run_cmd!(umount $path);
560509
}
561-
Ok(String::from_utf8(output.stdout)?)
562510
}
563511

564512
fn mapping_root(mapper_name: &str) -> Result<String> {
565-
let status = command_stdout(Command::new("veritysetup").args(["status", mapper_name]))?;
513+
let status = run_fun!(veritysetup status $mapper_name)?;
566514
status
567515
.lines()
568516
.find_map(|line| {
@@ -574,22 +522,6 @@ fn mapping_root(mapper_name: &str) -> Result<String> {
574522
.context("verity mapping status has no root hash")
575523
}
576524

577-
fn run(command: &mut Command) -> Result<Output> {
578-
command.output().context("running command")
579-
}
580-
581-
fn checked(command: &mut Command, operation: &str) -> Result<Output> {
582-
let output = command.output().with_context(|| operation.to_string())?;
583-
if !output.status.success() {
584-
bail!(
585-
"{operation} failed with {}: {}",
586-
output.status,
587-
String::from_utf8_lossy(&output.stderr).trim()
588-
);
589-
}
590-
Ok(output)
591-
}
592-
593525
#[cfg(test)]
594526
mod tests {
595527
use super::*;

0 commit comments

Comments
 (0)