use std::path::PathBuf;
#[derive(Debug, Default)]
pub struct PrivilegeDrop {
pub chroot_dir: Option<PathBuf>,
pub uid: Option<nix::unistd::Uid>,
pub gid: Option<nix::unistd::Gid>,
}
impl PrivilegeDrop {
#[cfg(target_os = "linux")]
pub fn apply(&self) -> anyhow::Result<()> {
use nix::unistd;
if let Some(dir) = &self.chroot_dir {
tracing::info!("chroot: entering {:?}", dir);
unistd::chroot(dir).map_err(|e| anyhow::anyhow!("chroot({:?}) failed: {e}", dir))?;
unistd::chdir("/")
.map_err(|e| anyhow::anyhow!("chdir('/') after chroot failed: {e}"))?;
tracing::info!("chroot: now rooted at {:?}", dir);
}
if let Some(gid) = self.gid {
unistd::setgroups(&[gid])
.map_err(|e| anyhow::anyhow!("setgroups([{gid}]) failed: {e}"))?;
unistd::setgid(gid).map_err(|e| anyhow::anyhow!("setgid({gid}) failed: {e}"))?;
tracing::info!("privilege-drop: gid set to {gid}");
}
if let Some(uid) = self.uid {
unistd::setuid(uid).map_err(|e| anyhow::anyhow!("setuid({uid}) failed: {e}"))?;
tracing::info!("privilege-drop: uid set to {uid}");
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub fn apply(&self) -> anyhow::Result<()> {
if self.chroot_dir.is_some() || self.uid.is_some() || self.gid.is_some() {
tracing::warn!(
"privilege-drop requested (chroot={:?}, uid={:?}, gid={:?}) \
but skipped: only supported on Linux",
self.chroot_dir,
self.uid,
self.gid
);
}
Ok(())
}
}
pub fn resolve_uid(name: &str) -> anyhow::Result<Option<nix::unistd::Uid>> {
if name.is_empty() {
return Ok(None);
}
let user = nix::unistd::User::from_name(name)
.map_err(|e| anyhow::anyhow!("lookup user {:?} failed: {e}", name))?
.ok_or_else(|| anyhow::anyhow!("user {:?} not found in system database", name))?;
Ok(Some(user.uid))
}
pub fn resolve_gid(name: &str) -> anyhow::Result<Option<nix::unistd::Gid>> {
if name.is_empty() {
return Ok(None);
}
let group = nix::unistd::Group::from_name(name)
.map_err(|e| anyhow::anyhow!("lookup group {:?} failed: {e}", name))?
.ok_or_else(|| anyhow::anyhow!("group {:?} not found in system database", name))?;
Ok(Some(group.gid))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_privilege_drop_noop_when_no_user_set() {
let drop = PrivilegeDrop::default();
drop.apply().expect("no-op drop must not fail");
}
#[cfg(target_os = "macos")]
#[test]
fn test_privilege_drop_warns_on_macos() {
let drop = PrivilegeDrop {
chroot_dir: Some(std::path::PathBuf::from("/tmp")),
uid: Some(nix::unistd::Uid::from_raw(99)),
gid: None,
};
assert!(drop.apply().is_ok(), "macOS path must be Ok()");
}
#[test]
fn test_resolve_uid_empty_returns_none() {
assert!(resolve_uid("").unwrap().is_none());
}
#[test]
fn test_resolve_gid_empty_returns_none() {
assert!(resolve_gid("").unwrap().is_none());
}
#[test]
fn test_resolve_uid_nonexistent_returns_err() {
assert!(resolve_uid("__nonexistent_rusmes_user__").is_err());
}
#[test]
fn test_resolve_gid_nonexistent_returns_err() {
assert!(resolve_gid("__nonexistent_rusmes_group__").is_err());
}
}