mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-10 19:55:41 +03:00
shh: rev2 switch to upstreamed patches for strace path fixing, clean up check patch, enable manpages and autocomplete with upstream patches,
update script, make cross-compile possible, make docgen feature and generation optional, add changelog Signed-off-by: kuflierl <41301536+kuflierl@users.noreply.github.com>
This commit is contained in:
parent
e067fb89ac
commit
3dc449dadb
3 changed files with 90 additions and 150 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<P>(exe_name: &P, path_env: OsString) -> Option<PathBuf>
|
||||
+ where P: AsRef<Path> + ?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<String> {
|
||||
+ 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::<Vec<_>>();
|
||||
+ 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!(
|
Loading…
Add table
Add a link
Reference in a new issue