Skip to content

Commit

Permalink
feat: support services with multiple ExecStartPre/ExecStart/ExecStart…
Browse files Browse the repository at this point in the history
…Post directives (WIP)
  • Loading branch information
desbma committed Dec 6, 2023
1 parent e549755 commit 9f98bce
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 25 deletions.
13 changes: 13 additions & 0 deletions src/cl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Command line interface
use std::path::PathBuf;

use clap::Parser;

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -29,6 +31,17 @@ pub enum Action {
/// How hard we should harden
#[arg(short, long, default_value_t, value_enum)]
mode: HardeningMode,
/// Generate profile data file to be merged with others instead of generating systemd options directly
#[arg(short, long, default_value = None)]
profile_data_path: Option<PathBuf>,
},
/// Merge profile data from previous runs to generate systemd options
MergeProfileData {
/// How hard we should harden
#[arg(short, long, default_value_t, value_enum)]
mode: HardeningMode,
/// Profile data paths
paths: Vec<PathBuf>,
},
/// Act on a systemd service unit
#[clap(subcommand)]
Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ fn main() -> anyhow::Result<()> {

// Handle CL args
match args.action {
cl::Action::Run { command, mode } => {
cl::Action::Run {
command,
mode,
profile_data_path,
} => {
// Build supported systemd options
let sd_opts = sd_options(&sd_version, &kernel_version, &mode)?;

Expand Down Expand Up @@ -74,6 +78,7 @@ fn main() -> anyhow::Result<()> {
// Report
systemd::report_options(resolved_opts);
}
cl::Action::MergeProfileData { mode, paths } => todo!(),
cl::Action::Service(cl::ServiceAction::StartProfile {
service,
mode,
Expand Down
58 changes: 34 additions & 24 deletions src/systemd/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
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())?;
Expand All @@ -104,17 +87,44 @@ impl Service {
}
// strace may slow down enough to risk reaching some service timeouts
writeln!(fragment_file, "TimeoutStartSec=infinity")?;
writeln!(fragment_file, "ExecStart=")?;

// Profile data dir
let profile_data_dir = PathBuf::from(format!("/{}_profile_data", env!("CARGO_PKG_NAME")));
writeln!(
fragment_file,
"ExecStart={} run -m {} -- {}",
env::current_exe()?
.to_str()
.ok_or_else(|| anyhow::anyhow!("Unable to decode current executable path"))?,
mode,
cmd
"TemporaryFileSystem={}",
profile_data_dir.to_str().unwrap()
)?;

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
let mut exec_start_idx = 1;
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 {
let profile_data_path = profile_data_dir.join(format!("{:03}", exec_start_idx));
exec_start_idx += 1;
writeln!(
fragment_file,
"{}={} run -m {} -p {} -- {}",
exec_start_opt,
shh_bin,
mode,
profile_data_path.to_str().unwrap(),
cmd
)?;
}
}

// TODO ExecStopPost shh invocation that merges previous profiles

log::info!("Config fragment written in {fragment_path:?}");
Ok(())
}
Expand Down

0 comments on commit 9f98bce

Please sign in to comment.