Skip to content

Commit 6b80ce1

Browse files
committed
cvm: Support for configuration of storage fs type
1 parent 8fa3068 commit 6b80ce1

File tree

3 files changed

+135
-18
lines changed

3 files changed

+135
-18
lines changed

docs/security-guide/cvm-boundaries.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ This is the main configuration file for the application in JSON format:
4141
| secure_time | boolean | Whether secure time is enabled |
4242
| pre_launch_script | string | Prelaunch bash script that runs before execute `docker compose up` |
4343
| init_script | string | Bash script that executed prior to dockerd startup |
44+
| storage_fs | string | Filesystem type for the data disk of the CVM. Supported values: "zfs", "ext4". default to "zfs". **ZFS:** Ensures filesystem integrity with built-in data protection features. **ext4:** Provides better performance for database applications with lower overhead and faster I/O operations, but no strong integrity protection. |
4445

4546

4647
The hash of this file content is extended to RTMR3 as event name `compose-hash`. Remote verifier can extract the compose-hash during remote attestation.

dstack-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub struct AppCompose {
3939
pub no_instance_id: bool,
4040
#[serde(default = "default_true")]
4141
pub secure_time: bool,
42+
#[serde(default)]
43+
pub storage_fs: Option<String>,
4244
}
4345

4446
fn default_true() -> bool {

dstack-util/src/system_setup.rs

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
collections::{BTreeMap, BTreeSet},
77
ops::Deref,
88
path::{Path, PathBuf},
9+
str::FromStr,
910
};
1011

1112
use anyhow::{anyhow, bail, Context, Result};
@@ -77,6 +78,58 @@ struct InstanceInfo {
7778
app_id: Vec<u8>,
7879
}
7980

81+
#[derive(Debug, Clone, Copy, PartialEq, Default)]
82+
enum FsType {
83+
#[default]
84+
Zfs,
85+
Ext4,
86+
}
87+
88+
impl FromStr for FsType {
89+
type Err = anyhow::Error;
90+
fn from_str(s: &str) -> Result<Self, Self::Err> {
91+
match s.to_lowercase().as_str() {
92+
"zfs" => Ok(FsType::Zfs),
93+
"ext4" => Ok(FsType::Ext4),
94+
_ => bail!("Invalid filesystem type: {s}, supported types: zfs, ext4"),
95+
}
96+
}
97+
}
98+
99+
#[derive(Debug, Clone, Default)]
100+
struct DstackOptions {
101+
storage_encrypted: bool,
102+
storage_fs: FsType,
103+
}
104+
105+
fn parse_dstack_options(shared: &HostShared) -> Result<DstackOptions> {
106+
let cmdline = fs::read_to_string("/proc/cmdline").context("Failed to read /proc/cmdline")?;
107+
108+
let mut options = DstackOptions {
109+
storage_encrypted: true, // Default to encryption enabled
110+
storage_fs: FsType::Zfs, // Default to ZFS
111+
};
112+
113+
for param in cmdline.split_whitespace() {
114+
if let Some(value) = param.strip_prefix("dstack.storage_encrypted=") {
115+
match value {
116+
"0" | "false" | "no" | "off" => options.storage_encrypted = false,
117+
"1" | "true" | "yes" | "on" => options.storage_encrypted = true,
118+
_ => {
119+
bail!("Invalid value for dstack.storage_encrypted: {value}");
120+
}
121+
}
122+
} else if let Some(value) = param.strip_prefix("dstack.storage_fs=") {
123+
options.storage_fs = value.parse().context("Failed to parse dstack.storage_fs")?;
124+
}
125+
}
126+
127+
if let Some(fs) = &shared.app_compose.storage_fs {
128+
options.storage_fs = fs.parse().context("Failed to parse storage_fs")?;
129+
}
130+
Ok(options)
131+
}
132+
80133
impl InstanceInfo {
81134
fn is_initialized(&self) -> bool {
82135
!self.instance_id_seed.is_empty()
@@ -433,36 +486,86 @@ impl<'a> Stage0<'a> {
433486
}
434487
}
435488

436-
async fn mount_data_disk(&self, initialized: bool, disk_crypt_key: &str) -> Result<()> {
489+
async fn mount_data_disk(
490+
&self,
491+
initialized: bool,
492+
disk_crypt_key: &str,
493+
opts: &DstackOptions,
494+
) -> Result<()> {
437495
let name = "dstack_data_disk";
438-
let fs_dev = "/dev/mapper/".to_string() + name;
439496
let mount_point = &self.args.mount_point;
497+
498+
// Determine the device to use based on encryption settings
499+
let fs_dev = if opts.storage_encrypted {
500+
format!("/dev/mapper/{name}")
501+
} else {
502+
self.args.device.to_string_lossy().to_string()
503+
};
504+
440505
if !initialized {
441506
self.vmm
442507
.notify_q("boot.progress", "initializing data disk")
443508
.await;
444-
info!("Setting up disk encryption");
445-
self.luks_setup(disk_crypt_key, name)?;
509+
510+
if opts.storage_encrypted {
511+
info!("Setting up disk encryption");
512+
self.luks_setup(disk_crypt_key, name)?;
513+
} else {
514+
info!("Skipping disk encryption as requested by kernel cmdline");
515+
}
516+
446517
cmd! {
447518
mkdir -p $mount_point;
448-
zpool create -o autoexpand=on dstack $fs_dev;
449-
zfs create -o mountpoint=$mount_point -o atime=off -o checksum=blake3 dstack/data;
519+
}?;
520+
521+
match opts.storage_fs {
522+
FsType::Zfs => {
523+
info!("Creating ZFS filesystem");
524+
cmd! {
525+
zpool create -o autoexpand=on dstack $fs_dev;
526+
zfs create -o mountpoint=$mount_point -o atime=off -o checksum=blake3 dstack/data;
527+
}
528+
.context("Failed to create zpool")?;
529+
}
530+
FsType::Ext4 => {
531+
info!("Creating ext4 filesystem");
532+
cmd! {
533+
mkfs.ext4 -F $fs_dev;
534+
mount $fs_dev $mount_point;
535+
}
536+
.context("Failed to create ext4 filesystem")?;
537+
}
450538
}
451-
.context("Failed to create zpool")?;
452539
} else {
453540
self.vmm
454541
.notify_q("boot.progress", "mounting data disk")
455542
.await;
456-
info!("Mounting encrypted data disk");
457-
self.open_encrypted_volume(disk_crypt_key, name)?;
458-
cmd! {
459-
zpool import dstack;
460-
zpool status dstack;
461-
zpool online -e dstack $fs_dev; // triggers autoexpand
543+
544+
if opts.storage_encrypted {
545+
info!("Mounting encrypted data disk");
546+
self.open_encrypted_volume(disk_crypt_key, name)?;
547+
} else {
548+
info!("Mounting unencrypted data disk");
462549
}
463-
.context("Failed to import zpool")?;
464-
if cmd!(mountpoint -q $mount_point).is_err() {
465-
cmd!(zfs mount dstack/data).context("Failed to mount zpool")?;
550+
551+
match opts.storage_fs {
552+
FsType::Zfs => {
553+
cmd! {
554+
zpool import dstack;
555+
zpool status dstack;
556+
zpool online -e dstack $fs_dev; // triggers autoexpand
557+
}
558+
.context("Failed to import zpool")?;
559+
if cmd!(mountpoint -q $mount_point).is_err() {
560+
cmd!(zfs mount dstack/data).context("Failed to mount zpool")?;
561+
}
562+
}
563+
FsType::Ext4 => {
564+
if cmd!(mountpoint -q $mount_point).is_err() {
565+
cmd!(mount $fs_dev $mount_point)
566+
.context("Failed to mount ext4 filesystem")?;
567+
}
568+
}
466569
}
467570
}
468571
Ok(())
@@ -614,9 +717,20 @@ impl<'a> Stage0<'a> {
614717
let keys_json = serde_json::to_string(&app_keys).context("Failed to serialize app keys")?;
615718
fs::write(self.app_keys_file(), keys_json).context("Failed to write app keys")?;
616719

720+
// Parse kernel command line options
721+
let opts = parse_dstack_options(&self.shared).context("Failed to parse kernel cmdline")?;
722+
info!(
723+
"Filesystem options: encryption={}, filesystem={:?}",
724+
opts.storage_encrypted, opts.storage_fs
725+
);
726+
617727
self.vmm.notify_q("boot.progress", "unsealing env").await;
618-
self.mount_data_disk(is_initialized, &hex::encode(&app_keys.disk_crypt_key))
619-
.await?;
728+
self.mount_data_disk(
729+
is_initialized,
730+
&hex::encode(&app_keys.disk_crypt_key),
731+
&opts,
732+
)
733+
.await?;
620734
self.vmm
621735
.notify_q(
622736
"instance.info",

0 commit comments

Comments
 (0)