diff --git a/proxmox-product-config/src/filesystem_helpers.rs b/proxmox-product-config/src/filesystem_helpers.rs index dfe432d9..efdc8f7c 100644 --- a/proxmox-product-config/src/filesystem_helpers.rs +++ b/proxmox-product-config/src/filesystem_helpers.rs @@ -7,14 +7,73 @@ use nix::fcntl::OFlag; use nix::sys::stat::Mode; use nix::unistd::{Gid, Uid}; +use proxmox_sys::fs::CreateOptions; + use super::product_config; -// Check file/directory permissions -// -// For security reasons, we want to make sure they are set correctly: -// * owned by uid/gid -// * nobody else can read (mode 0700) -pub fn check_permissions>(dir: P, uid: Uid, gid: Gid, mode: u32) -> Result<(), Error> { +/// Return [CreateOptions] for files owned by `api_user.uid/api_user.gid` with mode `0640`. +pub fn default_create_options() -> CreateOptions { + let api_user = &product_config().api_user; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); + proxmox_sys::fs::CreateOptions::new() + .perm(mode) + .owner(api_user.uid) + .group(api_user.gid) +} + +/// Return [CreateOptions] for files owned by `root:api-user.gid` with permission `0640`. +/// +/// Only the superuser can write those files, but group `api-user.gid` can read them. +pub fn privileged_create_options() -> CreateOptions { + let api_user = &product_config().api_user; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); + proxmox_sys::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(api_user.gid) +} + +/// Return [CreateOptions] for files owned by `root:root` with permission `0600`. +/// +/// Only the superuser can read and write those files. +pub fn secret_create_options() -> CreateOptions { + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); + proxmox_sys::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(nix::unistd::Gid::from_raw(0)) +} + +/// Return [CreateOptions] for files owned by `root:root` with permission `0644`. +/// +/// Everyone can read, but only the superuser can write those files. This is usually used +/// for system configuration files inside "/etc/" (i.e. "/etc/resolv.conf"). +pub fn system_config_create_options() -> CreateOptions { + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644); + proxmox_sys::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(nix::unistd::Gid::from_raw(0)) +} + +/// Return [CreateOptions] for lock files, owner `api_user.uid/api_user.gid` and mode `0660`. +pub fn lockfile_create_options() -> CreateOptions { + let api_user = &product_config().api_user; + proxmox_sys::fs::CreateOptions::new() + .perm(nix::sys::stat::Mode::from_bits_truncate(0o660)) + .owner(api_user.uid) + .group(api_user.gid) +} + +/// Check file/directory permissions. +/// +/// Make sure that the file or dir is owned by uid/gid and has the correct mode. +pub fn check_permissions>( + dir: P, + uid: Uid, + gid: Gid, + mode: u32, +) -> Result<(), Error> { let uid = uid.as_raw(); let gid = gid.as_raw(); let dir = dir.as_ref(); @@ -43,7 +102,12 @@ pub fn check_permissions>(dir: P, uid: Uid, gid: Gid, mode: u32) /// Create a new directory with uid/gid and mode. /// /// Returns Ok if the directory already exists with correct access permissions. -pub fn mkdir_permissions>(dir: P, uid: Uid, gid: Gid, mode: u32) -> Result<(), Error> { +pub fn mkdir_permissions>( + dir: P, + uid: Uid, + gid: Gid, + mode: u32, +) -> Result<(), Error> { let nix_mode = Mode::from_bits(mode).expect("bad mode bits for nix crate"); let dir = dir.as_ref(); @@ -69,78 +133,47 @@ pub fn mkdir_permissions>(dir: P, uid: Uid, gid: Gid, mode: u32) Ok(()) } -/// Atomically write data to file owned by `root:api-user` with permission `0640` +/// Atomically write data to file owned by `root:api-user.gid` with permission `0640` /// /// Only the superuser can write those files, but group 'api-user' can read them. pub fn replace_privileged_config>( path: P, data: &[u8], ) -> Result<(), Error> { - let api_user = &product_config().api_user; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= api-user - let options = proxmox_sys::fs::CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(api_user.gid); - + let options = privileged_create_options(); proxmox_sys::fs::replace_file(path, data, options, true)?; - Ok(()) } -/// Atomically write data to file owned by `api-user:api-user` with permission `0660`. +/// Atomically write data to file owned by `api-user.uid:api-user.gid` with permission `0660`. pub fn replace_config>(path: P, data: &[u8]) -> Result<(), Error> { - let api_user = &product_config().api_user; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= api-user - let options = proxmox_sys::fs::CreateOptions::new() - .perm(mode) - .owner(api_user.uid) - .group(api_user.gid); - + let options = default_create_options(); proxmox_sys::fs::replace_file(path, data, options, true)?; - Ok(()) } -/// Atomically write data to file owned by "root:root" with permission "0600" +/// Atomically write data to file owned by `root:root` with permission `0600`. /// /// Only the superuser can read and write those files. pub fn replace_secret_config>(path: P, data: &[u8]) -> Result<(), Error> { - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= root - let options = proxmox_sys::fs::CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(nix::unistd::Gid::from_raw(0)); - + let options = secret_create_options(); proxmox_sys::fs::replace_file(path, data, options, true)?; - Ok(()) } -/// Atomically write data to file owned by "root:root" with permission "0644" +/// Atomically write data to file owned by `root:root` with permission `0644`. /// /// Everyone can read, but only the superuser can write those files. This is usually used /// for system configuration files inside "/etc/" (i.e. "/etc/resolv.conf"). pub fn replace_system_config>(path: P, data: &[u8]) -> Result<(), Error> { - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644); - // set the correct owner/group/permissions while saving file - // owner(rw) = root, group(r)= root - let options = proxmox_sys::fs::CreateOptions::new() - .perm(mode) - .owner(nix::unistd::ROOT) - .group(nix::unistd::Gid::from_raw(0)); - + let options = system_config_create_options(); proxmox_sys::fs::replace_file(path, data, options, true)?; - Ok(()) } +/// Lock guard used by [open_api_lockfile] +/// +/// The lock is released if you drop this guard. #[allow(dead_code)] pub struct ApiLockGuard(Option); @@ -150,26 +183,22 @@ pub unsafe fn create_mocked_lock() -> ApiLockGuard { ApiLockGuard(None) } -/// Open or create a lock file owned by user "api-user" and lock it. +/// Open or create a lock file owned by user `api-user` and lock it. /// -/// Owner/Group of the file is set to api-user/api-group. -/// File mode is 0660. +/// Owner/Group of the file is set to `api-user.uid/api-user.gid`. +/// File mode is `0660`. /// Default timeout is 10 seconds. /// -/// Note: This method needs to be called by user "root" or "api-user". +/// The lock is released as soon as you drop the returned lock guard. +/// +/// Note: This method needs to be called by user `root` or `api-user`. pub fn open_api_lockfile>( path: P, timeout: Option, exclusive: bool, ) -> Result { - let api_user = &product_config().api_user; - let options = proxmox_sys::fs::CreateOptions::new() - .perm(nix::sys::stat::Mode::from_bits_truncate(0o660)) - .owner(api_user.uid) - .group(api_user.gid); - + let options = lockfile_create_options(); let timeout = timeout.unwrap_or(std::time::Duration::new(10, 0)); - let file = proxmox_sys::fs::open_file_locked(&path, timeout, exclusive, options)?; Ok(ApiLockGuard(Some(file))) }