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}