Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/security/cvm-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ dstack uses encrypted environment variables to allow app developers to securely
- CVM decrypts the ciphertext using AESGCM with the derived shared secret
- CVM parses the JSON and only stores variables listed in allowed_envs from app-compose.json
- CVM performs basic regex validation on values
- Final result is stored as /dstack/.hostshared/.decrypted-env and loaded system-wide via app-compose.service
- Final result is stored as /dstack/.host-shared/.decrypted-env.json and passed to app-compose.service through `dstack-util exec-with-env`

This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security-best-practices.md](./security-best-practices.md) for more details.

Expand Down
27 changes: 27 additions & 0 deletions dstack/dstack-util/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ enum Commands {
Encrypt(EncryptArgs),
/// Sample NVIDIA GPU telemetry through NVML and print it as JSON
GpuInfo,
/// Exec a command with extra environment variables from a JSON object file
ExecWithEnv(ExecWithEnvArgs),
}

#[derive(Parser)]
struct ExecWithEnvArgs {
/// JSON object mapping variable names to values
#[arg(long)]
env_file: PathBuf,
/// Command to exec, with its arguments
#[arg(last = true, required = true)]
command: Vec<String>,
}

#[derive(Parser)]
Expand Down Expand Up @@ -721,6 +733,20 @@ async fn cmd_get_keys(args: GetKeysArgs) -> Result<()> {
Ok(())
}

fn cmd_exec_with_env(args: ExecWithEnvArgs) -> Result<()> {
use std::os::unix::process::CommandExt;

let envs: std::collections::BTreeMap<String, String> =
utils::deserialize_json_file(&args.env_file)
.with_context(|| format!("failed to load env from {}", args.env_file.display()))?;
let (program, program_args) = args.command.split_first().context("no command given")?;
let err = std::process::Command::new(program)
.args(program_args)
.envs(envs)
.exec();
Err(err).with_context(|| format!("failed to exec {program}"))
}

fn cmd_decrypt(args: DecryptArgs) -> Result<()> {
use dstack_types::shared_filenames::{host_shared_dir, APP_KEYS};

Expand Down Expand Up @@ -1667,6 +1693,7 @@ async fn main() -> Result<()> {
Commands::GpuInfo => {
gpu_info::cmd_gpu_info()?;
}
Commands::ExecWithEnv(args) => cmd_exec_with_env(args)?,
}

Ok(())
Expand Down
5 changes: 2 additions & 3 deletions os/common/rootfs/app-compose.service
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ OnFailure=dstack-boot-error@%n.service
[Service]
Type=oneshot
RemainAfterExit=true
EnvironmentFile=-/dstack/.host-shared/.decrypted-env
WorkingDirectory=/dstack
ExecStart=/bin/app-compose.sh
ExecStop=/bin/app-compose.sh stop
ExecStart=/bin/dstack-util exec-with-env --env-file /dstack/.host-shared/.decrypted-env.json -- /bin/app-compose.sh
ExecStop=/bin/dstack-util exec-with-env --env-file /dstack/.host-shared/.decrypted-env.json -- /bin/app-compose.sh stop
StandardOutput=journal+console
StandardError=journal+console

Expand Down
Loading