From 7bfc04efc8c413f2db0d937924d0d09310a55f95 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Sat, 15 Jun 2024 14:35:29 +1000 Subject: [PATCH] feat: add configurable socket permissions Have `pueue` set the permissions of the socket created. Previously, the permissions where unspecified and (at least for me) defaulted to `755`. As part of this change, the default settings are moving to `700` which is more restricted than the previous default. Signed-off-by: JP-Ellis --- CHANGELOG.md | 1 + Cargo.lock | 23 +++++++- pueue/tests/daemon/integration/mod.rs | 1 + .../daemon/integration/socket_permissions.rs | 56 +++++++++++++++++++ pueue_lib/Cargo.toml | 1 + pueue_lib/src/network/socket/unix.rs | 33 ++++++++++- pueue_lib/src/settings.rs | 8 +++ pueue_lib/tests/helper.rs | 2 + 8 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 pueue/tests/daemon/integration/socket_permissions.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index baa4b540..8f07e87e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The new state design fixes this issue, which allows Pueue to do subprocess state - Add `--all` and `--group` to `pueue log`. [#509](https://github.com/Nukesor/pueue/issues/509) - Add `pueue reset --groups [group_names]` to allow resetting individual groups. [#482](https://github.com/Nukesor/pueue/issues/482) \ This also refactors the way resets are done internally, resulting in a cleaner code architecture. +- Ability to set the Unix socket permissions through the new `unix_socket_permissions` configuration option. [#544](https://github.com/Nukesor/pueue/pull/544) ## \[3.4.1\] - 2024-06-04 diff --git a/Cargo.lock b/Cargo.lock index 4622cf18..06c209f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,6 +273,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -858,7 +864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -997,7 +1003,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.5.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases 0.2.1", "libc", ] @@ -1317,6 +1335,7 @@ dependencies = [ "handlebars", "libproc", "log", + "nix 0.29.0", "portpicker", "pretty_assertions", "procfs", diff --git a/pueue/tests/daemon/integration/mod.rs b/pueue/tests/daemon/integration/mod.rs index f99afe33..65099ac7 100644 --- a/pueue/tests/daemon/integration/mod.rs +++ b/pueue/tests/daemon/integration/mod.rs @@ -16,6 +16,7 @@ mod restart; mod restore; /// Tests for shutting down the daemon. mod shutdown; +mod socket_permissions; mod spawn; mod start; mod stashed; diff --git a/pueue/tests/daemon/integration/socket_permissions.rs b/pueue/tests/daemon/integration/socket_permissions.rs new file mode 100644 index 00000000..f19e813b --- /dev/null +++ b/pueue/tests/daemon/integration/socket_permissions.rs @@ -0,0 +1,56 @@ +use anyhow::Result; +use std::fs; +use std::os::unix::fs::PermissionsExt; + +use anyhow::Context; + +use crate::helper::*; + +/// Make sure that the socket permissions are appropriately set. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[cfg(not(target_os = "windows"))] +async fn test_socket_permissions_default() -> Result<()> { + let (settings, _tempdir) = daemon_base_setup()?; + let shared = &settings.shared; + let mut child = standalone_daemon(shared).await?; + + assert_eq!( + fs::metadata(shared.unix_socket_path())? + .permissions() + .mode() + // The permissions are masked with 0o777 to only get the last 3 + // digits. + & 0o777, + 0o700 + ); + + child.kill()?; + Ok(()) +} + +/// Make sure that the socket permissions can be changed +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[cfg(not(target_os = "windows"))] +async fn test_socket_permissions_modified() -> Result<()> { + let (mut settings, _tempdir) = daemon_base_setup()?; + settings.shared.unix_socket_permissions = Some(0o777); + let shared = &settings.shared; + settings + .save(&Some(settings.shared.runtime_directory().join("pueue.yml"))) + .context("Couldn't write pueue config to temporary directory")?; + + let mut child = standalone_daemon(shared).await?; + + assert_eq!( + fs::metadata(shared.unix_socket_path())? + .permissions() + .mode() + // The permissions are masked with 0o777 to only get the last 3 + // digits. + & 0o777, + 0o777 + ); + + child.kill()?; + Ok(()) +} diff --git a/pueue_lib/Cargo.toml b/pueue_lib/Cargo.toml index b71b7293..a75fda90 100644 --- a/pueue_lib/Cargo.toml +++ b/pueue_lib/Cargo.toml @@ -23,6 +23,7 @@ command-group = { workspace = true } dirs = "5.0" handlebars = { workspace = true } log = { workspace = true } +nix = { version = "0.29.0", features = ["fs"] } rand = "0.8" rcgen = "0.13" rev_buf_reader = "0.3" diff --git a/pueue_lib/src/network/socket/unix.rs b/pueue_lib/src/network/socket/unix.rs index 8b1f707b..558c972c 100644 --- a/pueue_lib/src/network/socket/unix.rs +++ b/pueue_lib/src/network/socket/unix.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use log::info; use rustls::pki_types::ServerName; use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::net::{TcpListener, TcpStream, UnixListener, UnixStream}; +use tokio::net::{TcpListener, TcpStream, UnixListener, UnixSocket, UnixStream}; use tokio_rustls::TlsAcceptor; use crate::error::Error; @@ -142,8 +142,35 @@ pub async fn get_listener(settings: &Shared) -> Result { })?; } - let unix_listener = UnixListener::bind(&socket_path) - .map_err(|err| Error::IoPathError(socket_path, "creating unix socket", err))?; + // The various nix platforms handle socket permissions in different + // ways, but generally prevent the socket's permissions from being + // changed once it is being listened on. + let socket = UnixSocket::new_stream() + .map_err(|err| Error::IoError("creating unix socket".to_string(), err))?; + socket.bind(&socket_path).map_err(|err| { + Error::IoPathError(socket_path.clone(), "binding unix socket to path", err) + })?; + + if let Some(mode) = settings.unix_socket_permissions { + nix::sys::stat::fchmodat( + None, + &socket_path, + nix::sys::stat::Mode::from_bits_truncate(mode), + nix::sys::stat::FchmodatFlags::FollowSymlink, + ) + .map_err(|err| { + Error::IoPathError( + socket_path.clone(), + "setting socket permissions", + std::io::Error::from_raw_os_error(err as i32), + ) + })?; + } + + let unix_listener = socket.listen(1024).map_err(|err| { + Error::IoPathError(socket_path.clone(), "listening on unix socket", err) + })?; + return Ok(Box::new(unix_listener)); } diff --git a/pueue_lib/src/settings.rs b/pueue_lib/src/settings.rs index 60b5099b..537adae6 100644 --- a/pueue_lib/src/settings.rs +++ b/pueue_lib/src/settings.rs @@ -46,6 +46,12 @@ pub struct Shared { /// The path to the unix socket. #[cfg(not(target_os = "windows"))] pub unix_socket_path: Option, + /// Unix socket permissions. Typically specified as an octal number and + /// defaults to `0o700` which grants only the current user access to the + /// socket. For a client to connect to the daemon, the client must have + /// read/write permissions. + #[cfg(not(target_os = "windows"))] + pub unix_socket_permissions: Option, /// The TCP hostname/ip address. #[serde(default = "default_host")] @@ -147,6 +153,8 @@ impl Default for Shared { unix_socket_path: None, #[cfg(not(target_os = "windows"))] use_unix_socket: true, + #[cfg(not(target_os = "windows"))] + unix_socket_permissions: Some(0o700), host: default_host(), port: default_port(), diff --git a/pueue_lib/tests/helper.rs b/pueue_lib/tests/helper.rs index 1b7a0c4e..a37282b6 100644 --- a/pueue_lib/tests/helper.rs +++ b/pueue_lib/tests/helper.rs @@ -20,6 +20,8 @@ pub fn get_shared_settings( use_unix_socket, #[cfg(not(target_os = "windows"))] unix_socket_path: None, + #[cfg(not(target_os = "windows"))] + unix_socket_permissions: Some(0o700), pid_path: None, host: "localhost".to_string(), port: pick_unused_port()