Browse Source

qubespolicy: run GUI code inside user session and expose it as dbus object

This way it will work independently from where qrexec-policy tool will
be called (in most cases - from a system service, as root).
This is also very similar architecture to what we'll need when moving to
GUI domain - there GUI part will also be separated from policy
evaluation logic.

QubesOS/qubes-issues#910
Marek Marczykowski-Górecki 7 years ago
parent
commit
a3da85bfda

+ 4 - 0
linux/system-config/Makefile

@@ -8,4 +8,8 @@ install:
 	ln -s block-snapshot $(DESTDIR)/etc/xen/scripts/block-origin
 	install -d $(DESTDIR)/etc/xdg/autostart
 	install -m 0644 qubes-guid.desktop $(DESTDIR)/etc/xdg/autostart/
+	install -m 0644 qrexec-policy-agent.desktop $(DESTDIR)/etc/xdg/autostart/
 	install -m 0644 -D tmpfiles-qubes.conf $(DESTDIR)/usr/lib/tmpfiles.d/qubes.conf
+	install -d $(DESTDIR)/etc/dbus-1/system.d
+	install -m 0644 dbus-org.qubesos.PolicyAgent.conf  \
+		$(DESTDIR)/etc/dbus-1/system.d/org.qubesos.PolicyAgent.conf

+ 19 - 0
linux/system-config/dbus-org.qubesos.PolicyAgent.conf

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
+
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <!-- User need to be in qubes group to own the service -->
+  <policy group="qubes">
+    <allow own="org.qubesos.PolicyAgent"/>
+  </policy>
+  <policy context="default">
+
+    <allow send_destination="org.qubesos.PolicyAgent"
+           send_interface="org.freedesktop.DBus.Introspectable"/>
+
+    <allow send_destination="org.qubesos.PolicyAgent"
+           send_interface="org.qubesos.PolicyAgent"/>
+  </policy>
+</busconfig>

+ 7 - 0
linux/system-config/qrexec-policy-agent.desktop

@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Qubes Qrexec Policy agent
+Comment=Agent for handling policy confirmation prompts
+Icon=qubes
+Exec=qrexec-policy-agent
+Terminal=false
+Type=Application

+ 70 - 0
qubespolicy/agent.py

@@ -0,0 +1,70 @@
+# -*- encoding: utf8 -*-
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2017 Marek Marczykowski-Górecki
+#                               <marmarek@invisiblethingslab.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+
+
+''' Agent running in user session, responsible for asking the user about policy
+decisions.'''
+
+import pydbus
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import GLib
+
+import qubespolicy.rpcconfirmation
+
+class PolicyAgent(object):
+    dbus = """
+    <node>
+      <interface name='org.qubesos.PolicyAgent'>
+        <method name='Ask'>
+          <arg type='s' name='source' direction='in'/>
+          <arg type='s' name='service_name' direction='in'/>
+          <arg type='as' name='targets' direction='in'/>
+          <arg type='s' name='default_target' direction='in'/>
+          <arg type='a{ss}' name='icons' direction='in'/>
+          <arg type='s' name='response' direction='out'/>
+        </method>
+      </interface>
+    </node>
+    """
+
+    def Ask(self, source, service_name, targets, default_target,
+            icons):
+        entries_info = {}
+        for target in targets:
+            entries_info[target] = {}
+            entries_info[target]['icon'] = icons.get(target, None)
+
+        response = qubespolicy.rpcconfirmation.confirm_rpc(
+            entries_info, source, service_name,
+            targets, default_target or None)
+        return response or ''
+
+
+def main():
+    loop = GLib.MainLoop()
+    bus = pydbus.SystemBus()
+    obj = PolicyAgent()
+    bus.publish('org.qubesos.PolicyAgent', obj)
+    loop.run()
+
+
+if __name__ == '__main__':
+    main()

+ 13 - 9
qubespolicy/cli.py

@@ -68,20 +68,24 @@ def main(args=None):
         action = policy.evaluate(system_info, args.domain, args.target)
         if action.action == qubespolicy.Action.ask:
             # late import to save on time for allow/deny actions
-            import qubespolicy.rpcconfirmation as rpcconfirmation
-            entries_info = system_info['domains'].copy()
+            import pydbus
+            bus = pydbus.SystemBus()
+            proxy = bus.get('org.qubesos.PolicyAgent',
+                '/org/qubesos/PolicyAgent')
+
+            icons = {name: system_info['domains'][name]['icon']
+                for name in system_info['domains'].keys()}
             for dispvm_base in system_info['domains']:
                 if not system_info['domains'][dispvm_base]['dispvm_allowed']:
                     continue
                 dispvm_api_name = '$dispvm:' + dispvm_base
-                entries_info[dispvm_api_name] = \
-                    system_info['domains'][dispvm_base].copy()
-                entries_info[dispvm_api_name]['icon'] = \
-                    entries_info[dispvm_api_name]['icon'].replace('app', 'disp')
+                icons[dispvm_api_name] = \
+                    system_info['domains'][dispvm_base]['icon']
+                icons[dispvm_api_name] = \
+                    icons[dispvm_api_name].replace('app', 'disp')
 
-            response = rpcconfirmation.confirm_rpc(
-                entries_info, args.domain, args.service_name,
-                action.targets_for_ask, action.target)
+            response = proxy.Ask(args.domain, args.service_name,
+                action.targets_for_ask, action.target or '', icons)
             if response:
                 action.handle_user_response(True, response)
             else:

+ 5 - 0
rpm_spec/core-dom0.spec

@@ -73,6 +73,7 @@ Requires:       python3
 Requires:       python3-docutils
 Requires:       python3-jinja2
 Requires:       python3-lxml
+Requires:       python3-pydbus
 Requires:       python3-qubesdb
 Requires:       python3-setuptools
 Requires:       python3-xen
@@ -210,11 +211,13 @@ fi
 %files
 %defattr(-,root,root,-)
 %config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/qmemman.conf
+%config(noreplace) /etc/dbus-1/system.d/org.qubesos.PolicyAgent.conf
 /usr/bin/qvm-*
 /usr/bin/qubes-*
 /usr/bin/qmemmand
 /usr/bin/qubesd*
 /usr/bin/qrexec-policy
+/usr/bin/qrexec-policy-agent
 
 %dir %{python3_sitelib}/qubes-*.egg-info
 %{python3_sitelib}/qubes-*.egg-info/*
@@ -385,6 +388,7 @@ fi
 %{python3_sitelib}/qubespolicy/__pycache__/*
 %{python3_sitelib}/qubespolicy/__init__.py
 %{python3_sitelib}/qubespolicy/cli.py
+%{python3_sitelib}/qubespolicy/agent.py
 %{python3_sitelib}/qubespolicy/gtkhelpers.py
 %{python3_sitelib}/qubespolicy/rpcconfirmation.py
 %{python3_sitelib}/qubespolicy/utils.py
@@ -454,5 +458,6 @@ fi
 %attr(2770,root,qubes) %dir /var/log/qubes
 %attr(0770,root,qubes) %dir /var/run/qubes
 /etc/xdg/autostart/qubes-guid.desktop
+/etc/xdg/autostart/qrexec-policy-agent.desktop
 
 /usr/share/doc/qubes/relaxng/*.rng

+ 1 - 0
setup.py

@@ -33,6 +33,7 @@ if __name__ == '__main__':
         entry_points={
             'console_scripts': list(get_console_scripts()) + [
                 'qrexec-policy = qubespolicy.cli:main',
+                'qrexec-policy-agent = qubespolicy.agent:main',
             ],
             'qubes.vm': [
                 'AppVM = qubes.vm.appvm:AppVM',