From 4a81a19e06a70921d165edb1e6bf4f7e9fdd17a8 Mon Sep 17 00:00:00 2001 From: desbma Date: Mon, 4 Dec 2023 22:38:25 +0100 Subject: [PATCH] feat: support services with multiple ExecStartPre/ExecStart/ExecStartPost directives (WIP) --- src/cl.rs | 2 ++ src/systemd/service.rs | 51 ++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/cl.rs b/src/cl.rs index 177bd35..081e39f 100644 --- a/src/cl.rs +++ b/src/cl.rs @@ -24,12 +24,14 @@ pub enum HardeningMode { pub enum Action { /// Run a program to profile its behavior Run { + // TODO profile data dir /// The command line to run command: Vec, /// How hard we should harden #[arg(short, long, default_value_t, value_enum)] mode: HardeningMode, }, + // TODO merge profile data /// Act on a systemd service unit #[clap(subcommand)] Service(ServiceAction), diff --git a/src/systemd/service.rs b/src/systemd/service.rs index a20fcb0..52d22b9 100644 --- a/src/systemd/service.rs +++ b/src/systemd/service.rs @@ -61,29 +61,12 @@ impl Service { "Hardening config already exists at {harden_fragment_path:?} and may conflict with profiling" ); - // Check we have no ExecStartPre/ExecStartPost - // Currently we don't profile those, so we'd rather throw an explicit error let config_paths_bufs = self.config_paths()?; let config_paths = config_paths_bufs .iter() .map(|p| p.as_path()) .collect::>(); - if !Self::config_vals("ExecStartPre", &config_paths)?.is_empty() - || !Self::config_vals("ExecStartPost", &config_paths)?.is_empty() - { - anyhow::bail!("Services with ExecStartPre/ExecStartPost are not supported") - } - - // Read current service startup command log::info!("Located unit config file(s): {config_paths:?}"); - let cmd = Self::config_vals("ExecStart", &config_paths)? - .into_iter() - .at_most_one() - .map_err(|_| { - anyhow::anyhow!("Services with multiple ExecStart directives are not supported") - })? - .ok_or_else(|| anyhow::anyhow!("Unable to get service ExecStart command"))?; - log::info!("Startup command: {cmd}"); // Write new fragment fs::create_dir_all(fragment_path.parent().unwrap())?; @@ -104,16 +87,30 @@ impl Service { } // strace may slow down enough to risk reaching some service timeouts writeln!(fragment_file, "TimeoutStartSec=infinity")?; - writeln!(fragment_file, "ExecStart=")?; - writeln!( - fragment_file, - "ExecStart={} run -m {} -- {}", - env::current_exe()? - .to_str() - .ok_or_else(|| anyhow::anyhow!("Unable to decode current executable path"))?, - mode, - cmd - )?; + + // TODO setup profile dir tmpfs + + let shh_bin = env::current_exe()? + .to_str() + .ok_or_else(|| anyhow::anyhow!("Unable to decode current executable path"))? + .to_string(); + + // wrap all ExecStartXxx directives + for exec_start_opt in ["ExecStartPre", "ExecStart", "ExecStartPost"] { + let exec_start_cmds = Self::config_vals(exec_start_opt, &config_paths)?; + if !exec_start_cmds.is_empty() { + writeln!(fragment_file, "{}=", exec_start_opt)?; + } + for cmd in exec_start_cmds { + writeln!( + fragment_file, + "{}={} run -m {} -- {}", + exec_start_opt, shh_bin, mode, cmd + )?; + } + } + + // TODO ExecStopPost shh invocation that merges previous profiles log::info!("Config fragment written in {fragment_path:?}"); Ok(())