Skip to content

Commit

Permalink
Merge pull request #188 from hashworks/feature/GroupAndWorkingDir
Browse files Browse the repository at this point in the history
Add ability to set group and working directory for command
  • Loading branch information
jamesmcm authored Aug 21, 2022
2 parents 14a4350 + b58c85e commit 2e22fad
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 21 deletions.
8 changes: 8 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ pub struct ExecCommand {
#[clap(long = "user", short = 'u')]
pub user: Option<String>,

/// Group with which to run the application
#[clap(long = "group", short = 'g')]
pub group: Option<String>,

/// Working directory in which to run the application (default is current working directory)
#[clap(long = "working-directory", short = 'w')]
pub working_directory: Option<String>,

/// Custom VPN Provider - OpenVPN or Wireguard config file (will override other settings)
#[clap(parse(from_os_str), long = "custom")]
pub custom_config: Option<PathBuf>,
Expand Down
60 changes: 54 additions & 6 deletions src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anyhow::{anyhow, bail};
use log::{debug, error, info, warn};
use signal_hook::{consts::SIGINT, iterator::Signals};
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::str::FromStr;
use std::{
fs::create_dir_all,
Expand Down Expand Up @@ -139,6 +140,32 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
command.user
};

// Group for application command
let group = if command.group.is_none() {
vopono_config_settings
.get("group")
.map_err(|e| {
debug!("vopono config.toml: {:?}", e);
anyhow!("Failed to read config file")
})
.ok()
} else {
command.group
};

// Working directory for application command
let working_directory = if command.working_directory.is_none() {
vopono_config_settings
.get("working-directory")
.map_err(|e| {
debug!("vopono config.toml: {:?}", e);
anyhow!("Failed to read config file")
})
.ok()
} else {
command.working_directory
};

// Assign DNS server from args or vopono config file
let base_dns = command.dns.clone().or_else(|| {
vopono_config_settings
Expand Down Expand Up @@ -328,6 +355,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
firewall,
predown,
user.clone(),
group.clone(),
)?;
let target_subnet = get_target_subnet()?;
ns.add_loopback()?;
Expand Down Expand Up @@ -482,13 +510,27 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
// Temporarily set env var referring to this network namespace name
if let Some(pucmd) = postup {
std::env::set_var("VOPONO_NS", &ns.name);
if user.is_some() {
std::process::Command::new("sudo")
.args(&["-Eu", user.as_ref().unwrap(), &pucmd])
.spawn()?;

let mut sudo_args = Vec::new();
if let Some(ref user) = user {
sudo_args.push("--user");
sudo_args.push(user);
}
if let Some(ref group) = group {
sudo_args.push("--group");
sudo_args.push(group);
}

if !sudo_args.is_empty() {
let mut args = vec!["--preserve-env"];
args.append(&mut sudo_args);
args.push(&pucmd);

std::process::Command::new("sudo").args(args).spawn()?;
} else {
std::process::Command::new(&pucmd).spawn()?;
}
};

std::env::remove_var("VOPONO_NS");
}
}
Expand All @@ -501,7 +543,13 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>

let ns = ns.write_lockfile(&command.application)?;

let application = ApplicationWrapper::new(&ns, &command.application, user)?;
let application = ApplicationWrapper::new(
&ns,
&command.application,
user,
group,
working_directory.map(PathBuf::from),
)?;

// Launch TCP proxy server on other threads if forwarding ports
// TODO: Fix when running as root
Expand Down
15 changes: 13 additions & 2 deletions vopono_core/src/network/application_wrapper.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use super::netns::NetworkNamespace;
use crate::util::get_all_running_process_names;
use log::warn;
Expand All @@ -11,6 +13,8 @@ impl ApplicationWrapper {
netns: &NetworkNamespace,
application: &str,
user: Option<String>,
group: Option<String>,
working_directory: Option<PathBuf>,
) -> anyhow::Result<Self> {
let running_processes = get_all_running_process_names();
let app_vec = application.split_whitespace().collect::<Vec<_>>();
Expand All @@ -33,8 +37,15 @@ impl ApplicationWrapper {
}
}

// TODO: Could allow user to set custom working directory here
let handle = netns.exec_no_block(app_vec.as_slice(), user, false, false, false, None)?;
let handle = netns.exec_no_block(
app_vec.as_slice(),
user,
group,
false,
false,
false,
working_directory,
)?;
Ok(Self { handle })
}

Expand Down
50 changes: 41 additions & 9 deletions vopono_core/src/network/netns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct NetworkNamespace {
pub firewall: Firewall,
pub predown: Option<String>,
pub predown_user: Option<String>,
pub predown_group: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -74,6 +75,7 @@ impl NetworkNamespace {
firewall: Firewall,
predown: Option<String>,
predown_user: Option<String>,
predown_group: Option<String>,
) -> anyhow::Result<Self> {
sudo_command(&["ip", "netns", "add", name.as_str()])
.with_context(|| format!("Failed to create network namespace: {}", &name))?;
Expand All @@ -96,13 +98,16 @@ impl NetworkNamespace {
firewall,
predown,
predown_user,
predown_group,
})
}

#[allow(clippy::too_many_arguments)]
pub fn exec_no_block(
&self,
command: &[&str],
user: Option<String>,
group: Option<String>,
silent: bool,
capture_output: bool,
capture_input: bool,
Expand All @@ -113,12 +118,26 @@ impl NetworkNamespace {
if let Some(cdir) = set_dir {
handle.current_dir(cdir);
}
let sudo_string = if user.is_some() {
handle.args(&["sudo", "-Eu", user.as_ref().unwrap()]);
Some(format!(" sudo -Eu {}", user.as_ref().unwrap()))

let mut sudo_args = Vec::new();
if let Some(ref user) = user {
sudo_args.push("--user");
sudo_args.push(user);
}
if let Some(ref group) = group {
sudo_args.push("--group");
sudo_args.push(group);
}

let sudo_string = if !sudo_args.is_empty() {
let mut args = vec!["sudo", "--preserve-env"];
args.append(&mut sudo_args);
handle.args(args.clone());
Some(format!(" {}", args.join(" ")))
} else {
None
};

if silent {
handle.stdout(Stdio::null());
handle.stderr(Stdio::null());
Expand All @@ -142,7 +161,7 @@ impl NetworkNamespace {
}

pub fn exec(&self, command: &[&str]) -> anyhow::Result<()> {
self.exec_no_block(command, None, false, false, false, None)?
self.exec_no_block(command, None, None, false, false, false, None)?
.wait()?;
Ok(())
}
Expand Down Expand Up @@ -505,14 +524,27 @@ impl Drop for NetworkNamespace {
.namespace_ip
.to_string(),
);
if self.predown_user.is_some() {
std::process::Command::new("sudo")
.args(&["-Eu", self.predown_user.as_ref().unwrap(), pdcmd])
.spawn()
.ok();

let mut sudo_args = Vec::new();
if let Some(ref predown_user) = self.predown_user {
sudo_args.push("--user");
sudo_args.push(predown_user);
}
if let Some(ref predown_group) = self.predown_group {
sudo_args.push("--group");
sudo_args.push(predown_group);
}

if !sudo_args.is_empty() {
let mut args = vec!["--preserve-env"];
args.append(&mut sudo_args);
args.push(pdcmd);

std::process::Command::new("sudo").args(args).spawn().ok();
} else {
std::process::Command::new(&pdcmd).spawn().ok();
}

std::env::remove_var("VOPONO_NS");
std::env::remove_var("VOPONO_NS_IP");
}
Expand Down
2 changes: 1 addition & 1 deletion vopono_core/src/network/openconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl OpenConnect {
}

let handle = netns
.exec_no_block(&command_vec, None, false, false, true, None)
.exec_no_block(&command_vec, None, None, false, false, true, None)
.context("Failed to launch OpenConnect - is openconnect installed?")?;

handle
Expand Down
2 changes: 1 addition & 1 deletion vopono_core/src/network/openfortivpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl OpenFortiVpn {

// TODO - better handle forwarding output when blocking on password entry (no newline!)
let mut handle = netns
.exec_no_block(&command_vec, None, false, true, false, None)
.exec_no_block(&command_vec, None, None, false, true, false, None)
.context("Failed to launch OpenFortiVPN - is openfortivpn installed?")?;
let stdout = handle.stdout.take().unwrap();
let id = handle.id();
Expand Down
10 changes: 9 additions & 1 deletion vopono_core/src/network/openvpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,15 @@ impl OpenVpn {
let working_dir = PathBuf::from(config_file_path.parent().unwrap());

let handle = netns
.exec_no_block(&command_vec, None, true, false, false, Some(working_dir))
.exec_no_block(
&command_vec,
None,
None,
true,
false,
false,
Some(working_dir),
)
.context("Failed to launch OpenVPN - is openvpn installed?")?;
let id = handle.id();
let mut buffer = String::with_capacity(16384);
Expand Down
2 changes: 1 addition & 1 deletion vopono_core/src/network/shadowsocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Shadowsocks {
];

let handle = netns
.exec_no_block(&command_vec, None, true, false, false, None)
.exec_no_block(&command_vec, None, None, true, false, false, None)
.context("Failed to launch Shadowsocks - is shadowsocks-libev installed?")?;

Ok(Self { pid: handle.id() })
Expand Down

0 comments on commit 2e22fad

Please sign in to comment.