mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-08-17 16:34:54 -05:00
feat: use polkit auth for admin actions (#1109)
This commit is contained in:
Generated
+26
-12
@@ -75,7 +75,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -86,7 +86,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -672,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1092,7 +1092,7 @@ dependencies = [
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1601,7 +1601,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1734,6 +1734,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"zbus",
|
||||
"zbus_polkit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2098,7 +2099,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2602,7 +2603,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2853,7 +2854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2960,7 +2961,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2970,7 +2971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3281,7 +3282,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3584,7 +3585,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4091,6 +4092,19 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_polkit"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad23d5c4d198c7e2641b33e6e0d1f866f117408ba66fe80bbe52e289eeb77c52"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"static_assertions",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
|
||||
@@ -22,6 +22,7 @@ install-resources:
|
||||
install -Dm644 res/io.github.ilya_zlobintsev.LACT.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/512x512/apps/io.github.ilya_zlobintsev.LACT.png
|
||||
install -Dm644 res/io.github.ilya_zlobintsev.LACT.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/io.github.ilya_zlobintsev.LACT.svg
|
||||
install -Dm644 res/io.github.ilya_zlobintsev.LACT.metainfo.xml $(DESTDIR)$(PREFIX)/share/metainfo/io.github.ilya_zlobintsev.LACT.metainfo.xml
|
||||
install -Dm644 res/io.github.ilya_zlobintsev.LACT.policy $(DESTDIR)$(PREFIX)/share/polkit-1/actions/io.github.ilya_zlobintsev.LACT.policy
|
||||
|
||||
.PHONY: install
|
||||
install: install-resources
|
||||
@@ -40,6 +41,7 @@ uninstall:
|
||||
rm $(DESTDIR)$(PREFIX)/share/icons/hicolor/512x512/apps/io.github.ilya_zlobintsev.LACT.png
|
||||
rm $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/io.github.ilya_zlobintsev.LACT.svg
|
||||
rm $(DESTDIR)$(PREFIX)/share/metainfo/io.github.ilya_zlobintsev.LACT.metainfo.xml
|
||||
rm $(DESTDIR)$(PREFIX)/share/polkit-1/actions/io.github.ilya_zlobintsev.LACT.policy
|
||||
|
||||
.PHONY: update-vulkan-schema
|
||||
update-vulkan-schema:
|
||||
|
||||
@@ -5238,6 +5238,19 @@
|
||||
"dest": "cargo/vendor/zbus_names-4.3.1",
|
||||
"dest-filename": ".cargo-checksum.json"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zbus_polkit/zbus_polkit-5.0.0.crate",
|
||||
"sha256": "ad23d5c4d198c7e2641b33e6e0d1f866f117408ba66fe80bbe52e289eeb77c52",
|
||||
"dest": "cargo/vendor/zbus_polkit-5.0.0"
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "{\"package\": \"ad23d5c4d198c7e2641b33e6e0d1f866f117408ba66fe80bbe52e289eeb77c52\", \"files\": {}}",
|
||||
"dest": "cargo/vendor/zbus_polkit-5.0.0",
|
||||
"dest-filename": ".cargo-checksum.json"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
|
||||
@@ -38,6 +38,7 @@ nvml-wrapper = "0.12.1"
|
||||
bitflags = "2.11.1"
|
||||
pciid-parser = { version = "0.8", features = ["serde"] }
|
||||
zbus = { version = "5.14.0", default-features = false, features = ["tokio"] }
|
||||
zbus_polkit = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
||||
libdrm_amdgpu_sys = { version = "0.8.16", default-features = false, features = [
|
||||
"dynamic_loading",
|
||||
] }
|
||||
|
||||
@@ -28,6 +28,7 @@ use tracing::level_filters::LevelFilter;
|
||||
use tracing::{Instrument, debug_span, error, info, warn};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::server::ClientContext;
|
||||
pub use system::BASE_MODULE_CONF_PATH;
|
||||
|
||||
const DRM_EVENT_TIMEOUT_PERIOD_MS: u64 = 100;
|
||||
@@ -83,8 +84,9 @@ pub fn run_embedded(stream: StdUnixStream) -> anyhow::Result<()> {
|
||||
let config = Config::default();
|
||||
let handler = Handler::new(config).await?;
|
||||
let stream = UnixStream::try_from(stream)?;
|
||||
let ctx = ClientContext::default();
|
||||
|
||||
handle_stream(stream, handler).await
|
||||
handle_stream(stream, handler, ctx).await
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{config::Config, socket, system};
|
||||
use anyhow::Context;
|
||||
use futures::future::join_all;
|
||||
use lact_schema::{Pong, Request, Response};
|
||||
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use tokio::{
|
||||
@@ -19,7 +20,7 @@ use tokio::{
|
||||
net::{TcpListener, UnixListener},
|
||||
sync::Notify,
|
||||
};
|
||||
use tracing::{debug, error, info, instrument, trace};
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
pub struct Server {
|
||||
pub handler: Handler,
|
||||
@@ -74,9 +75,15 @@ impl Server {
|
||||
loop {
|
||||
match self.unix_listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let client_credentials = getsockopt(&stream, PeerCredentials)
|
||||
.inspect_err(|err| warn!("could not get client credentials: {err:#}"))
|
||||
.ok();
|
||||
let ctx = ClientContext {
|
||||
client_pid: client_credentials.map(|creds| creds.pid().cast_unsigned()),
|
||||
};
|
||||
let handler = unix_handler.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
if let Err(error) = handle_stream(stream, handler).await {
|
||||
if let Err(error) = handle_stream(stream, handler, ctx).await {
|
||||
error!("{error}");
|
||||
}
|
||||
});
|
||||
@@ -94,9 +101,11 @@ impl Server {
|
||||
loop {
|
||||
match tcp_listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let ctx = ClientContext::default();
|
||||
let handler = self.handler.clone();
|
||||
|
||||
tokio::task::spawn_local(async move {
|
||||
if let Err(error) = handle_stream(stream, handler).await {
|
||||
if let Err(error) = handle_stream(stream, handler, ctx).await {
|
||||
error!("{error}");
|
||||
}
|
||||
});
|
||||
@@ -114,10 +123,16 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(crate) struct ClientContext {
|
||||
pub client_pid: Option<u32>,
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(stream, handler))]
|
||||
pub async fn handle_stream<T: AsyncRead + AsyncWrite + Unpin>(
|
||||
stream: T,
|
||||
handler: Handler,
|
||||
ctx: ClientContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let disconnect_notify = std::sync::Arc::new(Notify::new());
|
||||
|
||||
@@ -129,7 +144,7 @@ pub async fn handle_stream<T: AsyncRead + AsyncWrite + Unpin>(
|
||||
|
||||
let maybe_request = serde_json::from_str(&buf);
|
||||
let response = match maybe_request {
|
||||
Ok(request) => match handle_request(request, &handler, &disconnect_notify).await {
|
||||
Ok(request) => match handle_request(request, &handler, &disconnect_notify, ctx).await {
|
||||
Ok(response) => response,
|
||||
Err(error) => serde_json::to_vec(&Response::<()>::from(error))?,
|
||||
},
|
||||
@@ -154,6 +169,7 @@ async fn handle_request<'a>(
|
||||
request: Request<'a>,
|
||||
handler: &'a Handler,
|
||||
disconnect_notify: &std::sync::Arc<Notify>,
|
||||
ctx: ClientContext,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
match request {
|
||||
Request::Ping => ok_response(ping()),
|
||||
@@ -209,7 +225,7 @@ async fn handle_request<'a>(
|
||||
.await?,
|
||||
),
|
||||
Request::CreateProfile { name, base } => {
|
||||
ok_response(handler.create_profile(name, base).await?)
|
||||
ok_response(handler.create_profile(name, base, ctx).await?)
|
||||
}
|
||||
Request::DeleteProfile { name } => ok_response(handler.delete_profile(name).await?),
|
||||
Request::MoveProfile { name, new_position } => {
|
||||
@@ -223,7 +239,7 @@ async fn handle_request<'a>(
|
||||
Request::ReleaseProfile { cookie } => ok_response(handler.release_profile(cookie).await?),
|
||||
Request::EvaluateProfileRule { rule } => ok_response(handler.evaluate_profile_rule(&rule)?),
|
||||
Request::SetProfileRule { name, rule, hooks } => {
|
||||
ok_response(handler.set_profile_rule(&name, rule, hooks).await?)
|
||||
ok_response(handler.set_profile_rule(&name, rule, hooks, ctx).await?)
|
||||
}
|
||||
Request::GetGpuConfig { id } => ok_response(handler.get_gpu_config(id).await?),
|
||||
Request::SetGpuConfig { id, config } => {
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::server::display;
|
||||
use crate::{
|
||||
bindings::intel::IntelDrm,
|
||||
config::Config,
|
||||
server::{gpu_controller::init_controller, profiles, system::DAEMON_VERSION},
|
||||
server::{ClientContext, gpu_controller::init_controller, profiles, system::DAEMON_VERSION},
|
||||
};
|
||||
use crate::{server::gpu_controller::NvidiaLibs, system::run_command};
|
||||
use amdgpu_sysfs::gpu_handle::{
|
||||
@@ -32,13 +32,11 @@ use nix::libc;
|
||||
use nvml_wrapper::Nvml;
|
||||
use pciid_parser::Database;
|
||||
use serde_json::json;
|
||||
#[cfg(not(test))]
|
||||
use std::collections::HashMap;
|
||||
#[cfg(all(not(test), feature = "nvidia"))]
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, HashMap},
|
||||
env,
|
||||
fs::{self, File, Permissions},
|
||||
io::{BufWriter, Cursor, Write},
|
||||
@@ -55,6 +53,7 @@ use tokio::{
|
||||
time::sleep,
|
||||
};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use zbus_polkit::policykit1::{self, AuthorityProxy};
|
||||
|
||||
const CONTROLLERS_LOAD_RETRY_ATTEMPTS: u8 = 5;
|
||||
const CONTROLLERS_LOAD_RETRY_INTERVAL: u64 = 3;
|
||||
@@ -86,6 +85,10 @@ const SNAPSHOT_EXCLUDED_FILENAME_PREFIXES: &[&str] = &[
|
||||
];
|
||||
const CONFIG_RESET_CMDLINE_ARG: &str = "lact-reset";
|
||||
|
||||
mod polkit_actions {
|
||||
pub const PROFILE_HOOK: &str = "io.github.ilya_zlobintsev.LACT.profile-hook";
|
||||
}
|
||||
|
||||
type ProfileHolds = Rc<RefCell<Vec<(u64, Rc<str>, mpsc::Sender<()>)>>>;
|
||||
type ProfileHoldSnapshot = Rc<RefCell<Option<(Option<Rc<str>>, bool)>>>;
|
||||
|
||||
@@ -101,6 +104,7 @@ pub struct Handler {
|
||||
profile_holds: ProfileHolds,
|
||||
profile_hold_snapshot: ProfileHoldSnapshot,
|
||||
next_hold_cookie: Rc<Cell<u64>>,
|
||||
polkit_proxy: Option<AuthorityProxy<'static>>,
|
||||
}
|
||||
|
||||
impl<'a> Handler {
|
||||
@@ -178,6 +182,13 @@ impl<'a> Handler {
|
||||
config.save(&Cell::new(Instant::now()))?;
|
||||
}
|
||||
|
||||
let polkit_proxy = connect_polkit_proxy()
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
warn!("could not connect to polkit, admin checks will not be available: {err:#}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
let handler = Self {
|
||||
gpu_controllers: Rc::new(RwLock::new(controllers)),
|
||||
config: Rc::new(RwLock::new(config)),
|
||||
@@ -189,6 +200,7 @@ impl<'a> Handler {
|
||||
profile_holds: Rc::new(RefCell::new(Vec::new())),
|
||||
profile_hold_snapshot: Rc::new(RefCell::new(None)),
|
||||
next_hold_cookie: Rc::new(Cell::new(1)),
|
||||
polkit_proxy,
|
||||
};
|
||||
if let Err(err) = handler.apply_current_config().await {
|
||||
error!("could not apply config: {err:#}");
|
||||
@@ -913,7 +925,12 @@ impl<'a> Handler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_profile(&self, name: String, base: ProfileBase) -> anyhow::Result<()> {
|
||||
pub async fn create_profile(
|
||||
&self,
|
||||
name: String,
|
||||
base: ProfileBase,
|
||||
ctx: ClientContext,
|
||||
) -> anyhow::Result<()> {
|
||||
{
|
||||
let mut config = self.config.write().await;
|
||||
if config.profiles.contains_key(name.as_str()) {
|
||||
@@ -924,7 +941,18 @@ impl<'a> Handler {
|
||||
ProfileBase::Empty => Profile::default(),
|
||||
ProfileBase::Default => config.default_profile(),
|
||||
ProfileBase::Profile(name) => config.profile(&name)?.clone(),
|
||||
ProfileBase::Provided(profile) => profile,
|
||||
ProfileBase::Provided(profile) => {
|
||||
if !profile.hooks.is_empty() {
|
||||
self.check_auth(
|
||||
polkit_actions::PROFILE_HOOK,
|
||||
"User was not authorized to set profile rule hooks",
|
||||
ctx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
profile
|
||||
}
|
||||
};
|
||||
config.profiles.insert(name.into(), profile);
|
||||
config.save(&self.config_last_saved)?;
|
||||
@@ -996,7 +1024,17 @@ impl<'a> Handler {
|
||||
name: &str,
|
||||
rule: Option<ProfileRule>,
|
||||
hooks: ProfileHooks,
|
||||
ctx: ClientContext,
|
||||
) -> anyhow::Result<()> {
|
||||
if !hooks.is_empty() {
|
||||
self.check_auth(
|
||||
polkit_actions::PROFILE_HOOK,
|
||||
"User was not authorized to set profile rule hooks",
|
||||
ctx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut config = self.config.write().await;
|
||||
let profile = config
|
||||
@@ -1195,6 +1233,37 @@ impl<'a> Handler {
|
||||
controller.cleanup().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_auth(
|
||||
&self,
|
||||
action: &str,
|
||||
error_msg: &str,
|
||||
ctx: ClientContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let polkit_proxy = self
|
||||
.polkit_proxy
|
||||
.as_ref()
|
||||
.context("Polkit not available, cannot ask for authorization")?;
|
||||
|
||||
let pid = ctx.client_pid.context("No client PID available")?;
|
||||
let subject = policykit1::Subject::new_for_owner(pid, None, None)?;
|
||||
let result = polkit_proxy
|
||||
.check_authorization(
|
||||
&subject,
|
||||
action,
|
||||
&HashMap::new(),
|
||||
policykit1::CheckAuthorizationFlags::AllowUserInteraction.into(),
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.context("Authorization could not be granted")?;
|
||||
|
||||
if result.is_authorized {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("{error_msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_config_to_controllers(
|
||||
@@ -1495,3 +1564,15 @@ fn controller_vendor_is_unique(
|
||||
== *vendor_id
|
||||
})
|
||||
}
|
||||
|
||||
async fn connect_polkit_proxy() -> anyhow::Result<AuthorityProxy<'static>> {
|
||||
let conn = zbus::Connection::system()
|
||||
.await
|
||||
.context("Could not establish dbus connection")?;
|
||||
|
||||
let proxy = AuthorityProxy::new(&conn)
|
||||
.await
|
||||
.context("Could not connect to polkit")?;
|
||||
|
||||
Ok(proxy)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<action id="io.github.ilya_zlobintsev.LACT.profile-hook">
|
||||
<description>Set LACT profile hooks</description>
|
||||
<message>Setting root profile hooks requires authorization</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
Reference in New Issue
Block a user