Skip to main content

running_process/broker/lifecycle/
privilege.rs

1//! Broker startup privilege checks.
2//!
3//! The broker control socket is a per-user boundary. Starting it as
4//! root or Windows LocalSystem would make that boundary ambiguous, so
5//! the binary refuses privileged startup unless a test environment
6//! explicitly opts out.
7
8/// Environment variable that permits privileged broker startup.
9///
10/// This exists for controlled test fixtures only. Production launchers
11/// should run the broker as the target user instead.
12pub const ALLOW_PRIVILEGED_ENV: &str = "RUNNING_PROCESS_BROKER_ALLOW_PRIVILEGED";
13
14/// Errors returned while checking broker startup privileges.
15#[derive(Debug, thiserror::Error)]
16pub enum PrivilegeError {
17    /// The current process is running as a privileged OS identity.
18    #[error(
19        "running-process-broker-v1 refuses to run as {identity} by default; set {ALLOW_PRIVILEGED_ENV}=1 only for isolated test environments"
20    )]
21    Privileged {
22        /// Privileged identity detected for the current process.
23        identity: PrivilegedIdentity,
24    },
25    /// The platform privilege lookup failed.
26    #[error("failed to determine broker process privilege: {0}")]
27    PlatformLookup(String),
28}
29
30/// Privileged identities that are forbidden for the broker by default.
31///
32/// Kept under this name for callers that already match on it. Which identities
33/// exist, and how each is detected, is the host's answer rather than the
34/// broker's; what the broker owns is that they are forbidden.
35pub use crate::platform::host::PrivilegedIdentity;
36
37/// Refuse to start the broker when the current process is privileged.
38///
39/// The check runs before the binary binds any socket. Set
40/// [`ALLOW_PRIVILEGED_ENV`] to `1` only for isolated test environments
41/// that intentionally exercise privileged startup behavior.
42pub fn refuse_privileged_run() -> Result<(), PrivilegeError> {
43    if allow_privileged_from_env() {
44        return Ok(());
45    }
46    refuse_process_privilege(current_process_privilege()?)
47}
48
49fn refuse_process_privilege(identity: Option<PrivilegedIdentity>) -> Result<(), PrivilegeError> {
50    match identity {
51        Some(identity) => Err(PrivilegeError::Privileged { identity }),
52        None => Ok(()),
53    }
54}
55
56fn allow_privileged_from_env() -> bool {
57    crate::env_vars::BROKER_ALLOW_PRIVILEGED.is_set()
58}
59
60fn current_process_privilege() -> Result<Option<PrivilegedIdentity>, PrivilegeError> {
61    crate::platform::host::current_process_privilege()
62        .map_err(|error| PrivilegeError::PlatformLookup(error.to_string()))
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::env_vars::EnvKind;
69
70    #[test]
71    fn refuses_privileged_identity() {
72        let err = refuse_process_privilege(Some(PrivilegedIdentity::UnixRoot)).unwrap_err();
73        assert!(matches!(
74            err,
75            PrivilegeError::Privileged {
76                identity: PrivilegedIdentity::UnixRoot
77            }
78        ));
79    }
80
81    #[test]
82    fn allows_unprivileged_identity() {
83        refuse_process_privilege(None).unwrap();
84    }
85
86    /// The guard opens for `1` and for nothing else -- not for `true`, not for
87    /// `yes`. Refusing a plausible spelling is the safe direction here: the
88    /// variable exists to let an isolated test environment start as root, and
89    /// a typo must leave the refusal in place.
90    ///
91    /// The rule now lives in the declaration
92    /// (`env_vars::BROKER_ALLOW_PRIVILEGED`, an `ExactValue` kind), so this
93    /// asserts the behaviour the guard actually gets rather than a private
94    /// copy of the comparison.
95    #[test]
96    fn allow_env_value_requires_exact_one() {
97        assert!(crate::env_vars::BROKER_ALLOW_PRIVILEGED.kind == EnvKind::ExactValue("1"));
98    }
99}