From 32dca2e8468d0b9c6c9beb6e84b3e8db2d8344bb Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 10 Mar 2023 14:00:20 -0800 Subject: [PATCH] Support busybox user/group modification, more informational errors (#319) --- Cargo.lock | 18 ++++ Cargo.toml | 1 + src/action/base/add_user_to_group.rs | 56 +++++++--- src/action/base/create_group.rs | 54 +++++++--- src/action/base/create_user.rs | 104 +++++++++++++------ src/action/common/create_users_and_groups.rs | 39 ++----- src/action/mod.rs | 16 +++ 7 files changed, 191 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cea850..9002ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -980,6 +986,7 @@ dependencies = [ "typetag", "url", "uuid", + "which", "xz2", ] @@ -2068,6 +2075,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 8d57f33..6185a0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ os-release = { version = "0.1.0", default-features = false, optional = true } is_ci = { version = "1.1.1", default-features = false, optional = true } strum = { version = "0.24.1", features = ["derive"] } nix-config-parser = { version = "0.1.2", features = ["serde"] } +which = "4.4.0" [dev-dependencies] eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] } diff --git a/src/action/base/add_user_to_group.rs b/src/action/base/add_user_to_group.rs index 444be54..9c7b8a5 100644 --- a/src/action/base/add_user_to_group.rs +++ b/src/action/base/add_user_to_group.rs @@ -202,14 +202,26 @@ impl Action for AddUserToGroup { .await?; }, _ => { - execute_command( - Command::new("gpasswd") - .process_group(0) - .args(["-a"]) - .args([&name.to_string(), &groupname.to_string()]) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("gpasswd").is_ok() { + execute_command( + Command::new("gpasswd") + .process_group(0) + .args(["-a"]) + .args([name, groupname]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("addgroup").is_ok() { + execute_command( + Command::new("addgroup") + .process_group(0) + .args([name, groupname]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingAddUserToGroupCommand); + } }, } @@ -255,14 +267,26 @@ impl Action for AddUserToGroup { .await?; }, _ => { - execute_command( - Command::new("gpasswd") - .process_group(0) - .args(["-d"]) - .args([&name.to_string(), &groupname.to_string()]) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("gpasswd").is_ok() { + execute_command( + Command::new("gpasswd") + .process_group(0) + .args(["-d"]) + .args([&name.to_string(), &groupname.to_string()]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("delgroup").is_ok() { + execute_command( + Command::new("delgroup") + .process_group(0) + .args([name, groupname]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingRemoveUserFromGroupCommand); + } }, }; diff --git a/src/action/base/create_group.rs b/src/action/base/create_group.rs index b16c83b..cc4b941 100644 --- a/src/action/base/create_group.rs +++ b/src/action/base/create_group.rs @@ -92,20 +92,32 @@ impl Action for CreateGroup { "Nix build group for nix-daemon", "-i", &format!("{gid}"), - name.as_str(), + name, ]) .stdin(std::process::Stdio::null()), ) .await?; }, _ => { - execute_command( - Command::new("groupadd") - .process_group(0) - .args(["-g", &gid.to_string(), "--system", &name]) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("groupadd").is_ok() { + execute_command( + Command::new("groupadd") + .process_group(0) + .args(["-g", &gid.to_string(), "--system", name]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("addgroup").is_ok() { + execute_command( + Command::new("addgroup") + .process_group(0) + .args(["-g", &gid.to_string(), "--system", name]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingGroupCreationCommand); + } }, }; @@ -143,13 +155,25 @@ impl Action for CreateGroup { if !output.status.success() {} }, _ => { - execute_command( - Command::new("groupdel") - .process_group(0) - .arg(&name) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("groupdel").is_ok() { + execute_command( + Command::new("groupdel") + .process_group(0) + .arg(name) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("delgroup").is_ok() { + execute_command( + Command::new("delgroup") + .process_group(0) + .arg(name) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingGroupDeletionCommand); + } }, }; diff --git a/src/action/base/create_user.rs b/src/action/base/create_user.rs index d264c5d..bf9cb59 100644 --- a/src/action/base/create_user.rs +++ b/src/action/base/create_user.rs @@ -98,7 +98,7 @@ impl Action for CreateUser { let Self { name, uid, - groupname: _, + groupname, gid, } = self; @@ -178,31 +178,57 @@ impl Action for CreateUser { .await?; }, _ => { - execute_command( - Command::new("useradd") - .process_group(0) - .args([ - "--home-dir", - "/var/empty", - "--comment", - &format!("\"Nix build user\""), - "--gid", - &gid.to_string(), - "--groups", - &gid.to_string(), - "--no-user-group", - "--system", - "--shell", - "/sbin/nologin", - "--uid", - &uid.to_string(), - "--password", - "\"!\"", - &name.to_string(), - ]) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("useradd").is_ok() { + execute_command( + Command::new("useradd") + .process_group(0) + .args([ + "--home-dir", + "/var/empty", + "--comment", + "Nix build user", + "--gid", + &gid.to_string(), + "--groups", + &gid.to_string(), + "--no-user-group", + "--system", + "--shell", + "/sbin/nologin", + "--uid", + &uid.to_string(), + "--password", + "!", + name, + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("adduser").is_ok() { + execute_command( + Command::new("adduser") + .process_group(0) + .args([ + "--home", + "/var/empty", + "--gecos", + "Nix build user", + "--ingroup", + groupname, + "--system", + "--shell", + "/sbin/nologin", + "--uid", + &uid.to_string(), + "--disabled-password", + name, + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingUserCreationCommand); + } }, } @@ -266,13 +292,25 @@ impl Action for CreateUser { } }, _ => { - execute_command( - Command::new("userdel") - .process_group(0) - .args([&name.to_string()]) - .stdin(std::process::Stdio::null()), - ) - .await?; + if which::which("userdel").is_ok() { + execute_command( + Command::new("userdel") + .process_group(0) + .arg(name) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else if which::which("deluser").is_ok() { + execute_command( + Command::new("deluser") + .process_group(0) + .arg(name) + .stdin(std::process::Stdio::null()), + ) + .await?; + } else { + return Err(ActionError::MissingUserDeletionCommand); + } }, }; diff --git a/src/action/common/create_users_and_groups.rs b/src/action/common/create_users_and_groups.rs index 07fb74c..6990e6b 100644 --- a/src/action/common/create_users_and_groups.rs +++ b/src/action/common/create_users_and_groups.rs @@ -5,8 +5,7 @@ use crate::{ }, settings::CommonSettings, }; -use tokio::task::JoinSet; -use tracing::{span, Instrument, Span}; +use tracing::{span, Span}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateUsersAndGroups { @@ -266,37 +265,11 @@ impl Action for CreateUsersAndGroups { nix_build_user_prefix: _, nix_build_user_id_base: _, } = self; - let mut set = JoinSet::new(); - - let mut errors = Vec::default(); - - for (idx, create_user) in create_users.iter().enumerate() { - let span = tracing::Span::current().clone(); - let mut create_user_clone = create_user.clone(); - let _abort_handle = set.spawn(async move { - create_user_clone - .try_revert() - .instrument(span) - .await - .map_err(|e| ActionError::Child(create_user_clone.action_tag(), Box::new(e)))?; - Result::<_, ActionError>::Ok((idx, create_user_clone)) - }); - } - - while let Some(result) = set.join_next().await { - match result { - Ok(Ok((idx, success))) => create_users[idx] = success, - Ok(Err(e)) => errors.push(Box::new(e)), - Err(e) => return Err(ActionError::Join(e))?, - }; - } - - if !errors.is_empty() { - if errors.len() == 1 { - return Err(*errors.into_iter().next().unwrap()); - } else { - return Err(ActionError::Children(errors)); - } + for create_user in create_users.iter_mut() { + create_user + .try_revert() + .await + .map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?; } // We don't actually need to do this, when a user is deleted they are removed from groups diff --git a/src/action/mod.rs b/src/action/mod.rs index 833941b..b8e12d6 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -427,6 +427,22 @@ pub enum ActionError { Plist(#[from] plist::Error), #[error("Unexpected binary tarball contents found, the build result from `https://releases.nixos.org/?prefix=nix/` or `nix build nix#hydraJobs.binaryTarball.$SYSTEM` is expected")] MalformedBinaryTarball, + #[error( + "Could not find a supported command to create users in PATH; please install `useradd` or `adduser`" + )] + MissingUserCreationCommand, + #[error("Could not find a supported command to create groups in PATH; please install `groupadd` or `addgroup`")] + MissingGroupCreationCommand, + #[error("Could not find a supported command to add users to groups in PATH; please install `gpasswd` or `addgroup`")] + MissingAddUserToGroupCommand, + #[error( + "Could not find a supported command to delete users in PATH; please install `userdel` or `deluser`" + )] + MissingUserDeletionCommand, + #[error("Could not find a supported command to delete groups in PATH; please install `groupdel` or `delgroup`")] + MissingGroupDeletionCommand, + #[error("Could not find a supported command to remove users from groups in PATH; please install `gpasswd` or `deluser`")] + MissingRemoveUserFromGroupCommand, } impl ActionError {