⎇ 48e41331a104ba136b34e7695dccb08ef9188703
crates/ente-polkit-compat/src/main.rs
History
Who touched what
raw
journal-query CLI, machined, polkit con grants reales, GNOME boot test
1//! ente-polkit-compat: shim de `org.freedesktop.PolicyKit1.Authority`.
2//!
3//! Polkit autoriza llamadas privilegiadas (e.g. SetHostname, PowerOff).
4//! En el fractal no usamos polkit como gatekeeper — la auth se hace en
5//! el bus interno via SO_PEERCRED y capability grants. Pero apps que
6//! usan polkit (gnome-control-center, etc) bloquean en `CheckAuthorization`
7//! si no responde nadie.
8//!
9//! Este shim responde "is_authorized=true" siempre — el fractal queda
10//! como sistema confiado. El logging deja audit trail de qué acciones se
11//! han pedido para futuro análisis.
12//!
13//! Producción real: integrar con el grant system del bus interno —
14//! CheckAuthorization solicita un token al graph y devuelve true/false
15//! según el resultado.
16
17use ente_bus::{BusClient, BusRequest, BusResponse, POLKIT_DECISION_IFACE, POLKIT_SERVICE_IFACE};
18use ente_card::Capability;
19use std::collections::HashMap;
20use tokio::signal::unix::{signal, SignalKind};
21use tracing::{debug, info, warn};
22use tracing_subscriber::EnvFilter;
23use zbus::{fdo, interface, zvariant::OwnedValue};
24
25const BUS_NAME: &str = "org.freedesktop.PolicyKit1";
26const OBJ_PATH: &str = "/org/freedesktop/PolicyKit1/Authority";
27
28#[tokio::main(flavor = "current_thread")]
29async fn main() -> anyhow::Result<()> {
30 init_tracing();
31 info!("ente-polkit-compat: arrancando");
32 announce_to_fractal().await;
33
34 let manager = PolkitAuthority;
35 let conn_result = zbus::connection::Builder::system()
36 .and_then(|b| b.name(BUS_NAME))
37 .and_then(|b| b.serve_at(OBJ_PATH, manager));
38 match conn_result {
39 Ok(builder) => match builder.build().await {
40 Ok(_conn) => {
41 info!(name = BUS_NAME, "name acquired, sirviendo");
42 wait_for_term().await
43 }
44 Err(e) => { warn!(?e, "build conn falló — modo idle"); wait_for_term().await }
45 },
46 Err(e) => { warn!(?e, "builder D-Bus falló — modo idle"); wait_for_term().await }
47 }
48}
49
50struct PolkitAuthority;
51
52/// Wire format de Polkit: `Subject = (s, a{sv})` — kind ("unix-session",
53/// "unix-process", "system-bus-name") + detalles. El detail típico:
54/// {"pid": u32, "start-time": u64, "uid": u32}
55type Subject = (String, HashMap<String, OwnedValue>);
56
57/// Resultado de `CheckAuthorization`: `(b, b, a{ss})` —
58/// is_authorized, is_challenge, details.
59type AuthResult = (bool, bool, HashMap<String, String>);
60
61#[interface(name = "org.freedesktop.PolicyKit1.Authority")]
62impl PolkitAuthority {
63 async fn check_authorization(
64 &self,
65 subject: Subject,
66 action_id: String,
67 _details: HashMap<String, String>,
68 _flags: u32,
69 _cancellation_id: String,
70 ) -> fdo::Result<AuthResult> {
71 let (subj_kind, subj_details) = subject;
72 let pid = subj_details.get("pid")
73 .and_then(|v| u32::try_from(v).ok());
74 let uid = subj_details.get("uid")
75 .and_then(|v| u32::try_from(v).ok());
76
77 // Pregunta al bus interno del fractal si hay un policy provider.
78 // Si lo hay, su decisión gobierna. Si no (NoProvider), default = allow.
79 let decision = query_policy(&action_id, pid, uid).await;
80 info!(%action_id, %subj_kind, ?pid, ?uid, ?decision, "CheckAuthorization");
81 Ok((decision.allow, false, HashMap::new()))
82 }
83
84 async fn check_authorization_by_async(
85 &self,
86 subject: Subject,
87 action_id: String,
88 details: HashMap<String, String>,
89 flags: u32,
90 cancellation_id: String,
91 ) -> fdo::Result<AuthResult> {
92 // Mismo comportamiento; algunos clientes llaman la versión async.
93 self.check_authorization(subject, action_id, details, flags, cancellation_id).await
94 }
95
96 async fn cancel_check_authorization(&self, _cancellation_id: String) -> fdo::Result<()> {
97 Ok(())
98 }
99
100 async fn enumerate_actions(&self, _locale: String) -> fdo::Result<Vec<EnumeratedAction>> {
101 // Devolvemos lista vacía — no enumeramos acciones registradas.
102 // El llamador (típicamente gnome-control-center settings panel)
103 // debería degradar grácilmente.
104 Ok(vec![])
105 }
106
107 async fn register_authentication_agent(
108 &self,
109 _subject: Subject,
110 _locale: String,
111 _object_path: String,
112 ) -> fdo::Result<()> {
113 info!("RegisterAuthenticationAgent (no-op)");
114 Ok(())
115 }
116
117 async fn register_authentication_agent_with_options(
118 &self,
119 _subject: Subject,
120 _locale: String,
121 _object_path: String,
122 _options: HashMap<String, OwnedValue>,
123 ) -> fdo::Result<()> {
124 Ok(())
125 }
126
127 async fn unregister_authentication_agent(
128 &self,
129 _subject: Subject,
130 _object_path: String,
131 ) -> fdo::Result<()> {
132 Ok(())
133 }
134
135 async fn authentication_agent_response(
136 &self,
137 _cookie: String,
138 _identity: (String, HashMap<String, OwnedValue>),
139 ) -> fdo::Result<()> {
140 Ok(())
141 }
142
143 async fn enumerate_temporary_authorizations(
144 &self,
145 _subject: Subject,
146 ) -> fdo::Result<Vec<TemporaryAuth>> {
147 Ok(vec![])
148 }
149
150 async fn revoke_temporary_authorizations(&self, _subject: Subject) -> fdo::Result<()> {
151 Ok(())
152 }
153
154 async fn revoke_temporary_authorization_by_id(&self, _id: String) -> fdo::Result<()> {
155 Ok(())
156 }
157
158 #[zbus(property)]
159 async fn backend_name(&self) -> String { "ente-polkit-compat".into() }
160
161 #[zbus(property)]
162 async fn backend_version(&self) -> String { env!("CARGO_PKG_VERSION").into() }
163
164 #[zbus(property)]
165 async fn backend_features(&self) -> u32 { 0 }
166}
167
168/// Wire signature de EnumerateActions item:
169/// `(ssssssuusa{ss})` — action_id, descripción, message, vendor, vendor_url,
170/// icon_name, implicit_any, implicit_inactive, implicit_active, annotations.
171type EnumeratedAction = (
172 String, String, String, String, String, String,
173 u32, u32, String, HashMap<String, String>,
174);
175
176/// Wire signature de TemporaryAuthorization:
177/// `(sssss)` — id, action_id, subject_kind, subject_detail, time_obtained, time_expires.
178/// Aquí `(string)` * 5 + 2 timestamps. Simplificamos al subset relevante.
179type TemporaryAuth = (String, String, (String, HashMap<String, OwnedValue>), u64, u64);
180
181/// Resultado de una consulta de policy al fractal.
182#[derive(Debug)]
183struct PolicyDecision {
184 allow: bool,
185 /// Origen: "fractal" si vino del bus, "default-allow" si no había proveedor.
186 source: &'static str,
187}
188
189/// Pregunta al bus interno: ¿hay alguien que decida sobre `action_id`?
190/// Wire format del blob: `pid_u32_be | uid_u32_be | action_id_utf8`.
191/// El proveedor responde con `Invoked { result: [0|1] }` — 1 = allow.
192async fn query_policy(action_id: &str, pid: Option<u32>, uid: Option<u32>) -> PolicyDecision {
193 let mut blob = Vec::with_capacity(8 + action_id.len());
194 blob.extend_from_slice(&pid.unwrap_or(0).to_be_bytes());
195 blob.extend_from_slice(&uid.unwrap_or(0).to_be_bytes());
196 blob.extend_from_slice(action_id.as_bytes());
197
198 let mut client = match BusClient::from_env().await {
199 Ok(c) => c,
200 Err(e) => {
201 debug!(?e, "no bus client — default allow");
202 return PolicyDecision { allow: true, source: "no-bus" };
203 }
204 };
205 let req = BusRequest::Invoke {
206 cap: Capability::Endpoint {
207 interface: POLKIT_DECISION_IFACE,
208 version: 1,
209 },
210 blob,
211 };
212 match client.call(req).await {
213 Ok(BusResponse::Invoked { result }) => {
214 let allow = result.first().copied().unwrap_or(1) != 0;
215 PolicyDecision { allow, source: "fractal" }
216 }
217 Ok(BusResponse::Error(msg)) if msg.contains("sin proveedor") => {
218 // No hay policy provider — default allow.
219 PolicyDecision { allow: true, source: "default-allow" }
220 }
221 Ok(other) => {
222 warn!(?other, "policy: respuesta inesperada — default allow");
223 PolicyDecision { allow: true, source: "default-allow" }
224 }
225 Err(e) => {
226 warn!(?e, "policy: bus call falló — default allow");
227 PolicyDecision { allow: true, source: "default-allow" }
228 }
229 }
230}
231
232async fn announce_to_fractal() {
233 if let Ok(mut client) = BusClient::from_env().await {
234 let req = BusRequest::Announce {
235 capabilities: vec![Capability::Endpoint {
236 interface: POLKIT_SERVICE_IFACE,
237 version: 1,
238 }],
239 };
240 match client.call(req).await {
241 Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
242 Ok(other) => warn!(?other, "Announce respuesta inesperada"),
243 Err(e) => warn!(?e, "Announce falló"),
244 }
245 }
246}
247
248async fn wait_for_term() -> anyhow::Result<()> {
249 let mut term = signal(SignalKind::terminate())?;
250 let mut int_ = signal(SignalKind::interrupt())?;
251 tokio::select! {
252 _ = term.recv() => info!("SIGTERM"),
253 _ = int_.recv() => info!("SIGINT"),
254 }
255 Ok(())
256}
257
258fn init_tracing() {
259 let filter = EnvFilter::try_from_default_env()
260 .unwrap_or_else(|_| EnvFilter::new("ente_polkit_compat=info"));
261 tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
262}