Skip to content

Commit

Permalink
feat: add network firewalling option
Browse files Browse the repository at this point in the history
  • Loading branch information
desbma authored and desbma-s1n committed Nov 15, 2024
1 parent fc69691 commit 4722239
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 32 deletions.
52 changes: 43 additions & 9 deletions src/cl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,52 @@ pub(crate) enum HardeningMode {
Aggressive,
}

#[derive(Debug, clap::Parser)]
pub(crate) struct HardeningOptions {
/// How hard we should harden
#[arg(short, long, default_value_t, value_enum)]
pub mode: HardeningMode,
/// Enable advanced network firewalling
#[arg(short = 'f', long, default_value_t)]
pub network_firewalling: bool,
}

impl HardeningOptions {
/// Build the most safe options
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) fn safe() -> Self {
Self {
mode: HardeningMode::Safe,
network_firewalling: false,
}
}

/// Build the most strict options
pub(crate) fn strict() -> Self {
Self {
mode: HardeningMode::Aggressive,
network_firewalling: true,
}
}

pub(crate) fn to_cmdline(&self) -> String {
format!(
"-m {}{}",
self.mode,
if self.network_firewalling { " -n" } else { "" }
)
}
}

#[derive(Debug, clap::Subcommand)]
pub(crate) enum Action {
/// Run a program to profile its behavior
Run {
/// The command line to run
#[arg(num_args = 1.., required = true)]
command: Vec<String>,
/// How hard we should harden
#[arg(short, long, default_value_t, value_enum)]
mode: HardeningMode,
#[command(flatten)]
hardening_opts: HardeningOptions,
/// 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>,
Expand All @@ -44,9 +80,8 @@ pub(crate) enum Action {
},
/// 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,
#[command(flatten)]
hardening_opts: HardeningOptions,
/// Profile data paths
#[arg(num_args = 1.., required = true)]
paths: Vec<PathBuf>,
Expand All @@ -64,9 +99,8 @@ pub(crate) enum ServiceAction {
StartProfile {
/// Service unit name
service: String,
/// How hard we should harden
#[arg(short, long, default_value_t, value_enum)]
mode: HardeningMode,
#[command(flatten)]
hardening_opts: HardeningOptions,
/// Disable immediate service restart
#[arg(short, long, default_value_t = false)]
no_restart: bool,
Expand Down
26 changes: 16 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ mod systemd;
fn sd_options(
sd_version: &systemd::SystemdVersion,
kernel_version: &systemd::KernelVersion,
mode: &cl::HardeningMode,
hardening_opts: &cl::HardeningOptions,
) -> Vec<systemd::OptionDescription> {
let sd_opts = systemd::build_options(sd_version, kernel_version, mode);
let sd_opts = systemd::build_options(sd_version, kernel_version, hardening_opts);
log::info!(
"Enabled support for systemd options: {}",
sd_opts
Expand Down Expand Up @@ -60,12 +60,12 @@ fn main() -> anyhow::Result<()> {
match args.action {
cl::Action::Run {
command,
mode,
hardening_opts,
profile_data_path,
strace_log_path,
} => {
// Build supported systemd options
let sd_opts = sd_options(&sd_version, &kernel_version, &mode);
let sd_opts = sd_options(&sd_version, &kernel_version, &hardening_opts);

// Run strace
let cmd = command.iter().map(|a| &**a).collect::<Vec<&str>>();
Expand Down Expand Up @@ -102,9 +102,12 @@ fn main() -> anyhow::Result<()> {
systemd::report_options(resolved_opts);
}
}
cl::Action::MergeProfileData { mode, paths } => {
cl::Action::MergeProfileData {
hardening_opts,
paths,
} => {
// Build supported systemd options
let sd_opts = sd_options(&sd_version, &kernel_version, &mode);
let sd_opts = sd_options(&sd_version, &kernel_version, &hardening_opts);

// Load and merge profile data
let mut actions: Vec<summarize::ProgramAction> = Vec::new();
Expand All @@ -129,11 +132,11 @@ fn main() -> anyhow::Result<()> {
}
cl::Action::Service(cl::ServiceAction::StartProfile {
service,
mode,
hardening_opts,
no_restart,
}) => {
let service = systemd::Service::new(&service);
service.add_profile_fragment(&mode)?;
service.add_profile_fragment(&hardening_opts)?;
if no_restart {
log::warn!("Profiling config will only be applied when systemd config is reloaded, and service restarted");
} else {
Expand Down Expand Up @@ -175,8 +178,11 @@ fn main() -> anyhow::Result<()> {
}
cl::Action::ListSystemdOptions => {
println!("# Supported systemd options");
let mut sd_opts =
sd_options(&sd_version, &kernel_version, &cl::HardeningMode::Aggressive);
let mut sd_opts = sd_options(
&sd_version,
&kernel_version,
&cl::HardeningOptions::strict(),
);
sd_opts.sort_unstable_by_key(|o| o.name);
for sd_opt in sd_opts {
println!("- [`{sd_opt}`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#{sd_opt}=)");
Expand Down
10 changes: 5 additions & 5 deletions src/systemd/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use itertools::Itertools;
use strum::IntoEnumIterator;

use crate::{
cl::HardeningMode,
cl::{HardeningMode, HardeningOptions},
summarize::{
CountableSetSpecifier, NetworkActivity, NetworkActivityKind, ProgramAction, SetSpecifier,
},
Expand Down Expand Up @@ -818,7 +818,7 @@ static SYSCALL_CLASSES: LazyLock<HashMap<&'static str, HashSet<&'static str>>> =
pub(crate) fn build_options(
systemd_version: &SystemdVersion,
kernel_version: &KernelVersion,
mode: &HardeningMode,
hardening_opts: &HardeningOptions,
) -> Vec<OptionDescription> {
let mut options = Vec::new();

Expand Down Expand Up @@ -1196,7 +1196,7 @@ pub(crate) fn build_options(
updater: None,
});

if let HardeningMode::Aggressive = mode {
if let HardeningMode::Aggressive = hardening_opts.mode {
// https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=
//
// For now we enable this option if no sockets are used at all, in theory this could break if
Expand Down Expand Up @@ -1256,7 +1256,7 @@ pub(crate) fn build_options(
.collect(),
),
}],
updater: Some(OptionUpdater {
updater: hardening_opts.network_firewalling.then_some(OptionUpdater {
effect: |e, a| {
let OptionValueEffect::DenyAction(ProgramAction::NetworkActivity(effect_na)) = e
else {
Expand Down Expand Up @@ -1546,7 +1546,7 @@ pub(crate) fn build_options(
updater: None,
});

if let HardeningMode::Aggressive = mode {
if let HardeningMode::Aggressive = hardening_opts.mode {
// https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=
//
// This is actually very safe to enable, but since we don't currently support checking for its
Expand Down
4 changes: 2 additions & 2 deletions src/systemd/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,14 +278,14 @@ mod tests {
use super::*;

use crate::{
cl::HardeningMode,
cl::HardeningOptions,
systemd::{build_options, KernelVersion, SystemdVersion},
};

fn test_options(names: &[&str]) -> Vec<OptionDescription> {
let sd_version = SystemdVersion::new(254, 0);
let kernel_version = KernelVersion::new(6, 4, 0);
build_options(&sd_version, &kernel_version, &HardeningMode::Safe)
build_options(&sd_version, &kernel_version, &HardeningOptions::safe())
.into_iter()
.filter(|o| names.contains(&o.name))
.collect()
Expand Down
15 changes: 9 additions & 6 deletions src/systemd/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use itertools::Itertools;
use rand::Rng;

use crate::{
cl::HardeningMode,
cl::HardeningOptions,
systemd::{options::OptionWithValue, END_OPTION_OUTPUT_SNIPPET, START_OPTION_OUTPUT_SNIPPET},
};

Expand Down Expand Up @@ -54,7 +54,10 @@ impl Service {
)
}

pub(crate) fn add_profile_fragment(&self, mode: &HardeningMode) -> anyhow::Result<()> {
pub(crate) fn add_profile_fragment(
&self,
hardening_opts: &HardeningOptions,
) -> anyhow::Result<()> {
// Check first if our fragment does not yet exist
let fragment_path = self.fragment_path(PROFILING_FRAGMENT_NAME, false);
anyhow::ensure!(
Expand Down Expand Up @@ -135,10 +138,10 @@ impl Service {
#[expect(clippy::unwrap_used)]
writeln!(
fragment_file,
"{}={} run -m {} -p {} -- {}",
"{}={} run {} -p {} -- {}",
exec_start_opt,
shh_bin,
mode,
hardening_opts.to_cmdline(),
profile_data_path.to_str().unwrap(),
cmd
)?;
Expand All @@ -151,9 +154,9 @@ impl Service {
#[expect(clippy::unwrap_used)]
writeln!(
fragment_file,
"ExecStopPost={} merge-profile-data -m {} {}",
"ExecStopPost={} merge-profile-data {} {}",
shh_bin,
mode,
hardening_opts.to_cmdline(),
profile_data_paths
.iter()
.map(|p| p.to_str().unwrap())
Expand Down

0 comments on commit 4722239

Please sign in to comment.