diff --git a/Cargo.lock b/Cargo.lock index a091e4d..8b860cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -592,6 +598,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "1.1.0" @@ -1382,6 +1397,7 @@ dependencies = [ "strip-ansi-escapes", "termion", "tokio", + "which", "xattr", ] @@ -1790,6 +1806,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -1920,6 +1948,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index c90c673..5fcc026 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,5 @@ serde_json = "1.0.128" strip-ansi-escapes = "0.2.0" termion = "4.0.3" tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } +which = "6.0.3" xattr = { version = "1.3.1", default-features = false } diff --git a/src/core/health.rs b/src/core/health.rs index e68a0a0..2447a00 100644 --- a/src/core/health.rs +++ b/src/core/health.rs @@ -1,5 +1,6 @@ -use std::{cmp::Ordering, path::Path}; +use std::{cmp::Ordering, future::Future, os::unix::fs::PermissionsExt, path::Path, pin::Pin}; +use futures::future::join_all; use libc::{fork, unshare, waitpid, CLONE_NEWUSER, PR_CAPBSET_READ}; use tokio::fs; @@ -40,35 +41,22 @@ pub async fn check_health() { errors.push("Your kernel does not support user namespaces"); } - if let Ok(content) = fs::read_to_string("/proc/sys/kernel/unprivileged_userns_clone").await { - if content.trim() == "0" { - errors.push("You must enable unprivileged_userns_clone"); - } - } - if let Ok(content) = fs::read_to_string("/proc/sys/user/max_user_namespaces").await { - if content.trim() == "0" { - errors.push("You must enable max_user_namespaces"); - } - } - if let Ok(content) = fs::read_to_string("/proc/sys/kernel/userns_restrict").await { - if content.trim() == "1" { - errors.push("You must disable userns_restrict"); - } - } - if let Ok(content) = - fs::read_to_string("/proc/sys/kernel/apparmor_restrict_unprivileged_userns").await - { - if content.trim() == "1" { - errors.push("You must disable apparmor_restrict_unprivileged_userns"); - } - } + let checks: Vec>>>> = vec![ + Box::pin(check_unprivileged_userns_clone()), + Box::pin(check_max_user_namespaces()), + Box::pin(check_userns_restrict()), + Box::pin(check_apparmor_restrict()), + Box::pin(check_capabilities()), + Box::pin(check_fusermount()), + ]; - if !check_capability(CAP_SYS_ADMIN) { - errors.push("Capability 'CAP_SYS_ADMIN' is not available."); - if !check_capability(CAP_MKNOD) { - errors.push("Capability 'CAP_MKNOD' is not available."); + let results = join_all(checks).await; + + results.into_iter().for_each(|result| { + if let Some(error) = result { + errors.push(error); } - } + }); for error in &errors { warn!("{}", error); @@ -83,3 +71,89 @@ pub async fn check_health() { success!("Everything is in order.") } } + +async fn check_unprivileged_userns_clone() -> Option<&'static str> { + let content = fs::read_to_string("/proc/sys/kernel/unprivileged_userns_clone") + .await + .ok()?; + if content.trim() == "0" { + Some("You must enable unprivileged_userns_clone") + } else { + None + } +} + +async fn check_max_user_namespaces() -> Option<&'static str> { + let content = fs::read_to_string("/proc/sys/user/max_user_namespaces") + .await + .ok()?; + if content.trim() == "0" { + Some("You must enable max_user_namespaces") + } else { + None + } +} + +async fn check_userns_restrict() -> Option<&'static str> { + let content = fs::read_to_string("/proc/sys/kernel/userns_restrict") + .await + .ok()?; + if content.trim() == "1" { + Some("You must disable userns_restrict") + } else { + None + } +} + +async fn check_apparmor_restrict() -> Option<&'static str> { + let content = fs::read_to_string("/proc/sys/kernel/apparmor_restrict_unprivileged_userns") + .await + .ok()?; + if content.trim() == "1" { + Some("You must disable apparmor_restrict_unprivileged_userns") + } else { + None + } +} + +async fn check_capabilities() -> Option<&'static str> { + if !check_capability(CAP_SYS_ADMIN) { + if !check_capability(CAP_MKNOD) { + return Some("Capability 'CAP_MKNOD' is not available."); + } + return Some("Capability 'CAP_SYS_ADMIN' is not available."); + } + None +} + +async fn check_fusermount() -> Option<&'static str> { + match which::which("fusermount") { + Ok(path) => match path.metadata() { + Ok(meta) => { + let permissions = meta.permissions().mode(); + if permissions != 0o104755 { + return Some(Box::leak( + format!( + "Invalid {} file mode bits. Set 4755 for {}", + "fusermount".color(Color::Blue), + path.to_string_lossy().color(Color::Green) + ) + .into_boxed_str(), + ) as &'static str); + } + } + Err(_) => return Some("Unable to read fusermount"), + }, + Err(_) => { + return Some(Box::leak( + format!( + "{} not found. Please install {}", + "fusermount".color(Color::Blue), + "fuse".color(Color::Blue) + ) + .into_boxed_str(), + )) + } + } + None +}