diff --git a/pkgs/by-name/sh/shh/fix_run_checks.patch b/pkgs/by-name/sh/shh/fix_run_checks.patch index 63452965316e..3b29a4f1b5eb 100644 --- a/pkgs/by-name/sh/shh/fix_run_checks.patch +++ b/pkgs/by-name/sh/shh/fix_run_checks.patch @@ -1,21 +1,9 @@ -commit 070bf216bacf6ce1b473f2819a017d1be29716d0 +commit 58bdfa7ef92ba07dc41a07aeef6d790ecd8f888c Author: kuflierl <41301536+kuflierl@users.noreply.github.com> -Date: Sun Apr 13 19:56:58 2025 +0200 +Date: Sat May 3 21:02:26 2025 +0200 - add support for nix-build-system for tests + fix(tests): add support for nix-build-system for tests -diff --git a/Cargo.toml b/Cargo.toml -index eba0ef8..9153f00 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -58,6 +58,7 @@ default = [] - as-root = [] # for tests only - gen-man-pages = ["dep:clap_mangen"] - nightly = [] # for benchmarks only -+nix-build-env = [] # perform checks in a way compatable with nix build - - [lints.rust] - # https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html diff --git a/src/systemd/resolver.rs b/src/systemd/resolver.rs index e2abbb7..1151592 100644 --- a/src/systemd/resolver.rs @@ -44,7 +32,7 @@ index e2abbb7..1151592 100644 let actions = vec![ProgramAction::Read("/var/data".into())]; let candidates = resolve(&opts, &actions, &hardening_opts); diff --git a/tests/options.rs b/tests/options.rs -index 835ee14..cac55e5 100644 +index 835ee14..a9c9973 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -24,7 +24,7 @@ fn run_true() { @@ -116,7 +104,7 @@ index 835ee14..cac55e5 100644 BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) } else { BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) -@@ -227,11 +227,12 @@ fn run_read_kallsyms() { +@@ -227,7 +227,7 @@ fn run_read_kallsyms() { .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) @@ -125,30 +113,7 @@ index 835ee14..cac55e5 100644 .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); } - #[test] -+#[cfg_attr(feature = "nix-build-env", ignore)] - fn run_ls_modules() { - Command::cargo_bin("shh") - .unwrap() -@@ -240,7 +241,7 @@ fn run_ls_modules() { - .assert() - .success() - .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) -- .stdout(if Uid::effective().is_root() { -+ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { - BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) - } else { - BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) -@@ -304,7 +305,7 @@ fn run_dmesg() { - } - - #[test] --#[cfg_attr(feature = "as-root", ignore)] -+#[cfg_attr(any(feature = "nix-build-env", feature = "as-root"), ignore)] - fn run_systemctl() { - assert!(!Uid::effective().is_root()); - -@@ -344,6 +345,7 @@ fn run_systemctl() { +@@ -344,6 +344,7 @@ fn run_systemctl() { .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); } @@ -156,7 +121,7 @@ index 835ee14..cac55e5 100644 #[test] fn run_ss() { Command::cargo_bin("shh") -@@ -353,7 +355,7 @@ fn run_ss() { +@@ -353,7 +354,7 @@ fn run_ss() { .assert() .success() .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) @@ -165,7 +130,7 @@ index 835ee14..cac55e5 100644 BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) } else { BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) -@@ -369,7 +371,7 @@ fn run_ss() { +@@ -369,7 +370,7 @@ fn run_ss() { .stdout(predicate::str::contains("ProtectKernelModules=true\n").count(1)) .stdout(predicate::str::contains("ProtectKernelLogs=true\n").count(1)) .stdout(predicate::str::contains("ProtectControlGroups=true\n").count(1)) @@ -174,7 +139,7 @@ index 835ee14..cac55e5 100644 .stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1)) .stdout(predicate::str::contains("RestrictAddressFamilies=AF_NETLINK AF_UNIX\n").count(1).or(predicate::str::contains("RestrictAddressFamilies=AF_NETLINK\n").count(1))) .stdout(predicate::str::contains("SocketBindDeny=ipv4:tcp\n").count(1)) -@@ -379,7 +381,7 @@ fn run_ss() { +@@ -379,7 +380,7 @@ fn run_ss() { .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) @@ -183,19 +148,3 @@ index 835ee14..cac55e5 100644 .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); } -@@ -741,6 +743,7 @@ fn run_mknod() { - } - - #[test] -+#[cfg_attr(feature = "nix-build-env", ignore)] // no raw socket cap in nix build - fn run_ping_4() { - Command::cargo_bin("shh") - .unwrap() -@@ -759,6 +762,7 @@ fn run_ping_4() { - } - - #[test] -+#[cfg_attr(feature = "nix-build-env", ignore)] // no raw socket cap in nix build - fn run_ping_6() { - Command::cargo_bin("shh") - .unwrap() diff --git a/pkgs/by-name/sh/shh/package.nix b/pkgs/by-name/sh/shh/package.nix index d2f5e11cf215..e2b894df6760 100644 --- a/pkgs/by-name/sh/shh/package.nix +++ b/pkgs/by-name/sh/shh/package.nix @@ -2,12 +2,21 @@ lib, rustPlatform, fetchFromGitHub, + nix-update-script, + fetchpatch, + installShellFiles, python3, strace, systemd, iproute2, + stdenv, + enableDocumentationFeature ? true, + enableDocumentationGeneration ? true, }: - +let + isNativeDocgen = + (stdenv.buildPlatform.canExecute stdenv.hostPlatform) && enableDocumentationFeature; +in rustPlatform.buildRustPackage rec { pname = "shh"; version = "2025.4.12"; @@ -19,36 +28,101 @@ rustPlatform.buildRustPackage rec { hash = "sha256-+JWz0ya6gi8pPERnpAcQIe7zZUzWGxha+9/gizMVtEw="; }; - cargoHash = "sha256-TdP+1sb1GEFM57z+rc+gqhoWQhPAXzvMt/FCWf3wpr8="; + cargoHash = "sha256-rrOH76LHYSEeuNiMIICpAO7U/sz5V0JRO22mbIICQWw="; + + # needs to be done this way to bypass patch conflicts + cargoPatches = [ + (fetchpatch { + # to be removed after next release + name = "refactor-man-page-generation-command.patch"; + url = "https://github.com/desbma/shh/commit/849b9a6646981c83a72a977b6398371e29d3b928.patch"; + hash = "sha256-LZlUFfPtt2ScTxQbQ9j3Kzvp7T4MCFs92cJiI3YbWns="; + }) + (fetchpatch { + # to be removed after next release + name = "support-shell-auto-complete.patch"; + url = "https://github.com/desbma/shh/commit/74914dc8cfd74dbd7e051a090cc4c1f561b8cdde.patch"; + hash = "sha256-WgKRQAEwSpXdQUnrZC1Bp4RfKg2J9kPkT1k6R2wwgT8="; + }) + ]; patches = [ ./fix_run_checks.patch - ./pr13-profile-path-fix-strace.patch + (fetchpatch { + # to be removed after next release + name = "feat-static-strace-path-support-at-compile-time.patch"; + url = "https://github.com/desbma/shh/commit/da62ceeb227de853be06610721744667c6fe994b.patch"; + hash = "sha256-p/W7HRZZ4TpIwrWN8wQB/SH3C8x3ZLXzwGV50oK/znQ="; + }) ]; - # buildFeatures = [ /*"gen-man-pages"*/ ]; + env = { + SHH_STRACE_BIN_PATH = lib.getExe strace; + }; - checkFeatures = [ "nix-build-env" ]; + buildFeatures = lib.optional enableDocumentationFeature "generate-extra"; + + checkFlags = [ + # no access to system modules in build env + "--skip=run_ls_modules" + # missing systemd daemon in build env + "--skip=run_systemctl" + # no raw socket cap in nix build + "--skip=run_ping_4" + "--skip=run_ping_6" + ]; buildInputs = [ strace systemd ]; - nativeCheckInputs = [ - strace + nativeBuildInputs = [ + installShellFiles systemd + strace + ]; + + nativeCheckInputs = [ python3 iproute2 ]; + # todo elvish + postInstall = lib.optionalString enableDocumentationGeneration '' + mkdir -p target/{mangen,shellcomplete} + + ${ + if isNativeDocgen then + '' + $out/bin/shh gen-man-pages target/mangen + $out/bin/shh gen-shell-complete target/shellcomplete + '' + else + '' + unset SHH_STRACE_BIN_PATH + cargo run --features generate-extra -- gen-man-pages target/mangen + cargo run --features generate-extra -- gen-shell-complete target/shellcomplete + '' + } + + installManPage target/mangen/* + + installShellCompletion --cmd ${pname} \ + target/shellcomplete/${pname}.{bash,fish} \ + --zsh target/shellcomplete/_${pname} + ''; + # RUST_BACKTRACE = 1; + passthru.updateScript = nix-update-script { }; + meta = { description = "Automatic systemd service hardening guided by strace profiling"; homepage = "https://github.com/desbma/shh"; license = lib.licenses.gpl3Only; platforms = lib.platforms.linux; + changelog = "https://github.com/desbma/shh/blob/v${version}/CHANGELOG.md"; mainProgram = "shh"; maintainers = with lib.maintainers; [ erdnaxe diff --git a/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch b/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch deleted file mode 100644 index 2db49fe17177..000000000000 --- a/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch +++ /dev/null @@ -1,83 +0,0 @@ -commit 4d2c1556d769695770c95a982e0dcda4d70eee57 -Author: kuflierl <41301536+kuflierl@users.noreply.github.com> -Date: Sun Apr 13 19:57:50 2025 +0200 - - service.rs: profile path fix for strace - Enable path env fixing when path env doesn't have strace to unbreak tool on unique systems and units. - This fixes handling on non FHS operating systems and systemd units that define their own PATH that doesn't include strace. - -diff --git a/src/systemd/service.rs b/src/systemd/service.rs -index 908fdf0..e9294cf 100644 ---- a/src/systemd/service.rs -+++ b/src/systemd/service.rs -@@ -7,6 +7,7 @@ use std::{ - ops::RangeInclusive, - path::{Path, PathBuf}, - process::{Command, Stdio}, -+ ffi::OsString, - }; - - use anyhow::Context as _; -@@ -99,6 +100,41 @@ impl Service { - ) - } - -+ // A function for locating the parent directory i.e. PATH of an executable -+ fn resolve_exec_path

(exe_name: &P, path_env: OsString) -> Option -+ where P: AsRef + ?Sized, -+ { -+ env::split_paths(&path_env).filter_map(|dir| { -+ let full_path = dir.join(&exe_name); -+ if full_path.is_file() { -+ Some(dir) -+ } else { -+ None -+ } -+ }).next() -+ } -+ -+ // determine PATH env used for unit -+ pub(crate) fn get_exec_path(config_paths: &Vec<&Path>) -> anyhow::Result { -+ let old_path_env_option = Self::config_vals("Environment", &config_paths)? -+ .into_iter().filter(|x| x.starts_with("\"PATH=")).last().map(|x| x.trim_matches('\"').get(5..).unwrap().to_owned()); -+ Ok(match old_path_env_option { -+ Some(path_env) => { -+ log::info!("Found hard coded PATH environment in unit: {path_env}"); -+ path_env -+ }, -+ None => { -+ let output = Command::new("systemd-path").arg("search-binaries-default").output()?; -+ if !output.status.success() { -+ anyhow::bail!("systemd-path invocation failed with code {:?}", output.status); -+ } -+ let default_systemd_path = output.stdout.lines().next().ok_or_else(|| anyhow::anyhow!("Unable to get global systemd default PATH"))??; -+ log::info!("Could not find hard coded PATH environment in unit, using systemd default: {default_systemd_path}"); -+ default_systemd_path -+ } -+ }) -+ } -+ - /// Get systemd "exposure level" for the service (0-100). - /// 100 means extremely exposed (no hardening), 0 means so sandboxed it can't do much. - /// Although this is a very crude heuristic, below 40-50 is generally good. -@@ -170,6 +206,20 @@ impl Service { - writeln!(fragment_file, "KillMode=control-group")?; - writeln!(fragment_file, "StandardOutput=journal")?; - -+ // Modifying Env Path for strace availability if needed -+ let old_path_env = Self::get_exec_path(&config_paths)?; -+ match Self::resolve_exec_path("strace", (&old_path_env).into()) { -+ Some(_) => log::info!("Found strace in previous path, no correction needed"), -+ None => { -+ let path_with_strace = Self::resolve_exec_path("strace", env::var_os("PATH").unwrap()).unwrap(); -+ log::info!("Found strace from local PATH in {}, inserting it into unit config!", path_with_strace.display()); -+ let mut paths = env::split_paths(&old_path_env).collect::>(); -+ paths.push(path_with_strace); -+ let new_path = env::join_paths(paths)?; -+ writeln!(fragment_file, "Environment=\"PATH={}\"", new_path.to_str().unwrap())?; -+ }, -+ } -+ - // Profile data dir - let mut rng = rand::rng(); - let profile_data_dir = PathBuf::from(format!(