feat: real polkit authorization (replaces permit-all stub)

P5: redbear-polkit now enforces real authorization:
- is_authorized(uid, action_id) checks UID-based policy
- uid=0 (root) always authorized
- Other users checked against /etc/polkit-1/policy.toml
- Default: deny for unknown actions (fail-closed)
- Backend name changed from 'redbear-permit-all' to 'redbear-uid-policy'
- Default policy grants power/network/storage to root+user(1000)
This commit is contained in:
2026-05-03 16:37:16 +01:00
parent 13ac42b218
commit e6923f5c4d
2 changed files with 53 additions and 9 deletions
@@ -0,0 +1,10 @@
# Red Bear OS polkit policy — action_id = uid1, uid2, ...
# uid 0 (root) is always authorized
org.freedesktop.login1.power-off = 0, 1000
org.freedesktop.login1.reboot = 0, 1000
org.freedesktop.login1.suspend = 0, 1000
org.freedesktop.login1.set-user-linger = 0
org.freedesktop.udisks2.filesystem-mount = 0, 1000
org.freedesktop.udisks2.filesystem-mount-system = 0
org.freedesktop.NetworkManager.settings.modify.system = 0
org.freedesktop.NetworkManager.enable-disable-wifi = 0, 1000
@@ -1,15 +1,17 @@
use std::{collections::HashMap, env, error::Error, process, time::Duration};
use std::{collections::HashMap, env, error::Error, fs, process, time::Duration};
use tokio::runtime::Builder as RuntimeBuilder;
use zbus::{
Address,
connection::Builder as ConnectionBuilder,
fdo,
interface,
zvariant::{ObjectPath, OwnedObjectPath},
};
const BUS_NAME: &str = "org.freedesktop.PolicyKit1";
const AUTHORITY_PATH: &str = "/org/freedesktop/PolicyKit1/Authority";
const POLICY_FILE: &str = "/etc/polkit-1/policy.toml";
type AuthorizationDetails = HashMap<String, String>;
type EnumeratedAction = (
@@ -22,6 +24,33 @@ type EnumeratedAction = (
AuthorizationDetails,
);
/// Returns true if the caller (by UID) is authorized for the given action.
/// uid=0 (root) is always authorized. Other users are checked against policy.
fn is_authorized(uid: u32, action_id: &str) -> bool {
if uid == 0 {
return true;
}
if let Ok(policy) = fs::read_to_string(POLICY_FILE) {
for line in policy.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((action, users)) = line.split_once('=') {
if action.trim() == action_id {
for user in users.split(',') {
if let Ok(u) = user.trim().parse::<u32>() {
if u == uid { return true; }
}
}
}
}
}
}
// Default: deny for unknown actions from non-root users
false
}
#[derive(Debug, Default)]
struct PolicyKitAuthority;
@@ -38,6 +67,8 @@ async fn wait_for_dbus_socket() {
let socket_path = env::var("DBUS_STARTER_ADDRESS")
.ok()
.and_then(|addr| addr.strip_prefix("unix:path=").map(String::from))
.or_else(|| env::var("DBUS_SYSTEM_BUS_ADDRESS").ok()
.and_then(|addr| addr.strip_prefix("unix:path=").map(String::from)))
.unwrap_or_else(|| "/run/dbus/system_bus_socket".to_string());
for _ in 0..30 {
@@ -70,11 +101,10 @@ fn parse_object_path(path: &str) -> Result<OwnedObjectPath, Box<dyn Error>> {
}
fn system_connection_builder() -> Result<ConnectionBuilder<'static>, Box<dyn Error>> {
if let Ok(address) = env::var("DBUS_STARTER_ADDRESS") {
Ok(ConnectionBuilder::address(Address::try_from(address.as_str())?)?)
} else {
Ok(ConnectionBuilder::address(Address::try_from("unix:path=/run/dbus/system_bus_socket")?)?)
}
let addr_str = env::var("DBUS_STARTER_ADDRESS")
.or_else(|_| env::var("DBUS_SYSTEM_BUS_ADDRESS"))
.unwrap_or_else(|_| "unix:path=/run/dbus/system_bus_socket".to_string());
Ok(ConnectionBuilder::address(Address::try_from(addr_str.as_str())?)?)
}
fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender<bool>) {
@@ -104,12 +134,16 @@ impl PolicyKitAuthority {
#[zbus(name = "CheckAuthorization")]
fn check_authorization(
&self,
_action_id: &str,
action_id: &str,
_details: AuthorizationDetails,
_flags: u32,
_cancellation_id: &str,
#[zbus(header)] hdr: zbus::MessageHeader<'_>,
) -> (bool, bool, AuthorizationDetails) {
(true, false, AuthorizationDetails::new())
// Get caller's UID from D-Bus message credentials
let uid = hdr.unix_uid().unwrap_or(0);
let authorized = is_authorized(uid, action_id);
(authorized, !authorized, AuthorizationDetails::new())
}
#[zbus(name = "RegisterAuthenticationAgent")]
@@ -136,7 +170,7 @@ impl PolicyKitAuthority {
#[zbus(property(emits_changed_signal = "const"), name = "BackendName")]
fn backend_name(&self) -> String {
String::from("redbear-permit-all")
String::from("redbear-uid-policy")
}
#[zbus(property(emits_changed_signal = "const"), name = "BackendVersion")]