diff --git a/rar-common/src/util.rs b/rar-common/src/util.rs index af610cb..45facf4 100644 --- a/rar-common/src/util.rs +++ b/rar-common/src/util.rs @@ -1,20 +1,23 @@ -use std::{error::Error, fs::File, os::fd::AsRawFd, path::PathBuf, ffi::CString, path::Path}; +use std::{ + error::Error, ffi::CString, fs::File, io, os::{ + fd::AsRawFd, + unix::fs::{MetadataExt, PermissionsExt}, + }, path::{Path, PathBuf} +}; +use capctl::{prctl, CapState}; use capctl::{Cap, CapSet, ParseCapError}; use libc::{FS_IOC_GETFLAGS, FS_IOC_SETFLAGS}; +use serde::Serialize; use strum::EnumIs; use tracing::{debug, warn, Level}; -use capctl::{prctl, CapState}; -use serde::Serialize; use tracing_subscriber::util::SubscriberInitExt; - pub const RST: &str = "\x1B[0m"; pub const BOLD: &str = "\x1B[1m"; pub const UNDERLINE: &str = "\x1B[4m"; pub const RED: &str = "\x1B[31m"; - #[macro_export] macro_rules! upweak { ($e:expr) => { @@ -51,19 +54,45 @@ pub enum ImmutableLock { Unset, } +fn immutable_required_privileges(file: &File, effective: bool) -> Result<(), capctl::Error> { + //get file owner + let metadata = file.metadata().unwrap(); + let uid = metadata.uid(); + let gid = metadata.gid(); + immutable_effective(effective)?; + // check if the current user is the owner + if nix::unistd::Uid::effective() != nix::unistd::Uid::from_raw(uid) + && nix::unistd::Gid::effective() != nix::unistd::Gid::from_raw(gid) + { + read_or_dac_override(effective)?; + fowner_effective(effective)?; + } + Ok(()) +} + +fn read_or_dac_override(effective: bool) -> Result<(), capctl::Error> { + Ok(match effective { + false => { + read_effective(false) + .and(dac_override_effective(false))?; + } + true => { + read_effective(true) + .or(dac_override_effective(true))?; + } + }) +} + /// Set or unset the immutable flag on a file /// # Arguments /// * `file` - The file to set the immutable flag on /// * `lock` - Whether to set or unset the immutable flag -pub fn toggle_lock_config(file: &PathBuf, lock: ImmutableLock) -> Result<(), String> { - let file = match open_with_privileges(file) { - Err(e) => return Err(e.to_string()), - Ok(f) => f, - }; +pub fn toggle_lock_config>(file: &P, lock: ImmutableLock) -> io::Result<()> { + let file = open_with_privileges(file)?; let mut val = 0; let fd = file.as_raw_fd(); if unsafe { nix::libc::ioctl(fd, FS_IOC_GETFLAGS, &mut val) } < 0 { - return Err(std::io::Error::last_os_error().to_string()); + return Err(std::io::Error::last_os_error()); } if lock.is_unset() { val &= !(FS_IMMUTABLE_FL); @@ -71,22 +100,13 @@ pub fn toggle_lock_config(file: &PathBuf, lock: ImmutableLock) -> Result<(), Str val |= FS_IMMUTABLE_FL; } debug!("Setting immutable privilege"); - immutable_effective(true).map_err(|e| e.to_string())?; - debug!("Setting dac override privilege"); - read_effective(true) - .or(dac_override_effective(true)) - .map_err(|e| e.to_string())?; - fowner_effective(true).map_err(|e| e.to_string())?; - debug!("Setting immutable flag"); + + immutable_required_privileges(&file, true)?; if unsafe { nix::libc::ioctl(fd, FS_IOC_SETFLAGS, &mut val) } < 0 { - return Err(std::io::Error::last_os_error().to_string()); + return Err(std::io::Error::last_os_error()); } debug!("Resetting immutable privilege"); - immutable_effective(false).map_err(|e| e.to_string())?; - read_effective(false) - .and(dac_override_effective(false)) - .map_err(|e| e.to_string())?; - fowner_effective(false).map_err(|e| e.to_string())?; + immutable_required_privileges(&file, false)?; Ok(()) } diff --git a/xtask/src/ebpf/mod.rs b/xtask/src/ebpf/mod.rs index 3267110..be34b09 100644 --- a/xtask/src/ebpf/mod.rs +++ b/xtask/src/ebpf/mod.rs @@ -10,7 +10,7 @@ pub mod run; pub fn build_all(opts: &BuildOptions) -> Result<(), anyhow::Error> { - build_ebpf(&opts.ebpf, &opts.profile).context("Error while building eBPF program")?; + build_ebpf(&opts.ebpf_toolchain, &opts.profile).context("Error while building eBPF program")?; build(opts).context("Error while building userspace application") } diff --git a/xtask/src/install/build.rs b/xtask/src/install/build.rs index a7990a7..cad2c68 100644 --- a/xtask/src/install/build.rs +++ b/xtask/src/install/build.rs @@ -9,6 +9,7 @@ pub fn build(options: &BuildOptions) -> Result<(), anyhow::Error> { if options.profile.is_release() { args.push("--release"); } + println!("Building sr and chsr with {:?}", &args); Command::new("cargo") .args(args) .status() diff --git a/xtask/src/install/configure.rs b/xtask/src/install/configure.rs index 21a5457..dbdd872 100644 --- a/xtask/src/install/configure.rs +++ b/xtask/src/install/configure.rs @@ -5,6 +5,7 @@ use std::path::Path; use anyhow::Context; use nix::unistd::{getresuid, getuid}; +use rar_common::util::toggle_lock_config; use serde::{Deserialize, Serialize}; use serde_json::Value; use strum::EnumIs; @@ -42,7 +43,6 @@ pub const CONFIG_FILE: &str = "/etc/security/rootasrole.json"; const DEFAULT_PATH: &str = "resources/rootasrole.json"; pub const PAM_CONFIG_PATH: &str = "/etc/pam.d/sr"; - fn is_running_in_container() -> bool { // Check for environment files that might indicate a container let container_env_files = ["/run/.containerenv", "/.dockerenv", "/run/container_type"]; @@ -64,7 +64,11 @@ fn is_running_in_container() -> bool { let reader = io::BufReader::new(file); for line in reader.lines() { if let Ok(line) = line { - if line.contains("docker") || line.contains("kubepods") || line.contains("lxc") || line.contains("containerd") { + if line.contains("docker") + || line.contains("kubepods") + || line.contains("lxc") + || line.contains("containerd") + { return true; } } @@ -81,15 +85,26 @@ fn check_filesystem() -> io::Result<()> { if let Some(fs_type) = get_filesystem_type(CONFIG_FILE)? { match fs_type.as_str() { "ext2" | "ext3" | "ext4" | "xfs" | "btrfs" | "ocfs2" | "jfs" | "reiserfs" => { + println!( + "{} is compatble for immutability, setting immutable flag", + fs_type + ); set_immutable(&mut config, true); + toggle_lock_config( + &CONFIG_FILE.to_string(), + rar_common::util::ImmutableLock::Set, + )?; + return Ok(()); } - _ => { - set_immutable(&mut config, false); - } + _ => println!( + "{} is not compatible for immutability, removing immutable flag", + fs_type + ), } } else { - set_immutable(&mut config, false); + println!("Failed to get filesystem type, removing immutable flag"); } + set_immutable(&mut config, false); Ok(()) } @@ -137,11 +152,29 @@ fn deploy_config_file() -> Result { let mut status = ConfigState::Unchanged; // Check if the target file exists if !Path::new(CONFIG_FILE).exists() { + println!("Config file does not exist, deploying default file"); // If the target file does not exist, copy the default file deploy_config(CONFIG_FILE)?; } else { status = config_state()?; } + + match status { + ConfigState::Unchanged => { + println!("Config file newly created or has not been modified checking if filesystem allows immutability"); + let res = check_filesystem().context("Failed to configure the filesystem parameter"); + if res.is_err() { + // If the filesystem check fails, ignore the error if running in a container as it may not have immutable access + if is_running_in_container() { + return Ok(status); + } + res?; + } + } + ConfigState::Modified => { + println!("Config file has been modified by the user, skipping immutable configuration"); + } + } Ok(status) } @@ -157,22 +190,19 @@ pub fn config_state() -> Result { Ok(status) } -fn deploy_config>(config_path: P) -> Result<(), anyhow::Error> { +fn deploy_config>(config_path: P) -> Result<(), anyhow::Error> { let config = File::open(DEFAULT_PATH)?; let mut buf = BufReader::new(config); let mut content = String::new(); // Read the default config file buf.read_to_string(&mut content)?; // Get the real user - + let user = retrieve_real_user()?; // Replace the placeholder with the current user, which will act as the main administrator match user { Some(user) => { - content = content.replace( - "\"ROOTADMINISTRATOR\"", - &format!("\"{}\"", user.name), - ); + content = content.replace("\"ROOTADMINISTRATOR\"", &format!("\"{}\"", user.name)); } None => { eprintln!("Failed to get the current user from passwd file, using UID instead"); @@ -189,19 +219,17 @@ fn deploy_config>(config_path: P) -> Result<(), anyhow::Error> { fn retrieve_real_user() -> Result, anyhow::Error> { // if sudo_user is not set, get the real user if let Ok(sudo_user) = env::var("SUDO_USER") { - let user = nix::unistd::User::from_name(&sudo_user) - .context("Failed to get the sudo user")?; + let user = + nix::unistd::User::from_name(&sudo_user).context("Failed to get the sudo user")?; return Ok(user); } else { let ruid = getresuid()?.real; - let user = nix::unistd::User::from_uid(ruid) - .context("Failed to get the real user")?; + let user = nix::unistd::User::from_uid(ruid).context("Failed to get the real user")?; Ok(user) } - } -pub fn default_pam_path(os : &OsTarget) -> &'static str { +pub fn default_pam_path(os: &OsTarget) -> &'static str { match os { OsTarget::Debian | OsTarget::Ubuntu => "resources/debian/deb_sr_pam.conf", OsTarget::RedHat | OsTarget::CentOS | OsTarget::Fedora => "resources/redhat/rh_sr_pam.conf", @@ -211,34 +239,25 @@ pub fn default_pam_path(os : &OsTarget) -> &'static str { fn deploy_pam_config(os: &OsTarget) -> io::Result { if fs::metadata(PAM_CONFIG_PATH).is_err() { + println!("Deploying PAM configuration file"); return fs::copy(default_pam_path(os), PAM_CONFIG_PATH); } Ok(0) } -pub fn configure(os: &Option) -> Result<(), anyhow::Error> { +pub fn configure(os: Option) -> Result<(), anyhow::Error> { let os = if let Some(os) = os { os } else { - &OsTarget::detect().context("Failed to detect the OS")? + OsTarget::detect() + .and_then(|t| { + println!("Detected OS is : {}", t); + Ok(t) + }) + .context("Failed to detect the OS")? }; - deploy_pam_config(os).context("Failed to deploy the PAM configuration file")?; - - deploy_config_file() - .context("Failed to configure the config file") - .and_then(|state| match state { - ConfigState::Unchanged => { - let res = check_filesystem().context("Failed to configure the filesystem parameter"); - if res.is_err() { - // If the filesystem check fails, ignore the error if running in a container as it may not have immutable access - if is_running_in_container() { - return Ok(()); - } - } - res - } - ConfigState::Modified => Ok(()), - }) + deploy_pam_config(&os).context("Failed to deploy the PAM configuration file")?; - + deploy_config_file().context("Failed to configure the config file")?; + Ok(()) } diff --git a/xtask/src/install/install.rs b/xtask/src/install/install.rs index a975461..bc29c7c 100644 --- a/xtask/src/install/install.rs +++ b/xtask/src/install/install.rs @@ -13,10 +13,15 @@ use super::util::{cap_clear, cap_effective}; use super::{InstallOptions, CAPABLE_DEST, CHSR_DEST, SR_DEST}; fn copy_files(profile: &Profile, ebpf: Option) -> Result<(), anyhow::Error> { - fs::copy(format!("target/{}/sr", profile), SR_DEST)?; - fs::copy(format!("target/{}/chsr", profile), CHSR_DEST)?; + let binding = std::env::current_dir()?; + let cwd = binding.to_str().context("unable to get current dir as string")?; + println!("Current working directory: {}", cwd); + println!("Copying files {}/target/{}/sr to {} and {}", cwd, profile, SR_DEST, CHSR_DEST); + fs::rename(format!("{}/target/{}/sr", cwd, profile), SR_DEST)?; + fs::rename(format!("{}/target/{}/chsr", cwd, profile), CHSR_DEST)?; if let Some(ebpf) = ebpf { - fs::copy(format!("target/{}/capable", ebpf), CAPABLE_DEST)?; + println!("Copying file {}/target/{}/capable to {}", cwd, ebpf, CAPABLE_DEST); + fs::rename(format!("{}/target/{}/capable", cwd, ebpf), CAPABLE_DEST)?; } chmod()?; @@ -64,7 +69,7 @@ pub fn install(options: &InstallOptions) -> Result<(), anyhow::Error> { // cp target/{release}/sr,chsr,capable /usr/bin copy_files( &options.build.profile, - options.ebpf_build, + if options.build_ebpf { Some(options.ebpf_build) } else { None }, ) .context("Failed to copy sr and chsr files")?; diff --git a/xtask/src/install/mod.rs b/xtask/src/install/mod.rs index 04e2d46..b2f198d 100644 --- a/xtask/src/install/mod.rs +++ b/xtask/src/install/mod.rs @@ -37,7 +37,7 @@ pub struct InstallOptions { /// Set the endianness of the BPF target #[clap(default_value = "bpfel-unknown-none", long)] - pub ebpf_build: Option, + pub ebpf_build: EbpfArchitecture, /// Executable to elevate privileges for installing (e.g. sr, sudo or doas) /// Default is sr, if not found, it will use sudo or doas. @@ -46,7 +46,7 @@ pub struct InstallOptions { /// Build the eBPF, requires nightly toolchain. Asks to install the nightly toolchain with rustup if not found. #[clap(long)] - pub ebpf: bool, + pub build_ebpf: bool, /// The OS target for PAM configuration #[clap(long)] @@ -59,7 +59,19 @@ pub struct InstallOptions { #[derive(Debug, Parser)] pub struct UninstallOptions { + /// Delete all configuration files + #[clap(long, short = 'c')] pub clean_config: bool, + + pub kind: UninstallKind, +} + +#[derive(Clone, Debug, ValueEnum, EnumIs, EnumString, Display)] +#[strum(serialize_all = "lowercase")] +pub enum UninstallKind { + All, + Sr, + Capable, } #[derive(Debug, Copy, Clone, EnumIs, EnumString, Display)] @@ -81,7 +93,7 @@ pub struct BuildOptions { pub toolchain: Toolchain, #[clap(default_value = "bpfel-unknown-none", long)] - pub ebpf: EbpfArchitecture, + pub ebpf_toolchain: EbpfArchitecture, /// Clean the target directory before building #[clap(long = "clean", short = 'b')] @@ -89,7 +101,7 @@ pub struct BuildOptions { } -#[derive(Debug, Clone, ValueEnum, EnumIs, EnumIter)] +#[derive(Debug, Clone, ValueEnum, EnumIs, EnumIter, Display)] #[clap(rename_all = "lowercase")] pub enum OsTarget { #[clap(alias = "deb")] @@ -243,12 +255,14 @@ impl FromStr for Toolchain { } } -pub(crate) fn configure(os: &Option) -> Result<(), anyhow::Error> { +pub(crate) fn configure(os: Option) -> Result<(), anyhow::Error> { configure::configure(os) } pub(crate) fn install(opts: &InstallOptions) -> Result<(), anyhow::Error> { - install::install(opts) + build(&opts.build)?; + install::install(&opts)?; + configure(opts.os.clone()) } pub(crate) fn build(opts: &BuildOptions) -> Result<(), anyhow::Error> { diff --git a/xtask/src/install/uninstall.rs b/xtask/src/install/uninstall.rs index b184b1f..3dc0919 100644 --- a/xtask/src/install/uninstall.rs +++ b/xtask/src/install/uninstall.rs @@ -1,22 +1,43 @@ +use anyhow::Context; +use rar_common::util::toggle_lock_config; use std::fs; -use super::{configure::{config_state, CONFIG_FILE, PAM_CONFIG_PATH}, util::files_are_equal, UninstallOptions, CAPABLE_DEST, CHSR_DEST, SR_DEST}; +use super::{ + configure::{config_state, CONFIG_FILE, PAM_CONFIG_PATH}, + util::files_are_equal, + UninstallOptions, CAPABLE_DEST, CHSR_DEST, SR_DEST, +}; -pub fn uninstall(opts : &UninstallOptions) -> Result<(), anyhow::Error> { - fs::remove_file(SR_DEST)?; - fs::remove_file(CHSR_DEST)?; - if fs::metadata(CAPABLE_DEST).is_ok() { - fs::remove_file(CAPABLE_DEST)?; - } - if opts.clean_config || files_are_equal("resources/debian/deb_sr_pam.conf", PAM_CONFIG_PATH)? +pub fn uninstall(opts: &UninstallOptions) -> Result<(), anyhow::Error> { + let mut errors = vec![]; + if opts.kind.is_all() || opts.kind.is_sr() { + errors.push(fs::remove_file(SR_DEST).context(SR_DEST)); + errors.push(fs::remove_file(CHSR_DEST).context(CHSR_DEST)); + if opts.clean_config + || files_are_equal("resources/debian/deb_sr_pam.conf", PAM_CONFIG_PATH)? || files_are_equal("resources/rh/rh_sr_pam.conf", PAM_CONFIG_PATH)? || files_are_equal("resources/arch/arch_sr_pam.conf", PAM_CONFIG_PATH)? - { - fs::remove_file(PAM_CONFIG_PATH)?; + { + errors.push(fs::remove_file(PAM_CONFIG_PATH).context(PAM_CONFIG_PATH)); + } + if opts.clean_config || config_state()?.is_unchanged() { + errors.push( + toggle_lock_config( + &CONFIG_FILE.to_string(), + rar_common::util::ImmutableLock::Unset, + ) + .context("Error while removing lock from config file"), + ); + errors.push(fs::remove_file(CONFIG_FILE).context(CONFIG_FILE)); + } + } + if opts.kind.is_all() || opts.kind.is_capable() { + errors.push(fs::remove_file(CAPABLE_DEST).context(CAPABLE_DEST)); } - if opts.clean_config || config_state()?.is_unchanged() { - fs::remove_file(CONFIG_FILE)?; + for error in errors { + if let Err(e) = error { + eprintln!("{}: {}", e.to_string(), e.source().unwrap().to_string()); + } } Ok(()) - } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 56627d3..e81697c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -35,7 +35,7 @@ fn main() { RunEbpf(opts) => ebpf::run(&opts), Build(opts) => install::build(&opts), Install(opts) => install::install(&opts), - Configure{ os } => install::configure(&os), + Configure{ os } => install::configure(os), Uninstall(opts) => install::uninstall(&opts), };