diff --git a/README.md b/README.md index 49768fb..8da6a8f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Top-level keys set defaults; `[profile.]` sections define named presets se ```toml # Global defaults whitelist = true -no-net = true +unshare-net = true ro = ["~/.aws"] # Named profile diff --git a/src/cli.rs b/src/cli.rs index 4863dea..ccbf64c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,12 +19,20 @@ pub struct Args { pub whitelist: bool, /// Harden: unshare IPC, PID, UTS; private /tmp, /dev, /run - #[arg(long)] + #[arg(long, overrides_with = "no_hardened")] pub hardened: bool, + /// Disable hardening (overrides config-file `hardened = true`) + #[arg(long, overrides_with = "hardened")] + pub no_hardened: bool, + /// Unshare the network namespace - #[arg(long)] - pub no_net: bool, + #[arg(long, overrides_with = "share_net")] + pub unshare_net: bool, + + /// Share the host network namespace (overrides config-file `unshare-net = true`) + #[arg(long, overrides_with = "unshare_net")] + pub share_net: bool, /// Bind an extra path read-write (repeatable) #[arg(long = "rw", value_name = "PATH", action = clap::ArgAction::Append)] @@ -35,9 +43,13 @@ pub struct Args { pub extra_ro: Vec, /// Print the bwrap command without executing - #[arg(long)] + #[arg(long, overrides_with = "no_dry_run")] pub dry_run: bool, + /// Disable dry-run (overrides config-file `dry-run = true`) + #[arg(long, overrides_with = "dry_run")] + pub no_dry_run: bool, + /// Working directory inside the sandbox (default: current directory) #[arg(long, value_name = "PATH")] pub chdir: Option, diff --git a/src/config.rs b/src/config.rs index 2c8199b..297acea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,9 +28,21 @@ pub fn build(args: Args, file_config: Option) -> Result Option { } } -fn merge_flag(cli: bool, profile: Option, globals: Option) -> bool { - if cli { - return true; +fn merge_flag(cli: Option, profile: Option, globals: Option) -> bool { + cli.or(profile).or(globals).unwrap_or(false) +} + +fn merge_flag_pair(enable: bool, disable: bool) -> Option { + if enable { + Some(true) + } else if disable { + Some(false) + } else { + None } - profile.or(globals).unwrap_or(false) } fn resolve_chdir( @@ -224,7 +243,7 @@ pub struct Options { pub blacklist: Option, pub whitelist: Option, pub hardened: Option, - pub no_net: Option, + pub unshare_net: Option, pub entrypoint: Option, pub command: Option, pub dry_run: Option, @@ -360,7 +379,7 @@ mod tests { const FULL_CONFIG_TOML: &str = r#" hardened = true - no-net = true + unshare-net = true rw = ["/tmp/a", "/tmp/b"] command = "zsh" @@ -383,7 +402,7 @@ mod tests { #[test] fn globals_scalars() { assert_eq!(CONFIG.options.hardened, Some(true)); - assert_eq!(CONFIG.options.no_net, Some(true)); + assert_eq!(CONFIG.options.unshare_net, Some(true)); } #[test] @@ -488,7 +507,7 @@ mod tests { profile: HashMap::from([( "nonet".into(), Options { - no_net: Some(false), + unshare_net: Some(false), ..Options::default() }, )]), @@ -496,11 +515,62 @@ mod tests { }; let args = Args { profile: Some("nonet".into()), - no_net: true, + unshare_net: true, ..args_with_command() }; let config = build(args, Some(file_config)).unwrap(); - assert!(config.no_net); + assert!(config.unshare_net); + } + + #[test] + fn build_cli_no_hardened_overrides_profile() { + let file_config = FileConfig { + options: Options { + hardened: Some(true), + ..Options::default() + }, + ..FileConfig::default() + }; + let args = Args { + no_hardened: true, + ..args_with_command() + }; + let config = build(args, Some(file_config)).unwrap(); + assert!(!config.hardened); + } + + #[test] + fn build_cli_share_net_overrides_profile() { + let file_config = FileConfig { + options: Options { + unshare_net: Some(true), + ..Options::default() + }, + ..FileConfig::default() + }; + let args = Args { + share_net: true, + ..args_with_command() + }; + let config = build(args, Some(file_config)).unwrap(); + assert!(!config.unshare_net); + } + + #[test] + fn build_cli_no_dry_run_overrides_profile() { + let file_config = FileConfig { + options: Options { + dry_run: Some(true), + ..Options::default() + }, + ..FileConfig::default() + }; + let args = Args { + no_dry_run: true, + ..args_with_command() + }; + let config = build(args, Some(file_config)).unwrap(); + assert!(!config.dry_run); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 4459e62..4ed6702 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ pub enum SandboxMode { pub struct SandboxConfig { pub mode: SandboxMode, pub hardened: bool, - pub no_net: bool, + pub unshare_net: bool, pub extra_rw: Vec, pub extra_ro: Vec, pub mask: Vec, diff --git a/src/sandbox.rs b/src/sandbox.rs index 3c0e2e7..f28cf32 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -13,7 +13,7 @@ pub fn build_command(config: &SandboxConfig) -> Result { cmd.args(["--unshare-ipc", "--unshare-pid", "--unshare-uts"]); cmd.args(["--hostname", "sandbox"]); } - if config.no_net { + if config.unshare_net { cmd.arg("--unshare-net"); } diff --git a/tests/integration.rs b/tests/integration.rs index ac1ab75..d2becce 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -112,8 +112,8 @@ fn ssh_dir_is_hidden() { } #[test] -fn no_net_blocks_network() { - let output = sandbox(&["--no-net"]) +fn unshare_net_blocks_network() { + let output = sandbox(&["--unshare-net"]) .args([ "--", "bash",