Optionally back /tmp and /var/tmp with stable host directories
This commit is contained in:
+93
-27
@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::cli::Args;
|
||||
use crate::session_key::SessionKey;
|
||||
use crate::{BindSpec, EnvEntry, SandboxConfig, SandboxError, SandboxMode};
|
||||
|
||||
pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfig, SandboxError> {
|
||||
@@ -13,72 +14,120 @@ pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfi
|
||||
let profile = c.resolve_profile(args.profile.as_deref())?;
|
||||
(c.options, profile)
|
||||
}
|
||||
None => (Options::default(), Options::default()),
|
||||
None => (
|
||||
Options::default(),
|
||||
ResolvedProfile {
|
||||
name: args.profile.clone(),
|
||||
options: Options::default(),
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
validate_mode(&globals)?;
|
||||
validate_mode(&profile)?;
|
||||
validate_mode(&profile.options)?;
|
||||
globals.validate_paths()?;
|
||||
profile.validate_paths()?;
|
||||
profile.options.validate_paths()?;
|
||||
|
||||
let (command, command_args) =
|
||||
resolve_command(args.entrypoint, args.command_and_args, &profile, &globals)?;
|
||||
let mode = merge_mode(args.blacklist, args.whitelist, &profile.options, &globals);
|
||||
let chdir = resolve_chdir(
|
||||
args.chdir.clone(),
|
||||
profile.options.chdir.clone(),
|
||||
globals.chdir.clone(),
|
||||
)?;
|
||||
let persistent_tmp_key = resolve_persistent_tmp_key(&args, &profile, &globals, &mode, &chdir);
|
||||
|
||||
let (command, command_args) = resolve_command(
|
||||
args.entrypoint,
|
||||
args.command_and_args,
|
||||
&profile.options,
|
||||
&globals,
|
||||
)?;
|
||||
let command = resolve_binary(&command)
|
||||
.ok_or_else(|| SandboxError::CommandNotFound(PathBuf::from(&command)))?;
|
||||
|
||||
let env = dedupe_env_last_wins(parse_env_entries(&merge_vecs(
|
||||
args.env,
|
||||
&profile.env,
|
||||
&profile.options.env,
|
||||
&globals.env,
|
||||
))?);
|
||||
let unsetenv = merge_vecs(args.unsetenv, &profile.unsetenv, &globals.unsetenv);
|
||||
let unsetenv = merge_vecs(args.unsetenv, &profile.options.unsetenv, &globals.unsetenv);
|
||||
reject_env_key_conflicts(&env, &unsetenv)?;
|
||||
|
||||
Ok(SandboxConfig {
|
||||
mode: merge_mode(args.blacklist, args.whitelist, &profile, &globals),
|
||||
mode,
|
||||
hardened: merge_flag(
|
||||
merge_flag_pair(args.hardened, args.no_hardened),
|
||||
profile.hardened,
|
||||
profile.options.hardened,
|
||||
globals.hardened,
|
||||
),
|
||||
unshare_net: merge_flag(
|
||||
merge_flag_pair(args.unshare_net, args.share_net),
|
||||
profile.unshare_net,
|
||||
profile.options.unshare_net,
|
||||
globals.unshare_net,
|
||||
),
|
||||
seccomp: merge_flag_with_default(
|
||||
merge_flag_pair(args.seccomp, args.no_seccomp),
|
||||
profile.seccomp,
|
||||
profile.options.seccomp,
|
||||
globals.seccomp,
|
||||
true,
|
||||
),
|
||||
env_filter: merge_flag_with_default(
|
||||
merge_flag_pair(args.env_filter, args.no_env_filter),
|
||||
profile.env_filter,
|
||||
profile.options.env_filter,
|
||||
globals.env_filter,
|
||||
true,
|
||||
),
|
||||
dry_run: merge_flag(
|
||||
merge_flag_pair(args.dry_run, args.no_dry_run),
|
||||
profile.dry_run,
|
||||
profile.options.dry_run,
|
||||
globals.dry_run,
|
||||
),
|
||||
chdir: resolve_chdir(args.chdir, profile.chdir, globals.chdir)?,
|
||||
extra_rw: merge_bind_specs(args.extra_rw, &profile.rw, &globals.rw)?,
|
||||
extra_ro: merge_bind_specs(args.extra_ro, &profile.ro, &globals.ro)?,
|
||||
mask: merge_vecs(args.mask, &profile.mask, &globals.mask),
|
||||
chdir,
|
||||
extra_rw: merge_bind_specs(args.extra_rw, &profile.options.rw, &globals.rw)?,
|
||||
extra_ro: merge_bind_specs(args.extra_ro, &profile.options.ro, &globals.ro)?,
|
||||
mask: merge_vecs(args.mask, &profile.options.mask, &globals.mask),
|
||||
env,
|
||||
unsetenv,
|
||||
bwrap_args: split_bwrap_args(merge_vecs(
|
||||
args.bwrap_args,
|
||||
&profile.bwrap_args,
|
||||
&profile.options.bwrap_args,
|
||||
&globals.bwrap_args,
|
||||
))?,
|
||||
command,
|
||||
command_args,
|
||||
persistent_tmp_key,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_persistent_tmp_key(
|
||||
args: &Args,
|
||||
profile: &ResolvedProfile,
|
||||
globals: &Options,
|
||||
mode: &SandboxMode,
|
||||
chdir: &Path,
|
||||
) -> Option<String> {
|
||||
if matches!(mode, SandboxMode::Blacklist) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let enabled = merge_flag(
|
||||
merge_flag_pair(args.persistent_tmp, args.no_persistent_tmp),
|
||||
profile.options.persistent_tmp,
|
||||
globals.persistent_tmp,
|
||||
);
|
||||
if !enabled {
|
||||
return None;
|
||||
}
|
||||
let label = args
|
||||
.persistent_key
|
||||
.clone()
|
||||
.or_else(|| profile.options.persistent_key.clone())
|
||||
.or_else(|| globals.persistent_key.clone());
|
||||
Some(
|
||||
label.unwrap_or_else(|| SessionKey::default().derive(profile.name.as_deref(), mode, chdir)),
|
||||
)
|
||||
}
|
||||
|
||||
fn merge_mode(
|
||||
cli_blacklist: bool,
|
||||
cli_whitelist: bool,
|
||||
@@ -297,6 +346,12 @@ pub struct FileConfig {
|
||||
_unknown: HashMap<String, toml::Value>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ResolvedProfile {
|
||||
pub name: Option<String>,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
impl FileConfig {
|
||||
pub fn load(path: &Path) -> Result<Self, SandboxError> {
|
||||
Self::load_file(path)?.load_extra()
|
||||
@@ -364,15 +419,22 @@ impl FileConfig {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn resolve_profile(&self, selected: Option<&str>) -> Result<Options, SandboxError> {
|
||||
match selected.or(self.options.profile.as_deref()) {
|
||||
Some(leaf) => self.resolve_chain(leaf),
|
||||
None => Ok(Options::default()),
|
||||
}
|
||||
fn resolve_profile(&self, selected: Option<&str>) -> Result<ResolvedProfile, SandboxError> {
|
||||
let Some(name) = selected
|
||||
.map(String::from)
|
||||
.or_else(|| self.options.profile.clone())
|
||||
else {
|
||||
return Ok(ResolvedProfile::default());
|
||||
};
|
||||
let options = self.resolve_chain(&name)?;
|
||||
Ok(ResolvedProfile {
|
||||
name: Some(name),
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_chain(&self, leaf: &str) -> Result<Options, SandboxError> {
|
||||
let chain = self.collect_chain(leaf)?;
|
||||
fn resolve_chain(&self, name: &str) -> Result<Options, SandboxError> {
|
||||
let chain = self.collect_chain(name)?;
|
||||
|
||||
let merged = chain
|
||||
.into_iter()
|
||||
@@ -382,10 +444,10 @@ impl FileConfig {
|
||||
Ok(merged)
|
||||
}
|
||||
|
||||
fn collect_chain(&self, leaf: &str) -> Result<Vec<Options>, SandboxError> {
|
||||
fn collect_chain(&self, name: &str) -> Result<Vec<Options>, SandboxError> {
|
||||
let mut visited: Vec<String> = Vec::new();
|
||||
let mut chain: Vec<Options> = Vec::new();
|
||||
let mut current = leaf.to_string();
|
||||
let mut current = name.to_string();
|
||||
|
||||
loop {
|
||||
if visited.iter().any(|seen| seen == ¤t) {
|
||||
@@ -424,6 +486,8 @@ pub struct Options {
|
||||
pub entrypoint: Option<CommandValue>,
|
||||
pub command: Option<CommandValue>,
|
||||
pub dry_run: Option<bool>,
|
||||
pub persistent_tmp: Option<bool>,
|
||||
pub persistent_key: Option<String>,
|
||||
pub chdir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub rw: Vec<String>,
|
||||
@@ -453,6 +517,8 @@ impl Options {
|
||||
entrypoint: extra.entrypoint.or(self.entrypoint),
|
||||
command: extra.command.or(self.command),
|
||||
dry_run: extra.dry_run.or(self.dry_run),
|
||||
persistent_tmp: extra.persistent_tmp.or(self.persistent_tmp),
|
||||
persistent_key: extra.persistent_key.or(self.persistent_key),
|
||||
chdir: extra.chdir.or(self.chdir),
|
||||
rw: append(self.rw, extra.rw),
|
||||
ro: append(self.ro, extra.ro),
|
||||
|
||||
Reference in New Issue
Block a user